-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🏗 Share dependencies in
bento.js
(#36432)
Partial for #36421 `bento-*.js` files now use shared modules from a global `BENTO`. Shared modules are listed on `shared-bento-symbols.js`. - We generate `bento.js` to import and provide the dependencies based on this list. We also generate `bento-shared.js` to use the dependencies from the global. - `bento-*.js` binaries use `module-resolver` so that the listed imports are replaced with `bento-shared.js`. Also adds the flag `--bento_runtime_only`.
- Loading branch information
1 parent
b6124cb
commit faf69c4
Showing
24 changed files
with
480 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
const { | ||
getSharedBentoSymbols, | ||
} = require('../compile/generate/shared-bento-symbols'); | ||
const {generateIntermediatePackage} = require('../compile/generate/bento'); | ||
const {getMinifiedConfig} = require('./minified-config'); | ||
const {getUnminifiedConfig} = require('./unminified-config'); | ||
const {outputFileSync} = require('fs-extra'); | ||
|
||
let modulePath; | ||
|
||
/** | ||
* @param {{[name: string]: string[]}} packages | ||
* @return {string} | ||
*/ | ||
function writeIntermediatePackage(packages) { | ||
if (!modulePath) { | ||
// Don't remove the `./` | ||
modulePath = './build/bento-shared.js'; | ||
outputFileSync(modulePath, generateIntermediatePackage(packages)); | ||
} | ||
return modulePath; | ||
} | ||
|
||
/** | ||
* @param {{[name: string]: string[]}} packages | ||
* @return {[string, {root: string[], alias: {[alias: string]: string}}, string]} | ||
*/ | ||
function getModuleResolver(packages) { | ||
const modulePath = writeIntermediatePackage(packages); | ||
const alias = Object.fromEntries( | ||
Object.entries(packages).map(([name]) => [`^${name}$`, modulePath]) | ||
); | ||
return [ | ||
'module-resolver', | ||
{root: ['.'], alias}, | ||
// Unique name because "module-resolver" is used elsewhere and babel will | ||
// throw a duplicate name error. | ||
'module-resolver-bento-shared', | ||
]; | ||
} | ||
|
||
/** | ||
* @param {!Object} config | ||
* @return {Object} | ||
*/ | ||
function withModuleResolver(config) { | ||
return { | ||
...config, | ||
plugins: [getModuleResolver(getSharedBentoSymbols()), ...config.plugins], | ||
}; | ||
} | ||
|
||
/** | ||
* @return {!Object} | ||
*/ | ||
function getBentoElementUnminifiedConfig() { | ||
return withModuleResolver(getUnminifiedConfig()); | ||
} | ||
|
||
/** | ||
* @return {!Object} | ||
*/ | ||
function getBentoElementMinifiedConfig() { | ||
return withModuleResolver(getMinifiedConfig()); | ||
} | ||
|
||
module.exports = { | ||
getBentoElementUnminifiedConfig, | ||
getBentoElementMinifiedConfig, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// For an explanation of the OWNERS rules and syntax, see: | ||
// https://github.com/ampproject/amp-github-apps/blob/main/owners/OWNERS.example | ||
|
||
{ | ||
rules: [ | ||
{ | ||
owners: [ | ||
{name: 'ampproject/wg-infra'}, | ||
{name: 'ampproject/wg-bento'}, | ||
{name: 'alanorozco', notify: true}, | ||
], | ||
}, | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/** | ||
* @fileoverview | ||
* Compile-time generators of entry-points for Bento-related binaries. | ||
*/ | ||
|
||
// TODO(alanorozco): Move generators of extension-related `defineElement()` | ||
// into this file. Add tests for them, now that we have tests here. | ||
|
||
const dedent = require('dedent'); | ||
const {getSharedBentoSymbols} = require('./shared-bento-symbols'); | ||
|
||
/** | ||
* @param {Object<string, string[]>} packageSymbols | ||
* @return {string} | ||
*/ | ||
function generateBentoRuntimeEntrypoint( | ||
packageSymbols = getSharedBentoSymbols() | ||
) { | ||
assertNoDupes(Object.values(packageSymbols).flat()); | ||
return dedent(` | ||
import {dict} from '#core/types/object'; | ||
import {isEsm} from '#core/mode'; | ||
import {install as installCustomElements} from '#polyfills/custom-elements'; | ||
${Object.entries(packageSymbols) | ||
.map( | ||
([name, symbols]) => `import {${symbols.join(', ')}} from '${name}';` | ||
) | ||
.join('\n')} | ||
if (!isEsm()) { | ||
installCustomElements(self, class {}); | ||
} | ||
const bento = self.BENTO || []; | ||
bento['_'] = dict({ | ||
${Object.entries(packageSymbols) | ||
.map(([name, symbols]) => [ | ||
`// ${name}`, | ||
...symbols.map((symbol) => `'${symbol}': ${symbol},`), | ||
]) | ||
.flat() | ||
.join('\n')} | ||
}); | ||
bento.push = (fn) => { | ||
fn(); | ||
}; | ||
self.BENTO = bento; | ||
for (const fn of bento) { | ||
bento.push(fn); | ||
} | ||
`); | ||
} | ||
|
||
/** | ||
* @param {Object<string, string[]>} packageSymbols | ||
* @return {string} | ||
*/ | ||
function generateIntermediatePackage(packageSymbols = getSharedBentoSymbols()) { | ||
assertNoDupes(Object.values(packageSymbols).flat()); | ||
return [ | ||
"const _ = (name) => self.BENTO['_'][name];", | ||
...Object.entries(packageSymbols).map(([name, symbols]) => [ | ||
`// ${name}`, | ||
...symbols.map( | ||
(symbol) => `export const ${symbol} = /*#__PURE__*/ _('${symbol}');` | ||
), | ||
]), | ||
] | ||
.flat() | ||
.join('\n'); | ||
} | ||
|
||
/** | ||
* @param {string[]} symbols | ||
*/ | ||
function assertNoDupes(symbols) { | ||
if (Array.from(new Set(symbols)).length !== symbols.length) { | ||
throw new Error( | ||
'Shred symbols should not duplicate names, even if they come from different packages.' | ||
); | ||
} | ||
} | ||
|
||
module.exports = { | ||
generateBentoRuntimeEntrypoint, | ||
generateIntermediatePackage, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* @fileoverview | ||
* These are the packages, and their exports that are included in `bento.js` | ||
* Extension `bento-*.js` binaries will use these exports as provided by | ||
* `bento.js` from the `BENTO` global. | ||
* | ||
* We specify each export explicitly by name. | ||
* Unlisted imports will be bundled with each binary. | ||
*/ | ||
|
||
const types = require('@babel/types'); | ||
const {parse} = require('@babel/parser'); | ||
const {readFileSync} = require('fs-extra'); | ||
const {relative} = require('path'); | ||
|
||
// These must be aliased from `src/`, e.g. `#preact` to `src/preact`. | ||
// See tsconfig.json for the list of aliases. | ||
const packages = [ | ||
'core/context', | ||
'preact', | ||
'preact/base-element', | ||
'preact/compat', | ||
'preact/component', | ||
'preact/context', | ||
'preact/slot', | ||
]; | ||
|
||
/** | ||
* @param {string} source | ||
* @return {string[]} | ||
*/ | ||
function getExportedSymbols(source) { | ||
const tree = parse(source, { | ||
sourceType: 'module', | ||
plugins: ['jsx', 'exportDefaultFrom'], | ||
}); | ||
const symbols = []; | ||
for (const node of tree.program.body) { | ||
if (types.isExportAllDeclaration(node)) { | ||
throw new Error('Should not "export *"'); | ||
} | ||
if (types.isExportDefaultDeclaration(node)) { | ||
throw new Error('Should not "export default"'); | ||
} | ||
if (!types.isExportNamedDeclaration(node)) { | ||
continue; | ||
} | ||
symbols.push( | ||
// @ts-ignore | ||
...(node.declaration?.declarations?.map(({id}) => id.name) ?? []) | ||
); | ||
// @ts-ignore | ||
symbols.push(node.declaration?.id?.name); | ||
symbols.push( | ||
...node.specifiers.map((node) => { | ||
if (types.isExportDefaultSpecifier(node)) { | ||
throw new Error('Should not export from a default import'); | ||
} | ||
if (types.isExportNamespaceSpecifier(node)) { | ||
throw new Error('Should not export a namespace'); | ||
} | ||
const {exported, local} = node; | ||
if (types.isStringLiteral(exported)) { | ||
throw new Error('Should not export symbol as string'); | ||
} | ||
if (local.name !== exported.name) { | ||
throw new Error( | ||
`Exported name "${exported.name}" should match local name "${local.name}"` | ||
); | ||
} | ||
return exported.name; | ||
}) | ||
); | ||
} | ||
return symbols.filter(Boolean); | ||
} | ||
|
||
let sharedBentoSymbols; | ||
|
||
/** | ||
* @return {Object<string, string[]>} | ||
*/ | ||
function getSharedBentoSymbols() { | ||
if (!sharedBentoSymbols) { | ||
const backToRoot = relative(__dirname, process.cwd()); | ||
const entries = packages.map((pkg) => { | ||
const filepath = require.resolve(`${backToRoot}/src/${pkg}`); | ||
try { | ||
const source = readFileSync(filepath, 'utf8'); | ||
const symbols = getExportedSymbols(source); | ||
return [`#${pkg}`, symbols]; | ||
} catch (e) { | ||
e.message = `${filepath}: ${e.message}`; | ||
throw e; | ||
} | ||
}); | ||
sharedBentoSymbols = Object.fromEntries(entries); | ||
} | ||
return sharedBentoSymbols; | ||
} | ||
|
||
module.exports = {getExportedSymbols, getSharedBentoSymbols}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
const dedent = require('dedent'); | ||
const test = require('ava'); | ||
const { | ||
generateBentoRuntimeEntrypoint, | ||
generateIntermediatePackage, | ||
} = require('../bento'); | ||
|
||
test('generateBentoRuntimeEntrypoint', (t) => { | ||
t.is( | ||
generateBentoRuntimeEntrypoint({ | ||
'#foo': ['bar', 'baz'], | ||
'#baz/bar': ['car'], | ||
}), | ||
dedent(` | ||
import {dict} from '#core/types/object'; | ||
import {isEsm} from '#core/mode'; | ||
import {install as installCustomElements} from '#polyfills/custom-elements'; | ||
import {bar, baz} from '#foo'; | ||
import {car} from '#baz/bar'; | ||
if (!isEsm()) { | ||
installCustomElements(self, class {}); | ||
} | ||
const bento = self.BENTO || []; | ||
bento['_'] = dict({ | ||
// #foo | ||
'bar': bar, | ||
'baz': baz, | ||
// #baz/bar | ||
'car': car, | ||
}); | ||
bento.push = (fn) => { | ||
fn(); | ||
}; | ||
self.BENTO = bento; | ||
for (const fn of bento) { | ||
bento.push(fn); | ||
} | ||
`) | ||
); | ||
}); | ||
|
||
test('generateIntermediatePackage', (t) => { | ||
t.is( | ||
generateIntermediatePackage({x: ['foo', 'bar'], y: ['baz']}), | ||
dedent(` | ||
const _ = (name) => self.BENTO['_'][name]; | ||
// x | ||
export const foo = /*#__PURE__*/ _('foo'); | ||
export const bar = /*#__PURE__*/ _('bar'); | ||
// y | ||
export const baz = /*#__PURE__*/ _('baz'); | ||
`) | ||
); | ||
}); |
Oops, something went wrong.