Skip to content

Commit 80ae7a9

Browse files
authored
2494 load extra bundles (#3689)
* adds async componenets and placeholders * verifies bundles config at init time * adds i18n key for bundles config warning * stores verified bundles in self.extraBundles to be accessible at page load * computes and passes widgets dependants bundles in req * adds methods to fill page template with needed bundles at page load time * modifies and adds test to verify that bundles are built, and that pages are loading the rights bundles * adds a test for logged users * adds changelog * names the bundles right depending if es5 or not
1 parent 489cd2e commit 80ae7a9

File tree

28 files changed

+617
-96
lines changed

28 files changed

+617
-96
lines changed

.eslintignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
**/vendor/**/*.js
22
**/blueimp/**/*.js
3-
**/node_modules
3+
**/node_modules
4+
test/public
5+
test/apos-build

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ coverage
1616
/node_modules
1717
/test/node_modules
1818
test/package.json
19+
test/public
20+
test/apos-build
1921

2022
# We do not commit CSS, only LESS, with the exception of a few vendor CSS files we don't have LESS for
2123
*/public/css/*.css

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
* `data-apos-test=""` selectors for certain elements frequently selected in QA tests, such as `data-apos-test="adminBar"`.
88
* Offer a simple way to set a Cache-Control max-age for Apostrophe page and GET REST API responses for pieces and pages.
9-
9+
* Adds possibility for modules to extend the webpack config as well as to add extra bundles for scss and js.
10+
* Loads the right bundles on the right pages depending on their config and the loaded widgets. Logged-in users have all the bundles on every page.
1011
## 3.15.0 (2022-03-02)
1112

1213
### Adds

modules/@apostrophecms/asset/index.js

+55-34
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ const path = require('path');
77
const express = require('express');
88
const { stripIndent } = require('common-tags');
99
const { merge: webpackMerge } = require('webpack-merge');
10+
const cuid = require('cuid');
1011
const {
1112
checkModulesWebpackConfig,
1213
getWebpackExtensions,
13-
fillExtraBundles
14+
fillExtraBundles,
15+
getBundlesNames
1416
} = require('./lib/webpack/utils');
1517

1618
module.exports = {
@@ -28,13 +30,22 @@ module.exports = {
2830
refreshOnRestart: false
2931
},
3032

31-
init(self) {
33+
async init(self) {
3234
self.restartId = self.apos.util.generateId();
3335
self.iconMap = {
3436
...globalIcons
3537
};
3638
self.configureBuilds();
3739
self.initUploadfs();
40+
41+
const { extensions, verifiedBundles } = await getWebpackExtensions({
42+
getMetadata: self.apos.synth.getMetadata,
43+
modulesToInstantiate: self.apos.modulesToBeInstantiated()
44+
});
45+
46+
self.extraBundles = fillExtraBundles(verifiedBundles);
47+
self.webpackExtensions = extensions;
48+
self.verifiedBundles = verifiedBundles;
3849
},
3950
handlers (self) {
4051
return {
@@ -56,6 +67,10 @@ module.exports = {
5667
'check-apos-build': true
5768
});
5869
}
70+
},
71+
injectAssetsPlaceholders() {
72+
self.apos.template.prepend('head', '@apostrophecms/asset:stylesheets');
73+
self.apos.template.append('body', '@apostrophecms/asset:scripts');
5974
}
6075
},
6176
'apostrophe:destroy': {
@@ -67,6 +82,28 @@ module.exports = {
6782
}
6883
};
6984
},
85+
components(self) {
86+
return {
87+
scripts(req, data) {
88+
const placeholder = `[scripts-placeholder:${cuid()}]`;
89+
90+
req.scriptsPlaceholder = placeholder;
91+
92+
return {
93+
placeholder
94+
};
95+
},
96+
stylesheets(req, data) {
97+
const placeholder = `[stylesheets-placeholder:${cuid()}]`;
98+
99+
req.stylesheetsPlaceholder = placeholder;
100+
101+
return {
102+
placeholder
103+
};
104+
}
105+
};
106+
},
70107
tasks(self) {
71108
return {
72109
build: {
@@ -78,7 +115,6 @@ module.exports = {
78115
const buildDir = `${self.apos.rootDir}/apos-build/${namespace}`;
79116
const bundleDir = `${self.apos.rootDir}/public/apos-frontend/${namespace}`;
80117
const modulesToInstantiate = self.apos.modulesToBeInstantiated();
81-
const extraBundles = [];
82118

83119
// Don't clutter up with previous builds.
84120
await fs.remove(buildDir);
@@ -133,8 +169,7 @@ module.exports = {
133169
await fs.mkdirp(bundleDir);
134170
await build({
135171
name,
136-
options,
137-
bundles: extraBundles
172+
options
138173
});
139174
}
140175
}
@@ -153,7 +188,12 @@ module.exports = {
153188
cwd: bundleDir,
154189
mark: true
155190
}).filter(match => !match.endsWith('/'));
156-
deployFiles = [ ...deployFiles, ...publicAssets, ...extraBundles ];
191+
deployFiles = [
192+
...deployFiles,
193+
...publicAssets,
194+
...getBundlesNames(self.extraBundles, self.options.es5)
195+
];
196+
157197
await deploy(deployFiles);
158198

159199
if (process.env.APOS_BUNDLE_ANALYZER) {
@@ -198,7 +238,7 @@ module.exports = {
198238
}
199239

200240
async function build({
201-
name, bundles, options
241+
name, options
202242
}) {
203243
self.apos.util.log(req.t('apostrophe:assetTypeBuilding', {
204244
label: req.t(options.label)
@@ -270,24 +310,16 @@ module.exports = {
270310
const webpack = Promise.promisify(webpackModule);
271311
const webpackBaseConfig = require(`./lib/webpack/${name}/webpack.config`);
272312

273-
const { extensions, verifiedBundles } = await getWebpackExtensions({
274-
name,
275-
getMetadata: self.apos.synth.getMetadata,
276-
modulesToInstantiate
277-
});
278-
279-
verifiedBundles && fillExtraBundles(verifiedBundles, bundles);
280-
281313
const webpackInstanceConfig = webpackBaseConfig({
282314
importFile,
283315
modulesDir,
284316
outputPath: bundleDir,
285317
outputFilename,
286-
bundles: verifiedBundles
318+
bundles: self.verifiedBundles
287319
}, self.apos);
288320

289-
const webpackInstanceConfigMerged = extensions
290-
? webpackMerge(webpackInstanceConfig, ...Object.values(extensions))
321+
const webpackInstanceConfigMerged = self.webpackExtensions
322+
? webpackMerge(webpackInstanceConfig, ...Object.values(self.webpackExtensions))
291323
: webpackInstanceConfig;
292324

293325
const result = await webpack(webpackInstanceConfigMerged);
@@ -410,8 +442,9 @@ module.exports = {
410442
};
411443

412444
const filesContent = Object.entries(self.builds)
413-
.filter(([ _, options ]) => filterBuilds(options)
414-
).map(([ name ]) => {
445+
.filter(([ _, options ]) => filterBuilds(options))
446+
.map(([ name ]) => {
447+
415448
const file = `${bundleDir}/${name}-build.${fileExt}`;
416449
const readFile = (n, f) => `/* BUILD: ${n} */\n${fs.readFileSync(f, 'utf8')}`;
417450

@@ -594,22 +627,10 @@ module.exports = {
594627
}
595628
},
596629
stylesheetsHelper(when) {
597-
const base = self.getAssetBaseUrl();
598-
const bundle = `<link href="${base}/${when}-bundle.css" rel="stylesheet" />`;
599-
return self.apos.template.safe(bundle);
630+
return '';
600631
},
601632
scriptsHelper(when) {
602-
const base = self.getAssetBaseUrl();
603-
if (self.options.es5) {
604-
return self.apos.template.safe(stripIndent`
605-
<script nomodule src="${base}/${when}-nomodule-bundle.js"></script>
606-
<script type="module" src="${base}/${when}-module-bundle.js"></script>
607-
`);
608-
} else {
609-
return self.apos.template.safe(stripIndent`
610-
<script src="${base}/${when}-module-bundle.js"></script>
611-
`);
612-
}
633+
return '';
613634
},
614635
shouldRefreshOnRestart() {
615636
return self.options.refreshOnRestart && (process.env.NODE_ENV !== 'production');

modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js

+20-7
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,24 @@ module.exports = ({
6868
};
6969

7070
function formatBundles (bundles, mainBundleName) {
71-
return bundles.reduce((acc, { bundleName, paths }) => ({
72-
...acc,
73-
[bundleName]: {
74-
import: paths,
75-
dependOn: mainBundleName
76-
}
77-
}), {});
71+
return bundles.reduce((acc, { bundleName, paths }) => {
72+
const jsPaths = paths.filter((p) => p.endsWith('.js'));
73+
const scssPaths = paths.filter((p) => p.endsWith('.scss'));
74+
75+
return {
76+
...acc,
77+
...jsPaths.length && {
78+
[`${bundleName}-module-bundle`]: {
79+
import: jsPaths,
80+
dependOn: mainBundleName
81+
}
82+
},
83+
...scssPaths.length && {
84+
[`${bundleName}-bundle`]: {
85+
import: scssPaths,
86+
dependOn: mainBundleName
87+
}
88+
}
89+
};
90+
}, {});
7891
}

modules/@apostrophecms/asset/lib/webpack/utils.js

+55-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const fs = require('fs-extra');
33
module.exports = {
44
checkModulesWebpackConfig(modules, t) {
55
const allowedProperties = [ 'extensions', 'bundles' ];
6-
76
for (const mod of Object.values(modules)) {
87
const webpackConfig = mod.__meta.webpack[mod.__meta.name];
98

@@ -23,16 +22,31 @@ module.exports = {
2322

2423
throw new Error(error);
2524
}
25+
26+
if (webpackConfig && webpackConfig.bundles) {
27+
const bundles = Object.values(webpackConfig.bundles);
28+
29+
bundles.forEach(bundle => {
30+
const bundleProps = Object.keys(bundle);
31+
if (
32+
bundleProps.length > 1 ||
33+
(bundleProps.length === 1 && !bundle.templates) ||
34+
(bundle.templates && !Array.isArray(bundle.templates))
35+
) {
36+
const error = t('apostrophe:assetWebpackBundlesWarning', {
37+
module: mod.__meta.name
38+
});
39+
40+
throw new Error(error);
41+
}
42+
});
43+
}
2644
}
2745
},
2846

2947
async getWebpackExtensions ({
30-
name, getMetadata, modulesToInstantiate
48+
getMetadata, modulesToInstantiate
3149
}) {
32-
if (name !== 'src') {
33-
return {};
34-
}
35-
3650
const modulesMeta = modulesToInstantiate
3751
.map((name) => getMetadata(name));
3852

@@ -48,17 +62,43 @@ module.exports = {
4862
};
4963
},
5064

51-
fillExtraBundles (verifiedBundles, bundles) {
52-
const bundlesPaths = verifiedBundles
53-
.reduce((acc, { paths }) => ([
54-
...acc,
55-
...paths.map((p) => p.substr(p.lastIndexOf('/') + 1)
56-
.replace(/\.scss$/, '.css'))
57-
]), []);
65+
fillExtraBundles (verifiedBundles = []) {
66+
const getFileName = (p) => p.substr(p.lastIndexOf('/') + 1)
67+
.replace(/\.(js|scss)$/, '');
5868

59-
bundlesPaths.forEach(bundle => {
60-
bundles.push(bundle);
69+
return verifiedBundles.reduce((acc, { paths }) => {
70+
return {
71+
js: [
72+
...acc.js,
73+
...paths.filter((p) => p.endsWith('.js')).map((p) => getFileName(p))
74+
],
75+
css: [
76+
...acc.css,
77+
...paths.filter((p) => p.endsWith('.scss')).map((p) => getFileName(p))
78+
]
79+
};
80+
}, {
81+
js: [],
82+
css: []
6183
});
84+
},
85+
86+
getBundlesNames (bundles, es5 = false) {
87+
return Object.entries(bundles).reduce((acc, [ ext, bundlesNames ]) => {
88+
const nameExtension = ext === 'css'
89+
? '-bundle'
90+
: '-module-bundle';
91+
92+
const es5Bundles = es5 && ext === 'js'
93+
? bundlesNames.map((name) => `${name}-nomodule-bundle.${ext}`)
94+
: [];
95+
96+
return [
97+
...acc,
98+
...bundlesNames.map((name) => `${name}${nameExtension}.${ext}`),
99+
...es5Bundles
100+
];
101+
}, []);
62102
}
63103
};
64104

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ data.placeholder }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ data.placeholder }}

modules/@apostrophecms/i18n/i18n/en.json

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"assetTypeBuildComplete": "👍 {{ label }} is complete!",
3232
"assetTypeBuilding": "🧑‍💻 Building the {{ label }}...",
3333
"assetWebpackConfigWarning": "⚠️ In the module {{ module }}, your webpack config is incorrect. It must be an object and should contain only two properties extensions and bundles.",
34+
"assetWebpackBundlesWarning": "⚠️ In the module {{ module }} your webpack config is incorrect. Each bundle can only have one property 'templates' that must be an array of strings.",
3435
"back": "Back",
3536
"backToHome": "Back to Home",
3637
"basics": "Basics",

modules/@apostrophecms/template/index.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ module.exports = {
119119
},
120120
methods(self) {
121121
return {
122+
...require('./lib/bundlesLoader')(self),
122123

123124
// Add helpers in the namespace for a particular module.
124125
// They will be visible in nunjucks at
@@ -630,8 +631,6 @@ module.exports = {
630631
// async function.
631632

632633
async renderPageForModule(req, template, data, module) {
633-
634-
let content;
635634
let scene = req.user ? 'apos' : 'public';
636635
if (req.scene) {
637636
scene = req.scene;
@@ -698,15 +697,25 @@ module.exports = {
698697
}
699698

700699
try {
701-
content = await module.render(req, template, args);
700+
const content = await module.render(req, template, args);
701+
702+
const filledContent = self.insertBundlesMarkup({
703+
page: req.data.bestPage,
704+
scene,
705+
template,
706+
content,
707+
scriptsPlaceholder: req.scriptsPlaceholder,
708+
stylesheetsPlaceholder: req.stylesheetsPlaceholder,
709+
widgetsBundles: req.widgetsBundles
710+
});
711+
712+
return filledContent;
702713
} catch (e) {
703714
// The page template threw an exception. Log where it
704715
// occurred for easier debugging
705716
return error(e);
706717
}
707718

708-
return content;
709-
710719
function error(e) {
711720
self.logError(req, e);
712721
req.statusCode = 500;

0 commit comments

Comments
 (0)