mirror of
https://github.com/google/blockly.git
synced 2026-01-05 08:00:09 +01:00
chore(build): Use chunked compilation (#5721)
* chore(build): Add "all" modules for blocks & generators
These modules (Blockly.blocks.all and Blockly.<Generator>.all) will
be the entry points for the corresponding chunks.
They also make it easier to pull in all the modules in each package
(e.g. for playground and tests).
It is necessary to set the Closure Compiler dependency_mode to
SORT_ONLY as otherwise it tries to compile the "all" modules before
their dependencies, which fails.
The only impact on the _compressed.js files is the addition of a short
string to the very end of each file, e.g.:
var module$exports$Blockly$JavaScript$all={};
* chore(deps): Add devDependency on closure-calculate-chunks
* feat(build): First pass at chunked complation
Add a new buildCompiled gulp target (npm run build:compiled) that
uses closure-calculate-chunks to do chunked compliation of core/,
blocks/ and generators/ all in a single pass.
This work is incomplete: the resulting *_compressed.js files don't
(yet) have UMD wrappers.
* chore(build): Generate chunk wrappers
A first pass; this does not have support for a namespace object yet.
* refactor(build): Use chunked compilation by default
Remove old "compressed" gulp tasks in favour of new "compiled" task.
* chore(build): Remove cruft from buildCompiled
Remove unneeded `done` parameter and commented-out options that had
been cargo-culted from the old build pipeline.
* fix(build): Fix test failures caused by new build pipeline
- Exclude closure/goog/base.js from compiler input; use
externs/goog-externs.js instead.
- Have the build:debug and build:strict targets only build the first
chunk (blockly_compressed.js).
- Fix namespace entries for blocks and generators.
* fix(build): Fix build failures on node v12
closure-calculate-chunks requires node.js v14 or later.
When running on node.js v14 or later have getChunkOptions save
the output of closure-calculate-chunks to
scripts/gulpfiles/chunks.json. When running on older versions of
node.js have it use this checked-in, cached output instead of
attempting to run closure-calculate-chunks.
* chore(build): enable --rename_prefix_namespace
This will allow modules in blocks/ and generators/ to use
goog.require to obtain the exports object of goog.modules from
core/.
* fix(build): Always build all chunks
The previous commit enabled --rename_prefix_namespace option to
Closure Compiler, and this causes the buildCompressed target to
work fine when run without --debug or --strict, but adding either
of those flags (as for example when `npm test` runs
`npm run build:debug`) causes an issue:
- Because of many compiler errors in blocks/ and generators/,
a previous commit added a hack to only build the first chunk
when doing debug/strict builds.
- When asked to build only one chunk, Closure Compiler ignores the
--rename_prefix_namespace flag, because it 'correctly' infers
that there are no later chunks that will need to access global
variables from the first chunk.
- This causes a test failure, because `npm test` first runs
`npm run build`, which generates a valid blockly_compressed.js,
but this is then overrwritten by an invalid one when it next runs
`npm run build:debug`.
(The invalid one is missing all `$.` prefixes on 'global' variables,
including on Blockly, so the wrapper's last two lines -
"$.Blockly.internal_ = $;" and "return $.Blockly" - fail.)
The fix is to add appropriate @suppress annotations to blocks/*.js and
generators/**/*.js and then remove the first-chunk-only hack.
* refactor(build): Just build once
Since the previous commit caused `npm run build:debug` to do
everything that `... build:compressed` does - and to produce
byte-for-byte identical output - it doesn't make sense to run
both when testing. To that end:
- Replace the build:debug and build:strict package scripts that
did `gulp buildCompressed --...` with new scripts build-debug
and build-strict that do `gulp build --...` instead.
(The target names are changed so as to extend our existing naming
convention as follows: a target named "foo:bar" does some sub-part
of the job done by target "foo", but a target named "foo-bar" does
all the work of the target "foo" with some extra options.)
- build:debug:log and build:strict:log are similarly replaced with
build-debug-log and build-strict-log.
- Modify run_all_tests.js to just do `npm run build-debug` instead of
doing both `npm run build` and `npm run build:debug`.
- Also remove the 'build:blocks' script that should have been removed
when the buildBlocks gulp task was deleted previously.
* refactor(build): Compile with base_minimal.js instead of base.js
Introduce a (very!) cut-down version of closure/goog/base.js named
base_minimal.js that is used as input to the compiler as an
alternative to using externs/goog-externs.js (which will be deleted
once the buildAdvancedCompilationTest target has been updated).
This will allow use of goog.setTestOnly since it will now exist in
compiled mode, and allows the changes made in 5b112db to filter
base.js out of the files for the first chunk to be reverted.
(It also obliges a change to the compiled-mode check in blockly.js.)
* fix(build): Fix buildAdvanceCompilationTest
- In build_tasks.js:
- Replace the old compile() function with a new one factored out of
buildCompiled().
- Update buildAdvancedCompilationTest to use the new compile()
and other helpers created in the meantime.
- Remove no-longer-used maybeAddClosureLibrary().
- Remove externs/{block,generator,goog}-externs.js, which are no longer
used by any compile pipeline.
- Update core/blockly.js to fix issue with detection of compiled mode
when using ADVANCED_OPTIMISATIONS.
- Update only other use of globalThis, in core/utils/xml.js, to
consistently treat it as a dictionary object.
- Update instructions in tests/compile/index.html.
This commit is sort-of-a-prerequisite to #5602; test:compile:advanced
was previously working but the generated `main_compresed.js` would
throw errors upon loading.
This commit is contained in:
committed by
GitHub
parent
ef0fa8331c
commit
985af10f6e
@@ -32,6 +32,86 @@ var {getPackageJson} = require('./helper_tasks');
|
||||
// Build //
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Suffix to add to compiled output files.
|
||||
*/
|
||||
const COMPILED_SUFFIX = '_compressed';
|
||||
|
||||
/**
|
||||
* Checked-in file to cache output of closure-calculate-chunks, to
|
||||
* allow for testing on node.js v12 (or earlier) which is not
|
||||
* compatible with closure-calculate-chunks.
|
||||
*/
|
||||
const CHUNK_CACHE_FILE = 'scripts/gulpfiles/chunks.json'
|
||||
|
||||
/**
|
||||
* Name of an object to be used as a shared "global" namespace by
|
||||
* chunks generated by the Closure Compiler with the
|
||||
* --rename_prefix_namespace option (see
|
||||
* https://github.com/google/closure-compiler/wiki/Chunk-output-for-dynamic-loading#using-global_namespace-as-the-chunk-output-type
|
||||
* for more information.) The wrapper for the first chunk will create
|
||||
* an object with this name and save it; wrappers for other chunks
|
||||
* will ensure that the same object is available with this same name.
|
||||
* The --rename_prefix_namespace option will then cause the compiled
|
||||
* chunks to create properties on this object instead of creating
|
||||
* "global" (really chunk-local) variables. This allows later chunks
|
||||
* to depend upon modules from earlier chunks.
|
||||
*
|
||||
* It can be any value that doesn't clash with a global variable or
|
||||
* wrapper argument, but as it will appear many times in the compiled
|
||||
* output it is preferable that it be short.
|
||||
*/
|
||||
const NAMESPACE_OBJECT = '$';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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 = [
|
||||
{
|
||||
name: 'blockly',
|
||||
entry: 'core/requires.js',
|
||||
namespace: 'Blockly',
|
||||
wrapperSetup: `const ${NAMESPACE_OBJECT}={};`,
|
||||
wrapperCleanup:
|
||||
`${NAMESPACE_OBJECT}.Blockly.internal_=${NAMESPACE_OBJECT};`,
|
||||
}, {
|
||||
name: 'blocks',
|
||||
entry: 'blocks/all.js',
|
||||
namespace: 'Blockly.blocks',
|
||||
}, {
|
||||
name: 'javascript',
|
||||
entry: 'generators/javascript/all.js',
|
||||
namespace: 'Blockly.JavaScript',
|
||||
}, {
|
||||
name: 'python',
|
||||
entry: 'generators/python/all.js',
|
||||
namespace: 'Blockly.Python',
|
||||
}, {
|
||||
name: 'php',
|
||||
entry: 'generators/php/all.js',
|
||||
namespace: 'Blockly.PHP',
|
||||
}, {
|
||||
name: 'lua',
|
||||
entry: 'generators/lua/all.js',
|
||||
namespace: 'Blockly.Lua',
|
||||
}, {
|
||||
name: 'dart',
|
||||
entry: 'generators/dart/all.js',
|
||||
namespace: 'Blockly.Dart',
|
||||
}
|
||||
];
|
||||
|
||||
const licenseRegex = `\\/\\*\\*
|
||||
\\* @license
|
||||
\\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology))
|
||||
@@ -103,232 +183,6 @@ var JSCOMP_ERROR = [
|
||||
'visibility'
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper method for calling the Closure compiler.
|
||||
* @param {*} compilerOptions
|
||||
* @param {boolean=} opt_verbose Optional option for verbose logging
|
||||
* @param {boolean=} opt_warnings_as_error Optional option for treating warnings
|
||||
* as errors.
|
||||
* @param {boolean=} opt_strict_typechecker Optional option for enabling strict
|
||||
* type checking.
|
||||
*/
|
||||
function compile(compilerOptions, opt_verbose, opt_warnings_as_error,
|
||||
opt_strict_typechecker) {
|
||||
const options = {};
|
||||
options.compilation_level = 'SIMPLE_OPTIMIZATIONS';
|
||||
options.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT';
|
||||
options.language_in = 'ECMASCRIPT6_STRICT',
|
||||
options.language_out = 'ECMASCRIPT5_STRICT';
|
||||
options.rewrite_polyfills = true;
|
||||
options.hide_warnings_for = 'node_modules';
|
||||
if (opt_warnings_as_error || opt_strict_typechecker) {
|
||||
options.jscomp_error = JSCOMP_ERROR;
|
||||
if (opt_strict_typechecker) {
|
||||
options.jscomp_error.push('strictCheckTypes');
|
||||
}
|
||||
}
|
||||
|
||||
const platform = ['native', 'java', 'javascript'];
|
||||
|
||||
return closureCompiler({...options, ...compilerOptions}, { platform });
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for possibly adding the Closure library into a sources array.
|
||||
* @param {Array<string>} srcs
|
||||
*/
|
||||
function maybeAddClosureLibrary(srcs) {
|
||||
if (argv.closureLibrary) {
|
||||
// If you require Google's Closure library, you can include it in your
|
||||
// build by adding the --closure-library flag.
|
||||
// You will also need to include the "google-closure-library" in your list
|
||||
// of devDependencies.
|
||||
console.log('Including the google-closure-library in your build.');
|
||||
if (!fs.existsSync('./node_modules/google-closure-library')) {
|
||||
throw Error('You must add the google-closure-library to your ' +
|
||||
'devDependencies in package.json, and run `npm install`.');
|
||||
}
|
||||
srcs.push('./node_modules/google-closure-library/closure/goog/**/**/*.js');
|
||||
}
|
||||
return srcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to return an closure compiler output wrapper that wraps the
|
||||
* body in a Universal Module Definition.
|
||||
* @param {string} namespace The export namespace.
|
||||
* @param {Array<Object>} dependencies An array of dependencies to inject.
|
||||
*/
|
||||
function outputWrapperUMD(namespace, dependencies) {
|
||||
const amdDeps = dependencies.map(d => '\'' + d.amd + '\'' ).join(', ');
|
||||
const cjsDeps = dependencies.map(d => `require('${d.cjs}')`).join(', ');
|
||||
const browserDeps = dependencies.map(d => 'root.' + d.name).join(', ');
|
||||
const imports = dependencies.map(d => d.name).join(', ');
|
||||
return `// Do not edit this file; automatically generated by gulp.
|
||||
|
||||
/* 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.${namespace} = factory(${browserDeps});
|
||||
}
|
||||
}(this, function(${imports}) {
|
||||
%output%
|
||||
return ${namespace};
|
||||
}));
|
||||
`;
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds Blockly's core files.
|
||||
* blockly_compressed.js
|
||||
*/
|
||||
function buildCompressed() {
|
||||
var packageJson = getPackageJson();
|
||||
const defines = 'Blockly.VERSION="' + packageJson.version + '"';
|
||||
return gulp.src(maybeAddClosureLibrary(['core/**/**/*.js']), {base: './'})
|
||||
.pipe(stripApacheLicense())
|
||||
.pipe(gulp.sourcemaps.init())
|
||||
// Directories in Blockly are used to group similar files together
|
||||
// but are not used to limit access with @package, instead the
|
||||
// method means something is internal to Blockly and not a public
|
||||
// API.
|
||||
// Flatten all files so they're in the same directory, but ensure that
|
||||
// files with the same name don't conflict.
|
||||
.pipe(gulp.rename(function(p) {
|
||||
var dirname = p.dirname.replace(
|
||||
new RegExp(path.sep.replace(/\\/, '\\\\'), "g"), "-");
|
||||
p.dirname = "";
|
||||
p.basename = dirname + "-" + p.basename;
|
||||
}))
|
||||
.pipe(compile(
|
||||
{
|
||||
dependency_mode: 'PRUNE',
|
||||
entry_point: './core-requires.js',
|
||||
js_output_file: 'blockly_compressed.js',
|
||||
externs: ['./externs/svg-externs.js', './externs/goog-externs.js'],
|
||||
define: defines,
|
||||
output_wrapper: outputWrapperUMD('Blockly', [])
|
||||
},
|
||||
argv.verbose, argv.debug, argv.strict))
|
||||
.pipe(gulp.sourcemaps.mapSources(function(sourcePath, file) {
|
||||
return sourcePath.replace(/-/g, '/');
|
||||
}))
|
||||
.pipe(
|
||||
gulp.sourcemaps.write('.', {includeContent: false, sourceRoot: './'}))
|
||||
.pipe(gulp.dest(BUILD_DIR));
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the Blockly's built in blocks.
|
||||
* blocks_compressed.js
|
||||
*/
|
||||
function buildBlocks() {
|
||||
return gulp.src(['blocks/*.js'], {base: './'})
|
||||
.pipe(stripApacheLicense())
|
||||
.pipe(gulp.sourcemaps.init())
|
||||
.pipe(compile({
|
||||
dependency_mode: 'NONE',
|
||||
externs: ['./externs/goog-externs.js', './externs/block-externs.js'],
|
||||
js_output_file: 'blocks_compressed.js',
|
||||
output_wrapper: outputWrapperUMD('Blockly.Blocks', [{
|
||||
name: 'Blockly',
|
||||
amd: './blockly_compressed.js',
|
||||
cjs: './blockly_compressed.js'
|
||||
}])
|
||||
}, argv.verbose, argv.debug, argv.strict))
|
||||
.pipe(gulp.sourcemaps.write('.', {
|
||||
includeContent: false,
|
||||
sourceRoot: './'
|
||||
}))
|
||||
.pipe(gulp.dest(BUILD_DIR));
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper method for building a Blockly code generator.
|
||||
* @param {string} language Generator language.
|
||||
* @param {string} namespace Language namespace.
|
||||
*/
|
||||
function buildGenerator(language, namespace) {
|
||||
return gulp.src([`generators/${language}.js`, `generators/${language}/*.js`], {base: './'})
|
||||
.pipe(stripApacheLicense())
|
||||
.pipe(gulp.sourcemaps.init())
|
||||
.pipe(compile({
|
||||
dependency_mode: 'NONE',
|
||||
externs: ['./externs/goog-externs.js', './externs/generator-externs.js'],
|
||||
js_output_file: `${language}_compressed.js`,
|
||||
output_wrapper: outputWrapperUMD(`Blockly.${namespace}`, [{
|
||||
name: 'Blockly',
|
||||
amd: './blockly_compressed.js',
|
||||
cjs: './blockly_compressed.js'
|
||||
}])
|
||||
}, argv.verbose, argv.debug, argv.strict))
|
||||
.pipe(gulp.sourcemaps.write('.', {
|
||||
includeContent: false,
|
||||
sourceRoot: './'
|
||||
}))
|
||||
.pipe(gulp.dest(BUILD_DIR));
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the javascript generator.
|
||||
* javascript_compressed.js
|
||||
*/
|
||||
function buildJavascript() {
|
||||
return buildGenerator('javascript', 'JavaScript');
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the python generator.
|
||||
* python_compressed.js
|
||||
*/
|
||||
function buildPython() {
|
||||
return buildGenerator('python', 'Python');
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the php generator.
|
||||
* php_compressed.js
|
||||
*/
|
||||
function buildPHP() {
|
||||
return buildGenerator('php', 'PHP');
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the lua generator.
|
||||
* lua_compressed.js
|
||||
*/
|
||||
function buildLua() {
|
||||
return buildGenerator('lua', 'Lua');
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds the dart generator:
|
||||
* dart_compressed.js
|
||||
*/
|
||||
function buildDart() {
|
||||
return buildGenerator('dart', 'Dart');
|
||||
};
|
||||
|
||||
/**
|
||||
* This tasks builds all the generators:
|
||||
* javascript_compressed.js
|
||||
* python_compressed.js
|
||||
* php_compressed.js
|
||||
* lua_compressed.js
|
||||
* dart_compressed.js
|
||||
*/
|
||||
const buildGenerators = gulp.parallel(
|
||||
buildJavascript,
|
||||
buildPython,
|
||||
buildPHP,
|
||||
buildLua,
|
||||
buildDart
|
||||
);
|
||||
|
||||
/**
|
||||
* This task updates tests/deps.js, used by blockly_uncompressed.js
|
||||
* when loading Blockly in uncompiled mode.
|
||||
@@ -418,61 +272,267 @@ 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}) {
|
||||
${chunk.wrapperSetup || `const ${NAMESPACE_OBJECT}=Blockly.internal_;`}
|
||||
%output%
|
||||
${chunk.wrapperCleanup || ''}
|
||||
return ${NAMESPACE_OBJECT}.${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.
|
||||
*
|
||||
* @return {{chunk: !Array<string>, js: !Array<string>}} The chunking
|
||||
* information, in the same form as emitted by
|
||||
* closure-calculate-chunks.
|
||||
*
|
||||
* TODO(cpcallen): maybeAddClosureLibrary? Or maybe remove base.js?
|
||||
*/
|
||||
function getChunkOptions() {
|
||||
const cccArgs = [
|
||||
'--closure-library-base-js-path ./closure/goog/base_minimal.js',
|
||||
'--deps-file ./tests/deps.js',
|
||||
...(chunks.map(chunk => `--entrypoint '${chunk.entry}'`)),
|
||||
];
|
||||
const cccCommand = `closure-calculate-chunks ${cccArgs.join(' ')}`;
|
||||
|
||||
// Because (as of 2021-11-25) closure-calculate-chunks v3.0.2
|
||||
// requries node.js v14 or later, we save the output of cccCommand
|
||||
// in a checked-in .json file, so we can use the contents of that
|
||||
// file when building on older versions of node.
|
||||
//
|
||||
// When this is no longer necessary the following section can be
|
||||
// replaced with:
|
||||
//
|
||||
// const rawOptions = JSON.parse(execSync(cccCommand));
|
||||
const nodeMajorVersion = /v(\d+)\./.exec(process.version)[1];
|
||||
let rawOptions;
|
||||
if (nodeMajorVersion >= 14) {
|
||||
rawOptions = JSON.parse(String(execSync(cccCommand)));
|
||||
// Replace absolute paths with relative ones, so they will be
|
||||
// valid on other machines. Only needed because we're saving this
|
||||
// output to use later on another machine.
|
||||
rawOptions.js = rawOptions.js.map(p => p.replace(process.cwd(), '.'));
|
||||
fs.writeFileSync(CHUNK_CACHE_FILE,
|
||||
JSON.stringify(rawOptions, null, 2) + '\n');
|
||||
} else {
|
||||
console.log(`Warning: using pre-computed chunks from ${CHUNK_CACHE_FILE}`);
|
||||
rawOptions = JSON.parse(String(fs.readFileSync(CHUNK_CACHE_FILE)));
|
||||
}
|
||||
|
||||
// rawOptions should now be of the form:
|
||||
//
|
||||
// {
|
||||
// chunk: [
|
||||
// 'requires:258',
|
||||
// 'all:10:requires',
|
||||
// 'all1:11:requires',
|
||||
// 'all2:11:requires',
|
||||
// /* ... remaining handful of chunks */
|
||||
// ],
|
||||
// js: [
|
||||
// './core/serialization/workspaces.js',
|
||||
// './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. 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.
|
||||
const chunkByNickname = Object.create(null);
|
||||
let jsFiles = rawOptions.js;
|
||||
const chunkList = rawOptions.chunk.map((element, index) => {
|
||||
const [nickname, numJsFiles, dependencyNicks] = element.split(':');
|
||||
const chunk = chunks[index];
|
||||
|
||||
// Replace nicknames with our names.
|
||||
chunkByNickname[nickname] = chunk;
|
||||
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}`;
|
||||
});
|
||||
|
||||
// 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};
|
||||
}
|
||||
|
||||
/**
|
||||
* RegExp that globally matches path.sep (i.e., "/" or "\").
|
||||
*/
|
||||
const pathSepRegExp = new RegExp(path.sep.replace(/\\/, '\\\\'), "g");
|
||||
|
||||
/**
|
||||
* Modify the supplied gulp.rename path object to relax @package
|
||||
* restrictions in core/.
|
||||
*
|
||||
* Background: subdirectories of core/ are used to group similar files
|
||||
* together but are not intended to limit access to names
|
||||
* marked @package; instead, that annotation is intended to mean only
|
||||
* that the annotated name not part of the public API.
|
||||
*
|
||||
* To make @package behave less strictly in core/, this function can
|
||||
* be used to as a gulp.rename filter, modifying the path object to
|
||||
* flatten all files in core/** so that they're in the same directory,
|
||||
* while ensuring that files with the same base name don't conflict.
|
||||
*
|
||||
* @param {{dirname: string, basename: string, extname: string}}
|
||||
* pathObject The path argument supplied by gulp.rename to its
|
||||
* callback. Modified in place.
|
||||
*/
|
||||
function flattenCorePaths(pathObject) {
|
||||
const dirs = pathObject.dirname.split(path.sep);
|
||||
if (dirs[0] === 'core') {
|
||||
pathObject.dirname = dirs[0];
|
||||
pathObject.basename =
|
||||
dirs.slice(1).concat(pathObject.basename).join('-slash-');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the effects of flattenCorePaths on a single path string.
|
||||
* @param string pathString The flattened path.
|
||||
* @return string The path after unflattening.
|
||||
*/
|
||||
function unflattenCorePaths(pathString) {
|
||||
return pathString.replace(/-slash-/g, path.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for calling the Closure compiler, establishing
|
||||
* default options (that can be overridden by the caller).
|
||||
* @param {*} options Caller-supplied options that will override the
|
||||
* defaultOptions.
|
||||
*/
|
||||
function compile(options) {
|
||||
const defaultOptions = {
|
||||
compilation_level: 'SIMPLE_OPTIMIZATIONS',
|
||||
warning_level: argv.verbose ? 'VERBOSE' : 'DEFAULT',
|
||||
language_in: 'ECMASCRIPT6_STRICT',
|
||||
language_out: 'ECMASCRIPT5_STRICT',
|
||||
rewrite_polyfills: true,
|
||||
hide_warnings_for: 'node_modules',
|
||||
externs: ['./externs/svg-externs.js'],
|
||||
};
|
||||
if (argv.debug || argv.strict) {
|
||||
defaultOptions.jscomp_error = [...JSCOMP_ERROR];
|
||||
if (argv.strict) {
|
||||
defaultOptions.jscomp_error.push('strictCheckTypes');
|
||||
}
|
||||
}
|
||||
// Extra options for Closure Compiler gulp plugin.
|
||||
const platform = ['native', 'java', 'javascript'];
|
||||
|
||||
return closureCompiler({...defaultOptions, ...options}, {platform});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function buildCompiled() {
|
||||
// Get chunking.
|
||||
const chunkOptions = getChunkOptions();
|
||||
// Closure Compiler options.
|
||||
const packageJson = getPackageJson(); // For version number.
|
||||
const options = {
|
||||
define: 'Blockly.VERSION="' + packageJson.version + '"',
|
||||
chunk: chunkOptions.chunk,
|
||||
chunk_wrapper: chunkOptions.chunk_wrapper,
|
||||
rename_prefix_namespace: NAMESPACE_OBJECT,
|
||||
// Don't supply the list of source files in chunkOptions.js as an
|
||||
// option to Closure Compiler; instead feed them as input via gulp.src.
|
||||
};
|
||||
|
||||
// Fire up compilation pipline.
|
||||
return gulp.src(chunkOptions.js, {base: './'})
|
||||
.pipe(stripApacheLicense())
|
||||
.pipe(gulp.sourcemaps.init())
|
||||
.pipe(gulp.rename(flattenCorePaths))
|
||||
.pipe(compile(options))
|
||||
.pipe(gulp.rename({suffix: COMPILED_SUFFIX}))
|
||||
.pipe(gulp.sourcemaps.mapSources(unflattenCorePaths))
|
||||
.pipe(
|
||||
gulp.sourcemaps.write('.', {includeContent: false, sourceRoot: './'}))
|
||||
.pipe(gulp.dest(BUILD_DIR));
|
||||
};
|
||||
|
||||
/**
|
||||
* This task builds Blockly core, blocks and generators together and uses
|
||||
* closure compiler's ADVANCED_COMPILATION mode.
|
||||
*/
|
||||
function buildAdvancedCompilationTest() {
|
||||
const srcs = [
|
||||
'tests/compile/main.js', 'tests/compile/test_blocks.js', 'core/**/**/*.js',
|
||||
'blocks/*.js', 'generators/**/*.js'
|
||||
'closure/goog/base_minimal.js',
|
||||
'core/**/*.js', 'blocks/**/*.js', 'generators/**/*.js',
|
||||
'tests/compile/main.js', 'tests/compile/test_blocks.js',
|
||||
];
|
||||
return gulp.src(maybeAddClosureLibrary(srcs), {base: './'})
|
||||
|
||||
// Closure Compiler options.
|
||||
const options = {
|
||||
dependency_mode: 'PRUNE',
|
||||
compilation_level: 'ADVANCED_OPTIMIZATIONS',
|
||||
entry_point: './tests/compile/main.js',
|
||||
js_output_file: 'main_compressed.js',
|
||||
};
|
||||
return gulp.src(srcs, {base: './'})
|
||||
.pipe(stripApacheLicense())
|
||||
.pipe(gulp.sourcemaps.init())
|
||||
// Directories in Blockly are used to group similar files together
|
||||
// but are not used to limit access with @package, instead the
|
||||
// method means something is internal to Blockly and not a public
|
||||
// API.
|
||||
// Flatten all files so they're in the same directory, but ensure that
|
||||
// files with the same name don't conflict.
|
||||
.pipe(gulp.rename(function(p) {
|
||||
if (p.dirname.indexOf('core') === 0) {
|
||||
var dirname = p.dirname.replace(
|
||||
new RegExp(path.sep.replace(/\\/, '\\\\'), "g"), "-");
|
||||
p.dirname = "";
|
||||
p.basename = dirname + "-" + p.basename;
|
||||
}
|
||||
}))
|
||||
.pipe(compile(
|
||||
{
|
||||
dependency_mode: 'PRUNE',
|
||||
compilation_level: 'ADVANCED_OPTIMIZATIONS',
|
||||
entry_point: './tests/compile/main.js',
|
||||
js_output_file: 'main_compressed.js',
|
||||
externs: ['./externs/svg-externs.js', './externs/goog-externs.js'],
|
||||
},
|
||||
argv.verbose, argv.strict))
|
||||
.pipe(gulp.sourcemaps.mapSources(function(sourcePath, file) {
|
||||
return sourcePath.replace(/-/g, '/');
|
||||
}))
|
||||
.pipe(gulp.rename(flattenCorePaths))
|
||||
.pipe(compile(options))
|
||||
.pipe(gulp.sourcemaps.mapSources(unflattenCorePaths))
|
||||
.pipe(gulp.sourcemaps.write(
|
||||
'.', {includeContent: false, sourceRoot: '../../'}))
|
||||
.pipe(gulp.dest('./tests/compile/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This tasks builds Blockly's core files:
|
||||
* blockly_compressed.js
|
||||
* blocks_compressed.js
|
||||
* blockly_uncompressed.js
|
||||
*/
|
||||
const buildCore = gulp.parallel(
|
||||
buildDeps,
|
||||
buildCompressed,
|
||||
buildBlocks,
|
||||
);
|
||||
|
||||
/**
|
||||
* This task builds all of Blockly:
|
||||
* blockly_compressed.js
|
||||
@@ -484,10 +544,10 @@ const buildCore = gulp.parallel(
|
||||
* dart_compressed.js
|
||||
* blockly_uncompressed.js
|
||||
* msg/json/*.js
|
||||
* test/deps*.js
|
||||
*/
|
||||
const build = gulp.parallel(
|
||||
buildCore,
|
||||
buildGenerators,
|
||||
gulp.series(buildDeps, buildCompiled),
|
||||
buildLangfiles,
|
||||
);
|
||||
|
||||
@@ -527,13 +587,10 @@ function format() {
|
||||
module.exports = {
|
||||
build: build,
|
||||
deps: buildDeps,
|
||||
core: buildCore,
|
||||
blocks: buildBlocks,
|
||||
generateLangfiles: generateLangfiles,
|
||||
langfiles: buildLangfiles,
|
||||
compressed: buildCompressed,
|
||||
compiled: buildCompiled,
|
||||
format: format,
|
||||
generators: buildGenerators,
|
||||
checkinBuilt: checkinBuilt,
|
||||
cleanBuildDir: cleanBuildDir,
|
||||
advancedCompilationTest: buildAdvancedCompilationTest,
|
||||
|
||||
Reference in New Issue
Block a user