mirror of
https://github.com/status-im/metro.git
synced 2025-03-03 04:00:29 +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,
|
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,
|
filename: string,
|
||||||
localPath: LocalPath,
|
localPath: LocalPath,
|
||||||
sourceCode: string,
|
sourceCode: string,
|
||||||
|
transformerPath: string,
|
||||||
isScript: boolean,
|
isScript: boolean,
|
||||||
options: Options,
|
options: Options,
|
||||||
transformFileStartLogEntry: LogEntry,
|
assetExts: $ReadOnlyArray<string>,
|
||||||
|
assetRegistryPath: string,
|
||||||
asyncRequireModulePath: string,
|
asyncRequireModulePath: string,
|
||||||
dynamicDepsInPackages: DynamicRequiresBehavior,
|
dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||||
receivedAst: ?Ast,
|
): Promise<Data> {
|
||||||
): 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
|
// 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.
|
// 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 transformFileEndLogEntry = getEndLogEntry(
|
||||||
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
|
transformFileStartLogEntry,
|
||||||
const transformFileEndLogEntry = {
|
filename,
|
||||||
action_name: 'Transforming file',
|
);
|
||||||
action_phase: 'end',
|
|
||||||
file_name: filename,
|
|
||||||
duration_ms,
|
|
||||||
log_entry_label: 'Transforming file',
|
|
||||||
};
|
|
||||||
|
|
||||||
let dependencies, wrappedAst;
|
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(
|
function minifyCode(
|
||||||
filename: string,
|
filename: string,
|
||||||
code: string,
|
code: string,
|
||||||
@ -292,6 +270,19 @@ function isAsset(filePath: string, assetExts: $ReadOnlyArray<string>): boolean {
|
|||||||
return assetExts.indexOf(path.extname(filePath).slice(1)) !== -1;
|
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 {
|
class InvalidRequireCallError extends Error {
|
||||||
innerError: collectDependencies.InvalidRequireCallError;
|
innerError: collectDependencies.InvalidRequireCallError;
|
||||||
filename: string;
|
filename: string;
|
||||||
|
@ -21,4 +21,4 @@ exports.fn = () => {
|
|||||||
|
|
||||||
const generateOptions = {concise: true};
|
const generateOptions = {concise: true};
|
||||||
exports.codeFromAst = ast => generate(ast, generateOptions).code;
|
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)]));
|
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(
|
function functionFromProgram(
|
||||||
program: Object,
|
program: Object,
|
||||||
parameters: Array<string>,
|
parameters: Array<string>,
|
||||||
@ -73,8 +81,7 @@ function renameRequires(ast: Object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
MODULE_FACTORY_PARAMETERS,
|
wrapJson,
|
||||||
POLYFILL_FACTORY_PARAMETERS,
|
|
||||||
wrapModule,
|
wrapModule,
|
||||||
wrapPolyfill,
|
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) {
|
function astFromCode(code) {
|
||||||
return babylon.parse(code, {plugins: ['dynamicImport']});
|
return babylon.parse(code, {plugins: ['dynamicImport']});
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ describe('transforming JS modules:', () => {
|
|||||||
const {code} = result.details.transformed.default;
|
const {code} = result.details.transformed.default;
|
||||||
expect(code.replace(/\s+/g, '')).toEqual(
|
expect(code.replace(/\s+/g, '')).toEqual(
|
||||||
'__d(function(global,require,module,exports){' +
|
'__d(function(global,require,module,exports){' +
|
||||||
`module.exports=${json}});`,
|
`module.exports=${json};});`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,9 +125,7 @@ function transformModule(
|
|||||||
function transformJSON(json, options): TransformedSourceFile {
|
function transformJSON(json, options): TransformedSourceFile {
|
||||||
const value = JSON.parse(json);
|
const value = JSON.parse(json);
|
||||||
const {filename} = options;
|
const {filename} = options;
|
||||||
const code = `__d(function(${JsFileWrapping.MODULE_FACTORY_PARAMETERS.join(
|
const code = JsFileWrapping.wrapJson(json);
|
||||||
', ',
|
|
||||||
)}) { module.exports = \n${json}\n});`;
|
|
||||||
|
|
||||||
const moduleData = {
|
const moduleData = {
|
||||||
code,
|
code,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user