Skip to content

Commit

Permalink
🏗 Use ESM to import peer Bento binaries (#37586)
Browse files Browse the repository at this point in the history
On `module` builds, use browser's `import` statement to share Bento dependencies like `bento.mjs`.

On `nomodule` builds, we can't use `import`. Instead, we use babel to output to a format similar to AMD. 

This change also simplifies some aspects:

- Prefers using `{remapDependencies}` for esbuild rather than `module-resolver` on babel.
- Removes `bento` compile wrapper.
- Removes generators. Uses `src/bento.js` directly, and the intermediate `bento-shared.js` module is no longer needed.
  • Loading branch information
alanorozco committed Feb 11, 2022
1 parent 37aac3c commit b6877bd
Show file tree
Hide file tree
Showing 43 changed files with 756 additions and 582 deletions.
3 changes: 1 addition & 2 deletions babel.config.js
Expand Up @@ -19,8 +19,7 @@ const {log} = require('./build-system/common/logging');
*/
const babelTransforms = new Map([
['babel-jest', 'getEmptyConfig'],
['bento-element-minified', 'getBentoElementMinifiedConfig'],
['bento-element-unminified', 'getBentoElementUnminifiedConfig'],
['nomodule-loader', 'getNoModuleLoaderConfig'],
['test', 'getTestConfig'],
['unminified', 'getUnminifiedConfig'],
['minified', 'getMinifiedConfig'],
Expand Down
70 changes: 0 additions & 70 deletions build-system/babel-config/bento-element-config.js

This file was deleted.

2 changes: 1 addition & 1 deletion build-system/babel-config/minified-config.js
Expand Up @@ -77,7 +77,7 @@ function getMinifiedConfig() {
return {
compact: false,
plugins,
sourceMaps: true,
sourceMaps: 'both',
presets: [presetTypescript, presetEnv],
retainLines: true,
assumptions: {
Expand Down
10 changes: 10 additions & 0 deletions build-system/babel-config/nomodule-loader-config.js
@@ -0,0 +1,10 @@
/** @return {{[string: string]: any}} */
function getNoModuleLoaderConfig() {
return {
plugins: ['./build-system/babel-plugins/babel-plugin-nomodule-loader'],
};
}

module.exports = {
getNoModuleLoaderConfig,
};
2 changes: 1 addition & 1 deletion build-system/babel-config/unminified-config.js
Expand Up @@ -55,7 +55,7 @@ function getUnminifiedConfig() {
compact: false,
plugins: unminifiedPlugins,
presets: unminifiedPresets,
sourceMaps: true,
sourceMaps: 'both',
assumptions: {
constantSuper: true,
noClassCalls: true,
Expand Down
@@ -0,0 +1,59 @@
// @ts-nocheck
/* eslint-disable no-var */
/* eslint-disable @typescript-eslint/no-unused-vars */

/* global __MODULE_NAME__ */
/* global __HAS_EXPORTS__ */
/* global __IMPORT_NAMES__ */
/* global __SINGLE_IMPORT_NO_EXPORTS__ */
/* global __ONLY_EXPORTS__ */

/**
* An async module loader similar to define() in AMD.
* Our implementation varies in that it can be compiled down to a minimal form
* based on the module's needs, rather than bundling a full implementation.
*/
(function defineish(defineCallback) {
// self.BENTO maps module names to callbacks to execute with their contents.
// interface ModuleCallbacks {
// [name: string]: ((Object) => void)[],
// }
var callbacks = (self.BENTO = self.BENTO || {});
var exec = __HAS_EXPORTS__
? function (_exports) {
defineCallback.apply(null, arguments);
var name = __MODULE_NAME__;
var awaiting = (callbacks[name] = callbacks[name] || []);
while (awaiting.length) {
awaiting.pop()(_exports);
}
awaiting.push = function (callback) {
callback(_exports);
};
}
: defineCallback;
// The most common cases are ONLY_EXPORTS and SINGLE_IMPORT_NO_EXPORTS.
// We provide them with single-purpose implementations whose output is
// significantly smaller than the worst case.
if (__ONLY_EXPORTS__) {
exec({});
} else if (__SINGLE_IMPORT_NO_EXPORTS__) {
var name = __SINGLE_IMPORT_NO_EXPORTS__;
(callbacks[name] = callbacks[name] || []).push(exec);
} else {
// Fallback general purpose implementation.
Promise.all(
__IMPORT_NAMES__.map(function (name) {
// exports is identified as the number 0
if (__HAS_EXPORTS__ && name === 0) {
return {};
}
return new Promise(function (resolve) {
(callbacks[name] = callbacks[name] || []).push(resolve);
});
})
).then(function (modules) {
exec.apply(null, modules);
});
}
})(function (__CALLBACK_ARGS__) {});
125 changes: 125 additions & 0 deletions build-system/babel-plugins/babel-plugin-nomodule-loader/index.js
@@ -0,0 +1,125 @@
/**
* @fileoverview
* Transforms ESM import statements into an async loader meant for `nomodule`
* builds.
*/

const {
buildNamespaceInitStatements,
ensureStatementsHoisted,
hasExports,
isModule,
rewriteModuleStatementsAndPrepareHeader,
} = require('@babel/helper-module-transforms');
const {readFileSync} = require('fs');
const {join: pathJoin, posix, relative} = require('path');

let wrapperTemplate;

module.exports = function (babel) {
const {template, types: t} = babel;

const pathToModuleName = (filename) =>
filename.replace(/^(\.\/)?dist\//, '').replace(/(\.max)?\.m?js$/, '');

const resolveModuleName = (filename, source) =>
pathToModuleName(posix.join(posix.dirname(filename), source));

/**
* @param {Object} replacements
* @return {babel.Node}
*/
function buildWrapper(replacements) {
if (!wrapperTemplate) {
const templateSource = readFileSync(
pathJoin(__dirname, 'define-template.js'),
'utf8'
);
wrapperTemplate = template(templateSource, {
placeholderPattern: /^__[A-Z0-9_]+__$/,
});
}
return wrapperTemplate(replacements);
}

/**
* @param {babel.NodePath<import('@babel/types').Program>} path
* @param {babel.Node} wrapper
*/
function injectWrapper(path, wrapper) {
const {body, directives} = path.node;
path.node.directives = [];
path.node.body = [];
const wrapperPath = path.pushContainer('body', wrapper)[0];
const callback = wrapperPath
.get('expression.arguments')
// @ts-ignore
.filter((arg) => arg.isFunctionExpression())[0]
.get('body');
callback.pushContainer('directives', directives);
callback.pushContainer('body', body);
}

return {
name: 'nomodule-loader',
visitor: {
Program: {
enter(path, state) {
// We can stop since this should be the last transform step.
// See nomodule-loader-config.js
path.stop();

if (!isModule(path)) {
throw new Error();
}
const loose = true;
const noInterop = true;
const {headers, meta} = rewriteModuleStatementsAndPrepareHeader(
path,
{loose, noInterop}
);

const filename = relative(process.cwd(), state.filename);
const importNames = [];
const callbackArgs = [];
const metaHasExports = hasExports(meta);
if (metaHasExports) {
// exports is identified as the number 0
importNames.push(t.numericLiteral(0));
callbackArgs.push(t.identifier(meta.exportName));
}
for (const [source, metadata] of meta.source) {
importNames.push(
t.stringLiteral(resolveModuleName(filename, source))
);
callbackArgs.push(t.identifier(metadata.name));
headers.push(
...buildNamespaceInitStatements(meta, metadata, loose)
);
}
if (importNames.length < 1) {
return;
}
ensureStatementsHoisted(headers);
path.unshiftContainer('body', headers);
injectWrapper(
path,
buildWrapper({
__MODULE_NAME__: t.stringLiteral(pathToModuleName(filename)),
__HAS_EXPORTS__: t.booleanLiteral(metaHasExports),
__ONLY_EXPORTS__: t.booleanLiteral(
metaHasExports && importNames.length === 1
),
__IMPORT_NAMES__: t.arrayExpression(importNames),
__SINGLE_IMPORT_NO_EXPORTS__:
importNames.length === 1 && !metaHasExports
? t.cloneNode(importNames[0])
: t.nullLiteral(),
__CALLBACK_ARGS__: callbackArgs,
})
);
},
},
},
};
};
@@ -0,0 +1 @@
export default function fn() {}
@@ -0,0 +1,5 @@
{
"plugins": [
"../../../.."
]
}
@@ -0,0 +1,42 @@
(function defineish(defineCallback) {
var callbacks = self.BENTO = self.BENTO || {};
var exec = true ? function (_exports) {
defineCallback.apply(null, arguments);
var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-default/input";
var awaiting = callbacks[name] = callbacks[name] || [];

while (awaiting.length) {
awaiting.pop()(_exports);
}

awaiting.push = function (callback) {
callback(_exports);
};
} : defineCallback;

if (true) {
exec({});
} else if (null) {
var name = null;
(callbacks[name] = callbacks[name] || []).push(exec);
} else {
Promise.all([0].map(function (name) {
if (true && name === 0) {
return {};
}

return new Promise(function (resolve) {
(callbacks[name] = callbacks[name] || []).push(resolve);
});
})).then(function (modules) {
exec.apply(null, modules);
});
}
})(function (_exports) {
"use strict";

_exports.__esModule = true;
_exports.default = fn;

function fn() {}
});
@@ -0,0 +1 @@
export {x, y, z} from './abc';
@@ -0,0 +1,5 @@
{
"plugins": [
"../../../.."
]
}
@@ -0,0 +1,43 @@
(function defineish(defineCallback) {
var callbacks = self.BENTO = self.BENTO || {};
var exec = true ? function (_exports) {
defineCallback.apply(null, arguments);
var name = "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/input";
var awaiting = callbacks[name] = callbacks[name] || [];

while (awaiting.length) {
awaiting.pop()(_exports);
}

awaiting.push = function (callback) {
callback(_exports);
};
} : defineCallback;

if (false) {
exec({});
} else if (null) {
var name = null;
(callbacks[name] = callbacks[name] || []).push(exec);
} else {
Promise.all([0, "build-system/babel-plugins/babel-plugin-nomodule-loader/test/fixtures/transform/export-from/abc"].map(function (name) {
if (true && name === 0) {
return {};
}

return new Promise(function (resolve) {
(callbacks[name] = callbacks[name] || []).push(resolve);
});
})).then(function (modules) {
exec.apply(null, modules);
});
}
})(function (_exports, _abc) {
"use strict";

_exports.__esModule = true;
_exports.z = _exports.y = _exports.x = void 0;
_exports.x = _abc.x;
_exports.y = _abc.y;
_exports.z = _abc.z;
});

0 comments on commit b6877bd

Please sign in to comment.