diff --git a/package.json b/package.json index db0f7c7f7b9f5c34728b306427b915a1f02a5c8c..f490dd68555e3e8d338e4f7ab4e053803fa896a9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "internal:eslint": "eslint --cache --max-warnings 0 --report-unused-disable-directives --ext .js,.vue,.graphql", "internal:stylelint": "stylelint -q --rd '{ee/,}app/assets/stylesheets/**/*.{css,scss}'", "prejest": "yarn check-dependencies", - "build:css": "node ./scripts/frontend/compile_css.mjs", + "build:css": "node scripts/frontend/build_css.mjs", "jest": "jest --config jest.config.js", "jest-debug": "node --inspect-brk node_modules/.bin/jest --runInBand", "jest:ci": "jest --config jest.config.js --ci --coverage --testSequencer ./scripts/frontend/parallel_ci_sequencer.js", diff --git a/scripts/frontend/build_css.mjs b/scripts/frontend/build_css.mjs new file mode 100755 index 0000000000000000000000000000000000000000..bc07362c3f95aeea0b258287bce6ae6ea3bcf050 --- /dev/null +++ b/scripts/frontend/build_css.mjs @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +import process from 'node:process'; +/* eslint-disable import/extensions */ +import { compileAllStyles } from './lib/compile_css.mjs'; +/* eslint-enable import/extensions */ + +const fileWatcher = await compileAllStyles({ shouldWatch: process.argv?.includes('--watch') }); + +process.on('SIGTERM', () => { + console.info('SIGTERM signal received.'); + fileWatcher?.close(); +}); diff --git a/scripts/frontend/compile_css.mjs b/scripts/frontend/lib/compile_css.mjs old mode 100755 new mode 100644 similarity index 76% rename from scripts/frontend/compile_css.mjs rename to scripts/frontend/lib/compile_css.mjs index 433c9117f8189503ed5c60f3da0757d05814ab73..64d694c4371a00025d88d40829942a9e65c33f27 --- a/scripts/frontend/compile_css.mjs +++ b/scripts/frontend/lib/compile_css.mjs @@ -1,18 +1,16 @@ -#!/usr/bin/env node - -import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; -import { argv } from 'node:process'; +import { env } from 'node:process'; import { compile, Logger } from 'sass'; import glob from 'glob'; /* eslint-disable import/extensions */ -import IS_EE from '../../config/helpers/is_ee_env.js'; -import IS_JH from '../../config/helpers/is_jh_env.js'; +import IS_EE from '../../../config/helpers/is_ee_env.js'; +import IS_JH from '../../../config/helpers/is_jh_env.js'; /* eslint-enable import/extensions */ // Note, in node > 21.2 we could replace the below with import.meta.dirname -const ROOT_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../'); +const ROOT_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../'); const OUTPUT_PATH = path.join(ROOT_PATH, 'app/assets/builds/'); const BASE_PATH = 'app/assets/stylesheets'; @@ -166,14 +164,7 @@ function resolveCompilationTargets() { return Object.fromEntries([...result.entries()].map((entry) => entry.reverse())); } -function writeContentToFile(content, srcFile, outputFile) { - if (!existsSync(path.dirname(outputFile))) { - mkdirSync(path.dirname(outputFile), { recursive: true }); - } - writeFileSync(outputFile, content.css); -} - -async function compileAllStyles() { +export async function compileAllStyles({ shouldWatch = false }) { const reverseDependencies = {}; const compilationTargets = resolveCompilationTargets(); @@ -184,38 +175,81 @@ async function compileAllStyles() { }; let fileWatcher = null; - if (argv?.includes('--watch')) { + if (shouldWatch) { const { watch } = await import('chokidar'); fileWatcher = watch([]); } - function compileSCSSFile(source, dest) { + async function compileSCSSFile(source, dest) { console.log(`\tcompiling source ${source} to ${dest}`); const content = compile(source, sassCompilerOptions); if (fileWatcher) { for (const dependency of content.loadedUrls) { if (dependency.protocol === 'file:') { - reverseDependencies[dependency.pathname] ||= new Set(); - reverseDependencies[dependency.pathname].add(source); - fileWatcher.add(dependency.pathname); + const dependencyPath = fileURLToPath(dependency); + reverseDependencies[dependencyPath] ||= new Set(); + reverseDependencies[dependencyPath].add(source); + fileWatcher.add(dependencyPath); } } } - writeContentToFile(content, source, dest); + // Create target folder if it doesn't exist + await mkdir(path.dirname(dest), { recursive: true }); + return writeFile(dest, content.css, 'utf-8'); } if (fileWatcher) { - fileWatcher.on('change', (changedFile) => { + fileWatcher.on('change', async (changedFile) => { console.warn(`${changedFile} changed, recompiling`); + const recompile = []; for (const source of reverseDependencies[changedFile]) { - compileSCSSFile(source, compilationTargets[source]); + recompile.push(compileSCSSFile(source, compilationTargets[source])); } + await Promise.all(recompile); }); } - for (const [source, dest] of Object.entries(compilationTargets)) { - compileSCSSFile(source, dest); + const initialCompile = Object.entries(compilationTargets).map(([source, dest]) => + compileSCSSFile(source, dest), + ); + + await Promise.all(initialCompile); + + return fileWatcher; +} + +function shouldUseNewPipeline() { + return /^(true|t|yes|y|1|on)$/i.test(`${env.USE_NEW_CSS_PIPELINE}`); +} + +export function viteCSSCompilerPlugin() { + if (!shouldUseNewPipeline()) { + return null; } + let fileWatcher = null; + return { + name: 'gitlab-css-compiler', + async configureServer() { + fileWatcher = await compileAllStyles({ shouldWatch: true }); + }, + buildEnd() { + return fileWatcher?.close(); + }, + }; } -await compileAllStyles(); +export function simplePluginForNodemon({ shouldWatch = true }) { + if (!shouldUseNewPipeline()) { + return null; + } + let fileWatcher = null; + return { + async start() { + await fileWatcher?.close(); + fileWatcher = await compileAllStyles({ shouldWatch }); + }, + stop() { + return fileWatcher?.close(); + }, + }; +} diff --git a/scripts/frontend/webpack_dev_server.js b/scripts/frontend/webpack_dev_server.js index ae73c14b501c8a52b4cb0d1711dfcac84c6aeaf5..296d0058b1ae6471db0a3866629f496f9b682f0d 100755 --- a/scripts/frontend/webpack_dev_server.js +++ b/scripts/frontend/webpack_dev_server.js @@ -47,6 +47,8 @@ else { }); } +let plugin = false; + // print useful messages for nodemon events nodemon .on('start', () => { @@ -56,11 +58,20 @@ nodemon console.log('The JavaScript assets are recompiled only if they change'); console.log('If you change them often, you might want to unset DEV_SERVER_STATIC'); } + /* eslint-disable import/extensions, promise/catch-or-return */ + import('./lib/compile_css.mjs').then(({ simplePluginForNodemon }) => { + plugin = simplePluginForNodemon({ shouldWatch: !STATIC_MODE }); + return plugin?.start(); + }); + /* eslint-enable import/extensions, promise/catch-or-return */ }) .on('quit', () => { + console.log('Shutting down CSS compilation process'); + plugin?.stop(); console.log('Shutting down webpack process'); process.exit(); }) .on('restart', (files) => { console.log('Restarting webpack process due to: ', files); + plugin?.start(); }); diff --git a/vite.config.js b/vite.config.js index 007706a195578d17ad4e871c0c607028e301d60e..2a395b5acb10adf7bc1e949fae50c40f4c19f7a6 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,9 @@ import { SOURCEGRAPH_PUBLIC_PATH, GITLAB_WEB_IDE_PUBLIC_PATH, } from './config/webpack.constants'; +/* eslint-disable import/extensions */ +import { viteCSSCompilerPlugin } from './scripts/frontend/lib/compile_css.mjs'; +/* eslint-enable import/extensions */ let viteGDKConfig; try { @@ -104,6 +107,7 @@ export default defineConfig({ ], }, plugins: [ + viteCSSCompilerPlugin(), viteGDKConfig.enabled ? autoRestartPlugin : null, fixedRubyPlugin, vue({