mirror of
https://github.com/google/blockly.git
synced 2026-01-08 17:40:09 +01:00
refactor(build): Don't use closure-calculate-chunks
Rewrite the getChunkOptions function to not use
closure-calculate-chunks, but instead just chunk the input files
(more or less) by subdirectory: first chunk is core/, second is
blocks/, etc.
This does make a material change to blockly_compressed.js,
because we end up feeding several empty modules that contain
only typescript interface declarations and which tsc
compiles to "export {};" in the input to Closure Compiler
(closure-calculate-chunks is smart enough to notice that
no other module depends on these), which results in ~1.7KiB of
superflous
var module$build$src$core$interfaces$i_ast_node_location_svg={};
declarations. This can be avoided by filtering such empty modules
out but that has been left for a future commit.
This adds the glob NPM package as a dev dependency, but gulp
and several other existing dev dependencies already depend on
it.
Build time is sped up by about a factor of 3x, due to removal
of the buildDeps step that was really slow:
$ time npm run build
before:
real 0m24.410s
user 0m16.010s
sys 0m1.140s
after:
real 0m8.397s
user 0m11.976s
sys 0m0.694s
This commit is contained in:
@@ -18,6 +18,7 @@ const fs = require('fs');
|
||||
const fsPromises = require('fs/promises');
|
||||
const {exec, execSync} = require('child_process');
|
||||
|
||||
const {globSync} = require('glob');
|
||||
const closureCompiler = require('google-closure-compiler').gulp();
|
||||
const argv = require('yargs').argv;
|
||||
const {rimraf} = require('rimraf');
|
||||
@@ -84,8 +85,10 @@ const NAMESPACE_PROPERTY = '__namespace__';
|
||||
* - .name: the name of the chunk. Used to label it when describing
|
||||
* it to Closure Compiler and forms the prefix of filename the chunk
|
||||
* will be written to.
|
||||
* - .files: A glob or array of globs, relative to TSC_OUTPUT_DIR,
|
||||
* matching the files to include in the chunk.
|
||||
* - .entry: the source .js file which is the entrypoint for the
|
||||
* chunk.
|
||||
* chunk, relative to TSC_OUTPUT_DIR.
|
||||
* - .exports: an expression evaluating to the exports/Module object
|
||||
* of module that is the chunk's entrypoint / top level module.
|
||||
* - .scriptExport: When the chunk is loaded as a script (e.g., via a
|
||||
@@ -97,20 +100,15 @@ const NAMESPACE_PROPERTY = '__namespace__';
|
||||
* loaded as a script, the specified named exports will be saved at
|
||||
* the specified locations (which again must be global variables or
|
||||
* properties on already-existing objects). Optional.
|
||||
*
|
||||
* The function getChunkOptions will, after running
|
||||
* closure-calculate-chunks, update each chunk to add the following
|
||||
* properties:
|
||||
*
|
||||
* - .parent: the parent chunk of the given chunk. Typically
|
||||
* chunks[0], except for chunk[0].parent which will be null.
|
||||
* - .wrapper: the generated chunk wrapper.
|
||||
* - .parent: the parent chunk of the given chunk; null for the root
|
||||
* chunk.
|
||||
*
|
||||
* Output files will be named <chunk.name><COMPILED_SUFFIX>.js.
|
||||
*/
|
||||
const chunks = [
|
||||
{
|
||||
name: 'blockly',
|
||||
files: 'core/**/*.js',
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'core', 'main.js'),
|
||||
moduleEntry: path.join(TSC_OUTPUT_DIR, 'core', 'blockly.js'),
|
||||
exports: 'module$build$src$core$blockly',
|
||||
@@ -118,12 +116,14 @@ const chunks = [
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
files: 'blocks/**/*.js',
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'blocks', 'blocks.js'),
|
||||
exports: 'module$build$src$blocks$blocks',
|
||||
scriptExport: 'Blockly.libraryBlocks',
|
||||
},
|
||||
{
|
||||
name: 'javascript',
|
||||
files: ['generators/javascript.js', 'generators/javascript/**/*.js'],
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'generators', 'javascript.js'),
|
||||
exports: 'module$build$src$generators$javascript',
|
||||
scriptExport: 'javascript',
|
||||
@@ -131,6 +131,7 @@ const chunks = [
|
||||
},
|
||||
{
|
||||
name: 'python',
|
||||
files: ['generators/python.js', 'generators/python/**/*.js'],
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'generators', 'python.js'),
|
||||
exports: 'module$build$src$generators$python',
|
||||
scriptExport: 'python',
|
||||
@@ -138,6 +139,7 @@ const chunks = [
|
||||
},
|
||||
{
|
||||
name: 'php',
|
||||
files: ['generators/php.js', 'generators/php/**/*.js'],
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'generators', 'php.js'),
|
||||
exports: 'module$build$src$generators$php',
|
||||
scriptExport: 'php',
|
||||
@@ -145,6 +147,7 @@ const chunks = [
|
||||
},
|
||||
{
|
||||
name: 'lua',
|
||||
files: ['generators/lua.js', 'generators/lua/**/*.js'],
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'generators', 'lua.js'),
|
||||
exports: 'module$build$src$generators$lua',
|
||||
scriptExport: 'lua',
|
||||
@@ -152,13 +155,19 @@ const chunks = [
|
||||
},
|
||||
{
|
||||
name: 'dart',
|
||||
files: ['generators/dart.js', 'generators/dart/**/*.js'],
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'generators', 'dart.js'),
|
||||
exports: 'module$build$src$generators$dart',
|
||||
scriptExport: 'dart',
|
||||
scriptNamedExports: {'Blockly.Dart': 'dartGenerator'},
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
chunks[0].parent = null;
|
||||
for (let i = 1; i < chunks.length; i++) {
|
||||
chunks[i].parent = chunks[0];
|
||||
}
|
||||
|
||||
const licenseRegex = `\\/\\*\\*
|
||||
\\* @license
|
||||
\\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology))
|
||||
@@ -507,87 +516,59 @@ return ${chunk.exports};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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!).
|
||||
* Compute the chunking options to pass to Closure Compiler. Output
|
||||
* is in the form:
|
||||
*
|
||||
* The generated options are modified to use the original chunk names
|
||||
* given in chunks instead of the entry-point based names used by ccc.
|
||||
* {
|
||||
* "chunk": [
|
||||
* "blockly:286",
|
||||
* "blocks:10:blockly",
|
||||
* "javascript:11:blockly",
|
||||
* // ... one per chunk
|
||||
* ],
|
||||
* "js": [
|
||||
* "build/src/core/any_aliases.js",
|
||||
* "build/src/core/block.js",
|
||||
* "build/src/core/block_animations.js",
|
||||
* // ... many more files, in order by chunk
|
||||
* ],
|
||||
* "chunk_wrapper": [
|
||||
* "blockly:// Do not edit this file...",
|
||||
* "blocks:// Do not edit this file...",
|
||||
* // ... one per chunk
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @return {{chunk: !Array<string>, js: !Array<string>}} The chunking
|
||||
* information, in the same form as emitted by
|
||||
* closure-calculate-chunks.
|
||||
* This is designed to be passed directly as-is as the options object
|
||||
* to the Closure Compiler node API, and be compatible with that
|
||||
* emitted by closure-calculate-chunks.
|
||||
*
|
||||
* @return {{chunk: !Array<string>,
|
||||
* js: !Array<string>,
|
||||
* chunk_wrapper: !Array<string>}}
|
||||
* The chunking options, in the format described above.
|
||||
*/
|
||||
function getChunkOptions() {
|
||||
const cccArgs = [
|
||||
`--deps-file './${DEPS_FILE}'`,
|
||||
...(chunks.map(chunk => `--entrypoint '${chunk.entry}'`)),
|
||||
];
|
||||
const cccCommand = `closure-calculate-chunks ${cccArgs.join(' ')}`;
|
||||
const chunkOptions = [];
|
||||
const allFiles = [];
|
||||
|
||||
const rawOptions = JSON.parse(execSync(cccCommand));
|
||||
|
||||
// rawOptions should now be of the form:
|
||||
//
|
||||
// {
|
||||
// chunk: [
|
||||
// 'blockly:258',
|
||||
// 'all:10:blockly',
|
||||
// 'all1:11:blockly',
|
||||
// 'all2:11:blockly',
|
||||
// /* ... remaining handful of chunks */
|
||||
// ],
|
||||
// js: [
|
||||
// './build/src/core/serialization/workspaces.js',
|
||||
// './build/src/core/serialization/variables.js',
|
||||
// /* ... remaining several hundred files */
|
||||
// ],
|
||||
// }
|
||||
//
|
||||
// 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. Unforutnately there's no
|
||||
// guarnatee they will be in the same order that the entry points
|
||||
// were supplied in (though it happens to work out that way if no
|
||||
// chunk depends on any chunk but the first), so we look for
|
||||
// one of the entrypoints amongst the files in each chunk.
|
||||
const chunkByNickname = Object.create(null);
|
||||
// Copy and convert to posix js file paths.
|
||||
// Result will be modified via `.splice`!
|
||||
const jsFiles = rawOptions.js.map(p => posixPath(p));
|
||||
const chunkList = rawOptions.chunk.map((element) => {
|
||||
const [nickname, numJsFiles, parentNick] = element.split(':');
|
||||
|
||||
// Get array of files for just this chunk.
|
||||
const chunkFiles = jsFiles.splice(0, numJsFiles);
|
||||
|
||||
// Figure out which chunk this is by looking for one of the
|
||||
// known chunk entrypoints in chunkFiles. N.B.: O(n*m). :-(
|
||||
const chunk = chunks.find(
|
||||
chunk => chunkFiles.find(f => {
|
||||
return f.endsWith('/' + chunk.entry.replaceAll('\\', '/'));
|
||||
}
|
||||
));
|
||||
if (!chunk) throw new Error('Unable to identify chunk');
|
||||
|
||||
// Replace nicknames with the names we chose.
|
||||
chunkByNickname[nickname] = chunk;
|
||||
if (!parentNick) { // Chunk has no parent.
|
||||
chunk.parent = null;
|
||||
return `${chunk.name}:${numJsFiles}`;
|
||||
}
|
||||
chunk.parent = chunkByNickname[parentNick];
|
||||
return `${chunk.name}:${numJsFiles}:${chunk.parent.name}`;
|
||||
});
|
||||
|
||||
// Generate a chunk wrapper for each chunk.
|
||||
for (const chunk of chunks) {
|
||||
chunk.wrapper = chunkWrapper(chunk);
|
||||
const globs = typeof chunk.files === 'string' ? [chunk.files] : chunk.files;
|
||||
const files = globs
|
||||
.flatMap((glob) => globSync(glob, {cwd: TSC_OUTPUT_DIR}))
|
||||
.map((s) => `${TSC_OUTPUT_DIR}/${s}`);
|
||||
chunkOptions.push(
|
||||
`${chunk.name}:${files.length}` +
|
||||
(chunk.parent ? `:${chunk.parent.name}` : ''),
|
||||
);
|
||||
allFiles.push(...files);
|
||||
}
|
||||
const chunkWrappers = chunks.map(chunk => `${chunk.name}:${chunk.wrapper}`);
|
||||
|
||||
return {chunk: chunkList, js: rawOptions.js, chunk_wrapper: chunkWrappers};
|
||||
const chunkWrappers = chunks.map(
|
||||
(chunk) => `${chunk.name}:${chunkWrapper(chunk)}`,
|
||||
);
|
||||
|
||||
return {chunk: chunkOptions, js: allFiles, chunk_wrapper: chunkWrappers};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,10 +611,6 @@ function compile(options) {
|
||||
/**
|
||||
* This task compiles the core library, blocks and generators, creating
|
||||
* blockly_compressed.js, blocks_compressed.js, etc.
|
||||
*
|
||||
* The deps.js file must be up-to-date.
|
||||
*
|
||||
* Prerequisite: buildDeps.
|
||||
*/
|
||||
function buildCompiled() {
|
||||
// Get chunking.
|
||||
@@ -765,7 +742,7 @@ function cleanBuildDir() {
|
||||
exports.cleanBuildDir = cleanBuildDir;
|
||||
exports.langfiles = buildLangfiles; // Build build/msg/*.js from msg/json/*.
|
||||
exports.tsc = buildJavaScript;
|
||||
exports.minify = gulp.series(exports.tsc, buildDeps, buildCompiled, buildShims);
|
||||
exports.minify = gulp.series(exports.tsc, buildCompiled, buildShims);
|
||||
exports.build = gulp.parallel(exports.minify, exports.langfiles);
|
||||
|
||||
// Manually-invokable targets, with prerequisites where required.
|
||||
|
||||
Reference in New Issue
Block a user