Skip to content

Commit 00d35cf

Browse files
committed
inline async chunks in template tag
1 parent 1b3c681 commit 00d35cf

File tree

9 files changed

+151
-20
lines changed

9 files changed

+151
-20
lines changed

build/webpack/plugins.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const
1515
config = require('@config/config'),
1616
webpack = require('webpack');
1717

18+
const AsyncChunksPlugin = include('build/webpack/plugins/async-chunks-plugin');
19+
1820
/**
1921
* Returns options for `webpack.plugins`
2022
* @returns {!Map}
@@ -54,9 +56,7 @@ module.exports = async function plugins({name}) {
5456
}
5557

5658
if (config.webpack.fatHTML()) {
57-
plugins.set('limit-chunk-count-plugin', new webpack.optimize.LimitChunkCountPlugin({
58-
maxChunks: 1
59-
}));
59+
plugins.set('async-chunk-plugin', new AsyncChunksPlugin());
6060
}
6161

6262
return plugins;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# build/webpack/plugins/async-chunks-plugin
2+
3+
This module provides a plugin that gathers information about asynchronous chunks and modifies the webpack runtime to load asynchronous modules from shadow storage in fat-html.
4+
5+
## Gathering Information
6+
7+
During the initial phase, the plugin gathers information about all emitted asynchronous chunks. This information is stored in a JSON file within the output directory and later used to inline those scripts into the HTML using a special template tag.
8+
9+
## Patching the Webpack Runtime
10+
11+
The plugin replaces the standard RuntimeGlobals.loadScript script. The new script attempts to locate a template tag with the ID of the chunk name and adds the located script to the page. If there is no such template with the script, the standard method is called to load the chunk from the network.
12+
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
'use strict';
2+
3+
/*!
4+
* V4Fire Client Core
5+
* https://github.com/V4Fire/Client
6+
*
7+
* Released under the MIT license
8+
* https://github.com/V4Fire/Client/blob/master/LICENSE
9+
*/
10+
11+
const fs = require('fs');
12+
const path = require('path');
13+
const RuntimeModule = require('webpack/lib/RuntimeModule');
14+
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
15+
16+
const {webpack} = require('@config/config');
17+
18+
class AsyncPlugRuntimeModule extends RuntimeModule {
19+
constructor() {
20+
super('async chunk loader for fat-html', RuntimeModule.STAGE_ATTACH);
21+
}
22+
23+
generate() {
24+
return `const loadScript = ${RuntimeGlobals.loadScript};
25+
${RuntimeGlobals.loadScript} = (path, cb, chunk, id) => {
26+
const tpl = document.getElementById(id);
27+
if (tpl?.content) {
28+
document.body.appendChild(tpl.content.cloneNode(true));
29+
cb();
30+
} else {
31+
loadScript(path, cb, chunk, id);
32+
}
33+
}`;
34+
}
35+
}
36+
37+
class Index {
38+
apply(compiler) {
39+
compiler.hooks.thisCompilation.tap(
40+
'AsyncChunksPlugin',
41+
(compilation) => {
42+
const onceForChunkSet = new WeakSet();
43+
44+
compilation.hooks.runtimeRequirementInTree
45+
.for(RuntimeGlobals.ensureChunkHandlers)
46+
.tap('AsyncChunksPlugin', (chunk, set) => {
47+
if (onceForChunkSet.has(chunk)) {
48+
return;
49+
}
50+
51+
onceForChunkSet.add(chunk);
52+
53+
const runtimeModule = new AsyncPlugRuntimeModule();
54+
set.add(RuntimeGlobals.loadScript);
55+
compilation.addRuntimeModule(chunk, runtimeModule);
56+
});
57+
}
58+
);
59+
60+
compiler.hooks.emit.tapAsync('AsyncChunksPlugin', (compilation, callback) => {
61+
const asyncChunks = [];
62+
if (compilation.name !== 'runtime') {
63+
callback();
64+
return;
65+
}
66+
67+
compilation.chunks.forEach((chunk) => {
68+
if (chunk.canBeInitial()) {
69+
return;
70+
}
71+
72+
asyncChunks.push({
73+
id: chunk.id,
74+
files: chunk.files.map((filename) => filename)
75+
});
76+
});
77+
78+
const outputPath = path.join(compiler.options.output.path, webpack.asyncAssetsJSON());
79+
80+
fs.writeFile(outputPath, JSON.stringify(asyncChunks, null, 2), (err) => {
81+
if (err) {
82+
compilation.errors.push(new Error(`Error write async chunks list to ${outputPath}`));
83+
}
84+
85+
callback();
86+
});
87+
});
88+
}
89+
}
90+
91+
module.exports = Index;

