diff --git a/.eslintignore b/.eslintignore index c884c6b9e..bcf2a79fb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,3 +16,4 @@ gulpfile.js /appengine/* /externs/* /closure/* +/scripts/gulpfiles/* diff --git a/gulpfile.js b/gulpfile.js index 27bed372d..a902eadaf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -28,958 +28,15 @@ var closureDeps = require('google-closure-deps'); var packageJson = require('./package.json'); var argv = require('yargs').argv; -const upstream_url = "https://github.com/google/blockly.git"; +var typings = require('./scripts/gulpfiles/typings'); +var buildTasks = require('./scripts/gulpfiles/build_tasks'); +var packageTasks = require('./scripts/gulpfiles/package_tasks'); +var gitTasks = require('./scripts/gulpfiles/git_tasks'); -//////////////////////////////////////////////////////////// -// Build // -//////////////////////////////////////////////////////////// - -const licenseRegex = `\\/\\*\\* - \\* @license - \\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology)) -( \\* All rights reserved. -)? \\* SPDX-License-Identifier: Apache-2.0 - \\*\\/`; - -/** - * 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`); -} - -/** - * Closure compiler warning groups used to treat warnings as errors. - * For a full list of closure compiler groups, consult: - * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L113 - */ -var JSCOMP_ERROR = [ - 'accessControls', - 'checkPrototypalTypes', - 'checkRegExp', - 'checkTypes', - 'checkVars', - 'conformanceViolations', - 'const', - 'constantProperty', - 'deprecated', - 'deprecatedAnnotations', - 'duplicateMessage', - 'es5Strict', - 'externsValidation', - 'functionParams', - 'globalThis', - 'invalidCasts', - 'misplacedTypeAnnotation', - 'missingGetCssName', - // 'missingOverride', - 'missingPolyfill', - 'missingProperties', - 'missingProvide', - 'missingRequire', - 'missingReturn', - // 'missingSourcesWarnings', - 'moduleLoad', - 'msgDescriptions', - 'nonStandardJsDocs', - // 'polymer', - // 'reportUnknownTypes', - // 'strictCheckTypes', - // 'strictMissingProperties', - 'strictModuleDepCheck', - // 'strictPrimitiveOperators', - 'suspiciousCode', - 'typeInvalidation', - 'undefinedNames', - 'undefinedVars', - 'underscore', - 'unknownDefines', - 'unusedLocalVariables', - // 'unusedPrivateMembers', - 'useOfGoogBase', - 'uselessCode', - 'untranspilableFeatures', - '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. - */ -function compile(compilerOptions, opt_verbose, opt_warnings_as_error) { - compilerOptions = compilerOptions || {}; - compilerOptions.compilation_level = 'SIMPLE_OPTIMIZATIONS'; - compilerOptions.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT'; - compilerOptions.language_in = - compilerOptions.language_in || 'ECMASCRIPT5_STRICT'; - compilerOptions.language_out = 'ECMASCRIPT5_STRICT'; - compilerOptions.rewrite_polyfills = false; - compilerOptions.hide_warnings_for = 'node_modules'; - if (opt_warnings_as_error) { - compilerOptions.jscomp_error = JSCOMP_ERROR; - } - - const platform = ['native', 'java', 'javascript']; - - return closureCompiler(compilerOptions, { platform }); -} - -/** - * Helper method for possibly adding the Closure library into a sources array. - * @param {Array.} 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; -} - -/** - * This task builds Blockly's core files. - * blockly_compressed.js - */ -function buildCompressed(cb) { - const defines = 'Blockly.VERSION="' + packageJson.version + '"'; - return gulp.src(maybeAddClosureLibrary(['core/**/**/*.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-requires.js', - js_output_file: 'blockly_compressed.js', - externs: ['./externs/svg-externs.js', './externs/goog-externs.js'], - define: defines, - language_in: - argv.closureLibrary ? 'ECMASCRIPT_2015' : 'ECMASCRIPT5_STRICT' - }, argv.verbose, argv.strict)) - .pipe(prependHeader()) - .pipe(gulp.dest('./')); -}; - -/** - * This task builds the Blockly's built in blocks. - * blocks_compressed.js - */ -function buildBlocks() { - // Add provides used throughout blocks/ in order to be compatible with the - // compiler. Anything added to this list must be removed from the compiled - // result using the remove regex steps below. - const provides = ` -goog.provide('Blockly'); -goog.provide('Blockly.Blocks'); -goog.provide('Blockly.Comment'); -goog.provide('Blockly.FieldCheckbox'); -goog.provide('Blockly.FieldColour'); -goog.provide('Blockly.FieldDropdown'); -goog.provide('Blockly.FieldImage'); -goog.provide('Blockly.FieldLabel'); -goog.provide('Blockly.FieldMultilineInput'); -goog.provide('Blockly.FieldNumber'); -goog.provide('Blockly.FieldTextInput'); -goog.provide('Blockly.FieldVariable'); -goog.provide('Blockly.Mutator'); -goog.provide('Blockly.Warning');`; - return gulp.src(maybeAddClosureLibrary(['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', - externs: ['./externs/goog-externs.js'], - js_output_file: 'blocks_compressed.js' - }, argv.verbose, argv.strict)) - .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n')) - // Remove Blockly.Blocks to be compatible with Blockly. - .pipe(gulp.replace(/var Blockly=\{[^;]*\};\n?/, '')) - // Remove Blockly Fields to be compatible with Blockly. - .pipe(gulp.replace(/Blockly\.Field[^=\(]+=\{[^;]*\};/g, '')) - // Remove Blockly Warning, Comment & Mutator to be compatible with Blockly. - .pipe(gulp.replace(/Blockly\.(Comment|Warning|Mutator)=\{[^;]*\};/g, '')) - .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'); -goog.provide('Blockly.utils.global'); -goog.provide('Blockly.utils.string');`; - 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', - externs: ['./externs/goog-externs.js'], - js_output_file: `${language}_compressed.js` - }, argv.verbose, argv.strict)) - .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=\{[^;]*\};\s*Blockly.utils.global={};\s*Blockly.utils.string={};\n?/, '')) - .pipe(prependHeader()) - .pipe(gulp.dest('./')); -}; - -/** - * 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 builds Blockly's uncompressed file. - * blockly_uncompressed.js - */ -function buildUncompressed() { - const closurePath = argv.closureLibrary ? - 'node_modules/google-closure-library/closure/goog' : - 'closure/goog'; - const header = `// Do not edit this file; automatically generated by gulp. -'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) { - // 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 { - document.write(''); - document.write(''); -} -`; - -let deps = []; -return gulp.src(maybeAddClosureLibrary(['core/**/**/*.js'])) - .pipe(through2.obj((file, _enc, cb) => { - const result = closureDeps.parser.parseFile(file.path); - for (const dep of result.dependencies) { - deps.push(dep); - } - cb(null); - })) - .on('end', () => { - // Update the path to closure for any files that we don't know the full path - // of (parsed from a goog.addDependency call). - for (const dep of deps) { - dep.setClosurePath(closurePath); - } - - const addDependency = closureDeps.depFile.getDepFileText(closurePath, deps); - - const requires = `goog.addDependency("base.js", [], []); - -// Load Blockly. -goog.require('Blockly.requires') -`; - fs.writeFileSync('blockly_uncompressed.js', - header + - addDependency + - requires + - footer); - }); -}; - -/** - * This task builds Blockly's lang files. - * msg/*.js - */ -function buildLangfiles(done) { - // Run js_to_json.py - const jsToJsonCmd = `python ./i18n/js_to_json.py \ ---input_file ${path.join('msg', 'messages.js')} \ ---output_dir ${path.join('msg', 'json')} \ ---quiet`; - execSync(jsToJsonCmd, { stdio: 'inherit' }); - - // Run create_messages.py - let json_files = fs.readdirSync(path.join('msg', 'json')); - json_files = json_files.filter(file => file.endsWith('json') && - !(new RegExp(/(keys|synonyms|qqq|constants)\.json$/).test(file))); - json_files = json_files.map(file => path.join('msg', 'json', file)); - const createMessagesCmd = `python ./i18n/create_messages.py \ - --source_lang_file ${path.join('msg', 'json', 'en.json')} \ - --source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \ - --source_constants_file ${path.join('msg', 'json', 'constants.json')} \ - --key_file ${path.join('msg', 'json', 'keys.json')} \ - --output_dir ${path.join('msg', 'js')} \ - --quiet ${json_files.join(' ')}`; - execSync(createMessagesCmd, { stdio: 'inherit' }); - - done(); -}; - -/** - * This tasks builds Blockly's core files: - * blockly_compressed.js - * blocks_compressed.js - * blockly_uncompressed.js - */ -const buildCore = gulp.parallel( - buildCompressed, - buildBlocks, - buildUncompressed -); - -/** - * 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 - * blockly_uncompressed.js - * msg/json/*.js - */ -const build = gulp.parallel( - buildCore, - buildGenerators, - buildLangfiles -); - -//////////////////////////////////////////////////////////// -// Typings // -//////////////////////////////////////////////////////////// - -// Generates the TypeScript definition file (d.ts) for Blockly. -// As well as generating the typings of each of the files under core/ and msg/, -// the script also pulls in a number of part files from typings/parts. -// This includes the header (incl License), additional useful interfaces -// including Blockly Options and Google Closure typings. -function typings() { - const tmpDir = './typings/tmp'; - const blocklySrcs = [ - "core/", - "core/components", - "core/components/tree", - "core/components/menu", - "core/keyboard_nav", - "core/renderers/common", - "core/renderers/measurables", - "core/theme", - "core/utils", - "msg/" - ]; - // Clean directory if exists. - if (fs.existsSync(tmpDir)) { - rimraf.sync(tmpDir); - } - fs.mkdirSync(tmpDir); - - // Find all files that will be included in the typings file. - let files = []; - blocklySrcs.forEach((src) => { - files = files.concat(fs.readdirSync(src) - .filter(fn => fn.endsWith('.js')) - .map(fn => path.join(src, fn))); - }); - - // Generate typings file for each file. - files.forEach((file) => { - const typescriptFileName = `${path.join(tmpDir, file)}.d.ts`; - if (file.indexOf('core/msg.js') > -1) { - return; - } - const cmd = `node ./node_modules/typescript-closure-tools/definition-generator/src/main.js ${file} ${typescriptFileName}`; - console.log(`Generating typings for ${file}`); - execSync(cmd, { stdio: 'inherit' }); - }); - - const srcs = [ - 'typings/parts/blockly-header.d.ts', - 'typings/parts/blockly-interfaces.d.ts', - `${tmpDir}/core/**`, - `${tmpDir}/core/components/**`, - `${tmpDir}/core/components/tree/**`, - `${tmpDir}/core/components/menu/**`, - `${tmpDir}/core/keyboard_nav/**`, - `${tmpDir}/core/renderers/common/**`, - `${tmpDir}/core/renderers/measurables/**`, - `${tmpDir}/core/utils/**`, - `${tmpDir}/core/theme/**`, - `${tmpDir}/msg/**` - ]; - return gulp.src(srcs) - .pipe(gulp.concat('blockly.d.ts')) - .pipe(gulp.dest('typings')) - .on('end', function () { - // Clean up tmp directory. - if (fs.existsSync(tmpDir)) { - rimraf.sync(tmpDir); - } - }); -}; - -//////////////////////////////////////////////////////////// -// NPM packaging tasks // -//////////////////////////////////////////////////////////// - -// The destination path where all the NPM distribution files will go. -const packageDistribution = './dist'; - -/** - * A helper method for wrapping a file into a Universal Module Definition. - * @param {string} namespace The export namespace. - * @param {Array} dependencies An array of dependencies to inject. - */ -function packageUMD(namespace, dependencies) { - return gulp.umd({ - dependencies: function () { return dependencies; }, - namespace: function () { return namespace; }, - exports: function () { return namespace; }, - template: path.join(__dirname, 'package/templates/umd.template') - }); -}; - -/** - * A helper method for wrapping a file into a CommonJS module for Node.js. - * @param {string} namespace The export namespace. - * @param {Array} dependencies An array of dependencies to inject. - */ -function packageCommonJS(namespace, dependencies) { - return gulp.umd({ - dependencies: function () { return dependencies; }, - namespace: function () { return namespace; }, - exports: function () { return namespace; }, - template: path.join(__dirname, 'package/templates/node.template') - }); -}; - -/** - * This task wraps blockly_compressed.js into a UMD module. - * @example import 'blockly/blockly'; - */ -function packageBlockly() { - return gulp.src('blockly_compressed.js') - .pipe(packageUMD('Blockly', [])) - .pipe(gulp.rename('blockly.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps blocks_compressed.js into a CommonJS module for Node.js. - * This is an equivelant task to package-blockly but for Node.js. - * @example import 'blockly/blockly-node'; - */ -function packageBlocklyNode() { - // Override textToDomDocument, providing a Node.js alternative to DOMParser. - return gulp.src('blockly_compressed.js') - .pipe(gulp.insert.append(` - if (typeof DOMParser !== 'function') { - var DOMParser = require("jsdom/lib/jsdom/living").DOMParser; - var XMLSerializer = require("jsdom/lib/jsdom/living").XMLSerializer; - var doc = Blockly.utils.xml.textToDomDocument( - ''); - Blockly.utils.xml.document = function() { - return doc; - }; - }`)) - .pipe(packageCommonJS('Blockly', [])) - .pipe(gulp.rename('blockly-node.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps blocks_compressed.js into a UMD module. - * @example import 'blockly/blocks'; - */ -function packageBlocks() { - return gulp.src('blocks_compressed.js') - .pipe(gulp.insert.prepend(` - Blockly.Blocks={};`)) - .pipe(packageUMD('Blockly.Blocks', [{ - name: 'Blockly', - amd: './core', - cjs: './core', - }])) - .pipe(gulp.rename('blocks.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps package/index.js into a UMD module. - * We implicitly require the Node entry point in CommonJS environments, - * and the Browser entry point for AMD environments. - * @example import * as Blockly from 'blockly'; - */ -function packageIndex() { - return gulp.src('package/index.js') - .pipe(packageUMD('Blockly', [{ - name: 'Blockly', - amd: './browser', - cjs: './node', - }])) - .pipe(gulp.rename('index.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps package/browser/index.js into a UMD module. - * By default, the module includes Blockly core and built-in blocks, - * as well as the JavaScript code generator and the English block - * localization files. - * This module is configured (in package.json) to replaces the module - * built by package-node in browser environments. - * @example import * as Blockly from 'blockly/browser'; - */ -function packageBrowser() { - return gulp.src('package/browser/index.js') - .pipe(packageUMD('Blockly', [{ - name: 'Blockly', - amd: './core-browser', - cjs: './core-browser', - },{ - name: 'En', - amd: './msg/en', - cjs: './msg/en', - },{ - name: 'BlocklyBlocks', - amd: './blocks', - cjs: './blocks', - },{ - name: 'BlocklyJS', - amd: './javascript', - cjs: './javascript', - }])) - .pipe(gulp.rename('browser.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps package/browser/core.js into a UMD module. - * By default, the module includes the Blockly core package and a - * helper method to set the locale. - * This module is configured (in package.json) to replaces the module - * built by package-node-core in browser environments. - * @example import * as Blockly from 'blockly/core'; - */ -function packageCore() { - return gulp.src('package/browser/core.js') - .pipe(packageUMD('Blockly', [{ - name: 'Blockly', - amd: './blockly', - cjs: './blockly', - }])) - .pipe(gulp.rename('core-browser.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps package/node/index.js into a CommonJS module for Node.js. - * By default, the module includes Blockly core and built-in blocks, - * as well as all the code generators and the English block localization files. - * This module is configured (in package.json) to be replaced by the module - * built by package-browser in browser environments. - * @example import * as Blockly from 'blockly/node'; - */ -function packageNode() { - return gulp.src('package/node/index.js') - .pipe(packageCommonJS('Blockly', [{ - name: 'Blockly', - cjs: './core', - },{ - name: 'En', - cjs: './msg/en', - },{ - name: 'BlocklyBlocks', - cjs: './blocks', - },{ - name: 'BlocklyJS', - cjs: './javascript', - },{ - name: 'BlocklyPython', - cjs: './python', - },{ - name: 'BlocklyPHP', - cjs: './php', - },{ - name: 'BlocklyLua', - cjs: './lua', - }, { - name: 'BlocklyDart', - cjs: './dart', - }])) - .pipe(gulp.rename('node.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps package/node/core.js into a CommonJS module for Node.js. - * By default, the module includes the Blockly core package for Node.js - * and a helper method to set the locale. - * This module is configured (in package.json) to be replaced by the module - * built by package-core in browser environments. - * @example import * as Blockly from 'blockly/core'; - */ -function packageNodeCore() { - return gulp.src('package/node/core.js') - .pipe(packageCommonJS('Blockly', [{ - name: 'Blockly', - amd: './blockly-node', - cjs: './blockly-node', - }])) - .pipe(gulp.rename('core.js')) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * A helper method for packaging a Blockly code generator into a UMD module. - * @param {string} file Source file name. - * @param {string} rename Destination file name. - * @param {string} generator Generator export namespace. - */ -function packageGenerator(file, rename, generator) { - return gulp.src(file) - .pipe(packageUMD(generator, [{ - name: 'Blockly', - amd: './core', - cjs: './core', - }])) - .pipe(gulp.rename(rename)) - .pipe(gulp.dest(packageDistribution)); -}; - -/** - * This task wraps javascript_compressed.js into a UMD module. - * @example import 'blockly/javascript'; - */ -function packageJavascript() { - return packageGenerator('javascript_compressed.js', 'javascript.js', 'Blockly.JavaScript'); -}; - -/** - * This task wraps python_compressed.js into a UMD module. - * @example import 'blockly/python'; - */ -function packagePython() { - return packageGenerator('python_compressed.js', 'python.js', 'Blockly.Python'); -}; - -/** - * This task wraps lua_compressed.js into a UMD module. - * @example import 'blockly/lua'; - */ -function packageLua() { - return packageGenerator('lua_compressed.js', 'lua.js', 'Blockly.Lua'); -}; - -/** - * This task wraps dart_compressed.js into a UMD module. - * @example import 'blockly/dart'; - */ -function packageDart() { - return packageGenerator('dart_compressed.js', 'dart.js', 'Blockly.Dart'); -}; - -/** - * This task wraps php_compressed.js into a UMD module. - * @example import 'blockly/php'; - */ -function packagePHP() { - return packageGenerator('php_compressed.js', 'php.js', 'Blockly.PHP'); -}; - -/** - * This task wraps each of the msg/js/* files into a UMD module. - * @example import * as En from 'blockly/msg/en'; - */ -function packageLocales() { - // Remove references to goog.provide and goog.require. - return gulp.src('msg/js/*.js') - .pipe(gulp.replace(/goog\.[^\n]+/g, '')) - .pipe(gulp.insert.prepend(` - var Blockly = {};Blockly.Msg={};`)) - .pipe(packageUMD('Blockly.Msg', [{ - name: 'Blockly', - amd: '../core', - cjs: '../core', - }])) - .pipe(gulp.dest(`${packageDistribution}/msg`)); -}; - -/** - * This task creates a UMD bundle of Blockly which includes the Blockly - * core files, the built-in blocks, the JavaScript code generator and the - * English localization files. - * @example - */ -function packageUMDBundle() { - var srcs = [ - 'blockly_compressed.js', - 'msg/js/en.js', - 'blocks_compressed.js', - 'javascript_compressed.js' - ]; - return gulp.src(srcs) - .pipe(gulp.concat('blockly.min.js')) - .pipe(packageUMD('Blockly', [])) - .pipe(gulp.dest(`${packageDistribution}`)) -}; - -/** - * This task copies all the media/* files into the distribution directory. - */ -function packageMedia() { - return gulp.src('./media/*') - .pipe(gulp.dest(`${packageDistribution}/media`)); -}; - -/** - * This task copies the package.json file into the distribution directory. - */ -function packageJSON(cb) { - const json = Object.assign({}, packageJson); - delete json['scripts']; - if (!fs.existsSync(packageDistribution)) { - fs.mkdirSync(packageDistribution); - } - fs.writeFileSync(`${packageDistribution}/package.json`, - JSON.stringify(json, null, 2)); - cb(); -}; - -/** - * This task copies the package/README.md file into the distribution directory. - * This file is what developers will see at https://www.npmjs.com/package/blockly. - */ -function packageReadme() { - return gulp.src('./package/README.md') - .pipe(gulp.dest(`${packageDistribution}`)); -}; - -/** - * This task copies the typings/blockly.d.ts TypeScript definition file into the - * distribution directory. - * The bundled declaration file is referenced in package.json in the types property. - */ -function packageDTS() { - return gulp.src('./typings/blockly.d.ts') - .pipe(gulp.dest(`${packageDistribution}`)); -}; - -/** - * This task prepares the NPM distribution files under the /dist directory. - */ -const package = gulp.parallel( - packageIndex, - packageBrowser, - packageNode, - packageCore, - packageNodeCore, - packageBlockly, - packageBlocklyNode, - packageBlocks, - packageJavascript, - packagePython, - packageLua, - packageDart, - packagePHP, - packageLocales, - packageMedia, - packageUMDBundle, - packageJSON, - packageReadme, - packageDTS -); - -// Stash current state, check out the named branch, and sync with -// google/blockly. -function syncBranch(branchName) { - return function(done) { - execSync('git stash save -m "Stash for sync"', { stdio: 'inherit' }); - execSync('git checkout ' + branchName, { stdio: 'inherit' }); - execSync('git pull ' + upstream_url + ' ' + branchName, - { stdio: 'inherit' }); - execSync('git push origin ' + branchName, { stdio: 'inherit' }); - done(); - } -} - -// Stash current state, check out develop, and sync with google/blockly. -function syncDevelop() { - return syncBranch('develop'); -}; - -// Stash current state, check out master, and sync with google/blockly. -function syncMaster() { - return syncBranch('master'); -}; - -// Helper function: get a name for a rebuild branch. Format: rebuild_mm_dd_yyyy. -function getRebuildBranchName() { - var date = new Date(); - var mm = date.getMonth() + 1; // Month, 0-11 - var dd = date.getDate(); // Day of the month, 1-31 - var yyyy = date.getFullYear(); - return 'rebuild_' + mm + '_' + dd + '_' + yyyy; -}; - -// Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm. -function getRCBranchName() { - var date = new Date(); - var mm = date.getMonth() + 1; // Month, 0-11 - var yyyy = date.getFullYear(); - return 'rc_' + yyyy + '_' + mm; -}; - -// Recompile and push to origin. -const recompile = gulp.series( - syncDevelop, - function(done) { - var branchName = getRebuildBranchName(); - console.log('make-rebuild-branch: creating branch ' + branchName); - execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); - done(); - }, - build, - typings, - function(done) { - console.log('push-rebuild-branch: committing rebuild'); - execSync('git commit -am "Rebuild"', { stdio: 'inherit' }); - var branchName = getRebuildBranchName(); - execSync('git push origin ' + branchName, { stdio: 'inherit' }); - console.log('Branch ' + branchName + ' pushed to GitHub.'); - console.log('Next step: create a pull request against develop.'); - done(); - } -); - -// Create and push an RC branch. -// Note that this pushes to google/blockly. -const createRC = gulp.series( - syncDevelop, - function(done) { - var branchName = getRCBranchName(); - execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); - execSync('git push ' + upstream_url + ' ' + branchName, - { stdio: 'inherit' }); - execSync('git checkout -b gh-pages'); - execSync('git push ' + upstream_url + ' gh-pages'); - done(); - }, -); // See https://docs.npmjs.com/cli/version. const preversion = gulp.series( - syncMaster, + gitTasks.syncMaster, function(done) { // Create a branch named bump_version for the bump and rebuild. execSync('git checkout -b bump_version', { stdio: 'inherit' }); @@ -999,20 +56,20 @@ function postversion(done) { }; module.exports = { - default: build, - build: build, - buildCore: buildCore, - buildBlocks: buildBlocks, - buildLangfiles: buildLangfiles, - buildUncompressed: buildUncompressed, - buildCompressed: buildCompressed, - buildGenerators: buildGenerators, - gitSyncDevelop: syncDevelop, - gitSyncMaster: syncMaster, + default: buildTasks.build, + build: buildTasks.build, + buildCore: buildTasks.core, + buildBlocks: buildTasks.blocks, + buildLangfiles: buildTasks.langfiles, + buildUncompressed: buildTasks.uncompressed, + buildCompressed: buildTasks.compressed, + buildGenerators: buildTasks.generators, preversion: preversion, postversion: postversion, - gitCreateRC: createRC, - gitRecompile: recompile, - typings: typings, - package: package + gitSyncDevelop: gitTasks.syncDevelop, + gitSyncMaster: gitTasks.syncMaster, + gitCreateRC: gitTasks.createRC, + gitRecompile: gitTasks.recompile, + typings: typings.typings, + package: packageTasks.package }; diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js new file mode 100644 index 000000000..3435c33de --- /dev/null +++ b/scripts/gulpfiles/build_tasks.js @@ -0,0 +1,460 @@ +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Gulp script to build Blockly for Node & NPM. + */ + +var gulp = require('gulp'); +gulp.replace = require('gulp-replace'); +gulp.rename = require('gulp-rename'); +gulp.insert = require('gulp-insert'); + +var path = require('path'); +var fs = require('fs'); +var execSync = require('child_process').execSync; +var through2 = require('through2'); + +var closureCompiler = require('google-closure-compiler').gulp(); +var closureDeps = require('google-closure-deps'); +var packageJson = require('../../package.json'); +var argv = require('yargs').argv; + + +//////////////////////////////////////////////////////////// +// Build // +//////////////////////////////////////////////////////////// + +const licenseRegex = `\\/\\*\\* + \\* @license + \\* (Copyright \\d+ (Google LLC|Massachusetts Institute of Technology)) +( \\* All rights reserved. +)? \\* SPDX-License-Identifier: Apache-2.0 + \\*\\/`; + +/** + * 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`); +} + +/** + * Closure compiler warning groups used to treat warnings as errors. + * For a full list of closure compiler groups, consult: + * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L113 + */ +var JSCOMP_ERROR = [ + 'accessControls', + 'checkPrototypalTypes', + 'checkRegExp', + 'checkTypes', + 'checkVars', + 'conformanceViolations', + 'const', + 'constantProperty', + 'deprecated', + 'deprecatedAnnotations', + 'duplicateMessage', + 'es5Strict', + 'externsValidation', + 'functionParams', + 'globalThis', + 'invalidCasts', + 'misplacedTypeAnnotation', + 'missingGetCssName', + // 'missingOverride', + 'missingPolyfill', + 'missingProperties', + 'missingProvide', + 'missingRequire', + 'missingReturn', + // 'missingSourcesWarnings', + 'moduleLoad', + 'msgDescriptions', + 'nonStandardJsDocs', + // 'polymer', + // 'reportUnknownTypes', + // 'strictCheckTypes', + // 'strictMissingProperties', + 'strictModuleDepCheck', + // 'strictPrimitiveOperators', + 'suspiciousCode', + 'typeInvalidation', + 'undefinedNames', + 'undefinedVars', + 'underscore', + 'unknownDefines', + 'unusedLocalVariables', + // 'unusedPrivateMembers', + 'useOfGoogBase', + 'uselessCode', + 'untranspilableFeatures', + '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. + */ +function compile(compilerOptions, opt_verbose, opt_warnings_as_error) { + compilerOptions = compilerOptions || {}; + compilerOptions.compilation_level = 'SIMPLE_OPTIMIZATIONS'; + compilerOptions.warning_level = opt_verbose ? 'VERBOSE' : 'DEFAULT'; + compilerOptions.language_in = + compilerOptions.language_in || 'ECMASCRIPT5_STRICT'; + compilerOptions.language_out = 'ECMASCRIPT5_STRICT'; + compilerOptions.rewrite_polyfills = false; + compilerOptions.hide_warnings_for = 'node_modules'; + if (opt_warnings_as_error) { + compilerOptions.jscomp_error = JSCOMP_ERROR; + } + + const platform = ['native', 'java', 'javascript']; + + return closureCompiler(compilerOptions, { platform }); +} + +/** + * Helper method for possibly adding the Closure library into a sources array. + * @param {Array.} 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; +} + +/** + * This task builds Blockly's core files. + * blockly_compressed.js + */ +function buildCompressed(cb) { + const defines = 'Blockly.VERSION="' + packageJson.version + '"'; + return gulp.src(maybeAddClosureLibrary(['core/**/**/*.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-requires.js', + js_output_file: 'blockly_compressed.js', + externs: ['./externs/svg-externs.js', './externs/goog-externs.js'], + define: defines, + language_in: + argv.closureLibrary ? 'ECMASCRIPT_2015' : 'ECMASCRIPT5_STRICT' + }, argv.verbose, argv.strict)) + .pipe(prependHeader()) + .pipe(gulp.dest('./')); +}; + +/** + * This task builds the Blockly's built in blocks. + * blocks_compressed.js + */ +function buildBlocks() { + // Add provides used throughout blocks/ in order to be compatible with the + // compiler. Anything added to this list must be removed from the compiled + // result using the remove regex steps below. + const provides = ` +goog.provide('Blockly'); +goog.provide('Blockly.Blocks'); +goog.provide('Blockly.Comment'); +goog.provide('Blockly.FieldCheckbox'); +goog.provide('Blockly.FieldColour'); +goog.provide('Blockly.FieldDropdown'); +goog.provide('Blockly.FieldImage'); +goog.provide('Blockly.FieldLabel'); +goog.provide('Blockly.FieldMultilineInput'); +goog.provide('Blockly.FieldNumber'); +goog.provide('Blockly.FieldTextInput'); +goog.provide('Blockly.FieldVariable'); +goog.provide('Blockly.Mutator'); +goog.provide('Blockly.Warning');`; + return gulp.src(maybeAddClosureLibrary(['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', + externs: ['./externs/goog-externs.js'], + js_output_file: 'blocks_compressed.js' + }, argv.verbose, argv.strict)) + .pipe(gulp.replace('\'use strict\';', '\'use strict\';\n\n\n')) + // Remove Blockly.Blocks to be compatible with Blockly. + .pipe(gulp.replace(/var Blockly=\{[^;]*\};\n?/, '')) + // Remove Blockly Fields to be compatible with Blockly. + .pipe(gulp.replace(/Blockly\.Field[^=\(]+=\{[^;]*\};/g, '')) + // Remove Blockly Warning, Comment & Mutator to be compatible with Blockly. + .pipe(gulp.replace(/Blockly\.(Comment|Warning|Mutator)=\{[^;]*\};/g, '')) + .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'); +goog.provide('Blockly.utils.global'); +goog.provide('Blockly.utils.string');`; + 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', + externs: ['./externs/goog-externs.js'], + js_output_file: `${language}_compressed.js` + }, argv.verbose, argv.strict)) + .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=\{[^;]*\};\s*Blockly.utils.global={};\s*Blockly.utils.string={};\n?/, '')) + .pipe(prependHeader()) + .pipe(gulp.dest('./')); +}; + +/** + * 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 builds Blockly's uncompressed file. + * blockly_uncompressed.js + */ +function buildUncompressed() { + const closurePath = argv.closureLibrary ? + 'node_modules/google-closure-library/closure/goog' : + 'closure/goog'; + const header = `// Do not edit this file; automatically generated by gulp. +'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) { + // 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 { + document.write(''); + document.write(''); +} +`; + +let deps = []; +return gulp.src(maybeAddClosureLibrary(['core/**/**/*.js'])) + .pipe(through2.obj((file, _enc, cb) => { + const result = closureDeps.parser.parseFile(file.path); + for (const dep of result.dependencies) { + deps.push(dep); + } + cb(null); + })) + .on('end', () => { + // Update the path to closure for any files that we don't know the full path + // of (parsed from a goog.addDependency call). + for (const dep of deps) { + dep.setClosurePath(closurePath); + } + + const addDependency = closureDeps.depFile.getDepFileText(closurePath, deps); + + const requires = `goog.addDependency("base.js", [], []); + +// Load Blockly. +goog.require('Blockly.requires') +`; + fs.writeFileSync('blockly_uncompressed.js', + header + + addDependency + + requires + + footer); + }); +}; + +/** + * This task builds Blockly's lang files. + * msg/*.js + */ +function buildLangfiles(done) { + // Run js_to_json.py + const jsToJsonCmd = `python ./i18n/js_to_json.py \ +--input_file ${path.join('msg', 'messages.js')} \ +--output_dir ${path.join('msg', 'json')} \ +--quiet`; + execSync(jsToJsonCmd, { stdio: 'inherit' }); + + // Run create_messages.py + let json_files = fs.readdirSync(path.join('msg', 'json')); + json_files = json_files.filter(file => file.endsWith('json') && + !(new RegExp(/(keys|synonyms|qqq|constants)\.json$/).test(file))); + json_files = json_files.map(file => path.join('msg', 'json', file)); + const createMessagesCmd = `python ./i18n/create_messages.py \ + --source_lang_file ${path.join('msg', 'json', 'en.json')} \ + --source_synonym_file ${path.join('msg', 'json', 'synonyms.json')} \ + --source_constants_file ${path.join('msg', 'json', 'constants.json')} \ + --key_file ${path.join('msg', 'json', 'keys.json')} \ + --output_dir ${path.join('msg', 'js')} \ + --quiet ${json_files.join(' ')}`; + execSync(createMessagesCmd, { stdio: 'inherit' }); + + done(); +}; + +/** + * This tasks builds Blockly's core files: + * blockly_compressed.js + * blocks_compressed.js + * blockly_uncompressed.js + */ +const buildCore = gulp.parallel( + buildCompressed, + buildBlocks, + buildUncompressed +); + +/** + * 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 + * blockly_uncompressed.js + * msg/json/*.js + */ +const build = gulp.parallel( + buildCore, + buildGenerators, + buildLangfiles +); + +module.exports = { + build: build, + core: buildCore, + blocks: buildBlocks, + langfiles: buildLangfiles, + uncompressed: buildUncompressed, + compressed: buildCompressed, + generators: buildGenerators, +} diff --git a/scripts/gulpfiles/git_tasks.js b/scripts/gulpfiles/git_tasks.js new file mode 100644 index 000000000..a0262cebc --- /dev/null +++ b/scripts/gulpfiles/git_tasks.js @@ -0,0 +1,101 @@ +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Git-related gulp tasks for Blockly. + */ + +var gulp = require('gulp'); +var execSync = require('child_process').execSync; + +var typings = require('./typings'); +var buildTasks = require('./build_tasks'); + +const upstream_url = "https://github.com/google/blockly.git"; + +// Stash current state, check out the named branch, and sync with +// google/blockly. +function syncBranch(branchName) { + return function(done) { + execSync('git stash save -m "Stash for sync"', { stdio: 'inherit' }); + execSync('git checkout ' + branchName, { stdio: 'inherit' }); + execSync('git pull ' + upstream_url + ' ' + branchName, + { stdio: 'inherit' }); + execSync('git push origin ' + branchName, { stdio: 'inherit' }); + done(); + } +} + +// Stash current state, check out develop, and sync with google/blockly. +function syncDevelop() { + return syncBranch('develop'); +}; + +// Stash current state, check out master, and sync with google/blockly. +function syncMaster() { + return syncBranch('master'); +}; + +// Helper function: get a name for a rebuild branch. Format: rebuild_mm_dd_yyyy. +function getRebuildBranchName() { + var date = new Date(); + var mm = date.getMonth() + 1; // Month, 0-11 + var dd = date.getDate(); // Day of the month, 1-31 + var yyyy = date.getFullYear(); + return 'rebuild_' + mm + '_' + dd + '_' + yyyy; +}; + +// Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm. +function getRCBranchName() { + var date = new Date(); + var mm = date.getMonth() + 1; // Month, 0-11 + var yyyy = date.getFullYear(); + return 'rc_' + yyyy + '_' + mm; +}; + +// Recompile and push to origin. +const recompile = gulp.series( + syncDevelop, + function(done) { + var branchName = getRebuildBranchName(); + console.log('make-rebuild-branch: creating branch ' + branchName); + execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); + done(); + }, + buildTasks.build, + typings.typings, + function(done) { + console.log('push-rebuild-branch: committing rebuild'); + execSync('git commit -am "Rebuild"', { stdio: 'inherit' }); + var branchName = getRebuildBranchName(); + execSync('git push origin ' + branchName, { stdio: 'inherit' }); + console.log('Branch ' + branchName + ' pushed to GitHub.'); + console.log('Next step: create a pull request against develop.'); + done(); + } +); + +// Create and push an RC branch. +// Note that this pushes to google/blockly. +const createRC = gulp.series( + syncDevelop, + function(done) { + var branchName = getRCBranchName(); + execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); + execSync('git push ' + upstream_url + ' ' + branchName, + { stdio: 'inherit' }); + execSync('git checkout -b gh-pages'); + execSync('git push ' + upstream_url + ' gh-pages'); + done(); + }, +); + +module.exports = { + syncDevelop: syncDevelop, + syncMaster: syncMaster, + createRC: createRC, + recompile: recompile +} diff --git a/scripts/gulpfiles/package_tasks.js b/scripts/gulpfiles/package_tasks.js new file mode 100644 index 000000000..604ce4aba --- /dev/null +++ b/scripts/gulpfiles/package_tasks.js @@ -0,0 +1,399 @@ +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Gulp tasks to package Blockly for distribution on NPM. + */ + +var gulp = require('gulp'); +gulp.concat = require('gulp-concat'); +gulp.replace = require('gulp-replace'); +gulp.rename = require('gulp-rename'); +gulp.insert = require('gulp-insert'); +gulp.umd = require('gulp-umd'); + +var path = require('path'); +var fs = require('fs'); + +var packageJson = require('../../package.json'); +var argv = require('yargs').argv; + +const upstream_url = "https://github.com/google/blockly.git"; + +const blocklyRoot = '../../'; + +// The destination path where all the NPM distribution files will go. +const packageDistribution = blocklyRoot + 'dist'; + + +/** + * A helper method for wrapping a file into a Universal Module Definition. + * @param {string} namespace The export namespace. + * @param {Array} dependencies An array of dependencies to inject. + */ +function packageUMD(namespace, dependencies) { + return gulp.umd({ + dependencies: function () { return dependencies; }, + namespace: function () { return namespace; }, + exports: function () { return namespace; }, + template: path.join(__dirname, `${blocklyRoot}/package/templates/umd.template`) + }); +}; + +/** + * A helper method for wrapping a file into a CommonJS module for Node.js. + * @param {string} namespace The export namespace. + * @param {Array} dependencies An array of dependencies to inject. + */ +function packageCommonJS(namespace, dependencies) { + return gulp.umd({ + dependencies: function () { return dependencies; }, + namespace: function () { return namespace; }, + exports: function () { return namespace; }, + template: path.join(__dirname, `${blocklyRoot}/package/templates/node.template`) + }); +}; + +/** + * This task wraps blockly_compressed.js into a UMD module. + * @example import 'blockly/blockly'; + */ +function packageBlockly() { + return gulp.src('blockly_compressed.js') + .pipe(packageUMD('Blockly', [])) + .pipe(gulp.rename('blockly.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps blocks_compressed.js into a CommonJS module for Node.js. + * This is an equivelant task to package-blockly but for Node.js. + * @example import 'blockly/blockly-node'; + */ +function packageBlocklyNode() { + // Override textToDomDocument, providing a Node.js alternative to DOMParser. + return gulp.src('blockly_compressed.js') + .pipe(gulp.insert.append(` + if (typeof DOMParser !== 'function') { + var DOMParser = require("jsdom/lib/jsdom/living").DOMParser; + var XMLSerializer = require("jsdom/lib/jsdom/living").XMLSerializer; + var doc = Blockly.utils.xml.textToDomDocument( + ''); + Blockly.utils.xml.document = function() { + return doc; + }; + }`)) + .pipe(packageCommonJS('Blockly', [])) + .pipe(gulp.rename('blockly-node.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps blocks_compressed.js into a UMD module. + * @example import 'blockly/blocks'; + */ +function packageBlocks() { + return gulp.src('blocks_compressed.js') + .pipe(gulp.insert.prepend(` + Blockly.Blocks={};`)) + .pipe(packageUMD('Blockly.Blocks', [{ + name: 'Blockly', + amd: './core', + cjs: './core', + }])) + .pipe(gulp.rename('blocks.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps package/index.js into a UMD module. + * We implicitly require the Node entry point in CommonJS environments, + * and the Browser entry point for AMD environments. + * @example import * as Blockly from 'blockly'; + */ +function packageIndex() { + return gulp.src('package/index.js') + .pipe(packageUMD('Blockly', [{ + name: 'Blockly', + amd: './browser', + cjs: './node', + }])) + .pipe(gulp.rename('index.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps package/browser/index.js into a UMD module. + * By default, the module includes Blockly core and built-in blocks, + * as well as the JavaScript code generator and the English block + * localization files. + * This module is configured (in package.json) to replaces the module + * built by package-node in browser environments. + * @example import * as Blockly from 'blockly/browser'; + */ +function packageBrowser() { + return gulp.src('package/browser/index.js') + .pipe(packageUMD('Blockly', [{ + name: 'Blockly', + amd: './core-browser', + cjs: './core-browser', + },{ + name: 'En', + amd: './msg/en', + cjs: './msg/en', + },{ + name: 'BlocklyBlocks', + amd: './blocks', + cjs: './blocks', + },{ + name: 'BlocklyJS', + amd: './javascript', + cjs: './javascript', + }])) + .pipe(gulp.rename('browser.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps package/browser/core.js into a UMD module. + * By default, the module includes the Blockly core package and a + * helper method to set the locale. + * This module is configured (in package.json) to replaces the module + * built by package-node-core in browser environments. + * @example import * as Blockly from 'blockly/core'; + */ +function packageCore() { + return gulp.src('package/browser/core.js') + .pipe(packageUMD('Blockly', [{ + name: 'Blockly', + amd: './blockly', + cjs: './blockly', + }])) + .pipe(gulp.rename('core-browser.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps package/node/index.js into a CommonJS module for Node.js. + * By default, the module includes Blockly core and built-in blocks, + * as well as all the code generators and the English block localization files. + * This module is configured (in package.json) to be replaced by the module + * built by package-browser in browser environments. + * @example import * as Blockly from 'blockly/node'; + */ +function packageNode() { + return gulp.src('package/node/index.js') + .pipe(packageCommonJS('Blockly', [{ + name: 'Blockly', + cjs: './core', + },{ + name: 'En', + cjs: './msg/en', + },{ + name: 'BlocklyBlocks', + cjs: './blocks', + },{ + name: 'BlocklyJS', + cjs: './javascript', + },{ + name: 'BlocklyPython', + cjs: './python', + },{ + name: 'BlocklyPHP', + cjs: './php', + },{ + name: 'BlocklyLua', + cjs: './lua', + }, { + name: 'BlocklyDart', + cjs: './dart', + }])) + .pipe(gulp.rename('node.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps package/node/core.js into a CommonJS module for Node.js. + * By default, the module includes the Blockly core package for Node.js + * and a helper method to set the locale. + * This module is configured (in package.json) to be replaced by the module + * built by package-core in browser environments. + * @example import * as Blockly from 'blockly/core'; + */ +function packageNodeCore() { + return gulp.src('package/node/core.js') + .pipe(packageCommonJS('Blockly', [{ + name: 'Blockly', + amd: './blockly-node', + cjs: './blockly-node', + }])) + .pipe(gulp.rename('core.js')) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * A helper method for packaging a Blockly code generator into a UMD module. + * @param {string} file Source file name. + * @param {string} rename Destination file name. + * @param {string} generator Generator export namespace. + */ +function packageGenerator(file, rename, generator) { + return gulp.src(file) + .pipe(packageUMD(generator, [{ + name: 'Blockly', + amd: './core', + cjs: './core', + }])) + .pipe(gulp.rename(rename)) + .pipe(gulp.dest(packageDistribution)); +}; + +/** + * This task wraps javascript_compressed.js into a UMD module. + * @example import 'blockly/javascript'; + */ +function packageJavascript() { + return packageGenerator('javascript_compressed.js', 'javascript.js', 'Blockly.JavaScript'); +}; + +/** + * This task wraps python_compressed.js into a UMD module. + * @example import 'blockly/python'; + */ +function packagePython() { + return packageGenerator('python_compressed.js', 'python.js', 'Blockly.Python'); +}; + +/** + * This task wraps lua_compressed.js into a UMD module. + * @example import 'blockly/lua'; + */ +function packageLua() { + return packageGenerator('lua_compressed.js', 'lua.js', 'Blockly.Lua'); +}; + +/** + * This task wraps dart_compressed.js into a UMD module. + * @example import 'blockly/dart'; + */ +function packageDart() { + return packageGenerator('dart_compressed.js', 'dart.js', 'Blockly.Dart'); +}; + +/** + * This task wraps php_compressed.js into a UMD module. + * @example import 'blockly/php'; + */ +function packagePHP() { + return packageGenerator('php_compressed.js', 'php.js', 'Blockly.PHP'); +}; + +/** + * This task wraps each of the msg/js/* files into a UMD module. + * @example import * as En from 'blockly/msg/en'; + */ +function packageLocales() { + // Remove references to goog.provide and goog.require. + return gulp.src('msg/js/*.js') + .pipe(gulp.replace(/goog\.[^\n]+/g, '')) + .pipe(gulp.insert.prepend(` + var Blockly = {};Blockly.Msg={};`)) + .pipe(packageUMD('Blockly.Msg', [{ + name: 'Blockly', + amd: '../core', + cjs: '../core', + }])) + .pipe(gulp.dest(`${packageDistribution}/msg`)); +}; + +/** + * This task creates a UMD bundle of Blockly which includes the Blockly + * core files, the built-in blocks, the JavaScript code generator and the + * English localization files. + * @example + */ +function packageUMDBundle() { + var srcs = [ + 'blockly_compressed.js', + 'msg/js/en.js', + 'blocks_compressed.js', + 'javascript_compressed.js' + ]; + return gulp.src(srcs) + .pipe(gulp.concat('blockly.min.js')) + .pipe(packageUMD('Blockly', [])) + .pipe(gulp.dest(`${packageDistribution}`)) +}; + +/** + * This task copies all the media/* files into the distribution directory. + */ +function packageMedia() { + return gulp.src('./media/*') + .pipe(gulp.dest(`${packageDistribution}/media`)); +}; + +/** + * This task copies the package.json file into the distribution directory. + */ +function packageJSON(cb) { + const json = Object.assign({}, packageJson); + delete json['scripts']; + if (!fs.existsSync(packageDistribution)) { + fs.mkdirSync(packageDistribution); + } + fs.writeFileSync(`${packageDistribution}/package.json`, + JSON.stringify(json, null, 2)); + cb(); +}; + +/** + * This task copies the package/README.md file into the distribution directory. + * This file is what developers will see at https://www.npmjs.com/package/blockly. + */ +function packageReadme() { + return gulp.src('./package/README.md') + .pipe(gulp.dest(`${packageDistribution}`)); +}; + +/** + * This task copies the typings/blockly.d.ts TypeScript definition file into the + * distribution directory. + * The bundled declaration file is referenced in package.json in the types property. + */ +function packageDTS() { + return gulp.src('./typings/blockly.d.ts') + .pipe(gulp.dest(`${packageDistribution}`)); +}; + +/** + * This task prepares the NPM distribution files under the /dist directory. + */ +const package = gulp.parallel( + packageIndex, + packageBrowser, + packageNode, + packageCore, + packageNodeCore, + packageBlockly, + packageBlocklyNode, + packageBlocks, + packageJavascript, + packagePython, + packageLua, + packageDart, + packagePHP, + packageLocales, + packageMedia, + packageUMDBundle, + packageJSON, + packageReadme, + packageDTS +); + +module.exports = { + package: package +}; diff --git a/scripts/gulpfiles/typings.js b/scripts/gulpfiles/typings.js new file mode 100644 index 000000000..86ba78b38 --- /dev/null +++ b/scripts/gulpfiles/typings.js @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Gulp script to generate the Typescript definition file (d.ts) + * for Blockly. + */ + +var gulp = require('gulp'); +gulp.concat = require('gulp-concat'); + +var path = require('path'); +var fs = require('fs'); +var rimraf = require('rimraf'); +var execSync = require('child_process').execSync; + +// Generates the TypeScript definition file (d.ts) for Blockly. +// As well as generating the typings of each of the files under core/ and msg/, +// the script also pulls in a number of part files from typings/parts. +// This includes the header (incl License), additional useful interfaces +// including Blockly Options and Google Closure typings. +function typings() { + const tmpDir = './typings/tmp'; + const blocklySrcs = [ + "core/", + "core/components", + "core/components/tree", + "core/components/menu", + "core/keyboard_nav", + "core/renderers/common", + "core/renderers/measurables", + "core/theme", + "core/utils", + "msg/" + ]; + // Clean directory if exists. + if (fs.existsSync(tmpDir)) { + rimraf.sync(tmpDir); + } + fs.mkdirSync(tmpDir); + + // Find all files that will be included in the typings file. + let files = []; + blocklySrcs.forEach((src) => { + files = files.concat(fs.readdirSync(src) + .filter(fn => fn.endsWith('.js')) + .map(fn => path.join(src, fn))); + }); + + // Generate typings file for each file. + files.forEach((file) => { + const typescriptFileName = `${path.join(tmpDir, file)}.d.ts`; + if (file.indexOf('core/msg.js') > -1) { + return; + } + const cmd = `node ./node_modules/typescript-closure-tools/definition-generator/src/main.js ${file} ${typescriptFileName}`; + console.log(`Generating typings for ${file}`); + execSync(cmd, { stdio: 'inherit' }); + }); + + const srcs = [ + 'typings/parts/blockly-header.d.ts', + 'typings/parts/blockly-interfaces.d.ts', + `${tmpDir}/core/**`, + `${tmpDir}/core/components/**`, + `${tmpDir}/core/components/tree/**`, + `${tmpDir}/core/components/menu/**`, + `${tmpDir}/core/keyboard_nav/**`, + `${tmpDir}/core/renderers/common/**`, + `${tmpDir}/core/renderers/measurables/**`, + `${tmpDir}/core/utils/**`, + `${tmpDir}/core/theme/**`, + `${tmpDir}/msg/**` + ]; + return gulp.src(srcs) + .pipe(gulp.concat('blockly.d.ts')) + .pipe(gulp.dest('typings')) + .on('end', function () { + // Clean up tmp directory. + if (fs.existsSync(tmpDir)) { + rimraf.sync(tmpDir); + } + }); +}; + +module.exports = { + typings: typings +};