Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(tests): Use goog.bootstrap to allow loading ES modules in playground #5931

Merged
merged 5 commits into from
Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 14 additions & 37 deletions closure/goog/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,14 @@ goog.global.CLOSURE_UNCOMPILED_DEFINES;
* var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
* </pre>
*
* @type {Object<string, (string|number|boolean)>|undefined}
* Currently the Closure Compiler will only recognize very simple definitions of
* this value when looking for values to apply to compiled code and ignore all
* other references. Specifically, it looks the value defined at the variable
* declaration, as with the example above.
*
* TODO(user): Improve the recognized definitions.
cpcallen marked this conversation as resolved.
Show resolved Hide resolved
*
* @type {!Object<string, (string|number|boolean)>|null|undefined}
*/
goog.global.CLOSURE_DEFINES;

Expand Down Expand Up @@ -3175,23 +3182,10 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
scriptEl.nonce = nonce;
}

if (goog.DebugLoader_.IS_OLD_IE_) {
// Execution order is not guaranteed on old IE, halt loading and write
// these scripts one at a time, after each loads.
controller.pause();
scriptEl.onreadystatechange = function() {
if (scriptEl.readyState == 'loaded' ||
scriptEl.readyState == 'complete') {
controller.loaded();
controller.resume();
}
};
} else {
scriptEl.onload = function() {
scriptEl.onload = null;
controller.loaded();
};
}
scriptEl.onload = function() {
scriptEl.onload = null;
controller.loaded();
};

scriptEl.src = goog.TRUSTED_TYPES_POLICY_ ?
goog.TRUSTED_TYPES_POLICY_.createScriptURL(this.path) :
Expand Down Expand Up @@ -3502,13 +3496,6 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
// If one thing is pending it is this.
var anythingElsePending = controller.pending().length > 1;

// If anything else is loading we need to lazy load due to bugs in old IE.
// Specifically script tags with src and script tags with contents could
// execute out of order if document.write is used, so we cannot use
// document.write. Do not pause here; it breaks old IE as well.
var useOldIeWorkAround =
anythingElsePending && goog.DebugLoader_.IS_OLD_IE_;

// Additionally if we are meant to defer scripts but the page is still
// loading (e.g. an ES6 module is loading) then also defer. Or if we are
// meant to defer and anything else is pending then defer (those may be
Expand All @@ -3517,7 +3504,7 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
var needsAsyncLoading = goog.Dependency.defer_ &&
(anythingElsePending || goog.isDocumentLoading_());

if (useOldIeWorkAround || needsAsyncLoading) {
if (needsAsyncLoading) {
// Note that we only defer when we have to rather than 100% of the time.
// Always defering would work, but then in theory the order of
// goog.require calls would then matter. We want to enforce that most of
Expand Down Expand Up @@ -3561,8 +3548,7 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
};
} else {
// Always eval on old IE.
if (goog.DebugLoader_.IS_OLD_IE_ || !goog.inHtmlDocument_() ||
!goog.isDocumentLoading_()) {
if (!goog.inHtmlDocument_() || !goog.isDocumentLoading_()) {
load();
} else {
fetchInOwnScriptThenLoad();
Expand Down Expand Up @@ -3706,15 +3692,6 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
};


/**
* Whether the browser is IE9 or earlier, which needs special handling
* for deferred modules.
* @const @private {boolean}
*/
goog.DebugLoader_.IS_OLD_IE_ = !!(
!goog.global.atob && goog.global.document && goog.global.document['all']);


/**
* @param {string} relPath
* @param {!Array<string>|undefined} provides
Expand Down
99 changes: 99 additions & 0 deletions closure/goog/goog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2018 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview ES6 module that exports symbols from base.js so that ES6
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sweet, I think this will make the typescript compiler happier. Did you copy it from closure, or write it yourself?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes: this is copied straight over. I didn't end up using it in this PR, but we will need it to do goog.declareModuleId in ES modules, if we are to comply with the recommended approach.

* modules do not need to use globals and so that is clear if a project is using
* Closure's base.js file. It is also a subset of properties in base.js, meaning
* it should be clearer what should not be used in ES6 modules
* (goog.module/provide are not exported here, for example). Though that is not
* to say that everything in this file should be used in an ES6 module; some
* depreciated functions are exported to make migration easier (e.g.
* goog.scope).
*
* Note that this does not load Closure's base.js file, it is still up to the
* programmer to include it. Nor does the fact that this is an ES6 module mean
* that projects no longer require deps.js files for debug loading - they do.
* Closure will need to load your ES6 modules for you if you have any Closure
* file (goog.provide/goog.module) dependencies, as they need to be available
* before the ES6 module evaluates.
*
* Also note that this file has special compiler handling! It is okay to export
* anything from this file, but the name also needs to exist on the global goog.
* This special compiler pass enforces that you always import this file as
* `import * as goog`, as many tools use regex based parsing to find
* goog.require calls.
*/

export const global = goog.global;
export const require = goog.require;
export const define = goog.define;
export const DEBUG = goog.DEBUG;
export const LOCALE = goog.LOCALE;
export const TRUSTED_SITE = goog.TRUSTED_SITE;
export const DISALLOW_TEST_ONLY_CODE = goog.DISALLOW_TEST_ONLY_CODE;
export const getGoogModule = goog.module.get;
export const setTestOnly = goog.setTestOnly;
export const forwardDeclare = goog.forwardDeclare;
export const getObjectByName = goog.getObjectByName;
export const basePath = goog.basePath;
export const addSingletonGetter = goog.addSingletonGetter;
export const typeOf = goog.typeOf;
export const isArrayLike = goog.isArrayLike;
export const isDateLike = goog.isDateLike;
export const isObject = goog.isObject;
export const getUid = goog.getUid;
export const hasUid = goog.hasUid;
export const removeUid = goog.removeUid;
export const mixin = goog.mixin;
export const now = Date.now;
export const globalEval = goog.globalEval;
export const getCssName = goog.getCssName;
export const setCssNameMapping = goog.setCssNameMapping;
export const getMsg = goog.getMsg;
export const getMsgWithFallback = goog.getMsgWithFallback;
export const exportSymbol = goog.exportSymbol;
export const exportProperty = goog.exportProperty;
export const nullFunction = goog.nullFunction;
export const abstractMethod = goog.abstractMethod;
export const cloneObject = goog.cloneObject;
export const bind = goog.bind;
export const partial = goog.partial;
export const inherits = goog.inherits;
export const scope = goog.scope;
export const defineClass = goog.defineClass;
export const declareModuleId = goog.declareModuleId;

// Export select properties of module. Do not export the function itself or
// goog.module.declareLegacyNamespace.
export const module = {
get: goog.module.get,
};

// Omissions include:
// goog.ENABLE_DEBUG_LOADER - define only used in base.
// goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING - define only used in base.
// goog.provide - ES6 modules do not provide anything.
// goog.module - ES6 modules cannot be goog.modules.
// goog.module.declareLegacyNamespace - ES6 modules cannot declare namespaces.
// goog.addDependency - meant to only be used by dependency files.
// goog.DEPENDENCIES_ENABLED - constant only used in base.
// goog.TRANSPILE - define only used in base.
// goog.TRANSPILER - define only used in base.
// goog.loadModule - should not be called by any ES6 module; exists for
// generated bundles.
// goog.LOAD_MODULE_USING_EVAL - define only used in base.
// goog.SEAL_MODULE_EXPORTS - define only used in base.
// goog.DebugLoader - used rarely, only outside of compiled code.
// goog.Transpiler - used rarely, only outside of compiled code.
1 change: 1 addition & 0 deletions tests/deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions tests/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<title>Blockly Playground</title>

<!-- This script loads uncompressed when on localhost and loads compressed when it is being hosted.
Please add any other dependencies to playgrounds/load_all.js.-->
<script src="playgrounds/load_all.js"></script>
<script>
'use strict';
Please add any other dependencies to playgrounds/prepare.js.-->
<script src="playgrounds/prepare.js"></script>
<script type=module>
import Blockly from './playgrounds/blockly.mjs';

const IS_UNCOMPRESSED = !!document.getElementById('blockly-uncompressed-script');
const IS_UNCOMPRESSED = Boolean(window.BlocklyLoader); // See prepare.js
var workspace = null;

function start() {
Expand Down Expand Up @@ -381,6 +381,12 @@
'next': { }
});

// Call start(). Because this <script> has type=module, it is
// automatically deferred, so it will not be run until after the
// document has been parsed, but before firing DOMContentLoaded.

start();

</script>

<style>
Expand Down Expand Up @@ -430,7 +436,7 @@
}
</style>
</head>
<body onload="start()">
<body>

