map urls in theme css bundles to their content-hashed counterparts

This commit is contained in:
Bruno Windels 2020-08-14 10:45:14 +02:00
parent 044360afaa
commit 0104e14e0b
2 changed files with 35 additions and 13 deletions

View File

@ -38,6 +38,7 @@
"postcss-css-variables": "^0.17.0", "postcss-css-variables": "^0.17.0",
"postcss-flexbugs-fixes": "^4.2.1", "postcss-flexbugs-fixes": "^4.2.1",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-url": "^8.0.0",
"regenerator-runtime": "^0.13.7", "regenerator-runtime": "^0.13.7",
"rollup": "^1.15.6", "rollup": "^1.15.6",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",

View File

@ -34,6 +34,8 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
// multi-entry plugin so we can add polyfill file to main // multi-entry plugin so we can add polyfill file to main
import multi from '@rollup/plugin-multi-entry'; import multi from '@rollup/plugin-multi-entry';
// replace urls of asset names with content hashed version
import postcssUrl from "postcss-url";
import cssvariables from "postcss-css-variables"; import cssvariables from "postcss-css-variables";
import flexbugsFixes from "postcss-flexbugs-fixes"; import flexbugsFixes from "postcss-flexbugs-fixes";
@ -73,7 +75,7 @@ async function build() {
// so do it first // so do it first
const themeAssets = await copyThemeAssets(themes, legacy); const themeAssets = await copyThemeAssets(themes, legacy);
const jsBundlePath = await (legacy ? buildJsLegacy() : buildJs()); const jsBundlePath = await (legacy ? buildJsLegacy() : buildJs());
const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes); const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes, themeAssets);
const assetPaths = createAssetPaths(jsBundlePath, cssBundlePaths, themeAssets); const assetPaths = createAssetPaths(jsBundlePath, cssBundlePaths, themeAssets);
if (offline) { if (offline) {
@ -96,7 +98,7 @@ function createAssetPaths(jsBundlePath, cssBundlePaths, themeAssets) {
cssMainBundle: () => trim(cssBundlePaths.main), cssMainBundle: () => trim(cssBundlePaths.main),
cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]), cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]),
cssThemeBundles: () => Object.values(cssBundlePaths.themes).map(a => trim(a)), cssThemeBundles: () => Object.values(cssBundlePaths.themes).map(a => trim(a)),
otherAssets: () => themeAssets.map(a => trim(a)) otherAssets: () => Object.values(themeAssets).map(a => trim(a))
}; };
} }
@ -125,7 +127,7 @@ async function createDirs(targetDir, themes) {
} }
async function copyThemeAssets(themes, legacy) { async function copyThemeAssets(themes, legacy) {
const assets = []; const assets = {};
for (const theme of themes) { for (const theme of themes) {
const themeDstFolder = path.join(targetDir, `themes/${theme}`); const themeDstFolder = path.join(targetDir, `themes/${theme}`);
const themeSrcFolder = path.join(cssSrcDir, `themes/${theme}`); const themeSrcFolder = path.join(cssSrcDir, `themes/${theme}`);
@ -133,7 +135,7 @@ async function copyThemeAssets(themes, legacy) {
const isUnneededFont = legacy ? file.endsWith(".woff2") : file.endsWith(".woff"); const isUnneededFont = legacy ? file.endsWith(".woff2") : file.endsWith(".woff");
return !file.endsWith(".css") && !isUnneededFont; return !file.endsWith(".css") && !isUnneededFont;
}); });
assets.push(...themeAssets); Object.assign(assets, themeAssets);
} }
return assets; return assets;
} }
@ -250,13 +252,20 @@ async function buildOffline(version, assetPaths) {
await fs.writeFile(path.join(targetDir, "icon-192.png"), icon); await fs.writeFile(path.join(targetDir, "icon-192.png"), icon);
} }
async function buildCssBundles(buildFn, themes) { async function buildCssBundles(buildFn, themes, themeAssets) {
const bundleCss = await buildFn(path.join(cssSrcDir, "main.css")); const bundleCss = await buildFn(path.join(cssSrcDir, "main.css"));
const mainDstPath = resource(`${PROJECT_ID}.css`, bundleCss); const mainDstPath = resource(`${PROJECT_ID}.css`, bundleCss);
await fs.writeFile(mainDstPath, bundleCss, "utf8"); await fs.writeFile(mainDstPath, bundleCss, "utf8");
const bundlePaths = {main: mainDstPath, themes: {}}; const bundlePaths = {main: mainDstPath, themes: {}};
for (const theme of themes) { for (const theme of themes) {
const themeCss = await buildFn(path.join(cssSrcDir, `themes/${theme}/theme.css`)); const urlBase = path.join(targetDir, `themes/${theme}/`);
const assetUrlMapper = ({absolutePath}) => {
const hashedDstPath = themeAssets[absolutePath];
if (hashedDstPath && hashedDstPath.startsWith(urlBase)) {
return hashedDstPath.substr(urlBase.length);
}
};
const themeCss = await buildFn(path.join(cssSrcDir, `themes/${theme}/theme.css`), assetUrlMapper);
const themeDstPath = resource(`themes/${theme}/bundle.css`, themeCss); const themeDstPath = resource(`themes/${theme}/bundle.css`, themeCss);
await fs.writeFile(themeDstPath, themeCss, "utf8"); await fs.writeFile(themeDstPath, themeCss, "utf8");
bundlePaths.themes[theme] = themeDstPath; bundlePaths.themes[theme] = themeDstPath;
@ -264,16 +273,28 @@ async function buildCssBundles(buildFn, themes) {
return bundlePaths; return bundlePaths;
} }
async function buildCss(entryPath) { async function buildCss(entryPath, urlMapper = null) {
const preCss = await fs.readFile(entryPath, "utf8"); const preCss = await fs.readFile(entryPath, "utf8");
const cssBundler = postcss([postcssImport]); const options = [postcssImport];
if (urlMapper) {
options.push(postcssUrl({url: urlMapper}));
}
const cssBundler = postcss(options);
const result = await cssBundler.process(preCss, {from: entryPath}); const result = await cssBundler.process(preCss, {from: entryPath});
return result.css; return result.css;
} }
async function buildCssLegacy(entryPath) { async function buildCssLegacy(entryPath, urlMapper = null) {
const preCss = await fs.readFile(entryPath, "utf8"); const preCss = await fs.readFile(entryPath, "utf8");
const cssBundler = postcss([postcssImport, cssvariables(), flexbugsFixes()]); const options = [
postcssImport,
cssvariables(),
flexbugsFixes()
];
if (urlMapper) {
options.push(postcssUrl({url: urlMapper}));
}
const cssBundler = postcss(options);
const result = await cssBundler.process(preCss, {from: entryPath}); const result = await cssBundler.process(preCss, {from: entryPath});
return result.css; return result.css;
} }
@ -297,19 +318,19 @@ async function removeDirIfExists(targetDir) {
} }
async function copyFolder(srcRoot, dstRoot, filter) { async function copyFolder(srcRoot, dstRoot, filter) {
const assetPaths = []; const assetPaths = {};
const dirEnts = await fs.readdir(srcRoot, {withFileTypes: true}); const dirEnts = await fs.readdir(srcRoot, {withFileTypes: true});
for (const dirEnt of dirEnts) { for (const dirEnt of dirEnts) {
const dstPath = path.join(dstRoot, dirEnt.name); const dstPath = path.join(dstRoot, dirEnt.name);
const srcPath = path.join(srcRoot, dirEnt.name); const srcPath = path.join(srcRoot, dirEnt.name);
if (dirEnt.isDirectory()) { if (dirEnt.isDirectory()) {
await fs.mkdir(dstPath); await fs.mkdir(dstPath);
assetPaths.push(... await copyFolder(srcPath, dstPath, filter)); Object.assign(assetPaths, await copyFolder(srcPath, dstPath, filter));
} else if (dirEnt.isFile() && filter(srcPath)) { } else if (dirEnt.isFile() && filter(srcPath)) {
const content = await fs.readFile(srcPath); const content = await fs.readFile(srcPath);
const hashedDstPath = resource(dstPath, content); const hashedDstPath = resource(dstPath, content);
await fs.writeFile(hashedDstPath, content); await fs.writeFile(hashedDstPath, content);
assetPaths.push(hashedDstPath); assetPaths[srcPath] = hashedDstPath;
} }
} }
return assetPaths; return assetPaths;