chore(build): Generate chunk wrappers

A first pass; this does not have support for a namespace object yet.
This commit is contained in:
Christopher Allen
2021-11-23 18:46:22 +00:00
parent a10024c43d
commit efea6addd6

View File

@@ -32,20 +32,34 @@ var {getPackageJson} = require('./helper_tasks');
// Build //
////////////////////////////////////////////////////////////
/**
* Suffix to add to compiled output files.
*/
const COMPILED_SUFFIX = '_compressed';
/**
* A list of chunks. Order matters: later chunks can depend on
* earlier ones, but not vice-versa. All chunks are assumed to depend
* on the first chunk.
*
* Output files will be named <chunk_name>_compressed.js.
* The function getChunkOptions will, after running
* closure-calculate-chunks, update each chunk to add the following
* properties:
*
* - .dependencies: a list of the chunks the chunk depends upon.
* - .wrapper: the chunk wrapper.
*
* Output files will be named <chunk.name><COMPILED_SUFFIX>.js.
*/
const CHUNKS = [
const chunks = [
{
name: 'blockly',
entry: 'core/requires.js'
entry: 'core/requires.js',
namespace: 'Blockly',
}, {
name: 'blocks',
entry: 'blocks/all.js'
entry: 'blocks/all.js',
namespace: 'Blockly.blocks',
}, {
name: 'javascript',
entry: 'generators/javascript/all.js',
@@ -460,13 +474,44 @@ function buildLangfiles(done) {
done();
};
/**
* A helper method to return an closure compiler chunk wrapper that
* wraps the compiler output for the given chunk in a Universal Module
* Definition.
*/
function chunkWrapper(chunk) {
const fileNames = chunk.dependencies.map(
d => JSON.stringify(`./${d.name}${COMPILED_SUFFIX}.js`));
const amdDeps = fileNames.join(', ');
const cjsDeps = fileNames.map(f => `require(${f})`).join(', ');
const browserDeps =
chunk.dependencies.map(d => `root.${d.namespace}`).join(', ');
const imports = chunk.dependencies.map(d => d.namespace).join(', ');
return `// Do not edit this file; automatically generated.
/* eslint-disable */
;(function(root, factory) {
if (typeof define === 'function' && define.amd) { // AMD
define([${amdDeps}], factory);
} else if (typeof exports === 'object') { // Node.js
module.exports = factory(${cjsDeps});
} else { // Browser
root.${chunk.namespace} = factory(${browserDeps});
}
}(this, function(${imports}) {
%output%
return ${chunk.namespace};
}));
`;
};
/**
* Get chunking options to pass to Closure Compiler by using
* closure-calculate-chunks (hereafter "ccc") to generate them based
* on the deps.js file (which must be up to date!).
*
* The generated options are modified to use the original chunk names
* given in CHUNKS instead of the entry-point based names used by ccc.
* given in chunks instead of the entry-point based names used by ccc.
*
* @return {{chunk: !Array<string>, js: !Array<string>}} The chunking
* information, in the same form as emitted by
@@ -478,7 +523,7 @@ function getChunkOptions() {
const cccArgs = [
'--closure-library-base-js-path ./closure/goog/base.js',
'--deps-file ./tests/deps.js',
...(CHUNKS.map(chunk => `--entrypoint '${chunk.entry}'`)),
...(chunks.map(chunk => `--entrypoint '${chunk.entry}'`)),
];
const cccCommand = `closure-calculate-chunks ${cccArgs.join(' ')}`;
const rawOptions= JSON.parse(String(execSync(cccCommand)));
@@ -503,21 +548,32 @@ function getChunkOptions() {
// This is designed to be passed directly as-is as the options
// object to the Closure Compiler node API, but we want to replace
// the unhelpful entry-point based chunk names (let's call these
// "nicknames") with the ones from CHUNKS. Luckily they will be in
// "nicknames") with the ones from chunks. Luckily they will be in
// the same order that the entry points were supplied in - i.e.,
// they correspond 1:1 with the entries in CHUNKS.
// they correspond 1:1 with the entries in chunks.
const chunkByNickname = Object.create(null);
const chunkList = rawOptions.chunk.map((element, index) => {
const [nickname, numJsFiles, depNicks] = element.split(':');
const chunk = CHUNKS[index];
const [nickname, numJsFiles, dependencyNicks] = element.split(':');
const chunk = chunks[index];
chunkByNickname[nickname] = chunk;
const depNames = depNicks ?
depNicks.split(',').map(nick => chunkByNickname[nick].name) .join(',')
: '';
return `${chunk.name}:${numJsFiles}:${depNames}`;
if (!dependencyNicks) { // Chunk has no dependencies.
chunk.dependencies = [];
return `${chunk.name}:${numJsFiles}`;
}
chunk.dependencies =
dependencyNicks.split(',').map(nick => chunkByNickname[nick]);
const dependencyNames =
chunk.dependencies.map(dependency => dependency.name).join(',');
return `${chunk.name}:${numJsFiles}:${dependencyNames}`;
});
return {chunk: chunkList, js: rawOptions.js};
// Generate a chunk wrapper for each chunk.
for (const chunk of chunks) {
chunk.wrapper = chunkWrapper(chunk);
}
const chunkWrappers = chunks.map(chunk => `${chunk.name}:${chunk.wrapper}`);
return {chunk: chunkList, js: rawOptions.js, chunk_wrapper: chunkWrappers};
}
/**
@@ -581,11 +637,11 @@ function buildCompiled(done) {
hide_warnings_for: 'node_modules',
// dependency_mode: 'PRUNE',
externs: ['./externs/svg-externs.js', /* './externs/goog-externs.js' */],
output_wrapper: outputWrapperUMD('Blockly', []),
define: 'Blockly.VERSION="' + packageJson.version + '"',
chunk: chunkOptions.chunk,
chunk_wrapper: chunkOptions.chunk_wrapper,
// Don't supply the list of source files in chunkOptions.js as an
// option to Closure Compiler; instead feed them to gulp.src.
// option to Closure Compiler; instead feed them as input via gulp.src.
};
if (argv.debug || argv.strict) {
options.jscomp_error = [...JSCOMP_ERROR];
@@ -602,7 +658,7 @@ function buildCompiled(done) {
.pipe(gulp.sourcemaps.init())
.pipe(gulp.rename(flattenCorePaths))
.pipe(closureCompiler(options, pluginOptions))
.pipe(gulp.rename({suffix: '_compressed'}))
.pipe(gulp.rename({suffix: COMPILED_SUFFIX}))
.pipe(gulp.sourcemaps.mapSources(unflattenCorePaths))
.pipe(
gulp.sourcemaps.write('.', {includeContent: false, sourceRoot: './'}))