From 46df7d132de5cf73d6decbbf4b9d03c758beecf4 Mon Sep 17 00:00:00 2001 From: Christopher Allen Date: Wed, 15 Jun 2022 19:35:01 +0100 Subject: [PATCH] refactor(tests): Revise `tests/playgrounds/prepare.js` as `tests/bootstrap.js` to improve support for ES modules and post-bootstrap scripts (#6214) * refactor(tests): Move and rename prepare.js, blockly.mjs Since prepare.js and blockly.mjs are going to be needed for running all tests in uncompiled mode (not just the playgrounds), move them tests/. Further, rename prepare.js to bootstrap.js to better reflect its purpose. * feat(tests): Introduce BLOCKLY_BOOTSTRAP_OPTIONS Provide a mechanism for web pages that use bootstrap.js to control what is loaded and how. * fix(tests): Use the blockly repository path for all script src= URLs Previously the (non-advanced) playground was only correctly loadging on localhost because you can put an arbitrary number of "../"s in front of a relative URL and it just takes you to the root directory. * fix(tests): Don't use template literals in bootstrap.js This is necessary (but not necessarily sufficient) to be able to load the file in IE 11. * fix(tests): Throw error if attempting to bootstrap in node.js * feat(tests): Make bootstrap.js more configurable. * Terminology change: use "compressed" and "uncompressed" to describe what Closure Compiler calls "compiled" and "uncompiled", to reduce confusion with the compilation that will be done by tsc. * Get the list of modules to bootstrap (in compressed mode), or scripts to load (in compressed mode) from BLOCKLY_BOOTSTRAP_OPTIONS, to allow calling scripts to to specify exactly what to load. * feat(tests): Use a proper quote function We need to generate string literals. Best to use a quote function instead of concatenating on quote marks withou escaping. Copy a well-tested one from Code City. * feat(tests): Support an additionalScripts option This is a list of scripts to load (in order) once the required modules have been bootstrapped. We do this using goog.addDependency to make the first script depend on the required modules, then each subsequent script depend on the previous one, and then finally goog.bootstrapping the last such script. * refactor(tests): Remove special handling of msg/messages.js * refactor(tests): Use additionalScripts for all script loading Use additionalScripts option for all script loading in playground.html and advanced_playground.html. * refactor(tests): Use bootstrap instead of uncompressed in Mocha tests Use tests/bootstrap.js instead of blockly_uncompressed.js to load blockly in uncompressed mode in the Mocha tests. This entails adding a new item, despFiles, to BLOCKLY_BOOTSTRAP_OPTIONS, to allow tests/deps.mocha.js to be loaded at the appropriate point. Mention of blockly_uncompressed.js is removed from tests/mocah/.mocharc.js; it's not clear to me what effect the "file:" directive in this file might have previously had and I was not able to find documentation for it on mochajs.org, but in any case removing it appears to have had no ill effect. * refactor(tests): Use bootstrap instead of uncompressed in generator tests This entails adding an additional check in bootstrap so as to load uncompressed when loading from a file: URL, since these are not localhost URLs - though in fact the generator tests run equally well in compressed mode, albeit against (for now) the previously-check-in build products rather than the live code. * refactor(test): Use bootstrap.js in multi_playground.html This removes the last use of load_all.js, so remove it. * chore(tests): Delete blockly_uncompressed.js Its function has now been entirely subsumed by tests/bootstrap.js, so remove it and update any remaining mentions of it. Also fix formatting and positions of some comments in playground.html. * fix(tests): Rewrite bootstrap sequencing code An earlier commit modified the generated '); - // Load dependency graph info from build/deps.js. To update - // deps.js, run `npm run build:deps`. - document.write( - ''); - // Load the rest of Blockly. - document.write(''); - } - /* eslint-disable-next-line no-invalid-this */ -})(this); diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index 9b6114bc5..1da8c3325 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -262,7 +262,7 @@ function buildJavaScript(done) { /** * This task updates DEPS_FILE (deps.js), used by - * blockly_uncompressed.js when loading Blockly in uncompiled mode. + * bootstrap.js when loading Blockly in uncompiled mode. * * Also updates TEST_DEPS_FILE (deps.mocha.js), used by the mocha test * suite. @@ -673,7 +673,6 @@ function buildAdvancedCompilationTest() { * php_compressed.js * lua_compressed.js * dart_compressed.js - * blockly_uncompressed.js * msg/json/*.js * test/deps*.js */ diff --git a/scripts/gulpfiles/config.js b/scripts/gulpfiles/config.js index 9080866fa..02df80291 100644 --- a/scripts/gulpfiles/config.js +++ b/scripts/gulpfiles/config.js @@ -19,17 +19,16 @@ var path = require('path'); // - tests/scripts/compile_typings.sh // - tests/scripts/check_metadata.sh // - tests/scripts/update_metadata.sh -// - blockly_uncompressed.js (for location of deps.js) -// - tests/playgrounds/prepare.js (for location of deps.js) +// - tests/bootstrap.js (for location of deps.js) // - tests/mocha/index.html (for location of deps.mocha.js) // Directory to write compiled output to. exports.BUILD_DIR = 'build'; -// Dependencies file (for blockly_uncompressed.js): +// Dependencies file (used by bootstrap.js in uncompiled mode): exports.DEPS_FILE = path.join(exports.BUILD_DIR, 'deps.js'); -// Dependencies file (for blockly_uncompressed.js): +// Mocha test dependencies file (used by tests/mocha/index.html): exports.TEST_DEPS_FILE = path.join(exports.BUILD_DIR, 'deps.mocha.js'); // Directory to write typings output to. diff --git a/tests/bootstrap.js b/tests/bootstrap.js new file mode 100644 index 000000000..45f64bb19 --- /dev/null +++ b/tests/bootstrap.js @@ -0,0 +1,246 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Bootstrap code to load Blockly, typically in + * uncompressed mode. + * + * Load this file in a '); + + // Prevent spurious transpilation warnings. + document.write(''); + + // Load dependency graph info from the specified deps files - + // typically just build/deps.js. To update deps after changing + // any module's goog.requires / imports, run `npm run build:deps`. + for (let i = 0; i < options.depsFiles.length; i++) { + document.write( + ''); + } + + // Record require targets for bootstrap_helper.js. + window.bootstrapInfo.requires = options.requires; + + // Assemble a list of module targets to bootstrap. + // + // The first group of targets are those listed in + // options.requires. + // + // The next target is a fake one that will load + // bootstrap_helper.js. We generate a call to goog.addDependency + // to tell the debug module loader that it can be loaded via a + // fake module name, and that it depends on all the targets in the + // first group (and indeed bootstrap_helper.js will make a call to + // goog.require for each one). + // + // We then create another target for each of + // options.additionalScripts, again generating calls to + // goog.addDependency for each one making it dependent on the + // previous one. + let requires = options.requires.slice(); + const scripts = + ['tests/bootstrap_helper.js'].concat(options.additionalScripts); + const scriptDeps = []; + for (let script, i = 0; script = scripts[i]; i++) { + const fakeModuleName = 'script.' + script.replace(/[./]/g, "-"); + scriptDeps.push(' goog.addDependency(' + + quote('../../' + script) + ', [' + quote(fakeModuleName) + + '], [' + requires.map(quote).join() + "], {'lang': 'es6'});\n"); + requires = [fakeModuleName]; + } + + // Finally, write out a script containing the generated + // goog.addDependency calls and a call to goog.bootstrap + // requesting the loading of the final target, which will cause + // all the previous ones to be loaded recursively. Wrap this in a + // promise and save it so it can be awaited in bootstrap_done.mjs. + document.write( + '\n'); + } else { + // We need to load Blockly in compressed mode. Load + // blockly_compressed.js et al. using '); + } + } + + return; // All done. Only helper functions after this point. + + /** + * Convert a string into a string literal. Strictly speaking we + * only need to escape backslash, \r, \n, \u2028 (line separator), + * \u2029 (paragraph separator) and whichever quote character we're + * using, but for simplicity we escape all the control characters. + * + * Based on https://github.com/google/CodeCity/blob/master/server/code.js + * + * @param {string} str The string to convert. + * @return {string} The value s as a eval-able string literal. + */ + function quote(str) { + /* eslint-disable no-control-regex, no-multi-spaces */ + /** Regexp for characters to be escaped in a single-quoted string. */ + const singleRE = /[\x00-\x1f\\\u2028\u2029']/g; + + /** Map of control character replacements. */ + const replacements = { + '\x00': '\\0', '\x01': '\\x01', '\x02': '\\x02', '\x03': '\\x03', + '\x04': '\\x04', '\x05': '\\x05', '\x06': '\\x06', '\x07': '\\x07', + '\x08': '\\b', '\x09': '\\t', '\x0a': '\\n', '\x0b': '\\v', + '\x0c': '\\f', '\x0d': '\\r', '\x0e': '\\x0e', '\x0f': '\\x0f', + '"': '\\"', "'": "\\'", '\\': '\\\\', + '\u2028': '\\u2028', '\u2029': '\\u2029', + }; + /* eslint-enable no-control-regex, no-multi-spaces */ + + /** + * Replacer function. + * @param {string} c Single UTF-16 code unit ("character") string to + * be replaced. + * @return {string} Multi-character string containing escaped + * representation of c. + */ + function replace(c) { + return replacements[c]; + } + + return "'" + str.replace(singleRE, replace) + "'"; + } +})(); diff --git a/tests/playgrounds/blockly.mjs b/tests/bootstrap_done.mjs similarity index 57% rename from tests/playgrounds/blockly.mjs rename to tests/bootstrap_done.mjs index cb836f539..d6d0c9b31 100644 --- a/tests/playgrounds/blockly.mjs +++ b/tests/bootstrap_done.mjs @@ -20,20 +20,20 @@ * * See tests/playground.html for example usage. */ - -let Blockly; - -if (window.BlocklyLoader) { - // Uncompiled mode. Use top-level await - // (https://v8.dev/features/top-level-await) to block loading of - // this module until goog.bootstrap()ping of Blockly is finished. - await window.BlocklyLoader; - Blockly = globalThis.Blockly; -} else if (window.Blockly) { - // Compiled mode. Retrieve the pre-installed Blockly global. - Blockly = globalThis.Blockly; -} else { - throw new Error('neither window.Blockly nor window.BlocklyLoader found'); +if (!window.bootstrapInfo) { + throw new Error('window.bootstrapInfo not found. ' + + 'Make sure to load bootstrap.js before importing bootstrap_done.mjs.'); } -export default Blockly; +if (window.bootstrapInfo.compressed) { + // Compiled mode. Nothing more to do. +} else { + // Uncompressed mode. Use top-level await + // (https://v8.dev/features/top-level-await) to block loading of + // this module until goog.bootstrap()ping of Blockly is finished. + await window.bootstrapInfo.done; + // Note that this module previously did an export default of the + // value returned by the bootstrapInfo.done promise. This was + // changed in PR #5995 because library blocks and generators cannot + // be accessed via that the core/blockly.js exports object. +} diff --git a/tests/bootstrap_helper.js b/tests/bootstrap_helper.js new file mode 100644 index 000000000..ba61ff2ea --- /dev/null +++ b/tests/bootstrap_helper.js @@ -0,0 +1,20 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Helper script for bootstrap.js + * + * This is loaded, via goog.bootstrap(), after the other top-level + * Blockly modules. It simply calls goog.require() for each of them, + * to force the debug module loader to finish loading them before any + * non-module scripts (like msg/messages.js) that might have + * undeclared dependencies on them. + */ + +/* eslint-disable-next-line no-undef */ +for (const require of window.bootstrapInfo.requires) { + goog.require(require); +} diff --git a/tests/generators/index.html b/tests/generators/index.html index 9e37f05b6..69badeed1 100644 --- a/tests/generators/index.html +++ b/tests/generators/index.html @@ -3,41 +3,36 @@ Blockly Generator Tests - - - - - - - - - + + - +
diff --git a/tests/mocha/.mocharc.js b/tests/mocha/.mocharc.js index 3c156db3e..0928b25d2 100644 --- a/tests/mocha/.mocharc.js +++ b/tests/mocha/.mocharc.js @@ -2,6 +2,5 @@ module.exports = { ui: 'tdd', - file: '../blockly_uncompressed.js', reporter: 'landing' }; diff --git a/tests/mocha/index.html b/tests/mocha/index.html index c3caf1469..10fe5ed45 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -5,9 +5,6 @@ Mocha Tests for Blockly - - - - +

Blockly Multi Playground

diff --git a/tests/playground.html b/tests/playground.html index 7a555392f..142eef3fb 100644 --- a/tests/playground.html +++ b/tests/playground.html @@ -4,15 +4,23 @@ Blockly Playground - - - - + + + diff --git a/tests/playgrounds/advanced_playground.html b/tests/playgrounds/advanced_playground.html index 1510555b7..0103b6961 100644 --- a/tests/playgrounds/advanced_playground.html +++ b/tests/playgrounds/advanced_playground.html @@ -3,15 +3,23 @@ Advanced Blockly Playground - - - - - + + + `); - document.write(``); - document.write(``); - document.write( - ``); - document.write(``); -} else { - document.write( - ``); - document.write(``); - document.write(``); - document.write(``); - document.write(``); - document.write(``); - document.write(``); - document.write(``); -} -})(); diff --git a/tests/playgrounds/prepare.js b/tests/playgrounds/prepare.js deleted file mode 100644 index 5f17a97f7..000000000 --- a/tests/playgrounds/prepare.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright 2021 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview Load this file in a '); - // Load dependency graph info from build/deps.js. To update - // deps.js, run `npm run build:deps`. - document.write(''); - - // Msg loading kludge. This should go away once #5409 and/or - // #1895 are fixed. - - // Load messages into a temporary Blockly.Msg object, deleting it - // afterwards (after saving the messages!) - window.Blockly = {Msg: Object.create(null)}; - document.write(''); - document.write(` - `); - - document.write(` - `); - } else { - // The below code is necessary for a few reasons: - // - We need an absolute path instead of relative path because the - // advanced_playground the and regular playground are in different folders. - // - We need to get the root directory for blockly because it is - // different for github.io, appspot and local. - const files = [ - 'blockly_compressed.js', - 'msg/messages.js', - 'blocks_compressed.js', - 'dart_compressed.js', - 'javascript_compressed.js', - 'lua_compressed.js', - 'php_compressed.js', - 'python_compressed.js', - ]; - - // We need to load Blockly in compiled mode. - const hostName = window.location.host.replaceAll('.', '\\.'); - const matches = new RegExp(hostName + '\\/(.*)tests').exec(window.location.href); - const root = matches && matches[1] ? matches[1] : ''; - - // Load blockly_compressed.js et al. using '); - } - } -})();