Skip to content

Commit a9ce84f

Browse files
authored
feat(front): add css processing to asset pipeline (#452)
## 📝 Description - Implemented a modern CSS build pipeline using PostCSS for better CSS processing and optimization - Consolidated CSS output to a single file for improved performance - Aligned with Phoenix 1.6+ asset management best practices - Updated the security toolbox to properly display JS dependency scan results ## ✅ Checklist - [x] I have tested this change - [ ] This change requires documentation update
1 parent 705d9db commit a9ce84f

File tree

21 files changed

+1617
-267
lines changed

21 files changed

+1617
-267
lines changed

.semaphore/daily-builds.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,11 +725,11 @@ blocks:
725725
jobs:
726726
- name: JS - dependencies
727727
commands:
728-
- make check.js.deps APP_DIRECTORY=assets
728+
- make check.js.deps APP_DIRECTORY=assets SCAN_RESULT_DIR=../out
729729
- name: JS - code
730730
commands:
731731
- export PATH=$PATH:/home/semaphore/.local/bin
732-
- make check.js.code APP_DIRECTORY=assets
732+
- make check.js.code APP_DIRECTORY=assets SCAN_RESULT_DIR=../out
733733
- name: Elixir - dependencies
734734
commands:
735735
- make check.ex.deps CHECK_DEPS_OPTS='--ignore-packages phoenix'

.semaphore/semaphore.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -789,11 +789,11 @@ blocks:
789789
jobs:
790790
- name: JS - dependencies
791791
commands:
792-
- make check.js.deps APP_DIRECTORY=assets
792+
- make check.js.deps APP_DIRECTORY=assets SCAN_RESULT_DIR=../out
793793
- name: JS - code
794794
commands:
795795
- export PATH=$PATH:/home/semaphore/.local/bin
796-
- make check.js.code APP_DIRECTORY=assets
796+
- make check.js.code APP_DIRECTORY=assets SCAN_RESULT_DIR=../out
797797
- name: Elixir - dependencies
798798
commands:
799799
- make check.ex.deps CHECK_DEPS_OPTS='--ignore-packages phoenix'

Makefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ DOCKER_BUILD_PATH=.
7676
EX_CATCH_WARRNINGS_FLAG=--warnings-as-errors
7777
CHECK_DEPS_EXTRA_OPTS?=-w feature_provider,grpc_health_check,tentacat,util,watchman,fun_registry,sentry_grpc,traceman,cacheman,log_tee,spec,proto,sys2app,looper,job_matrix,definition_validator,gofer_client,open_api_spex,when,uuid,esaml,openid_connect,block
7878
ROOT_MAKEFILE_PATH := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
79+
SCAN_RESULT_DIR?=out
7980

8081
#
8182
# Security checks
@@ -111,10 +112,10 @@ ifeq ($(CI),)
111112
-v $$(pwd):/app \
112113
-v $(ROOT_MAKEFILE_PATH)/security-toolbox:$(SECURITY_TOOLBOX_TMP_DIR) \
113114
registry.semaphoreci.com/ruby:3 \
114-
bash -c 'cd $(APP_DIRECTORY) && $(SECURITY_TOOLBOX_TMP_DIR)/dependencies --language $(LANGUAGE) -d $(CHECK_DEPS_OPTS)'
115+
bash -c 'cd $(APP_DIRECTORY) && $(SECURITY_TOOLBOX_TMP_DIR)/dependencies --language $(LANGUAGE) -d --output-dir $(SCAN_RESULT_DIR) $(CHECK_DEPS_OPTS)'
115116
else
116117
# ruby version is set in prologue
117-
cd $(APP_DIRECTORY) && $(ROOT_MAKEFILE_PATH)/security-toolbox/dependencies --language $(LANGUAGE) -d $(CHECK_DEPS_OPTS)
118+
cd $(APP_DIRECTORY) && $(ROOT_MAKEFILE_PATH)/security-toolbox/dependencies --language $(LANGUAGE) -d --output-dir $(SCAN_RESULT_DIR) $(CHECK_DEPS_OPTS)
118119
endif
119120

120121
check.ex.deps:

front/assets/build.js

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
11
const esbuild = require('esbuild')
22
const fs = require('fs-extra')
33
const path = require('path')
4+
const { exec } = require('child_process')
5+
const { promisify } = require('util')
46

7+
const execAsync = promisify(exec)
58
const bundle = true
69
const logLevel = process.env.ESBUILD_LOG_LEVEL || 'silent'
710
const watch = !!process.env.ESBUILD_WATCH
11+
const isProd = process.env.MIX_ENV === 'prod' || process.env.NODE_ENV === 'production'
812

913
const plugins = [
1014
// Add and configure plugins here
1115
]
1216

1317
const outputDir = '../priv/static/assets'
1418

19+
// Function to process CSS files
20+
const processCss = async () => {
21+
console.log('Processing CSS files...')
22+
23+
try {
24+
// Process main.css which imports all other CSS files
25+
const inputFile = 'css/main.css'
26+
const outputFile = path.join(outputDir, 'css/app.css')
27+
28+
// Set NODE_ENV for PostCSS to handle minification
29+
const env = isProd ? 'NODE_ENV=production' : 'NODE_ENV=development'
30+
const postcssCmd = `${env} npx postcss ${inputFile} -o ${outputFile}`
31+
32+
await execAsync(postcssCmd)
33+
console.log(`CSS processed successfully (${isProd ? 'production' : 'development'} mode)`)
34+
35+
} catch (error) {
36+
console.error('Error processing CSS:', error)
37+
throw error
38+
}
39+
}
40+
1541
// Function to copy static assets
16-
const copyAssets = () => {
17-
console.log('Copying original assets to output directory...')
42+
const copyAssets = async () => {
43+
console.log('Copying static assets to output directory...')
1844

1945
fs.ensureDirSync(path.join(outputDir, 'css'))
2046
fs.ensureDirSync(path.join(outputDir, 'fonts'))
2147
fs.ensureDirSync(path.join(outputDir, 'images'))
2248

23-
fs.copySync('css', path.join(outputDir, 'css'), { overwrite: true })
49+
// Process CSS files
50+
await processCss()
51+
52+
// Copy fonts and images
2453
fs.copySync('fonts', path.join(outputDir, 'fonts'), { overwrite: true })
2554
fs.copySync('images', path.join(outputDir, 'images'), { overwrite: true })
2655

@@ -53,18 +82,53 @@ const buildOptions = {
5382
}
5483

5584
if (watch) {
56-
esbuild.context(buildOptions).then(context => {
85+
esbuild.context(buildOptions).then(async context => {
5786
context.watch()
58-
copyAssets()
87+
await copyAssets()
88+
89+
const chokidar = require('chokidar')
90+
const cssDir = path.join(__dirname, 'css')
91+
92+
const cssWatcher = chokidar.watch(cssDir, {
93+
persistent: true,
94+
ignoreInitial: true,
95+
usePolling: true, // REQUIRED for macOS Docker
96+
interval: 1000,
97+
binaryInterval: 1000,
98+
awaitWriteFinish: {
99+
stabilityThreshold: 500,
100+
pollInterval: 100
101+
},
102+
useFsEvents: false,
103+
alwaysStat: true,
104+
depth: 99,
105+
atomic: false
106+
})
107+
108+
cssWatcher
109+
.on('change', async (path) => {
110+
// Only process CSS files
111+
if (path.endsWith('.css')) {
112+
console.log('CSS file changed, reprocessing...')
113+
try {
114+
await processCss()
115+
} catch (error) {
116+
console.error('Error processing CSS:', error)
117+
}
118+
}
119+
})
120+
.on('ready', () => console.log('CSS watcher ready'))
121+
59122
process.stdin.on('close', () => {
123+
cssWatcher.close()
60124
context.dispose()
61125
process.exit(0)
62126
})
63127
process.stdin.resume()
64128
})
65129
} else {
66-
esbuild.build(buildOptions).then(() => {
67-
copyAssets()
130+
esbuild.build(buildOptions).then(async () => {
131+
await copyAssets()
68132
}).catch(error => {
69133
console.error('Build error:', error)
70134
process.exit(1)

front/assets/css/app-semaphore-min.css

Lines changed: 0 additions & 1 deletion
This file was deleted.

front/assets/css/app-semaphore.css

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,7 +2624,7 @@ template {
26242624
}
26252625
/* Modules */
26262626
/*
2627-
2627+
26282628
BOX SIZING
26292629
26302630
*/
@@ -2656,7 +2656,7 @@ blockquote,
26562656
figcaption,
26572657
figure,
26582658
textarea,
2659-
table,
2659+
table,
26602660
td,
26612661
th,
26622662
tr,
@@ -2895,7 +2895,7 @@ img { max-width: 100%; }
28952895
border-top-right-radius: 0;
28962896
border-bottom-right-radius: 0;
28972897
}
2898-
/*
2898+
/*
28992899
@media (--breakpoint-not-small) {
29002900
.br0-ns { border-radius: 0; }
29012901
.br1-ns { border-radius: .125rem; }
@@ -3000,7 +3000,7 @@ img { max-width: 100%; }
30003000
.b--dashed { border-style: dashed; }
30013001
.b--solid { border-style: solid; }
30023002
.b--none { border-style: none; }
3003-
/*
3003+
/*
30043004
@media (--breakpoint-not-small) {
30053005
.b--dotted-ns { border-style: dotted; }
30063006
.b--dashed-ns { border-style: dashed; }
@@ -3192,7 +3192,7 @@ img { max-width: 100%; }
31923192
bottom: 0;
31933193
left: 0;
31943194
}
3195-
/*
3195+
/*
31963196
@media (--breakpoint-not-small) {
31973197
.top-0-ns { top: 0; }
31983198
.left-0-ns { left: 0; }
@@ -3798,7 +3798,7 @@ code, .code, pre {
37983798
*/
37993799
.i { font-style: italic; }
38003800
.fs-normal { font-style: normal; }
3801-
/*
3801+
/*
38023802
@media (--breakpoint-not-small) {
38033803
.i-ns { font-style: italic; }
38043804
.fs-normal-ns { font-style: normal; }
@@ -3850,7 +3850,7 @@ code, .code, pre {
38503850
.fw7 { font-weight: 700; }
38513851
.fw8 { font-weight: 800; }
38523852
.fw9 { font-weight: 900; }
3853-
/*
3853+
/*
38543854
@media (--breakpoint-not-small) {
38553855
.normal-ns { font-weight: normal; }
38563856
.b-ns { font-weight: bold; }
@@ -3896,7 +3896,7 @@ code, .code, pre {
38963896
/*
38973897
38983898
FORMS
3899-
3899+
39003900
*/
39013901
.input-reset {
39023902
-webkit-appearance: none;
@@ -4037,7 +4037,7 @@ code, .code, pre {
40374037
.tracked { letter-spacing: .1em; }
40384038
.tracked-tight { letter-spacing: -.05em; }
40394039
.tracked-mega { letter-spacing: .25em; }
4040-
/*
4040+
/*
40414041
@media (--breakpoint-not-small) {
40424042
.tracked-ns { letter-spacing: .1em; }
40434043
.tracked-tight-ns { letter-spacing: -.05em; }
@@ -6326,7 +6326,7 @@ code, .code, pre {
63266326
.ttl { text-transform: lowercase; }
63276327
.ttu { text-transform: uppercase; }
63286328
.ttn { text-transform: none; }
6329-
/*
6329+
/*
63306330
@media (--breakpoint-not-small) {
63316331
.ttc-ns { text-transform: capitalize; }
63326332
.ttl-ns { text-transform: lowercase; }
@@ -7063,4 +7063,4 @@ kbd {
70637063
line-height: 1;
70647064
padding: 2px 6px;
70657065
white-space: nowrap;
7066-
}
7066+
}

front/assets/css/app.css

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,4 +464,23 @@ sem-popover {
464464
-moz-user-select: none;
465465
-ms-user-select: none;
466466
user-select: none;
467-
}
467+
}
468+
469+
.material-symbols-outlined {
470+
font-variation-settings:
471+
'FILL' 0,
472+
'wght' 300,
473+
'GRAD' 0,
474+
'opsz' 20
475+
}
476+
.material-symbols-outlined.fill {
477+
font-variation-settings:
478+
'FILL' 1,
479+
'wght' 300,
480+
'GRAD' 0,
481+
'opsz' 20
482+
}
483+
.material-symbols-outlined.md-18 { font-size: 18px; }
484+
.material-symbols-outlined.md-24 { font-size: 24px; }
485+
.material-symbols-outlined.md-36 { font-size: 36px; }
486+
.material-symbols-outlined.md-48 { font-size: 48px; }

front/assets/css/main.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* Main CSS entry point - combines all CSS files */
2+
@import "./app-semaphore.css";
3+
@import "./app.css";

front/assets/js/app.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import "phoenix_html";
2-
import "../css/app.css";
32

43
import $ from "jquery";
54
import { install } from '@github/hotkey';

0 commit comments

Comments
 (0)