Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readdir, readFile, writeFile } from 'node:fs/promises';
import { existsSync, readFileSync } from 'node:fs';
import { dirname, extname, resolve } from 'node:path';
import { transform, composeVisitors } from 'lightningcss';
import { Timer } from './utils/timer.mjs';
import { Timer } from '../utils/timer.mjs';

const RootPath = process.cwd();
const skipExternal = true;
Expand Down Expand Up @@ -37,17 +37,13 @@ function version(urlString, fromFile) {
* @param {from: String} - the filepath for the css file
* @returns {import('lightningcss').Visitor} - A visitor that replaces the url
*/
function urlVersioning(fromFile) {
return {
/**
* @param {import('lightningcss').Url} url - The url object to transform
* @returns {import('lightningcss').Url} - The transformed url object
*/
Url(url) {
return { ...url, ...{ url: version(url.url, fromFile) } };
},
};
}
const urlVersioning = (fromFile) => ({
/**
* @param {import('lightningcss').Url} url - The url object to transform
* @returns {import('lightningcss').Url} - The transformed url object
*/
Url: (url) => ({ ...url, url: version(url.url, fromFile) }),
});

/**
* Adds a hash to the url() parts of the static css
Expand All @@ -57,8 +53,25 @@ function urlVersioning(fromFile) {
*/
const fixVersion = async (file) => {
try {
let content = await readFile(file, { encoding: 'utf8' });
// To preserve the licence the comment needs to start at the beginning of the file
const replaceUTF8String = file.endsWith('.min.css') ? '@charset "UTF-8";' : '@charset "UTF-8";\n';
content = content.startsWith(replaceUTF8String) ? content.replace(replaceUTF8String, '') : content;

// Preserve a leading license comment (/** ... */)
const firstLine = content.split(/\r?\n/)[0] || '';
if (firstLine.includes('/*') && !firstLine.includes('/*!')) {
const endCommentIdx = content.indexOf('*/');
if (endCommentIdx !== -1
&& (content.substring(0, endCommentIdx).includes('license')
|| content.substring(0, endCommentIdx).includes('copyright'))
) {
content = firstLine.includes('/**') ? content.replace('/**', '/*!') : content.replace('/*', '/*!');
}
}

const { code } = transform({
code: await readFile(file),
code: Buffer.from(content),
minify: file.endsWith('.min.css'),
visitor: composeVisitors([urlVersioning(file)]),
});
Expand All @@ -76,13 +89,15 @@ const fixVersion = async (file) => {
*
* @returns {Promise<void>}
*/
export const cssVersioning = async () => {
const cssVersioningVendor = async () => {
const bench = new Timer('Versioning');

const cssFiles = (await readdir(`${RootPath}/media`, { withFileTypes: true, recursive: true }))
const cssFiles = (await readdir(`${RootPath}/media/vendor`, { withFileTypes: true, recursive: true }))
.filter((file) => (!file.isDirectory() && extname(file.name) === '.css'))
.map((file) => `${file.path}/${file.name}`);

Promise.all(cssFiles.map((file) => fixVersion(file)))
.then(() => bench.stop());
};

export { urlVersioning, cssVersioningVendor };
37 changes: 29 additions & 8 deletions build/build-modules-js/stylesheets/handle-css.mjs
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
import { dirname, sep } from 'node:path';

import pkg from 'fs-extra';
import { transform as transformCss } from 'lightningcss';
import { transform as transformCss, composeVisitors } from 'lightningcss';
import { urlVersioning } from './css-versioning.mjs';

const {
copy, readFile, writeFile, ensureDir,
readFile, writeFile, ensureDir,
} = pkg;

export const handleCssFile = async (file) => {
const outputFile = file.replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);
try {
// CSS file, we will copy the file and then minify it in place
// CSS file, we will process the file and then minify it in place
// Ensure that the directories exist or create them
await ensureDir(dirname(outputFile), { recursive: true, mode: 0o755 });

let content = await readFile(file, { encoding: 'utf8' });

// To preserve the licence the comment needs to start at the beginning of the file
content = content.startsWith('@charset "UTF-8";\n') ? content.replace('@charset "UTF-8";\n', '') : content;

if (file !== outputFile) {
await copy(file, outputFile, { preserveTimestamps: true, overwrite: true });
const { code: css } = transformCss({
code: Buffer.from(content),
minify: false,
visitor: composeVisitors([urlVersioning(file)]), // Adds a hash to the url() parts of the static css
});

// Save optimized css file
await writeFile(
outputFile,
content.startsWith('@charset "UTF-8";')
? css
: `@charset "UTF-8";
${css}`,
{ encoding: 'utf8', mode: 0o644 },
);
}

const content = await readFile(file, { encoding: 'utf8' });
const { code } = transformCss({
// Process the file and minify it in place
const { code: cssMin } = transformCss({
code: Buffer.from(content),
minify: true,
visitor: composeVisitors([urlVersioning(outputFile)]), // Adds a hash to the url() parts of the static css
});

// Ensure the folder exists or create it
await writeFile(outputFile.replace('.css', '.min.css'), `@charset "UTF-8";${code}`, { encoding: 'utf8', mode: 0o644 });
// Save minified css file
await writeFile(outputFile.replace('.css', '.min.css'), `@charset "UTF-8";${cssMin}`, { encoding: 'utf8', mode: 0o644 });

console.log(`✅ CSS file copied/minified: ${file}`);
} catch (err) {
Expand Down
28 changes: 24 additions & 4 deletions build/build-modules-js/stylesheets/handle-scss.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { dirname, sep } from 'node:path';

import rtlcss from 'rtlcss';
import { ensureDir } from 'fs-extra';
import { transform as transformCss, Features } from 'lightningcss';
import { transform as transformCss, Features, composeVisitors } from 'lightningcss';
import { compileAsync } from 'sass-embedded';
import { urlVersioning } from './css-versioning.mjs';

const getOutputFile = (file) => file.replace(`${sep}scss${sep}`, `${sep}css${sep}`).replace('.scss', '.css').replace(`${sep}build${sep}media_source${sep}`, `${sep}media${sep}`);

Expand All @@ -23,24 +24,43 @@ export const handleScssFile = async (file) => {
contents = rtlcss.process(contents);
}

// To preserve the licence the comment needs to start at the beginning of the file
contents = contents.startsWith('@charset "UTF-8";\n') ? contents.replace('@charset "UTF-8";\n', '') : contents;

// Ensure the folder exists or create it
await ensureDir(dirname(cssFile), {});

const { code: css } = transformCss({
code: Buffer.from(contents),
minify: false,
exclude: Features.VendorPrefixes,
visitor: composeVisitors([urlVersioning(file)]), // Adds a hash to the url() parts of the static css
});

// Save optimized css file
await writeFile(
cssFile,
`@charset "UTF-8";
${contents}`,
contents.startsWith('@charset "UTF-8";')
? css
: `@charset "UTF-8";
${css}`,
{ encoding: 'utf8', mode: 0o644 },
);

const { code: cssMin } = transformCss({
code: Buffer.from(contents),
minify: true,
exclude: Features.VendorPrefixes,
visitor: composeVisitors([urlVersioning(cssFile)]), // Adds a hash to the url() parts of the static css
});

// Ensure the folder exists or create it
await ensureDir(dirname(cssFile.replace('.css', '.min.css')), {});
await writeFile(cssFile.replace('.css', '.min.css'), `@charset "UTF-8";${cssMin}`, { encoding: 'utf8', mode: 0o644 });
await writeFile(
cssFile.replace('.css', '.min.css'),
`@charset "UTF-8";${cssMin}`,
{ encoding: 'utf8', mode: 0o644 },
);

console.log(`✅ SCSS File compiled: ${cssFile}`);
};
14 changes: 3 additions & 11 deletions build/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
* node build.mjs --com-media will compile the media manager Vue application
* node build.mjs --watch-com-media will watch and compile the media manager Vue application
* node build.mjs --gzip will create gzip files for all the minified stylesheets and scripts.
* node build.mjs --cssversioning will update all the url entries providing accurate versions for stylesheets.
* node build.mjs --versioning will update all the joomla.assets.json files providing accurate versions for stylesheets and scripts.
*/

Expand All @@ -34,10 +33,10 @@ import { recreateMediaFolder } from './build-modules-js/init/recreate-media.mjs'
import { watching } from './build-modules-js/watch.mjs';
import { mediaManager, watchMediaManager } from './build-modules-js/javascript/build-com_media-js.mjs';
import { compressFiles } from './build-modules-js/compress.mjs';
import { cssVersioning } from './build-modules-js/css-versioning.mjs';
import { versioning } from './build-modules-js/versioning.mjs';
import { Timer } from './build-modules-js/utils/timer.mjs';
import { compileCodemirror } from './build-modules-js/javascript/build-codemirror.mjs';
import { cssVersioningVendor } from './build-modules-js/stylesheets/css-versioning.mjs';

const require = createRequire(import.meta.url);

Expand Down Expand Up @@ -103,10 +102,6 @@ Program.allowUnknownOption()
)
.option('--gzip', 'Compress all the minified stylesheets and scripts.')
.option('--prepare', 'Run all the needed tasks to initialise the repo')
.option(
'--cssversioning',
'Update all the url() versions on their relative stylesheet files',
)
.option(
'--versioning',
'Update all the .js/.css versions on their relative joomla.assets.json',
Expand All @@ -130,6 +125,7 @@ if (cliOptions.copyAssets) {
.then(() => cleanVendors())
.then(() => localisePackages(options))
.then(() => patchPackages(options))
.then(() => cssVersioningVendor())
.then(() => minifyVendor())
.catch((error) => handleError(error, 1));
}
Expand Down Expand Up @@ -185,11 +181,6 @@ if (cliOptions.versioning) {
versioning().catch((err) => handleError(err, 1));
}

// Update the url() versions in the .css files
if (cliOptions.cssversioning) {
cssVersioning().catch((err) => handleError(err, 1));
}

// Prepare the repo for dev work
if (cliOptions.prepare) {
const bench = new Timer('Build');
Expand All @@ -201,6 +192,7 @@ if (cliOptions.prepare) {
.then(() => minifyVendor())
.then(() => createErrorPages(options))
.then(() => stylesheets(options, Program.args[0]))
.then(() => cssVersioningVendor())
.then(() => scripts(options, Program.args[0]))
.then(() => mediaManager())
.then(() => bootstrapJs())
Expand Down
5 changes: 2 additions & 3 deletions build/build.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,9 @@ function capture_or_fail(string $command): string
exit(1);
}

run_and_check('npm install --unsafe-perm');

// Install dependencies and build the media assets
// Create version entries of the urls inside the static css files
run_and_check('npm run cssversioning');
run_and_check('npm ci');

// Create gzipped version of the static assets
run_and_check('npm run gzip');
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"install": "node build/build.mjs --prepare",
"update": "node build/build.mjs --copy-assets && node build/build.mjs --build-pages && node build/build.mjs --compile-js && node build/build.mjs --compile-css && node build/build.mjs --compile-bs && node --env-file=./build/production.env build/build.mjs --com-media",
"gzip": "node build/build.mjs --gzip",
"cssversioning": "node build/build.mjs --cssversioning",
"versioning": "node build/build.mjs --versioning",
"browserlist:update": "npx browserslist@latest --update-db",
"cypress:install": "cypress install",
Expand Down
Loading