mirror of
https://github.com/status-im/metro.git
synced 2025-03-02 11:40:55 +00:00
Avoid parsing JSON files with babel
Summary: Metro does not need to use babel to parse JSON files, it only needs to wrap the stringified JSON directly into the `define` function. This makes the transformation of JSON files much faster, plus prevent out of memory exceptions when having huge JSON files. This diff fixes https://github.com/facebook/metro/issues/146 Reviewed By: mjesun Differential Revision: D7227095 fbshipit-source-id: 5d1a9cb2d1c7162a403c00dc43e46f781fbd1514
This commit is contained in:
parent
f2b6232c5d
commit
bfecccd180
@ -94,30 +94,91 @@ export type Data = {
|
||||
transformFileEndLogEntry: LogEntry,
|
||||
};
|
||||
|
||||
function postTransform(
|
||||
function getDynamicDepsBehavior(
|
||||
inPackages: DynamicRequiresBehavior,
|
||||
filename: string,
|
||||
): DynamicRequiresBehavior {
|
||||
switch (inPackages) {
|
||||
case 'reject':
|
||||
return 'reject';
|
||||
case 'throwAtRuntime':
|
||||
const isPackage = /(?:^|[/\\])node_modules[/\\]/.test(filename);
|
||||
return isPackage ? inPackages : 'reject';
|
||||
default:
|
||||
(inPackages: empty);
|
||||
throw new Error(
|
||||
`invalid value for dynamic deps behavior: \`${inPackages}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function transformCode(
|
||||
filename: string,
|
||||
localPath: LocalPath,
|
||||
sourceCode: string,
|
||||
transformerPath: string,
|
||||
isScript: boolean,
|
||||
options: Options,
|
||||
transformFileStartLogEntry: LogEntry,
|
||||
assetExts: $ReadOnlyArray<string>,
|
||||
assetRegistryPath: string,
|
||||
asyncRequireModulePath: string,
|
||||
dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
receivedAst: ?Ast,
|
||||
): Data {
|
||||
): Promise<Data> {
|
||||
const transformFileStartLogEntry = {
|
||||
action_name: 'Transforming file',
|
||||
action_phase: 'start',
|
||||
file_name: filename,
|
||||
log_entry_label: 'Transforming file',
|
||||
start_timestamp: process.hrtime(),
|
||||
};
|
||||
|
||||
if (filename.endsWith('.json')) {
|
||||
const code = JsFileWrapping.wrapJson(sourceCode);
|
||||
|
||||
const transformFileEndLogEntry = getEndLogEntry(
|
||||
transformFileStartLogEntry,
|
||||
filename,
|
||||
);
|
||||
|
||||
return {
|
||||
result: {dependencies: [], code, map: []},
|
||||
transformFileStartLogEntry,
|
||||
transformFileEndLogEntry,
|
||||
};
|
||||
}
|
||||
|
||||
const plugins = options.dev
|
||||
? []
|
||||
: [[inlinePlugin, options], [constantFoldingPlugin, options]];
|
||||
|
||||
// $FlowFixMe TODO t26372934 Plugin system
|
||||
const transformer: Transformer<*> = require(transformerPath);
|
||||
|
||||
const transformerArgs = {
|
||||
filename,
|
||||
localPath,
|
||||
options,
|
||||
plugins,
|
||||
src: sourceCode,
|
||||
};
|
||||
|
||||
const transformResult = isAsset(filename, assetExts)
|
||||
? await assetTransformer.transform(
|
||||
transformerArgs,
|
||||
assetRegistryPath,
|
||||
options.assetDataPlugins,
|
||||
)
|
||||
: await transformer.transform(transformerArgs);
|
||||
|
||||
// Transformers can ouptut null ASTs (if they ignore the file). In that case
|
||||
// we need to parse the module source code to get their AST.
|
||||
const ast = receivedAst || babylon.parse(sourceCode, {sourceType: 'module'});
|
||||
const ast =
|
||||
transformResult.ast || babylon.parse(sourceCode, {sourceType: 'module'});
|
||||
|
||||
const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp);
|
||||
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
|
||||
const transformFileEndLogEntry = {
|
||||
action_name: 'Transforming file',
|
||||
action_phase: 'end',
|
||||
file_name: filename,
|
||||
duration_ms,
|
||||
log_entry_label: 'Transforming file',
|
||||
};
|
||||
const transformFileEndLogEntry = getEndLogEntry(
|
||||
transformFileStartLogEntry,
|
||||
filename,
|
||||
);
|
||||
|
||||
let dependencies, wrappedAst;
|
||||
|
||||
@ -185,89 +246,6 @@ function postTransform(
|
||||
};
|
||||
}
|
||||
|
||||
function getDynamicDepsBehavior(
|
||||
inPackages: DynamicRequiresBehavior,
|
||||
filename: string,
|
||||
): DynamicRequiresBehavior {
|
||||
switch (inPackages) {
|
||||
case 'reject':
|
||||
return 'reject';
|
||||
case 'throwAtRuntime':
|
||||
const isPackage = /(?:^|[/\\])node_modules[/\\]/.test(filename);
|
||||
return isPackage ? inPackages : 'reject';
|
||||
default:
|
||||
(inPackages: empty);
|
||||
throw new Error(
|
||||
`invalid value for dynamic deps behavior: \`${inPackages}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function transformCode(
|
||||
filename: string,
|
||||
localPath: LocalPath,
|
||||
sourceCode: string,
|
||||
transformerPath: string,
|
||||
isScript: boolean,
|
||||
options: Options,
|
||||
assetExts: $ReadOnlyArray<string>,
|
||||
assetRegistryPath: string,
|
||||
asyncRequireModulePath: string,
|
||||
dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
): Data | Promise<Data> {
|
||||
const isJson = filename.endsWith('.json');
|
||||
|
||||
if (isJson) {
|
||||
sourceCode = 'module.exports=' + sourceCode;
|
||||
}
|
||||
|
||||
const transformFileStartLogEntry = {
|
||||
action_name: 'Transforming file',
|
||||
action_phase: 'start',
|
||||
file_name: filename,
|
||||
log_entry_label: 'Transforming file',
|
||||
start_timestamp: process.hrtime(),
|
||||
};
|
||||
|
||||
const plugins = options.dev
|
||||
? []
|
||||
: [[inlinePlugin, options], [constantFoldingPlugin, options]];
|
||||
|
||||
// $FlowFixMe TODO t26372934 Plugin system
|
||||
const transformer: Transformer<*> = require(transformerPath);
|
||||
|
||||
const transformerArgs = {
|
||||
filename,
|
||||
localPath,
|
||||
options,
|
||||
plugins,
|
||||
src: sourceCode,
|
||||
};
|
||||
|
||||
const transformResult = isAsset(filename, assetExts)
|
||||
? assetTransformer.transform(
|
||||
transformerArgs,
|
||||
assetRegistryPath,
|
||||
options.assetDataPlugins,
|
||||
)
|
||||
: transformer.transform(transformerArgs);
|
||||
|
||||
const postTransformArgs = [
|
||||
filename,
|
||||
localPath,
|
||||
sourceCode,
|
||||
isScript,
|
||||
options,
|
||||
transformFileStartLogEntry,
|
||||
asyncRequireModulePath,
|
||||
dynamicDepsInPackages,
|
||||
];
|
||||
|
||||
return transformResult instanceof Promise
|
||||
? transformResult.then(({ast}) => postTransform(...postTransformArgs, ast))
|
||||
: postTransform(...postTransformArgs, transformResult.ast);
|
||||
}
|
||||
|
||||
function minifyCode(
|
||||
filename: string,
|
||||
code: string,
|
||||
@ -292,6 +270,19 @@ function isAsset(filePath: string, assetExts: $ReadOnlyArray<string>): boolean {
|
||||
return assetExts.indexOf(path.extname(filePath).slice(1)) !== -1;
|
||||
}
|
||||
|
||||
function getEndLogEntry(startLogEntry: LogEntry, filename: string): LogEntry {
|
||||
const timeDelta = process.hrtime(startLogEntry.start_timestamp);
|
||||
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
|
||||
|
||||
return {
|
||||
action_name: 'Transforming file',
|
||||
action_phase: 'end',
|
||||
file_name: filename,
|
||||
duration_ms,
|
||||
log_entry_label: 'Transforming file',
|
||||
};
|
||||
}
|
||||
|
||||
class InvalidRequireCallError extends Error {
|
||||
innerError: collectDependencies.InvalidRequireCallError;
|
||||
filename: string;
|
||||
|
@ -21,4 +21,4 @@ exports.fn = () => {
|
||||
|
||||
const generateOptions = {concise: true};
|
||||
exports.codeFromAst = ast => generate(ast, generateOptions).code;
|
||||
exports.comparableCode = code => code.trim().replace(/\s\s+/g, ' ');
|
||||
exports.comparableCode = code => code.trim().replace(/\s+/g, ' ');
|
||||
|
@ -41,6 +41,14 @@ function wrapPolyfill(fileAst: Object): Object {
|
||||
return t.file(t.program([t.expressionStatement(iife)]));
|
||||
}
|
||||
|
||||
function wrapJson(source: string): string {
|
||||
return [
|
||||
`__d(function(${MODULE_FACTORY_PARAMETERS.join(', ')}) {`,
|
||||
` module.exports = ${source};`,
|
||||
`});`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function functionFromProgram(
|
||||
program: Object,
|
||||
parameters: Array<string>,
|
||||
@ -73,8 +81,7 @@ function renameRequires(ast: Object) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MODULE_FACTORY_PARAMETERS,
|
||||
POLYFILL_FACTORY_PARAMETERS,
|
||||
wrapJson,
|
||||
wrapModule,
|
||||
wrapPolyfill,
|
||||
};
|
||||
|
@ -100,6 +100,41 @@ it('wraps a polyfill correctly', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('wraps a JSON file correctly', () => {
|
||||
const source = JSON.stringify(
|
||||
{
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
baz: true,
|
||||
qux: null,
|
||||
arr: [1, 2, 3, 4],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
|
||||
const wrappedJson = JsFileWrapping.wrapJson(source);
|
||||
|
||||
expect(comparableCode(wrappedJson)).toEqual(
|
||||
comparableCode(
|
||||
`__d(function(global, require, module, exports) {
|
||||
module.exports = {
|
||||
"foo": "foo",
|
||||
"bar": "bar",
|
||||
"baz": true,
|
||||
"qux": null,
|
||||
"arr": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
]
|
||||
};
|
||||
});`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
function astFromCode(code) {
|
||||
return babylon.parse(code, {plugins: ['dynamicImport']});
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ describe('transforming JS modules:', () => {
|
||||
const {code} = result.details.transformed.default;
|
||||
expect(code.replace(/\s+/g, '')).toEqual(
|
||||
'__d(function(global,require,module,exports){' +
|
||||
`module.exports=${json}});`,
|
||||
`module.exports=${json};});`,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -125,9 +125,7 @@ function transformModule(
|
||||
function transformJSON(json, options): TransformedSourceFile {
|
||||
const value = JSON.parse(json);
|
||||
const {filename} = options;
|
||||
const code = `__d(function(${JsFileWrapping.MODULE_FACTORY_PARAMETERS.join(
|
||||
', ',
|
||||
)}) { module.exports = \n${json}\n});`;
|
||||
const code = JsFileWrapping.wrapJson(json);
|
||||
|
||||
const moduleData = {
|
||||
code,
|
||||
|
Loading…
x
Reference in New Issue
Block a user