Skip to content

Commit f305848

Browse files
authored
[4.0] Proper cache invalidation of the static assets [.js/.css] (#32485)
1 parent 6e6a12b commit f305848

File tree

13 files changed

+201
-37
lines changed

13 files changed

+201
-37
lines changed

build/build-modules-js/compilecss.es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,5 @@ module.exports.stylesheets = async (options, path) => {
8484
scssFilesPromises.push(handleScssFile(file, outputFile));
8585
}
8686

87-
await Promise.all([...cssFilesPromises, ...scssFilesPromises]);
87+
return Promise.all([...cssFilesPromises, ...scssFilesPromises]);
8888
};

build/build-modules-js/compress.es6.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const { getFiles } = require('@dgrammatiko/compress/src/getFiles.js');
22
const { compressFile } = require('@dgrammatiko/compress/src/compressFile.js');
3+
const { Timer } = require('./utils/timer.es6.js');
34

45
/**
56
* Method that will pre compress (gzip) all .css/.js files
67
* in the templates and in the media folder
78
*/
89
module.exports.compressFiles = async (enableBrotli = false) => {
10+
const bench = new Timer('Gzip');
911
const paths = [
1012
`${process.cwd()}/media`,
1113
`${process.cwd()}/installation/template`,
@@ -23,4 +25,5 @@ module.exports.compressFiles = async (enableBrotli = false) => {
2325
await Promise.all(compressTasks);
2426
// eslint-disable-next-line no-console
2527
console.log('Done 👍');
28+
bench.stop();
2629
};

build/build-modules-js/error-pages.es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ module.exports.createErrorPages = async (options) => {
155155

156156
Object.keys(options.settings.errorPages).forEach((name) => processPages.push(processPage(name)));
157157

158-
await Promise.all(processPages).catch((err) => {
158+
return Promise.all(processPages).catch((err) => {
159159
// eslint-disable-next-line no-console
160160
console.error(err);
161161
process.exit(-1);

build/build-modules-js/javascript/build-bootstrap-js.es6.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ const buildLegacy = async () => {
132132
await bundle.write({
133133
format: 'iife',
134134
sourcemap: false,
135-
name: 'Bootstrap',
135+
name: 'bootstrap',
136136
file: resolve(outputFolder, 'bootstrap-es5.js'),
137137
});
138138

build/build-modules-js/javascript/build-com_media-js.es6.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ module.exports.mediaManager = async () => {
110110

111111
// eslint-disable-next-line no-console
112112
console.log('ES2017 Media Manager ready ✅');
113-
114113
minifyJs('media/com_media/js/media-manager.js');
115114
return buildLegacy(resolve('media/com_media/js/media-manager.js'));
116115
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Debounce
3+
* https://gist.github.com/nmsdvid/8807205
4+
*
5+
* @param { function } callback The callback function to be executed
6+
* @param { int } time The time to wait before firing the callback
7+
* @param { int } interval The interval
8+
*/
9+
// eslint-disable-next-line max-len, no-param-reassign, no-return-assign
10+
module.exports.debounce = (callback, time = 250, interval) => (...args) => clearTimeout(interval, interval = setTimeout(callback, time, ...args));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { createHash } = require('crypto');
2+
const { createReadStream } = require('fs');
3+
4+
/**
5+
* Get an SHA1 hash for a given file
6+
* @param filePath
7+
* @returns {Promise<unknown>}
8+
*/
9+
module.exports.createHashFromFile = (filePath) => new Promise((res) => {
10+
const hash = createHash('sha1');
11+
createReadStream(filePath).on('data', (data) => hash.update(data)).on('end', () => res(hash.digest('hex')));
12+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Simple timer
3+
*
4+
* @param name
5+
* @returns {{stop: stop}}
6+
*/
7+
class Timer {
8+
constructor(name) {
9+
this.start = new Date();
10+
this.name = name;
11+
}
12+
13+
stop() {
14+
const end = new Date();
15+
const time = end.getTime() - this.start.getTime();
16+
// eslint-disable-next-line no-console
17+
console.log('Timer:', this.name, 'finished in', time, 'ms');
18+
}
19+
}
20+
21+
module.exports.Timer = Timer;
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const {
2+
lstat, readdir, readFile, writeFile,
3+
} = require('fs-extra');
4+
const {
5+
basename, dirname, resolve, sep,
6+
} = require('path');
7+
const { createHashFromFile } = require('./utils/hashfromfile.es6.js');
8+
const { Timer } = require('./utils/timer.es6.js');
9+
10+
const RootPath = process.cwd();
11+
const exclusion = [
12+
// We will skip these:
13+
'.DS_Store',
14+
'index.html',
15+
'cache',
16+
'vendors',
17+
];
18+
const final = {};
19+
20+
/**
21+
* Update a given joomla.assets.json entry
22+
*
23+
* @param asset
24+
* @param directory
25+
* @returns {Promise<{type}|*>}
26+
*/
27+
const updateAsset = async (asset, directory) => {
28+
const currentDir = `${RootPath}${sep}media${sep}${directory}`;
29+
if (!asset.type) {
30+
final[directory].push(asset);
31+
return;
32+
}
33+
let subDir;
34+
if (asset.type === 'script') {
35+
subDir = 'js';
36+
}
37+
if (asset.type === 'style') {
38+
subDir = 'css';
39+
}
40+
if (!subDir) {
41+
final[directory].push(asset);
42+
return;
43+
}
44+
45+
let path = `${currentDir}${sep}${subDir}${sep}${basename(asset.uri)}`;
46+
if (`${directory}/${basename(asset.uri)}` !== asset.uri) {
47+
if (dirname(asset.uri) === 'system/fields') {
48+
path = `${currentDir}${sep}${subDir}${sep}fields${sep}${basename(asset.uri)}`;
49+
} else {
50+
final[directory].push(asset);
51+
return;
52+
}
53+
}
54+
55+
const jAssetFile = await lstat(path);
56+
57+
if (!jAssetFile.isFile()) {
58+
final[directory].push(asset);
59+
return;
60+
}
61+
62+
const hash = await createHashFromFile(path);
63+
64+
asset.version = hash;
65+
final[directory].push(asset);
66+
};
67+
68+
/**
69+
* Read the joomla.assets.json and loop the assets
70+
*
71+
* @param directory
72+
* @returns {Promise<void>}
73+
*/
74+
const fixVersion = async (directory) => {
75+
let jAssetFile;
76+
try {
77+
jAssetFile = await lstat(`${RootPath}${sep}media${sep}${directory}${sep}joomla.asset.json`);
78+
} catch (err) {
79+
return;
80+
}
81+
82+
if (!jAssetFile.isFile()) {
83+
return;
84+
}
85+
86+
const jAssetFileContent = await readFile(`${RootPath}${sep}media${sep}${directory}${sep}joomla.asset.json`, { encoding: 'utf8' });
87+
let jsonData;
88+
try {
89+
jsonData = JSON.parse(jAssetFileContent);
90+
} catch (err) {
91+
throw new Error(`media\\${directory}\\joomla.asset.json is not a valid JSON file!!!`);
92+
}
93+
94+
if (!jsonData || !jsonData.assets.length) {
95+
return;
96+
}
97+
98+
const processes = [];
99+
jsonData.assets.map((asset) => processes.push(updateAsset(asset, directory)));
100+
101+
await Promise.all(processes);
102+
103+
jsonData.assets = final[directory];
104+
await writeFile(`${RootPath}${sep}media${sep}${directory}${sep}joomla.asset.json`, JSON.stringify(jsonData, '', 2), { encoding: 'utf8' });
105+
};
106+
107+
/**
108+
* Loop the media folder and add version to all .js/.css entries in all
109+
* the joomla.assets.json files
110+
*
111+
* @returns {Promise<void>}
112+
*/
113+
module.exports.versioning = async () => {
114+
const bench = new Timer('Versioning');
115+
const tasks = [];
116+
let mediaDirectories = await readdir(resolve(RootPath, 'media'));
117+
mediaDirectories = mediaDirectories.filter((dir) => !exclusion.includes(dir));
118+
119+
mediaDirectories.forEach((directory) => {
120+
final[directory] = [];
121+
tasks.push(fixVersion(directory));
122+
});
123+
124+
await Promise.all(tasks);
125+
126+
bench.stop();
127+
};

build/build-modules-js/watch.es6.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,10 @@ const watch = require('watch');
22
const { join, extname } = require('path');
33
const { handleESMFile } = require('./javascript/compile-to-es2017.es6.js');
44
const { handleES5File } = require('./javascript/handle-es5.es6.js');
5+
const { debounce } = require('./utils/debounce.es6.js');
56

67
const RootPath = process.cwd();
78

8-
/**
9-
* Debounce
10-
* https://gist.github.com/nmsdvid/8807205
11-
*
12-
* @param { function } callback The callback function to be executed
13-
* @param { int } time The time to wait before firing the callback
14-
* @param { int } interval The interval
15-
*/
16-
// eslint-disable-next-line max-len, no-param-reassign, no-return-assign
17-
const debounce = (callback, time = 250, interval) => (...args) => clearTimeout(interval, interval = setTimeout(callback, time, ...args));
18-
199
module.exports.watching = () => {
2010
watch.createMonitor(join(RootPath, 'build/media_source'), (monitor) => {
2111
monitor.on('created', (file) => {

0 commit comments

Comments
 (0)