diff --git a/gulpfile.js b/gulpfile.js
index 931bba5e7..386535042 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -36,15 +36,295 @@ var fs = require('fs');
var rimraf = require('rimraf');
var execSync = require('child_process').execSync;
-// Rebuilds Blockly, including the following:
-// - blockly_compressed.js
-// - blocks_compressed.js
-// - Localization string tables in msg/js/*.js
-// - Generators in generators/*.js
-// These files are already up-to-date in the master branch.
-gulp.task('build', gulp.shell.task([
- 'python build.py'
-]));
+var closureCompiler = require('google-closure-compiler').gulp();
+var packageJson = require('./package.json');
+var argv = require('yargs').argv;
+
+
+////////////////////////////////////////////////////////////
+// Build //
+////////////////////////////////////////////////////////////
+
+const licenseRegex = `\\/\\*\\*
+ \\* @license
+ \\* [\\w: ]+
+ \\*
+ \\* (Copyright \\d+ (Google Inc.|Massachusetts Institute of Technology))
+ \\* (https://developers.google.com/blockly/|All rights reserved.)
+ \\*
+ \\* Licensed under the Apache License, Version 2.0 \\(the "License"\\);
+ \\* you may not use this file except in compliance with the License.
+ \\* You may obtain a copy of the License at
+ \\*
+ \\* http://www.apache.org/licenses/LICENSE-2.0
+ \\*
+ \\* Unless required by applicable law or agreed to in writing, software
+ \\* distributed under the License is distributed on an "AS IS" BASIS,
+ \\* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ \\* See the License for the specific language governing permissions and
+ \\* limitations under the License.
+ \\*\\/`;
+
+/**
+ * Helper method for stripping the Google's and MIT's Apache Licenses.
+ */
+function stripApacheLicense() {
+ // Strip out Google's and MIT's Apache licences.
+ // Closure Compiler preserves dozens of Apache licences in the Blockly code.
+ // Remove these if they belong to Google or MIT.
+ // MIT's permission to do this is logged in Blockly issue #2412.
+ return gulp.replace(new RegExp(licenseRegex, "g"), '');
+}
+
+/**
+ * Helper method for prepending the auto-generated header text.
+ */
+function prependHeader() {
+ return gulp.insert.prepend(`// Do not edit this file; automatically generated by gulp.\n`);
+}
+
+/**
+ * Helper method for calling the Closure compiler.
+ * @param {*} compilerOptions
+ * @param {boolean=} opt_verbose Optional option for verbose logging
+ */
+function compile(compilerOptions, opt_verbose) {
+ if (!compilerOptions) compilerOptions = {};
+ compilerOptions.compilation_level = 'SIMPLE_OPTIMIZATIONS';
+ compilerOptions.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT';
+ compilerOptions.language_out = 'ECMASCRIPT5_STRICT';
+ compilerOptions.rewrite_polyfills = false;
+ compilerOptions.generate_exports = true;
+ compilerOptions.hide_warnings_for = 'node_modules';
+
+ const platform = ['native', 'java', 'javascript'];
+
+ return closureCompiler(compilerOptions, { platform });
+}
+
+/**
+ * This task builds Blockly's core files.
+ * blockly_compressed.js
+ */
+gulp.task('build-core', function () {
+ const defines = 'Blockly.VERSION="' + packageJson.version + '"';
+ return gulp.src([
+ 'core/**/**/*.js',
+ './node_modules/google-closure-library/closure/goog/base.js'
+ ], {base: './'})
+ // 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, "g"), "-");
+ p.dirname = "";
+ p.basename = dirname + "-" + p.basename;
+ }))
+ .pipe(stripApacheLicense())
+ .pipe(compile({
+ dependency_mode: 'PRUNE',
+ entry_point: './core-blockly.js',
+ js_output_file: 'blockly_compressed.js',
+ externs: './externs/svg-externs.js',
+ define: defines
+ }, argv.verbose))
+ .pipe(prependHeader())
+ .pipe(gulp.dest('./'));
+});
+
+/**
+ * This task builds the Blockly's built in blocks.
+ * blocks_compressed.js
+ */
+gulp.task('build-blocks', function () {
+ const provides = `goog.provide('Blockly');\ngoog.provide('Blockly.Blocks');\n`;
+ return gulp.src('blocks/*.js', {base: './'})
+ // Add Blockly.Blocks to be compatible with the compiler.
+ .pipe(gulp.replace(`goog.provide('Blockly.Constants.Colour');`,
+ `${provides}goog.provide('Blockly.Constants.Colour');`))
+ .pipe(stripApacheLicense())
+ .pipe(compile({
+ dependency_mode: 'NONE',
+ js_output_file: 'blocks_compressed.js'
+ }, argv.verbose))
+ .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n'))
+ // Remove Blockly.Blocks to be compatible with Blockly.
+ .pipe(gulp.replace('var Blockly={Blocks:{}};', ''))
+ .pipe(prependHeader())
+ .pipe(gulp.dest('./'));
+});
+
+/**
+ * A helper method for building a Blockly code generator.
+ * @param {string} language Generator language.
+ * @param {string} namespace Language namespace.
+ */
+function buildGenerator(language, namespace) {
+ var provides = `goog.provide('Blockly.Generator');\ngoog.provide('Blockly.utils.string');\n`;
+ return gulp.src([`generators/${language}.js`, `generators/${language}/*.js`], {base: './'})
+ .pipe(stripApacheLicense())
+ // Add Blockly.Generator and Blockly.utils.string to be compatible with the compiler.
+ .pipe(gulp.replace(`goog.provide('Blockly.${namespace}');`,
+ `${provides}goog.provide('Blockly.${namespace}');`))
+ .pipe(compile({
+ dependency_mode: 'NONE',
+ js_output_file: `${language}_compressed.js`
+ }, argv.verbose))
+ .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n'))
+ // Remove Blockly.Generator and Blockly.utils.string to be compatible with Blockly.
+ .pipe(gulp.replace('var Blockly={Generator:{},utils:{}};Blockly.utils.string={};', ''))
+ .pipe(prependHeader())
+ .pipe(gulp.dest('./'));
+};
+
+/**
+ * This task builds the javascript generator.
+ * javascript_compressed.js
+ */
+gulp.task('build-javascript', function() {
+ return buildGenerator('javascript', 'JavaScript');
+});
+
+/**
+ * This task builds the python generator.
+ * python_compressed.js
+ */
+gulp.task('build-python', function() {
+ return buildGenerator('python', 'Python');
+});
+
+/**
+ * This task builds the php generator.
+ * php_compressed.js
+ */
+gulp.task('build-php', function() {
+ return buildGenerator('php', 'PHP');
+});
+
+/**
+ * This task builds the lua generator.
+ * lua_compressed.js
+ */
+gulp.task('build-lua', function() {
+ return buildGenerator('lua', 'Lua');
+});
+
+/**
+ * This task builds the dart generator:
+ * dart_compressed.js
+ */
+gulp.task('build-dart', function() {
+ return buildGenerator('dart', 'Dart');
+});
+
+/**
+ * This task builds Blockly's uncompressed file.
+ * blockly_uncompressed.js
+ */
+gulp.task('build-uncompressed', function() {
+ const header = `// Do not edit this file; automatically generated by build.py.
+'use strict';
+
+this.IS_NODE_JS = !!(typeof module !== 'undefined' && module.exports);
+
+this.BLOCKLY_DIR = (function(root) {
+ if (!root.IS_NODE_JS) {
+ // Find name of current directory.
+ var scripts = document.getElementsByTagName('script');
+ var re = new RegExp('(.+)[\\\/]blockly_(.*)uncompressed\\\.js$');
+ for (var i = 0, script; script = scripts[i]; i++) {
+ var match = re.exec(script.src);
+ if (match) {
+ return match[1];
+ }
+ }
+ alert('Could not detect Blockly\\'s directory name.');
+ }
+ return '';
+})(this);
+
+this.BLOCKLY_BOOT = function(root) {
+ var dir = '';
+ if (root.IS_NODE_JS) {
+ dir = 'blockly';
+ } else {
+ dir = this.BLOCKLY_DIR.match(/[^\\/]+$/)[0];
+ }
+ // Execute after Closure has loaded.
+`;
+ const footer = `
+delete root.BLOCKLY_DIR;
+delete root.BLOCKLY_BOOT;
+delete root.IS_NODE_JS;
+};
+
+if (this.IS_NODE_JS) {
+ this.BLOCKLY_BOOT(this);
+ module.exports = Blockly;
+} else {
+ // Delete any existing Closure (e.g. Soy's nogoog_shim).
+ document.write('');
+ // Load fresh Closure Library.
+ document.write('');
+ document.write('');
+}
+`;
+ const file = 'blockly_uncompressed.js';
+ // Run depswriter.py and which scans the core directory and writes out a ``goog.addDependency`` line for each file.
+ const cmd = `python ./node_modules/google-closure-library/closure/bin/build/depswriter.py \
+ --root_with_prefix="./core ../core" > ${file}`;
+ execSync(cmd, { stdio: 'inherit' });
+
+ let providesBuilder = `\n// Load Blockly.\n`;
+ const provides = new Set();
+ const dependencies = fs.readFileSync(file, "utf8");
+ const re = /\'(Blockly[^\']*)\'/gi;
+ let m;
+ while (m = re.exec(dependencies)) {
+ provides.add(`goog.require('${m[1]}');`);
+ }
+ providesBuilder += Array.from(provides).sort().join('\n') + '\n';
+
+ return gulp.src(file)
+ // Remove comments so we're compatible with the build.py script
+ .pipe(gulp.replace(/\/\/.*\n/gm, ''))
+ // Replace quotes to be compatible with build.py
+ .pipe(gulp.replace(/\'(.*\.js)\'/gm, '"$1"'))
+ // Find the Blockly directory name and replace it with a JS variable.
+ // This allows blockly_uncompressed.js to be compiled on one computer and be
+ // used on another, even if the directory name differs.
+ .pipe(gulp.replace(/\.\.\/core/gm, `../../" + dir + "/core`))
+ .pipe(gulp.insert.wrap(header, providesBuilder + footer))
+ .pipe(gulp.dest('./'));
+});
+
+/**
+ * This task builds all of Blockly:
+ * blockly_compressed.js
+ * blocks_compressed.js
+ * javascript_compressed.js
+ * python_compressed.js
+ * php_compressed.js
+ * lua_compressed.js
+ * dart_compressed.js
+ */
+gulp.task('build', gulp.parallel(
+ 'build-core',
+ 'build-blocks',
+ 'build-javascript',
+ 'build-python',
+ 'build-php',
+ 'build-lua',
+ 'build-dart'
+));
+
+////////////////////////////////////////////////////////////
+// Node //
+////////////////////////////////////////////////////////////
// Concatenates the necessary files to load Blockly in a Node.js VM. Blockly's
// individual libraries target use in a browser, where globals (via the window
diff --git a/package.json b/package.json
index 27f9b77b1..5e2890abb 100644
--- a/package.json
+++ b/package.json
@@ -17,12 +17,13 @@
"name": "Neil Fraser"
},
"scripts": {
- "prepare": "gulp blockly_node_javascript_en",
+ "build": "gulp build",
"lint": "eslint .",
- "typings": "gulp typings",
- "test": "tests/run_all_tests.sh",
"package": "gulp package",
- "release": "gulp release"
+ "prepare": "gulp blockly_node_javascript_en",
+ "release": "gulp release",
+ "test": "tests/run_all_tests.sh",
+ "typings": "gulp typings"
},
"main": "./index.js",
"umd": "./blockly.min.js",
@@ -54,7 +55,8 @@
"pngjs": "^3.4.0",
"rimraf": "^2.6.3",
"typescript-closure-tools": "^0.0.7",
- "webdriverio": "^5.11.5"
+ "webdriverio": "^5.11.5",
+ "yargs": "^14.0.0"
},
"jshintConfig": {
"globalstrict": true,