Skip to content
Draft
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
7 changes: 1 addition & 6 deletions build/monic/attach-component-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
*/

const
$C = require('collection.js'),
{webpack} = require('@config/config');
$C = require('collection.js');

const
path = require('upath'),
Expand All @@ -29,10 +28,6 @@ const
* @returns {!Promise<string>}
*/
module.exports = async function attachComponentDependencies(str, filePath) {
if (webpack.fatHTML()) {
return str;
}

const
{components} = await graph;

Expand Down
7 changes: 3 additions & 4 deletions build/monic/dynamic-component-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/

const
{typescript, webpack} = require('@config/config'),
{typescript} = require('@config/config'),
{commentModuleExpr: commentExpr} = include('build/const');

const importRgxp = new RegExp(
Expand All @@ -19,8 +19,7 @@ const importRgxp = new RegExp(

const
hasImport = importRgxp.removeFlags('g'),
isESImport = typescript().client.compilerOptions.module === 'ES2020',
fatHTML = webpack.fatHTML();
isESImport = typescript().client.compilerOptions.module === 'ES2020';

/**
* Monic replacer to enable dynamic imports of components
Expand Down Expand Up @@ -63,7 +62,7 @@ module.exports = function dynamicComponentImportReplacer(str) {
imports.push(decl);
}

if (!fatHTML) {
{
let
decl;

Expand Down
17 changes: 1 addition & 16 deletions build/webpack/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ module.exports = async function module({plugins}) {
g = await projectGraph,
isProd = webpack.mode() === 'production';

const
fatHTML = webpack.fatHTML();

const loaders = {
rules: new Map()
};
Expand All @@ -80,18 +77,13 @@ module.exports = async function module({plugins}) {
{
loader: 'monic-loader',
options: inherit(monic.typescript, {
replacers: [].concat(
fatHTML ?
[] :
replacers: [
include('build/monic/attach-component-dependencies'),

[
include('build/monic/require-context'),
include('build/monic/super-import'),
include('build/monic/ts-import'),
include('build/monic/dynamic-component-import')
]
)
})
}
];
Expand Down Expand Up @@ -243,13 +235,6 @@ module.exports = async function module({plugins}) {
}
},

'extract-loader',

{
loader: 'html-loader',
options: config.html()
},

{
loader: 'monic-loader',
options: inherit(monic.html, {
Expand Down
6 changes: 3 additions & 3 deletions build/webpack/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const
config = require('@config/config'),
webpack = require('webpack');

const AsyncChunksPlugin = include('build/webpack/plugins/async-chunks-plugin');

/**
* Returns options for `webpack.plugins`
* @returns {!Map}
Expand Down Expand Up @@ -54,9 +56,7 @@ module.exports = async function plugins({name}) {
}

if (config.webpack.fatHTML()) {
plugins.set('limit-chunk-count-plugin', new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}));
plugins.set('async-chunk-plugin', new AsyncChunksPlugin());
}

return plugins;
Expand Down
12 changes: 12 additions & 0 deletions build/webpack/plugins/async-chunks-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# build/webpack/plugins/async-chunks-plugin

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.

## Gathering Information

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.

## Patching the Webpack Runtime

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.

109 changes: 109 additions & 0 deletions build/webpack/plugins/async-chunks-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict';

/*!
* V4Fire Client Core
* https://github.com/V4Fire/Client
*
* Released under the MIT license
* https://github.com/V4Fire/Client/blob/master/LICENSE
*/

const fs = require('fs');
const path = require('path');
const RuntimeModule = require('webpack/lib/RuntimeModule');
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');

const {webpack} = require('@config/config');

class AsyncPlugRuntimeModule extends RuntimeModule {
constructor() {
super('async chunk loader for fat-html', RuntimeModule.STAGE_ATTACH);
}

generate() {
return `var loadScript = ${RuntimeGlobals.loadScript};
function loadScriptReplacement(path, cb, chunk, id) {
var tpl = document.getElementById(id);
if (tpl && tpl.content) {
document.body.appendChild(tpl.content.cloneNode(true));
cb();
} else {
loadScript(path, cb, chunk, id);
}
}
${RuntimeGlobals.loadScript} = loadScriptReplacement`;
}
}

class Index {
apply(compiler) {
compiler.hooks.thisCompilation.tap(
'AsyncChunksPlugin',
(compilation) => {
const onceForChunkSet = new WeakSet();

compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.ensureChunkHandlers)
.tap('AsyncChunksPlugin', (chunk, set) => {
if (onceForChunkSet.has(chunk)) {
return;
}

onceForChunkSet.add(chunk);

const runtimeModule = new AsyncPlugRuntimeModule();
set.add(RuntimeGlobals.loadScript);
compilation.addRuntimeModule(chunk, runtimeModule);
});
}
);

compiler.hooks.emit.tapAsync('AsyncChunksPlugin', (compilation, callback) => {
const asyncChunks = [];
if (compilation.name !== 'runtime') {
callback();
return;
}

compilation.chunks.forEach((chunk) => {
if (chunk.canBeInitial()) {
return;
}

asyncChunks.push({
id: chunk.id,
files: chunk.files.map((filename) => filename)
});
});

const outputPath = path.join(compiler.options.output.path, webpack.asyncAssetsJSON());

fs.writeFile(outputPath, JSON.stringify(asyncChunks, null, 2), (err) => {
if (err) {
compilation.errors.push(new Error(`Error write async chunks list to ${outputPath}`));
}

callback();
});
});

compiler.hooks.done.tapAsync('AsyncChunksPlugin', (stat, callback) => {
if (stat.compilation.name === 'html') {
const
filePath = path.join(compiler.options.output.path, webpack.asyncAssetsJSON()),
fileContent = fs.readFileSync(filePath, 'utf-8'),
asyncChunks = JSON.parse(fileContent);

asyncChunks.forEach((chunk) => {
chunk.files.forEach((file) => {
fs.rmSync(path.join(compiler.options.output.path, file));
});
});
}

callback();
});
}
}

module.exports = Index;
17 changes: 17 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,23 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, {
*/
assetsJS() {
return path.changeExt(this.assetsJSON(), '.js');
},

/**
* Returns the path to the generated async assets chunks list within the output directory.
* It contains an array of async chunks and their file names to inline them into fat-html.
* ...
* [
* {
* id: 'chunk_id',
* files: ['filename.ext']
* }
* ]
*
* @return {string}
*/
asyncAssetsJSON() {
return 'async-chunks-to-inline.json';
}
},

Expand Down
1 change: 1 addition & 0 deletions src/super/i-static-page/i-static-page.interface.ss
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
+= h.getPageStyleDepsDecl(ownDeps, {assets, wrap: true})

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

Expand Down
31 changes: 31 additions & 0 deletions src/super/i-static-page/modules/ss-helpers/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,37 @@ function getPageScriptDepsDecl(dependencies, {assets, wrap} = {}) {
return decl;
}

exports.getPageAsyncScripts = getPageAsyncScripts;

function getPageAsyncScripts() {
if (!needInline()) {
return '';
}

const
fileName = webpack.asyncAssetsJSON(),
filePath = src.clientOutput(fileName);

try {
const
fileContent = fs.readFileSync(filePath, 'utf-8'),
asyncChunks = JSON.parse(fileContent);

if (webpack.mode() === 'production') {
return asyncChunks.reduce((result, chunk) => `${result}<template id="${chunk.id}"><script>${
chunk.files.map((fileName) => `include('${src.clientOutput(fileName)}');\n`).join()
}</script></template>`, '');
}

return `<div id="scripts-shadow-store">${asyncChunks.reduce((result, chunk) => `${result}<template id="${chunk.id}"><script id="${chunk.id}">${
chunk.files.map((fileName) => `include('${src.clientOutput(fileName)}');\n`).join()
}</script></template>`, '')}</div>`;

} catch (e) {
return '';
}
}

exports.getPageStyleDepsDecl = getPageStyleDepsDecl;

/**
Expand Down
Loading