feat(build)!: Introduce exports section in package.json (#7822)

* fix(typings): Remove bogus .d.ts files; add new languages
  
  PR #3821 added .d.ts files for every file in msg/json/, but several
  of these are internal utility files rather than translations, and
  do not result in a langfile being output by create_messages.py
  when building langfiles.
  
  In the meantime we have added a few new languages that are being
  published but which have (until now) not had the corresponding
  type declarations.
  
* feat(build)!: Add exports section to package.json
  
  Add an exports stanza to package.json, enumerating existing
  entrypoints in a new format.
  
  - The original main entrypoint, index.js, is removed since the
    exports section can point directly at node.js or browser.js.
  - No change made (yet) to other entrypoints (core, blocks,
    generators); these will be dealt with in a subsequent PR.
  - The msg/en entrypoint is included in the top-level package.json
    as an example; entries for all other languages created as part
    of the packageJSON package task.
  
  BREAKING CHANGE: The introduction of an exports stanza means that
  correctly-behaved tools (node.js, bundlers like webpack, etc.)
  will only allow importing of the specified entrypoints.  Here
  is the full list of permitted entrypoints that can be imported
  or required:
  
  - blockly
  - blockly/core
  - blockly/blocks
  - blockly/dart
  - blockly/lua
  - blockly/javascript
  - blockly/php
  - blockly/python
  - blockly/msg/<lang>, for all supported language codes <lang>
    (e.g blockly/msg/en, blockly/msg/fr, blockly/msg/de, etc.)
  
  If you previously impored any other paths from the blockly package
  you will need to update your imports.  Here are the most common
  paths that may have been used, and their correct replacements:
  
  | If you previously imported:      | Import instead:            |
  | -------------------------------- | -------------------------- |
  | blockly/index.js                 | blockly                    |
  | blockly/node.js                  | blockly                    |
  | blockly/browser.js               | blockly                    |
  | blockly/blockly.min | This file should only be loaded as a <script>. |
  | blockly/core.js                  | blockly/core               |
  | blockly/core-browser.js          | blockly/core               |
  | blockly/blockly_compressed.js    | blockly/core               |
  | blockly/blocks.js                | blockly/blocks             |
  | blockly/blocks_compressed.js     | blockly/blocks             |
  | blockly/dart.js                  | blockly/dart               |
  | blockly/dart_compressed.js       | blockly/dart               |
  | blockly/lua.js                   | blockly/lua                |
  | blockly/lua_compressed.js        | blockly/lua                |
  | blockly/javascript.js            | blockly/javascript         |
  | blockly/javascript_compressed.js | blockly/javascript         |
  | blockly/php.js                   | blockly/php                |
  | blockly/php_compressed.js        | blockly/php                |
  | blockly/python.js                | blockly/python             |
  | blockly/python_compressed.js     | blockly/python             |
  | blockly/msg/en.js                | blockly/msg/en             |
  
* fix(build): Use package-paths (blockly/*) in wrapper imports
  
  Use 'blockly/core' instead of './core' when importing core into
  other wrappers (and similarly for other entries in package.json
  exports stanza), so that (e.g.) dist/javascript.js won't
  import dist/core.js (the node.js version that loads jsdom) when
  being loaded in a browser environment.
  
  This fixes an issue where blockly attempts to load jsdom even
  in browser environments because the browser stanza in
  package.json, which caused attempts to load core.js to load
  core-browser.js instead in browser environments, was removed
  in a previous commit.
  
* refactor(build): Remove unnecessray wrappers
  
  Remove pointless wrapper modules that no longer server any
  purpose; use exports stanza in package.json to point directly to
  compiled chunks where possible.
  
* refactor(build)!: Eliminate separate browser and node entrypoints
  
  Combine scripts/package/browser/index.js (becomes dist/browser.js)
  and scripts/package/node/index.js (becomes dist/node.js) into
  a single environment-agnostic index.js.
  
  BREAKING CHANGE: Historically, importing the main 'blockly' package
  would import 'blockly/core', 'blockly/blocks', 'blockly/en' and
  'blockly/javascript' - and additionally, in node.js, also import
  'blockly/dart', 'blockly/lua', 'blockly/php' and 'blockly/python'.
  
  Now the main 'blockly' package entrypoint never loads any of the
  generator modules.
  
  This change has been made because of changes to generator exports
  made in blockly v9.0.0 that make necessary to always separately
  import generator modules.
  
  Note that this change does not affect loading the blockly package
  via <script src="https://unpkg.com/blockly"; that continues to
  load to blockly.min.js, which includes javascript_compressed.js
  and (due to being loaded as a script) makes it available via
  Blockly.JavaScript.
  
* refactor(build): Simplify core entrypoint wrapper for node.js
  
  Move scripts/package/node/core.js to scripts/package/core-node.js,
  and have it packaged as dist/core-node.js rather than dist/core.js
  - without a UMD wrapper, since it will always be loaded as a CJS
  module.
  
* chore(build): Remove disused packageCommonJS helper
  
* refactor(build): Use subpath pattern (wildcard) for msg/* exports
  
  Use a subpath pattern (wildcard) for the msg/* entrypoints,
  obviating the need for special handling in packageJSON.
  
* fix(tests): Fix node tests
  
  run_node_test.js previously directly require()d the dist/blockly.js
  and dist/javascript.js wrapper module, which no longer exist.
  
  Change it to require('blockly-test') (and …blockly-test/javascript)
  and create a symlink ./node_modules/blocky-test -> dist/ to satisfy
  this.
  
* fix(build): Add types: and default: entries to exports['./core']
  
  In the 'blockly/core' export:
  
  - Replace the browser: entrypoint with a default: one.
  - Add a types: entrypoint for core.
This commit is contained in:
Christopher Allen
2024-03-15 23:09:41 +01:00
committed by GitHub
parent 8fc439f090
commit c97b13632c
27 changed files with 181 additions and 865 deletions

View File

@@ -23,7 +23,7 @@ const closureCompiler = require('google-closure-compiler').gulp();
const argv = require('yargs').argv;
const {rimraf} = require('rimraf');
const {BUILD_DIR, RELEASE_DIR, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config');
const {BUILD_DIR, LANG_BUILD_DIR, RELEASE_DIR, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config');
const {getPackageJson} = require('./helper_tasks');
const {posixPath, quote} = require('../helpers');
@@ -350,8 +350,7 @@ this removal!
*/
function buildLangfiles(done) {
// Create output directory.
const outputDir = path.join(BUILD_DIR, 'msg');
fs.mkdirSync(outputDir, {recursive: true});
fs.mkdirSync(LANG_BUILD_DIR, {recursive: true});
// Run create_messages.py.
let json_files = fs.readdirSync(path.join('msg', 'json'));
@@ -364,7 +363,7 @@ function buildLangfiles(done) {
--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 ${outputDir} \
--output_dir ${LANG_BUILD_DIR} \
--quiet ${json_files.join(' ')}`;
execSync(createMessagesCmd, {stdio: 'inherit'});

View File

@@ -26,6 +26,9 @@ exports.BUILD_DIR = 'build';
// Directory to write typings output to.
exports.TYPINGS_BUILD_DIR = path.join(exports.BUILD_DIR, 'declarations');
// Directory to write langfile output to.
exports.LANG_BUILD_DIR = path.join(exports.BUILD_DIR, 'msg');
// Directory where typescript compiler output can be found.
// Matches the value in tsconfig.json: outDir
exports.TSC_OUTPUT_DIR = path.join(exports.BUILD_DIR, 'src');

View File

@@ -21,7 +21,7 @@ const fs = require('fs');
const {rimraf} = require('rimraf');
const build = require('./build_tasks');
const {getPackageJson} = require('./helper_tasks');
const {BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config');
const {BUILD_DIR, LANG_BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config');
// Path to template files for gulp-umd.
const TEMPLATE_DIR = 'scripts/package/templates';
@@ -40,244 +40,54 @@ function packageUMD(namespace, dependencies, template = 'umd.template') {
});
};
/**
* A helper method for wrapping a file into a CommonJS module for Node.js.
* @param {string} namespace The export namespace.
* @param {Array<Object>} 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(TEMPLATE_DIR, 'node.template')
});
};
/**
* This task wraps scripts/package/blockly.js into a UMD module.
* @example import 'blockly/blockly';
*/
function packageBlockly() {
return gulp.src('scripts/package/blockly.js')
.pipe(packageUMD('Blockly', [{
name: 'Blockly',
amd: './blockly_compressed',
cjs: './blockly_compressed',
}]))
.pipe(gulp.rename('blockly.js'))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps scripts/package/blocks.js into a UMD module.
* @example import 'blockly/blocks';
*/
function packageBlocks() {
return gulp.src('scripts/package/blocks.js')
.pipe(packageUMD('BlocklyBlocks', [{
name: 'BlocklyBlocks',
amd: './blocks_compressed',
cjs: './blocks_compressed',
}]))
.pipe(gulp.rename('blocks.js'))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps scripts/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';
*
* This module is the main entrypoint for the blockly package, and
* loads blockly/core, blockly/blocks and blockly/msg/en and then
* calls setLocale(en).
*/
function packageIndex() {
return gulp.src('scripts/package/index.js')
.pipe(packageUMD('Blockly', [{
name: 'Blockly',
amd: './browser',
cjs: './node',
amd: 'blockly/core',
cjs: 'blockly/core',
},{
name: 'en',
amd: 'blockly/msg/en',
cjs: 'blockly/msg/en',
global: 'Blockly.Msg',
},{
name: 'blocks',
amd: 'blockly/blocks',
cjs: 'blockly/blocks',
global: 'Blockly.Blocks',
}]))
.pipe(gulp.rename('index.js'))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps scripts/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';
* This task copies scripts/package/core-node.js into into the
* package. This module will be the 'blockly/core' entrypoint for
* node.js environments.
*
* Note that, unlike index.js, this file does not get a UMD wrapper.
* This is because it is only used in node.js environments and so is
* guaranteed to be loaded as a CJS module.
*/
function packageBrowser() {
return gulp.src('scripts/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'))
function packageCoreNode() {
return gulp.src('scripts/package/core-node.js')
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps scripts/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('scripts/package/browser/core.js')
.pipe(packageUMD('Blockly', [{
name: 'Blockly',
amd: './blockly',
cjs: './blockly',
}]))
.pipe(gulp.rename('core-browser.js'))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps scripts/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('scripts/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(RELEASE_DIR));
};
/**
* This task wraps scripts/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('scripts/package/node/core.js')
.pipe(packageCommonJS('Blockly', [{
name: 'Blockly',
amd: './blockly',
cjs: './blockly',
}]))
.pipe(gulp.rename('core.js'))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* A helper method for wrapping a generator file into a UMD module.
* @param {string} file Source file name.
* @param {string} rename Destination file name.
* @param {string} namespace Export namespace.
*/
function packageGenerator(file, rename, namespace) {
return gulp.src(`scripts/package/${rename}`)
.pipe(packageUMD(`Blockly${namespace}`, [{
name: 'Blockly',
amd: './core',
cjs: './core',
}, {
name: `Blockly${namespace}`,
amd: `./${file}`,
cjs: `./${file}`,
}]))
.pipe(gulp.rename(rename))
.pipe(gulp.dest(RELEASE_DIR));
};
/**
* This task wraps javascript_compressed.js into a UMD module.
* @example import 'blockly/javascript';
*/
function packageJavascript() {
return packageGenerator('javascript_compressed.js', 'javascript.js', 'JavaScript');
};
/**
* This task wraps python_compressed.js into a UMD module.
* @example import 'blockly/python';
*/
function packagePython() {
return packageGenerator('python_compressed.js', 'python.js', 'Python');
};
/**
* This task wraps lua_compressed.js into a UMD module.
* @example import 'blockly/lua';
*/
function packageLua() {
return packageGenerator('lua_compressed.js', 'lua.js', 'Lua');
};
/**
* This task wraps dart_compressed.js into a UMD module.
* @example import 'blockly/dart';
*/
function packageDart() {
return packageGenerator('dart_compressed.js', 'dart.js', 'Dart');
};
/**
* This task wraps php_compressed.js into a UMD module.
* @example import 'blockly/php';
*/
function packagePHP() {
return packageGenerator('php_compressed.js', 'php.js', 'PHP');
};
/**
* This task wraps each of the files in ${BUILD_DIR/msg/ 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(`${BUILD_DIR}/msg/*.js`)
return gulp.src(`${LANG_BUILD_DIR}/*.js`)
.pipe(gulp.replace(/goog\.[^\n]+/g, ''))
.pipe(packageUMD('Blockly.Msg', [], 'umd-msg.template'))
.pipe(gulp.dest(`${RELEASE_DIR}/msg`));
@@ -310,12 +120,19 @@ function packageMedia() {
};
/**
* This task copies the package.json file into the release directory.
* This task copies the package.json file into the release directory,
* with modifications:
*
* - The scripts section is removed.
*
* Prerequisite: buildLangfiles.
*/
function packageJSON(cb) {
const packageJson = getPackageJson();
const json = Object.assign({}, packageJson);
// Copy package.json, so we can safely modify it.
const json = JSON.parse(JSON.stringify(getPackageJson()));
// Remove unwanted entries.
delete json['scripts'];
// Write resulting package.json file to release directory.
if (!fs.existsSync(RELEASE_DIR)) {
fs.mkdirSync(RELEASE_DIR, {recursive: true});
}
@@ -377,17 +194,7 @@ const package = gulp.series(
build.build,
gulp.parallel(
packageIndex,
packageBrowser,
packageNode,
packageCore,
packageNodeCore,
packageBlockly,
packageBlocks,
packageJavascript,
packagePython,
packageLua,
packageDart,
packagePHP,
packageCoreNode,
packageMedia,
gulp.series(packageLocales, packageUMDBundle),
packageJSON,