<div id="blocklyDiv"></div>

Expand Down
38 changes: 38 additions & 0 deletions tests/playgrounds/blockly.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Finishes loading Blockly and exports it as this
* module's default export.
*
* It is exported as the default export to avoid having to
* re-export each property on Blockly individually, because you
* can't do:
*
* export * from <dynamically computed source>; // SYNTAX ERROR
*
* You must use a <script> tag to load prepare.js first, before
* importing this module in a <script type=module> to obtain the
* loaded value.
cpcallen marked this conversation as resolved.
Show resolved Hide resolved
*
* 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.
Blockly = await window.BlocklyLoader;
} 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');
}

export default Blockly;
85 changes: 85 additions & 0 deletions tests/playgrounds/prepare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview Load this file in a <script> tag to prepare for
* importing Blockly into a web page.
*
* You must use a <script> tag to load this script first, then
* import blockly.mjs in a <script type=module> to obtain the
* loaded value.
*
* See tests/playground.html for example usage.
*/
'use strict';

(function() {
// Decide whether we can load Blockly uncompiled, or must load the
// compiled version. Please see issue #5557 for more information.
const isIe = navigator.userAgent.indexOf('MSIE') !== -1 ||
navigator.appVersion.indexOf('Trident/') > -1;
const localhosts = ['localhost', '127.0.0.1', '[::1]'];

if (localhosts.includes(location.hostname) && !isIe) {
// We can load Blockly in uncompiled mode.

// Disable loading of closure/goog/deps.js (which doesn't exist).
window.CLOSURE_NO_DEPS = true;
// Load the Closure Library's base.js (the only part of the
cpcallen marked this conversation as resolved.
Show resolved Hide resolved
// libary we use, mainly for goog.require / goog.provide /
// goog.module).
document.write('<script src="../../closure/goog/base.js"></script>');
// Load dependency graph info from test/deps.js. To update
// deps.js, run `npm run build:deps`.
document.write('<script src="../../tests/deps.js"></script>');

// 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('<script src="../../msg/messages.js"></script>');
document.write(`
<script>
window.BlocklyMsg = window.Blockly.Msg;
delete window.Blockly;
</script>`);

document.write(`
<script>
window.BlocklyLoader = new Promise((resolve, reject) => {
goog.bootstrap(
[
'Blockly',
'Blockly.blocks.all',
'Blockly.Dart.all',
'Blockly.JavaScript.all',
'Blockly.Lua.all',
'Blockly.PHP.all',
'Blockly.Python.all',
], resolve);
}).then(() => {
// Copy Messages from temporary Blockly.Msg object to the real one:
Object.assign(goog.module.get('Blockly').Msg, window.BlocklyMsg);
}).then(() => {
return goog.module.get('Blockly');
});
</script>`);
} else {
// We need to load Blockly in compiled mode.

// Load blockly_compressed.js et al. using <script> tags.
document.write('<script src="../../blockly_compressed.js"></script>');
document.write('<script src="../../dart_compressed.js"></script>');
document.write('<script src="../../javascript_compressed.js"></script>');
document.write('<script src="../../lua_compressed.js"></script>');
document.write('<script src="../../php_compressed.js"></script>');
document.write('<script src="../../python_compressed.js"></script>');
document.write('<script src="../../blocks_compressed.js"></script>');
document.write('<script src="../../msg/messages.js"></script>');
}
})();