Skip to content

Publish Blockly as ES modules #7449

@cpcallen

Description

@cpcallen

Check for duplicates

  • I have searched for similar issues before opening a new one.

Problem

At the moment much of our documentation and examples (including most of the code in the blockly-samples repository) load Blockly by doing

import * as Blockly from 'blockly'

as if Blockly were published as ES modules—but in fact the blockly NPM package contains only UMD modules which are (in practice) either require()ed as CJS modules or simply loaded as <script>s.

Certain JavaScript runtimes—notably node.js—can deal with this, allowing developers to load UMD modules (as CJS modules) using the import keyword, but no browser supports this. This means that none of the examples we publish can be run without some kind of bundler, such as webpack or the Closure Compiler, or other build step, to combine and/or convert the developer code and Blockly modules into a compatible form.

Request

  1. Make it possible to import Blockly in a browser without needing a build step, by publishing Blockly as an ES module (or, more accurately, as one ES module for each chunk: blockly, blocks, javascript etc.) Although import … from 'blockly' cannot work in a browser without an import map, it should be possible to import * as Blockly from './path/to/blockly.js';(or'…/blockly.mjs'`, depending on choices discussed below).
  2. Continue, for the time being at least, to support loading Blockly as a <script> and/or as a CJS module via require().

Alternatives considered

The NPM format allows publication of hybrid ESM+CJS modules. This is done by having separate "main": and "module": entries in the package's package.json file, pointing at the CJS and MJS entry points for the package respectively. For subpackages such as blockly/blocks there is an "exports": directive supported by node.js and by webpack, but this is not part of the official NPM package.json format.

There are at least two possible approaches to how such hybrid packages work, however:

  1. The package can contain two completely separate copies of each entry point module: one built as an ES module and the other as a CJS module. Here is a description of how such a dual-binary package might be created.
  2. The package can each entry point module built as a CJS module, and an thin ESM wrapper that loads the CJS module and re-exports its exports. This is roughly akin to what the loading shims introduced in PR refactor(tests): Introduce loading shims, use in playgrounds #7380 do when loading Blockly in compressed mode. This article explains why this approach might be preferred and details about how to implement it (along with much additional background information about the ESM vs. CJS situation).

Additional context

  • Although the UMD format allows one to publish a single file that can be treated as a plain script, as an AMD module or as a CJS module, there is no way for a file to be simultaneously both a UMD module and also an ES module—at least, not if it has any exports, because the export keyword is only legal in an ES module, and it is not possible to export anything from an ESM without using the export keyword. This means that there must be at least two entry point files present (one for ESM and one for UMD) in any package if it is to support being loaded both with import and require().

  • This 2ality article provides some additional (though partially outdated) information about different ways to structure a hybrid ESM+CJS NPM package.

  • Although we should continue to support loading Blockly as a CJS module for the time being—at minimum to provide a substantial transition period in which developers can move to using the ESM version—we may well wish to ultimately ship the blockly NPM as ESM-only. There may be some value in continuing to support CJS in the long term, but UMD's abilito to allow one to load Blockly via <script> is largely unnecessary when all current browsers now support <script type="module"> import * as Blockly from './path/to/blockly.js';.

Metadata

Metadata

Assignees

Labels

issue: feature requestDescribes a new feature and why it should be added

Type

No type

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions