Skip to content

Commit 15d591e

Browse files
authored
πŸ—πŸš€ Use AMP.config.urls in extensions (ampproject#38020)
When a binary depends on the runtime (e.g. an extension), it can reference `AMP.config.urls` instead of importing every URL. This makes a small difference in size, but also prevents re-calculating URLs.
1 parent 14ee128 commit 15d591e

File tree

16 files changed

+116
-4
lines changed

16 files changed

+116
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @fileoverview
3+
* Replaces imports of `src/config/urls` into references to self.AMP.config.urls
4+
* This allows us to re-use the value defined by the runtime in extensions.
5+
* Binaries that don't depend on the runtime should not use this transform, so
6+
* that they can directly use the values from `src/config/urls`.
7+
*/
8+
const {dirname, join, posix, relative, sep} = require('path');
9+
10+
const importSource = join(process.cwd(), 'src', 'config', 'urls');
11+
const reference = 'self.AMP.config.urls';
12+
13+
/**
14+
* @param {string} fromFilename
15+
* @param {string} toModule
16+
* @return {string}
17+
*/
18+
function relativeModule(fromFilename, toModule) {
19+
const resolved = relative(dirname(fromFilename), toModule);
20+
const resolvedPosix = resolved.split(sep).join(posix.sep);
21+
return resolvedPosix.startsWith('.') ? resolvedPosix : `./${resolvedPosix}`;
22+
}
23+
24+
/**
25+
* @param {import('@babel/core')} babel
26+
* @return {import('@babel/core').PluginObj}
27+
*/
28+
module.exports = function (babel) {
29+
let importSourceRelative;
30+
const {template, types: t} = babel;
31+
const buildNamespace = template.statement(
32+
`const name = /* #__PURE__ */ (() => ${reference})()`,
33+
{preserveComments: true, placeholderPattern: /^name$/}
34+
);
35+
return {
36+
name: 'amp-config-urls',
37+
visitor: {
38+
Program: {
39+
enter(_, state) {
40+
const {filename} = state;
41+
if (!filename) {
42+
throw new Error(
43+
'babel-plugin-amp-config-urls must be called with a filename'
44+
);
45+
}
46+
importSourceRelative = relativeModule(filename, importSource);
47+
},
48+
},
49+
ImportDeclaration(path) {
50+
const {source} = path.node;
51+
if (!t.isStringLiteral(source, {value: importSourceRelative})) {
52+
return;
53+
}
54+
for (const specifier of path.get('specifiers')) {
55+
if (!specifier.isImportNamespaceSpecifier()) {
56+
throw specifier.buildCodeFrameError(
57+
`Unresolvable specifier. You must import \`urls\` as a namespace:\n` +
58+
`\`import * as urls from '${source.value}';\``
59+
);
60+
}
61+
const {name} = specifier.node.local;
62+
path.replaceWith(buildNamespace({name}));
63+
}
64+
},
65+
},
66+
};
67+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import urls from '../../../../../../../src/config/urls';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": [
3+
"../../../.."
4+
],
5+
"throws": "Unresolvable specifier. You must import `urls` as a namespace:\n`import * as urls from '../../../../../../../src/config/urls';`"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import {named, foo as bar} from '../../../../../../../src/config/urls';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"plugins": [
3+
"../../../.."
4+
],
5+
"throws": "Unresolvable specifier. You must import `urls` as a namespace:\n`import * as urls from '../../../../../../../src/config/urls';`"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import * as urls from 'something other than src/config/urls';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"plugins": [
3+
"../../../.."
4+
]
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import * as urls from 'something other than src/config/urls';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import * as urls from '../../../../../../../src/config/urls';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"plugins": [
3+
"../../../.."
4+
]
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const urls = /* #__PURE__ */(() => self.AMP.config.urls)();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const runner = require('@babel/helper-plugin-test-runner').default;
2+
3+
runner(__dirname);

Diff for: β€Žbuild-system/common/esbuild-babel.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ let transformCache;
2929
* @param {string} callerName
3030
* @param {boolean} enableCache
3131
* @param {{
32-
* preSetup?: function():void,
33-
* postLoad?: function():void,
32+
* preSetup?: () => void,
33+
* postLoad?: () => void,
34+
* plugins?: null | import('@babel/core').PluginItem[],
3435
* }} callbacks
3536
* @return {!Object}
3637
*/
3738
function getEsbuildBabelPlugin(
3839
callerName,
3940
enableCache,
40-
{preSetup = () => {}, postLoad = () => {}} = {}
41+
{preSetup = () => {}, postLoad = () => {}, plugins} = {}
4142
) {
4243
const babelMaps = new Map();
4344

@@ -94,6 +95,9 @@ function getEsbuildBabelPlugin(
9495
babel.loadOptions({caller: {name: callerName}, filename}) || {}
9596
);
9697
babelOptions.sourceMaps = true;
98+
if (plugins) {
99+
babelOptions.plugins = [...babelOptions.plugins, ...plugins];
100+
}
97101

98102
const {contents, hash} = await batchedRead(filename);
99103
const rehash = md5(

Diff for: β€Žbuild-system/tasks/extension-helpers.js

+6
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,11 @@ function generateBentoEntryPointSource(name, toExport, outputFilename) {
802802
`).replace('__name__', JSON.stringify(name));
803803
}
804804

805+
/** @type {import('@babel/core').PluginItem[]} */
806+
const extensionBabelPlugins = [
807+
'./build-system/babel-plugins/babel-plugin-amp-config-urls',
808+
];
809+
805810
/**
806811
* Build the JavaScript for the extension specified
807812
*
@@ -837,6 +842,7 @@ async function buildExtensionJs(dir, name, options) {
837842
minifiedName: `${name}-${version}.js`,
838843
aliasName: isLatest ? `${name}-latest.js` : '',
839844
wrapper: resolvedWrapper,
845+
babelPlugins: wrapper === 'extension' ? extensionBabelPlugins : null,
840846
});
841847

842848
// If an incremental watch build fails, simply return.

Diff for: β€Žbuild-system/tasks/helpers.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ async function esbuildCompile(srcDir, srcFilename, destDir, options) {
327327

328328
const babelPlugin = getEsbuildBabelPlugin(
329329
babelCaller,
330-
/* enableCache */ true
330+
/* enableCache */ true,
331+
{plugins: options.babelPlugins}
331332
);
332333
const plugins = [babelPlugin];
333334

Diff for: β€Žsrc/config/urls.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import {pure} from '#core/types/pure';
99
*/
1010
const env = (key) => (self.AMP_CONFIG ? self.AMP_CONFIG[key] : null);
1111

12+
// Important: Make sure that every exported symbol has the same name as in
13+
// `global.AMP.config.urls` (see runtime.js for reference)
14+
1215
// Important: Make sure to wrap every exported symbol with `pure()`. This allows
1316
// this module to be dead-code-eliminated.
1417

0 commit comments

Comments
Β (0)