components-lock.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"hash": "8a737608822631444b83b6d0279de056239be7d66672d3613cc7b86852b58ef8",
2+
"hash": "2f5fada731c323e0c1d179b63b9e2b95ede66f4f042e15b7240ea7fcf646baa5",
33
"data": {
44
"%data": "%data:Map",
55
"%data:Map": [
@@ -2066,9 +2066,6 @@
20662066
"parent": "i-static-page",
20672067
"dependencies": [
20682068
"b-v4-component-demo",
2069-
"p-v4-dynamic-page1",
2070-
"p-v4-dynamic-page2",
2071-
"p-v4-dynamic-page3",
20722069
"b-remote-provider",
20732070
"b-router",
20742071
"b-dynamic-page",
@@ -2113,9 +2110,6 @@
21132110
"parent": "i-static-page",
21142111
"dependencies": [
21152112
"b-v4-component-demo",
2116-
"p-v4-dynamic-page1",
2117-
"p-v4-dynamic-page2",
2118-
"p-v4-dynamic-page3",
21192113
"b-remote-provider",
21202114
"b-router",
21212115
"b-dynamic-page",

config/default.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,23 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, {
777777
*/
778778
assetsJS() {
779779
return path.changeExt(this.assetsJSON(), '.js');
780+
},
781+
782+
/**
783+
* Returns the path to the generated async assets chunks list within the output directory.
784+
* It contains an array of async chunks and their file names to inline them into fat-html.
785+
* ...
786+
* [
787+
* {
788+
* id: 'chunk_id',
789+
* files: ['filename.ext']
790+
* }
791+
* ]
792+
*
793+
* @return {string}
794+
*/
795+
asyncAssetsJSON() {
796+
return 'async-chunks-to-inline.json';
780797
}
781798
},
782799

fat-html.components-lock.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"hash": "dec16388517a24917e76c3c083008795ea8f7ba29b3b8b96508ca3ab26f0024f",
2+
"hash": "ad63996757b87e2a19595d19f380d8d72ce5fd3922ed0f3d5b1286c2a68653b2",
33
"data": {
44
"%data": "%data:Map",
55
"%data:Map": [
@@ -2066,9 +2066,6 @@
20662066
"parent": "i-static-page",
20672067
"dependencies": [
20682068
"b-v4-component-demo",
2069-
"p-v4-dynamic-page1",
2070-
"p-v4-dynamic-page2",
2071-
"p-v4-dynamic-page3",
20722069
"b-remote-provider",
20732070
"b-router",
20742071
"b-dynamic-page",
@@ -2113,9 +2110,6 @@
21132110
"parent": "i-static-page",
21142111
"dependencies": [
21152112
"b-v4-component-demo",
2116-
"p-v4-dynamic-page1",
2117-
"p-v4-dynamic-page2",
2118-
"p-v4-dynamic-page3",
21192113
"b-remote-provider",
21202114
"b-router",
21212115
"b-dynamic-page",

src/pages/p-v4-components-demo/index.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ package('p-v4-components-demo')
1313
.extends('i-static-page')
1414
.dependencies(
1515
'b-v4-component-demo',
16-
'p-v4-dynamic-page1',
17-
'p-v4-dynamic-page2',
18-
'p-v4-dynamic-page3',
1916

2017
'b-remote-provider',
2118
'b-router',

src/super/i-static-page/i-static-page.interface.ss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
+= h.getPageStyleDepsDecl(ownDeps, {assets, wrap: true})
163163

164164
- block scripts
165+
+= h.getPageAsyncScripts()
165166
+= h.getScriptDeclByName('std', {assets, optional: true, wrap: true})
166167
+= await h.loadLibs(deps.scripts, {assets, wrap: true, js: true})
167168

src/super/i-static-page/modules/ss-helpers/page.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ function getPageScriptDepsDecl(dependencies, {assets, wrap} = {}) {
8181
return decl;
8282
}
8383

84+
exports.getPageAsyncScripts = getPageAsyncScripts;
85+
86+
function getPageAsyncScripts() {
87+
if (!needInline()) {
88+
return '';
89+
}
90+
91+
const
92+
fileName = webpack.asyncAssetsJSON(),
93+
filePath = src.clientOutput(fileName);
94+
95+
try {
96+
const
97+
fileContent = fs.readFileSync(filePath, 'utf-8'),
98+
asyncChunks = JSON.parse(fileContent);
99+
100+
return asyncChunks.reduce((result, chunk) => `${result}<template id="${chunk.id}"><script>${
101+
chunk.files.map((fileName) => `include('${src.clientOutput(fileName)}');\n`).join()
102+
}</script></template>`, '');
103+
104+
} catch (e) {
105+
return '';
106+
}
107+
}
108+
84109
exports.getPageStyleDepsDecl = getPageStyleDepsDecl;
85110

86111
/**

0 commit comments

Comments
 (0)