mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40:08 +01:00
refactor(tests): Introduce loading shims, use in playgrounds (#7380)
* refactor(build): Simplify implementation of posixPath
* fix(closure): Make safe to import in node.js
Make the implementation declareModlueId safe to import and
execute in node.js, which does not provide a window global.
(N.B. because this is an ESM and therefore automatically
strict, using window?.goog?.declareModuleId doesn't work
because window being undefined is an early error.)
* feat(tests): Introduce chunk loading shims
- Add a buildShims task to build_tasks.js that, for each chunk,
creates a correspondingly-named build/<chunk>.mjs that will
either (in uncompressed mode) import and reexport that chunk's
entry point module (e.g. core/blockly.js) or (in compressed
mode) load dist/<chunk>_compressed.js using a <script> tag
and then export the corresponding properties on the chunk's
exports object.
- Provide helper methods used by these shims in
tests/scripts/loading.mjs, including code to detect whether
to load in compressed or uncompressed mode.
- Add a quote() function to scripts/helpers.js, used by
buildShims. This is copied from tests/bootstrap_helper.js,
which will be removed in a later commit.
* refactor(tests): Update playground.html to use new loading shims
* refactor(tests): Update advanced_playground.html to use new loading shims
* refactor(tests): Update multi_playground.html to use new loading shims
* chore(tests): Delete playgrounds/shared_procedures.html
Shared procedure support was moved to a plugin and this should
have been removed from core along with it.
* docs(tests): Typo corrections.
* chore(tests): Add ".loader" infix to shim filenames.
Per suggestion on PR #7380, have buildShims name the shims
${chunk.name}.loader.mjs instead of just `${chunk.name}.mjs`.
This commit is contained in:
committed by
GitHub
parent
86f3182b46
commit
5b5a56586c
@@ -72,9 +72,9 @@ export const global = globalThis;
|
||||
// export const scope = goog.scope;
|
||||
// export const defineClass = goog.defineClass;
|
||||
export const declareModuleId = function(namespace) {
|
||||
if (window.goog && window.goog.declareModuleId) {
|
||||
window.goog.declareModuleId.call(this, namespace);
|
||||
}
|
||||
// Use globalThis instead of window to find goog, so this can be
|
||||
// imported in node.js (e.g. when running buildShims gulp task).
|
||||
globalThis?.goog?.declareModuleId.call(this, namespace);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ gulp.sourcemaps = require('gulp-sourcemaps');
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fsPromises = require('fs/promises');
|
||||
const {exec, execSync} = require('child_process');
|
||||
|
||||
const closureCompiler = require('google-closure-compiler').gulp();
|
||||
@@ -24,7 +25,7 @@ const {rimraf} = require('rimraf');
|
||||
const {BUILD_DIR, DEPS_FILE, RELEASE_DIR, TEST_DEPS_FILE, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config');
|
||||
const {getPackageJson} = require('./helper_tasks');
|
||||
|
||||
const {posixPath} = require('../helpers');
|
||||
const {posixPath, quote} = require('../helpers');
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Build //
|
||||
@@ -106,6 +107,7 @@ const chunks = [
|
||||
{
|
||||
name: 'blockly',
|
||||
entry: path.join(TSC_OUTPUT_DIR, 'core', 'main.js'),
|
||||
moduleEntry: path.join(TSC_OUTPUT_DIR, 'core', 'blockly.js'),
|
||||
exports: 'module$build$src$core$blockly',
|
||||
scriptExport: 'Blockly',
|
||||
},
|
||||
@@ -672,6 +674,57 @@ function buildCompiled() {
|
||||
.pipe(gulp.dest(RELEASE_DIR));
|
||||
}
|
||||
|
||||
/**
|
||||
* This task builds the shims used by the playgrounds and tests to
|
||||
* load Blockly in either compressed or uncompressed mode, creating
|
||||
* build/blockly.loader.mjs, blocks.loader.mjs, javascript.loader.mjs,
|
||||
* etc.
|
||||
*
|
||||
* Prerequisite: getChunkOptions (via buildCompiled, for chunks[].parent).
|
||||
*/
|
||||
async function buildShims() {
|
||||
// Install a package.json file in BUILD_DIR to tell node.js that the
|
||||
// .js files therein are ESM not CJS, so we can import the
|
||||
// entrypoints to enumerate their exported names.
|
||||
//
|
||||
// N.B.: There is an exception: core/main.js is a goog.module not
|
||||
// ESM, but fortunately we don't attempt to import or require this
|
||||
// file from node.js - we only feed it to Closure Compiler, which
|
||||
// uses the type information in deps.js rather than package.json.
|
||||
await fsPromises.writeFile(
|
||||
path.join(BUILD_DIR, 'package.json'),
|
||||
'{"type": "module"}'
|
||||
);
|
||||
|
||||
// Import each entrypoint module, enumerate its exports, and write
|
||||
// a shim to load the chunk either by importing the entrypoint
|
||||
// module or by loading the compiled script.
|
||||
await Promise.all(chunks.map(async (chunk) => {
|
||||
const modulePath = posixPath(chunk.moduleEntry ?? chunk.entry);
|
||||
const scriptPath =
|
||||
path.posix.join(RELEASE_DIR, `${chunk.name}${COMPILED_SUFFIX}.js`);
|
||||
const shimPath = path.join(BUILD_DIR, `${chunk.name}.loader.mjs`);
|
||||
const parentImport =
|
||||
chunk.parent ? `import ${quote(`./${chunk.parent.name}.mjs`)};` : '';
|
||||
const exports = await import(`../../${modulePath}`);
|
||||
|
||||
await fsPromises.writeFile(shimPath,
|
||||
`import {loadChunk} from '../tests/scripts/load.mjs';
|
||||
${parentImport}
|
||||
|
||||
export const {
|
||||
${Object.keys(exports).map((name) => ` ${name},`).join('\n')}
|
||||
} = await loadChunk(
|
||||
${quote(modulePath)},
|
||||
${quote(scriptPath)},
|
||||
${quote(chunk.scriptExport)},
|
||||
);
|
||||
`);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This task builds Blockly core, blocks and generators together and uses
|
||||
* Closure Compiler's ADVANCED_COMPILATION mode.
|
||||
@@ -728,7 +781,7 @@ exports.cleanBuildDir = cleanBuildDir;
|
||||
exports.langfiles = buildLangfiles; // Build build/msg/*.js from msg/json/*.
|
||||
exports.tsc = buildJavaScript;
|
||||
exports.deps = gulp.series(exports.tsc, buildDeps);
|
||||
exports.minify = gulp.series(exports.deps, buildCompiled);
|
||||
exports.minify = gulp.series(exports.deps, buildCompiled, buildShims);
|
||||
exports.build = gulp.parallel(exports.minify, exports.langfiles);
|
||||
|
||||
// Manually-invokable targets, with prerequisites where required.
|
||||
|
||||
@@ -11,25 +11,64 @@
|
||||
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Escape regular expression pattern
|
||||
* @param {string} pattern regular expression pattern
|
||||
* @return {string} escaped regular expression pattern
|
||||
*/
|
||||
function escapeRegex(pattern) {
|
||||
return pattern.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces OS-specific path with POSIX style path.
|
||||
* Simplified implementation based on
|
||||
* https://stackoverflow.com/a/63251716/4969945
|
||||
*
|
||||
* @param {string} target target path
|
||||
* @return {string} posix path
|
||||
*/
|
||||
function posixPath(target) {
|
||||
const osSpecificSep = new RegExp(escapeRegex(path.sep), 'g');
|
||||
return target.replace(osSpecificSep, path.posix.sep);
|
||||
return target.split(path.sep).join(path.posix.sep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
|
||||
return "'" + str.replace(singleRE, (c) => replacements[c]) + "'";
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
posixPath,
|
||||
quote,
|
||||
};
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>Multi-toolbox Playground</title>
|
||||
|
||||
<!-- This script loads uncompressed when on localhost and loads
|
||||
compressed when it is being hosted or on Internet Explorer. -->
|
||||
<script src="bootstrap.js"></script>
|
||||
<script type="module">
|
||||
// Wait for Blockly to finish loading.
|
||||
import './bootstrap_done.mjs';
|
||||
import {COMPRESSED, loadScript} from './scripts/load.mjs';
|
||||
|
||||
const IS_UNCOMPRESSED = !window.bootstrapInfo.compressed; // See bootstrap.js
|
||||
import * as Blockly from '../build/blockly.loader.mjs';
|
||||
import '../build/blocks.loader.mjs';
|
||||
|
||||
await loadScript('../build/msg/en.js');
|
||||
|
||||
var options = {
|
||||
comments: true,
|
||||
@@ -87,7 +86,7 @@
|
||||
|
||||
function setBackgroundColour() {
|
||||
// Set background colour to differentiate between compressed and uncompressed mode.
|
||||
if (IS_UNCOMPRESSED) {
|
||||
if (!COMPRESSED) {
|
||||
document.body.style.backgroundColor = '#d6d6ff'; // Familiar lilac.
|
||||
} else {
|
||||
document.body.style.backgroundColor = '#60fcfc'; // Unfamiliar blue.
|
||||
|
||||
@@ -4,23 +4,21 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>Blockly Playground</title>
|
||||
|
||||
<!-- This script loads uncompressed when on localhost and loads
|
||||
compressed when it is being hosted or on Internet Explorer. -->
|
||||
<script>
|
||||
var BLOCKLY_BOOTSTRAP_OPTIONS = {
|
||||
scripts: [
|
||||
'build/msg/en.js',
|
||||
'tests/playgrounds/screenshot.js',
|
||||
'node_modules/@blockly/dev-tools/dist/index.js',
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<script src="bootstrap.js"></script>
|
||||
<script type="module">
|
||||
// Wait for Blockly to finish loading.
|
||||
import './bootstrap_done.mjs';
|
||||
import {COMPRESSED, loadScript} from './scripts/load.mjs';
|
||||
|
||||
import * as Blockly from '../build/blockly.loader.mjs';
|
||||
import '../build/blocks.loader.mjs';
|
||||
import {dartGenerator} from '../build/dart.loader.mjs';
|
||||
import {luaGenerator} from '../build/lua.loader.mjs';
|
||||
import {javascriptGenerator} from '../build/javascript.loader.mjs';
|
||||
import {phpGenerator} from '../build/php.loader.mjs';
|
||||
import {pythonGenerator} from '../build/python.loader.mjs';
|
||||
|
||||
await loadScript('../build/msg/en.js');
|
||||
await loadScript('playgrounds/screenshot.js');
|
||||
await loadScript('../node_modules/@blockly/dev-tools/dist/index.js');
|
||||
|
||||
const IS_UNCOMPRESSED = !window.bootstrapInfo.compressed; // See bootstrap.js
|
||||
var workspace = null;
|
||||
|
||||
function start() {
|
||||
@@ -96,9 +94,12 @@
|
||||
addEventHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background colour to differentiate between compressed and
|
||||
* uncompressed mode.
|
||||
*/
|
||||
function setBackgroundColour() {
|
||||
// Set background colour to differentiate between compressed and uncompressed mode.
|
||||
if (IS_UNCOMPRESSED) {
|
||||
if (!COMPRESSED) {
|
||||
document.body.style.backgroundColor = '#d6d6ff'; // Familiar lilac.
|
||||
} else {
|
||||
document.body.style.backgroundColor = '#60fcfc'; // Unfamiliar blue.
|
||||
|
||||
@@ -4,22 +4,20 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>Advanced Blockly Playground</title>
|
||||
|
||||
<script>
|
||||
var BLOCKLY_BOOTSTRAP_OPTIONS = {
|
||||
scripts: [
|
||||
'build/msg/en.js',
|
||||
'tests/playgrounds/screenshot.js',
|
||||
'tests/themes/test_themes.js',
|
||||
'node_modules/@blockly/dev-tools/dist/index.js',
|
||||
'node_modules/@blockly/theme-modern/dist/index.js',
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<script src="../bootstrap.js"></script>
|
||||
|
||||
<script type="module">
|
||||
// Wait for Blockly to finish loading.
|
||||
import '../bootstrap_done.mjs';
|
||||
import {loadScript} from '../scripts/load.mjs';
|
||||
|
||||
import * as Blockly from '../../build/blockly.loader.mjs';
|
||||
import '../../build/blocks.loader.mjs';
|
||||
// Generators not needed (but see also bug #6597).
|
||||
|
||||
await loadScript('../../build/msg/en.js');
|
||||
await loadScript('screenshot.js');
|
||||
await loadScript('../themes/test_themes.js');
|
||||
await loadScript('../../node_modules/@blockly/dev-tools/dist/index.js');
|
||||
await loadScript(
|
||||
'../../node_modules/@blockly/theme-modern/dist/index.js',
|
||||
);
|
||||
|
||||
function start() {
|
||||
setBackgroundColour();
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Shared Procedures Playground</title>
|
||||
|
||||
<!-- This script loads uncompressed when on localhost and loads
|
||||
compressed when it is being hosted or on Internet Explorer. -->
|
||||
<script>
|
||||
var BLOCKLY_BOOTSTRAP_OPTIONS = {
|
||||
scripts: [
|
||||
'build/msg/en.js',
|
||||
'node_modules/@blockly/dev-tools/dist/index.js',
|
||||
],
|
||||
};
|
||||
</script>
|
||||
<script src="../bootstrap.js"></script>
|
||||
<script type="module">
|
||||
// Wait for Blockly to finish loading.
|
||||
import '../bootstrap_done.mjs';
|
||||
|
||||
const IS_UNCOMPRESSED = !window.bootstrapInfo.compressed; // See bootstrap.js
|
||||
var workspace = null;
|
||||
|
||||
function start() {
|
||||
setBackgroundColour();
|
||||
|
||||
var toolbox = {
|
||||
'kind': 'categoryToolbox',
|
||||
'contents': [
|
||||
{
|
||||
'kind': 'category',
|
||||
'name': 'Other Blocks',
|
||||
'categorystyle': 'logic_category',
|
||||
'contents': [
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'controls_if',
|
||||
},
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'logic_operation',
|
||||
},
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'logic_negate',
|
||||
},
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'logic_boolean',
|
||||
},
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'text_print',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'kind': 'category',
|
||||
'name': 'Variables',
|
||||
'categorystyle': 'variable_category',
|
||||
'custom': 'VARIABLE',
|
||||
},
|
||||
{
|
||||
'kind': 'category',
|
||||
'name': 'Procedures',
|
||||
'categorystyle': 'procedure_category',
|
||||
'custom': 'PROCEDURE',
|
||||
},
|
||||
],
|
||||
};
|
||||
var options = {
|
||||
comments: true,
|
||||
collapse: true,
|
||||
disable: true,
|
||||
grid: {
|
||||
spacing: 25,
|
||||
length: 3,
|
||||
colour: '#ccc',
|
||||
snap: true,
|
||||
},
|
||||
horizontalLayout: false,
|
||||
maxBlocks: Infinity,
|
||||
maxInstances: {'test_basic_limit_instances': 3},
|
||||
maxTrashcanContents: 256,
|
||||
media: '../../media/',
|
||||
oneBasedIndex: true,
|
||||
readOnly: false,
|
||||
rtl: false,
|
||||
move: {
|
||||
scrollbars: true,
|
||||
drag: true,
|
||||
wheel: false,
|
||||
},
|
||||
toolbox: toolbox,
|
||||
toolboxPosition: 'start',
|
||||
renderer: 'geras',
|
||||
zoom: {
|
||||
controls: true,
|
||||
wheel: true,
|
||||
startScale: 1.0,
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
scaleSpeed: 1.1,
|
||||
},
|
||||
};
|
||||
workspace = Blockly.inject('blocklyDiv1', options);
|
||||
workspace = Blockly.inject('blocklyDiv2', options);
|
||||
}
|
||||
|
||||
function setBackgroundColour() {
|
||||
// Set background colour to differentiate between compressed and uncompressed mode.
|
||||
if (IS_UNCOMPRESSED) {
|
||||
document.body.style.backgroundColor = '#d6d6ff'; // Familiar lilac.
|
||||
} else {
|
||||
document.body.style.backgroundColor = '#60fcfc'; // Unfamiliar blue.
|
||||
}
|
||||
}
|
||||
|
||||
start();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: #fff;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
h1 {
|
||||
font-weight: normal;
|
||||
font-size: 140%;
|
||||
}
|
||||
#blocklyDiv1 {
|
||||
float: left;
|
||||
height: 95%;
|
||||
width: 49%;
|
||||
}
|
||||
#blocklyDiv2 {
|
||||
float: right;
|
||||
height: 95%;
|
||||
width: 49%;
|
||||
}
|
||||
#importExport {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ioLabel > .blocklyFlyoutLabelText {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#blocklyDiv.renderingDebug .blockRenderDebug {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.playgroundToggleOptions {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.playgroundToggleOptions li {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.zelos-renderer .blocklyFlyoutButton .blocklyText {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Shared Procedures Playground</h1>
|
||||
|
||||
<div id="blocklyDiv1"></div>
|
||||
<div id="blocklyDiv2"></div>
|
||||
</body>
|
||||
</html>
|
||||
140
tests/scripts/load.mjs
Normal file
140
tests/scripts/load.mjs
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Helper functions for loading Blockly in tests.
|
||||
*
|
||||
* Used by playgrounds and test harnesses, both directly and via the
|
||||
* shims generated by the buildShims function in
|
||||
* scripts/gulpfiles/build_tasks.js.
|
||||
*
|
||||
*/
|
||||
|
||||
if (typeof window !== 'object') {
|
||||
// Not running in a browser. Maybe we wish to support this?
|
||||
// blockly_uncompressed formerly supported node.js, though it
|
||||
// appears that the code had not been working for some time (at
|
||||
// least since PR #5718 back in December 2021. For now just throw
|
||||
// an error.
|
||||
throw new Error('Bootstrapping without window is not supported.');
|
||||
}
|
||||
|
||||
/**
|
||||
* URL of the blockly repository. This is needed for a few reasons:
|
||||
*
|
||||
* - We need an absolute path instead of relative path because the
|
||||
* advanced_playground and the 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.
|
||||
*
|
||||
* The formulation given here will work so long as top-level page is loaded from
|
||||
* somewhere in tests/.
|
||||
*/
|
||||
export const ROOT = window.location.href.replace(/\/tests\/.*$/, '/');
|
||||
|
||||
/**
|
||||
* Decide whether to use compressed mode or not.
|
||||
*
|
||||
* Honours a "?compressed=" query parameter if present; otherwise uses
|
||||
* uncompressed for when loading from local machine and compressed
|
||||
* otherwise. See issue #5557 for additional background.
|
||||
*
|
||||
* @return {boolean} True if should load in compressed mode.
|
||||
*/
|
||||
function compressed() {
|
||||
const param = location.search.match(/compressed=([^&]+)/)?.[1];
|
||||
if (param) {
|
||||
if (['y', 'yes', 'true', '1'].includes(param)) return true;
|
||||
if (['n', 'no', 'false', '0'].includes(param)) return false;
|
||||
console.error(`Unrecognised compressed parameter "${param}"`);
|
||||
}
|
||||
|
||||
const LOCALHOSTS = ['localhost', '127.0.0.1', '[::1]'];
|
||||
const isLocalhost = LOCALHOSTS.includes(location.hostname);
|
||||
const isFileUrl = location.origin === 'file://';
|
||||
return !(isLocalhost || isFileUrl);
|
||||
}
|
||||
|
||||
/** @type {boolean} Load in compressed mode. */
|
||||
export const COMPRESSED = compressed();
|
||||
|
||||
/**
|
||||
* Load a chunk, either by importing its ESM entrypoint or loading the
|
||||
* compressed chunk script.
|
||||
*
|
||||
* When loading in uncompressed mode, if scriptExports is a simple
|
||||
* variable name (e.g. 'Blockly') then globalThis[scriptExports] will
|
||||
* be set to the the chunk's Module object. This attempts to provide
|
||||
* backward compatibility with loading the compressed chunk as a
|
||||
* script, where this is done by the compressed chunk's UMD wrapper.
|
||||
* The compatibility is not complete, however: since Module objects
|
||||
* are immutable, it is not possible to set (e.g.)
|
||||
* Blockly.libraryBlocks.
|
||||
*
|
||||
* The intention is to allow the chunk to be accessed either via
|
||||
* the returned Module / exports object (preferred) or via the global
|
||||
* scope (needed by dev-tools) regardless of whether it was loaded
|
||||
* compressed or uncompressed.
|
||||
*
|
||||
* Paths should be relative to the repository root.
|
||||
*
|
||||
* @param {string} modulePath Path to the chunk's ESM entry point.
|
||||
* @param {string} scriptPath Path to the chunk's _compressed.js file.
|
||||
* @param {string} scriptExports The global variable name (or
|
||||
* dotted-identifier path relative from a global variable) in
|
||||
* which the compressed chunk's UMD wrapper will save the module's
|
||||
* exports object. Should be the same as the correspodning
|
||||
* chunks[].scriptExport property in
|
||||
* scripts/gulpfiles/build_tasks.js.
|
||||
* @return {Module} The module's Module or exports object.
|
||||
*/
|
||||
export async function loadChunk(modulePath, scriptPath, scriptExports) {
|
||||
if (COMPRESSED) {
|
||||
await loadScript(`${ROOT}${scriptPath}`);
|
||||
return get(scriptExports);
|
||||
} else {
|
||||
const exports = await import(`../../${modulePath}`);
|
||||
if (!scriptExports.includes('.')) globalThis[scriptExports] = exports;
|
||||
return exports;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a given URL using a <script> tag.
|
||||
*
|
||||
* Unlike document.write('<script src=...>'), this is safe to use even
|
||||
* after page loading is complete.
|
||||
*
|
||||
* @param {string} src The src attribute for the <script> tag.
|
||||
* @return {Promise} A promise that resolves (or rejects) once the
|
||||
* script is loaded.
|
||||
*/
|
||||
export function loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value referred to by a dotted-identifier path
|
||||
* (e.g. foo.bar.baz). Throws TypeError if path is is not valid,
|
||||
* i.e., if any component but the last does not exist).
|
||||
*
|
||||
* @param {string} path The path referring to the desired value.
|
||||
* @return {string|null} The value referred to.
|
||||
*/
|
||||
function get(path) {
|
||||
let obj = globalThis;
|
||||
for (const part of path.split('.')) {
|
||||
obj = obj[part];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
Reference in New Issue
Block a user