mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-22 22:14:39 +08:00
Since we have the major version jump, we needed to update quite
a few things to make this work:
1. rollup4 ships with native binaries. We can't check them into
our node_modules for all platforms. Luckily they also ship a
a wasm version. This requires us to adjust the 'binary' path
to `node_modules/@rollup/wasm-node/dist/bin/rollup` instead.
2. rollup4 is more strict when it comes to ES modules vs
CommonJS. We adjust this by renaming the rollup config files.
3. Some plugins got deprecated so we had to replace them with
the newer versions (source maps and terser).
Bug: None
Change-Id: If13d590c8185db6ad85b3992d0a869111909dc8c
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5999255
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
246 lines
7.4 KiB
JavaScript
246 lines
7.4 KiB
JavaScript
import path from 'path';
|
|
import { walk } from 'estree-walker';
|
|
import MagicString from 'magic-string';
|
|
import fastGlob from 'fast-glob';
|
|
import { generate } from 'astring';
|
|
import { createFilter } from '@rollup/pluginutils';
|
|
|
|
class VariableDynamicImportError extends Error {}
|
|
|
|
/* eslint-disable-next-line no-template-curly-in-string */
|
|
const example = 'For example: import(`./foo/${bar}.js`).';
|
|
|
|
function sanitizeString(str) {
|
|
if (str === '') return str;
|
|
if (str.includes('*')) {
|
|
throw new VariableDynamicImportError('A dynamic import cannot contain * characters.');
|
|
}
|
|
return fastGlob.escapePath(str);
|
|
}
|
|
|
|
function templateLiteralToGlob(node) {
|
|
let glob = '';
|
|
|
|
for (let i = 0; i < node.quasis.length; i += 1) {
|
|
glob += sanitizeString(node.quasis[i].value.raw);
|
|
if (node.expressions[i]) {
|
|
glob += expressionToGlob(node.expressions[i]);
|
|
}
|
|
}
|
|
|
|
return glob;
|
|
}
|
|
|
|
function callExpressionToGlob(node) {
|
|
const { callee } = node;
|
|
if (
|
|
callee.type === 'MemberExpression' &&
|
|
callee.property.type === 'Identifier' &&
|
|
callee.property.name === 'concat'
|
|
) {
|
|
return `${expressionToGlob(callee.object)}${node.arguments.map(expressionToGlob).join('')}`;
|
|
}
|
|
return '*';
|
|
}
|
|
|
|
function binaryExpressionToGlob(node) {
|
|
if (node.operator !== '+') {
|
|
throw new VariableDynamicImportError(`${node.operator} operator is not supported.`);
|
|
}
|
|
|
|
return `${expressionToGlob(node.left)}${expressionToGlob(node.right)}`;
|
|
}
|
|
|
|
function expressionToGlob(node) {
|
|
switch (node.type) {
|
|
case 'TemplateLiteral':
|
|
return templateLiteralToGlob(node);
|
|
case 'CallExpression':
|
|
return callExpressionToGlob(node);
|
|
case 'BinaryExpression':
|
|
return binaryExpressionToGlob(node);
|
|
case 'Literal': {
|
|
return sanitizeString(node.value);
|
|
}
|
|
default:
|
|
return '*';
|
|
}
|
|
}
|
|
|
|
const defaultProtocol = 'file:';
|
|
const ignoredProtocols = ['data:', 'http:', 'https:'];
|
|
|
|
function shouldIgnore(glob) {
|
|
const containsAsterisk = glob.includes('*');
|
|
|
|
const globURL = new URL(glob, defaultProtocol);
|
|
|
|
const containsIgnoredProtocol = ignoredProtocols.some(
|
|
(ignoredProtocol) => ignoredProtocol === globURL.protocol
|
|
);
|
|
|
|
return !containsAsterisk || containsIgnoredProtocol;
|
|
}
|
|
|
|
function dynamicImportToGlob(node, sourceString) {
|
|
let glob = expressionToGlob(node);
|
|
|
|
if (shouldIgnore(glob)) {
|
|
return null;
|
|
}
|
|
|
|
glob = glob.replace(/\*\*/g, '*');
|
|
|
|
if (glob.startsWith('*')) {
|
|
throw new VariableDynamicImportError(
|
|
`invalid import "${sourceString}". It cannot be statically analyzed. Variable dynamic imports must start with ./ and be limited to a specific directory. ${example}`
|
|
);
|
|
}
|
|
|
|
if (glob.startsWith('/')) {
|
|
throw new VariableDynamicImportError(
|
|
`invalid import "${sourceString}". Variable absolute imports are not supported, imports must start with ./ in the static part of the import. ${example}`
|
|
);
|
|
}
|
|
|
|
if (!glob.startsWith('./') && !glob.startsWith('../')) {
|
|
throw new VariableDynamicImportError(
|
|
`invalid import "${sourceString}". Variable bare imports are not supported, imports must start with ./ in the static part of the import. ${example}`
|
|
);
|
|
}
|
|
|
|
// Disallow ./*.ext
|
|
const ownDirectoryStarExtension = /^\.\/\*\.\w+$/;
|
|
if (ownDirectoryStarExtension.test(glob)) {
|
|
throw new VariableDynamicImportError(
|
|
`${
|
|
`invalid import "${sourceString}". Variable imports cannot import their own directory, ` +
|
|
'place imports in a separate directory or make the import filename more specific. '
|
|
}${example}`
|
|
);
|
|
}
|
|
|
|
if (path.extname(glob) === '') {
|
|
throw new VariableDynamicImportError(
|
|
`invalid import "${sourceString}". A file extension must be included in the static part of the import. ${example}`
|
|
);
|
|
}
|
|
|
|
return glob;
|
|
}
|
|
|
|
function dynamicImportVariables({ include, exclude, warnOnError, errorWhenNoFilesFound } = {}) {
|
|
const filter = createFilter(include, exclude);
|
|
|
|
return {
|
|
name: 'rollup-plugin-dynamic-import-variables',
|
|
|
|
transform(code, id) {
|
|
if (!filter(id)) {
|
|
return null;
|
|
}
|
|
|
|
const parsed = this.parse(code);
|
|
|
|
let dynamicImportIndex = -1;
|
|
let ms;
|
|
|
|
walk(parsed, {
|
|
enter: (node) => {
|
|
if (node.type !== 'ImportExpression') {
|
|
return;
|
|
}
|
|
dynamicImportIndex += 1;
|
|
|
|
let importArg;
|
|
if (node.arguments && node.arguments.length > 0) {
|
|
// stringify the argument node, without indents, removing newlines and using single quote strings
|
|
importArg = generate(node.arguments[0], { indent: '' })
|
|
.replace(/\n/g, '')
|
|
.replace(/"/g, "'");
|
|
}
|
|
|
|
try {
|
|
// see if this is a variable dynamic import, and generate a glob expression
|
|
const glob = dynamicImportToGlob(node.source, code.substring(node.start, node.end));
|
|
|
|
if (!glob) {
|
|
// this was not a variable dynamic import
|
|
return;
|
|
}
|
|
|
|
// execute the glob
|
|
const result = fastGlob.sync(glob, { cwd: path.dirname(id) });
|
|
const paths = result.map((r) =>
|
|
r.startsWith('./') || r.startsWith('../') ? r : `./${r}`
|
|
);
|
|
|
|
if (errorWhenNoFilesFound && paths.length === 0) {
|
|
const error = new Error(
|
|
`No files found in ${glob} when trying to dynamically load concatted string from ${id}`
|
|
);
|
|
if (warnOnError) {
|
|
this.warn(error);
|
|
} else {
|
|
this.error(error);
|
|
}
|
|
}
|
|
|
|
// create magic string if it wasn't created already
|
|
ms = ms || new MagicString(code);
|
|
// unpack variable dynamic import into a function with import statements per file, rollup
|
|
// will turn these into chunks automatically
|
|
ms.prepend(
|
|
`function __variableDynamicImportRuntime${dynamicImportIndex}__(path) {
|
|
switch (path) {
|
|
${paths
|
|
.map((p) => ` case '${p}': return import('${p}'${importArg ? `, ${importArg}` : ''});`)
|
|
.join('\n')}
|
|
${` default: return new Promise(function(resolve, reject) {
|
|
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
|
|
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
|
|
);
|
|
})\n`} }
|
|
}\n\n`
|
|
);
|
|
|
|
// call the runtime function instead of doing a dynamic import, the import specifier will
|
|
// be evaluated at runtime and the correct import will be returned by the injected function
|
|
ms.overwrite(
|
|
node.start,
|
|
node.start + 6,
|
|
`__variableDynamicImportRuntime${dynamicImportIndex}__`
|
|
);
|
|
} catch (error) {
|
|
if (error instanceof VariableDynamicImportError) {
|
|
// TODO: line number
|
|
if (warnOnError) {
|
|
this.warn(error);
|
|
} else {
|
|
this.error(error);
|
|
}
|
|
} else {
|
|
this.error(error);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (ms && dynamicImportIndex !== -1) {
|
|
return {
|
|
code: ms.toString(),
|
|
map: ms.generateMap({
|
|
file: id,
|
|
includeContent: true,
|
|
hires: true
|
|
})
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
}
|
|
|
|
export { VariableDynamicImportError, dynamicImportVariables as default, dynamicImportToGlob };
|
|
//# sourceMappingURL=index.js.map
|