RN packager: transform-module.js: syncify everything

Reviewed By: davidaurelio

Differential Revision: D5803768

fbshipit-source-id: 06c28e6c30fce347780d953312646a449d0026d9
This commit is contained in:
Jean Lauliac 2017-09-11 07:38:39 -07:00 committed by Facebook Github Bot
parent 7ccbcc5ebc
commit 69eb3430b6
4 changed files with 132 additions and 208 deletions

View File

@ -26,4 +26,4 @@ type Asyncify = <A, B, C>((A, B) => C) => (A, B, Callback<C>) => void;
exports.optimizeModule =
(wrapWorkerFn(asyncify(optimizeModule)): WorkerFnWithIO<OptimizationOptions>);
exports.transformModule =
(wrapWorkerFn(transformModule): WorkerFnWithIO<TransformOptions>);
(wrapWorkerFn(asyncify(transformModule)): WorkerFnWithIO<TransformOptions>);

View File

@ -34,14 +34,9 @@ describe('optimizing JS modules', () => {
}`;
let transformResult;
beforeAll(done => {
transformModule(originalCode, {filename, transformer}, (error, result) => {
if (error) {
throw error;
}
transformResult = JSON.stringify({type: 'code', details: result.details});
done();
});
beforeAll(() => {
const result = transformModule(originalCode, {filename, transformer});
transformResult = JSON.stringify({type: 'code', details: result.details});
});
it('copies everything from the transformed file, except for transform results', () => {

View File

@ -45,49 +45,35 @@ describe('transforming JS modules:', () => {
ast: t.file(t.program(body)),
});
it('passes through file name and code', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(result.type).toBe('code');
expect(result.details).toEqual(
expect.objectContaining({
code: sourceCode,
file: filename,
}),
);
done();
});
it('passes through file name and code', () => {
const result = transformModule(sourceCode, options());
expect(result.type).toBe('code');
expect(result.details).toEqual(
expect.objectContaining({
code: sourceCode,
file: filename,
}),
);
});
it('exposes a haste ID if present', done => {
it('exposes a haste ID if present', () => {
const hasteID = 'TheModule';
const codeWithHasteID = `/** @providesModule ${hasteID} */`;
transformModule(codeWithHasteID, options(), (error, result) => {
expect(result.type).toBe('code');
expect(result.details).toEqual(expect.objectContaining({hasteID}));
done();
});
const result = transformModule(codeWithHasteID, options());
expect(result.type).toBe('code');
expect(result.details).toEqual(expect.objectContaining({hasteID}));
});
it('sets `type` to `"module"` by default', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(result.type).toBe('code');
expect(result.details).toEqual(expect.objectContaining({type: 'module'}));
done();
});
it('sets `type` to `"module"` by default', () => {
const result = transformModule(sourceCode, options());
expect(result.type).toBe('code');
expect(result.details).toEqual(expect.objectContaining({type: 'module'}));
});
it('sets `type` to `"script"` if the input is a polyfill', done => {
transformModule(
sourceCode,
{...options(), polyfill: true},
(error, result) => {
expect(result.type).toBe('code');
expect(result.details).toEqual(
expect.objectContaining({type: 'script'}),
);
done();
},
);
it('sets `type` to `"script"` if the input is a polyfill', () => {
const result = transformModule(sourceCode, {...options(), polyfill: true});
expect(result.type).toBe('code');
expect(result.details).toEqual(expect.objectContaining({type: 'script'}));
});
const defaults = {
@ -102,95 +88,79 @@ describe('transforming JS modules:', () => {
it(
'calls the passed-in transform function with code, file name, and options ' +
'for all passed in variants',
done => {
() => {
const variants = {dev: {dev: true}, prod: {dev: false}};
transformModule(sourceCode, options(variants), () => {
expect(transformer.transform).toBeCalledWith({
filename,
localPath: filename,
options: {...defaults, ...variants.dev},
src: sourceCode,
});
expect(transformer.transform).toBeCalledWith({
filename,
localPath: filename,
options: {...defaults, ...variants.prod},
src: sourceCode,
});
done();
transformModule(sourceCode, options(variants));
expect(transformer.transform).toBeCalledWith({
filename,
localPath: filename,
options: {...defaults, ...variants.dev},
src: sourceCode,
});
expect(transformer.transform).toBeCalledWith({
filename,
localPath: filename,
options: {...defaults, ...variants.prod},
src: sourceCode,
});
},
);
it('calls back with any error yielded by the transform function', done => {
it('calls back with any error yielded by the transform function', () => {
const error = new Error();
transformer.transform.stub.throws(error);
transformModule(sourceCode, options(), e => {
try {
transformModule(sourceCode, options());
throw new Error('should not be reached');
} catch (e) {
expect(e).toBe(error);
done();
});
}
});
it('wraps the code produced by the transform function into a module factory', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(error).toEqual(null);
it('wraps the code produced by the transform function into a module factory', () => {
const result = transformModule(sourceCode, options());
const {code, dependencyMapName} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`,
);
done();
});
});
it('wraps the code produced by the transform function into an IIFE for polyfills', done => {
transformModule(
sourceCode,
{...options(), polyfill: true},
(error, result) => {
expect(error).toEqual(null);
const {code} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
`(function(global){${transformedCode}})(this);`,
);
done();
},
const {code, dependencyMapName} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`,
);
});
it('creates source maps', done => {
transformModule(sourceCode, options(), (error, result) => {
const {code, map} = result.details.transformed.default;
const position = findColumnAndLine(code, 'code');
expect(position).not.toBeNull();
const consumer = new SourceMapConsumer(map);
expect(consumer.originalPositionFor(position)).toEqual(
expect.objectContaining({line: 1, column: sourceCode.indexOf('code')}),
);
done();
});
it('wraps the code produced by the transform function into an IIFE for polyfills', () => {
const result = transformModule(sourceCode, {...options(), polyfill: true});
const {code} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
`(function(global){${transformedCode}})(this);`,
);
});
it('extracts dependencies (require calls)', done => {
it('creates source maps', () => {
const result = transformModule(sourceCode, options());
const {code, map} = result.details.transformed.default;
const position = findColumnAndLine(code, 'code');
expect(position).not.toBeNull();
const consumer = new SourceMapConsumer(map);
expect(consumer.originalPositionFor(position)).toEqual(
expect.objectContaining({line: 1, column: sourceCode.indexOf('code')}),
);
});
it('extracts dependencies (require calls)', () => {
const dep1 = 'foo';
const dep2 = 'bar';
const code = `require('${dep1}'),require('${dep2}')`;
const {body} = parse(code).program;
transformer.transform.stub.returns(transformResult(body));
transformModule(code, options(), (error, result) => {
expect(result.details.transformed.default).toEqual(
expect.objectContaining({dependencies: [dep1, dep2]}),
);
done();
});
const result = transformModule(code, options());
expect(result.details.transformed.default).toEqual(
expect.objectContaining({dependencies: [dep1, dep2]}),
);
});
it('transforms for all variants', done => {
it('transforms for all variants', () => {
const variants = {dev: {dev: true}, prod: {dev: false}};
transformer.transform.stub
.withArgs(filename, sourceCode, variants.dev)
@ -198,49 +168,35 @@ describe('transforming JS modules:', () => {
.withArgs(filename, sourceCode, variants.prod)
.returns(transformResult([]));
transformModule(sourceCode, options(variants), (error, result) => {
const {dev, prod} = result.details.transformed;
expect(dev.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${dev.dependencyMapName}){arbitrary(code);});`,
);
expect(prod.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${prod.dependencyMapName}){arbitrary(code);});`,
);
done();
});
const result = transformModule(sourceCode, options(variants));
const {dev, prod} = result.details.transformed;
expect(dev.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${dev.dependencyMapName}){arbitrary(code);});`,
);
expect(prod.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${prod.dependencyMapName}){arbitrary(code);});`,
);
});
it('prefixes JSON files with `module.exports = `', done => {
it('prefixes JSON files with `module.exports = `', () => {
const json = '{"foo":"bar"}';
transformModule(
json,
{...options(), filename: 'some.json'},
(error, result) => {
const {code} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
'__d(function(global,require,module,exports){' +
`module.exports=${json}});`,
);
done();
},
const result = transformModule(json, {...options(), filename: 'some.json'});
const {code} = result.details.transformed.default;
expect(code.replace(/\s+/g, '')).toEqual(
'__d(function(global,require,module,exports){' +
`module.exports=${json}});`,
);
});
it('does not create source maps for JSON files', done => {
transformModule(
'{}',
{...options(), filename: 'some.json'},
(error, result) => {
expect(result.details.transformed.default).toEqual(
expect.objectContaining({map: null}),
);
done();
},
it('does not create source maps for JSON files', () => {
const result = transformModule('{}', {...options(), filename: 'some.json'});
expect(result.details.transformed.default).toEqual(
expect.objectContaining({map: null}),
);
});
it('adds package data for `package.json` files', done => {
it('adds package data for `package.json` files', () => {
const pkg = {
name: 'package-name',
main: 'package/main',
@ -248,14 +204,11 @@ describe('transforming JS modules:', () => {
'react-native': {'react-native': 'defs'},
};
transformModule(
JSON.stringify(pkg),
{...options(), filename: 'arbitrary/package.json'},
(error, result) => {
expect(result.details.package).toEqual(pkg);
done();
},
);
const result = transformModule(JSON.stringify(pkg), {
...options(),
filename: 'arbitrary/package.json',
});
expect(result.details.package).toEqual(pkg);
});
});

View File

@ -9,29 +9,28 @@
* @flow
* @format
*/
'use strict';
const JsFileWrapping = require('./JsFileWrapping');
const asyncify = require('async/asyncify');
const collectDependencies = require('./collect-dependencies');
const defaults = require('../../defaults');
const docblock = require('jest-docblock');
const generate = require('./generate');
const invariant = require('fbjs/lib/invariant');
const path = require('path');
const series = require('async/series');
const {basename} = require('path');
import type {
Callback,
TransformedCodeFile,
TransformedSourceFile,
Transformer,
TransformerResult,
TransformResult,
TransformVariants,
} from '../types.flow';
import type {Ast} from 'babel-core';
export type TransformOptions = {|
filename: string,
@ -55,68 +54,46 @@ const ASSET_EXTENSIONS = new Set(defaults.assetExts);
function transformModule(
content: Buffer,
options: TransformOptions,
callback: Callback<TransformedSourceFile>,
): void {
): TransformedSourceFile {
if (ASSET_EXTENSIONS.has(path.extname(options.filename).substr(1))) {
transformAsset(content, options, callback);
return;
return transformAsset(content, options);
}
const code = content.toString('utf8');
if (options.filename.endsWith('.json')) {
transformJSON(code, options, callback);
return;
return transformJSON(code, options);
}
const {filename, transformer, variants = defaultVariants} = options;
const tasks = {};
Object.keys(variants).forEach(name => {
tasks[name] = asyncify(() =>
transformer.transform({
filename,
localPath: filename,
options: {...defaultTransformOptions, ...variants[name]},
src: code,
}),
);
});
const {filename, transformer, polyfill, variants = defaultVariants} = options;
const transformed: {[key: string]: TransformResult} = {};
series(tasks, (error, results: {[key: string]: TransformerResult}) => {
if (error) {
callback(error);
return;
}
const transformed: {[key: string]: TransformResult} = {};
//$FlowIssue #14545724
Object.entries(results).forEach(([key, value]: [*, TransformFnResult]) => {
transformed[key] = makeResult(
value.ast,
filename,
code,
options.polyfill,
);
for (const variantName of Object.keys(variants)) {
const {ast} = transformer.transform({
filename,
localPath: filename,
options: {...defaultTransformOptions, ...variants[variantName]},
src: code,
});
invariant(ast != null, 'ast required from the transform results');
transformed[variantName] = makeResult(ast, filename, code, polyfill);
}
const annotations = docblock.parse(docblock.extract(code));
const annotations = docblock.parse(docblock.extract(code));
callback(null, {
type: 'code',
details: {
assetContent: null,
code,
file: filename,
hasteID: annotations.providesModule || null,
transformed,
type: options.polyfill ? 'script' : 'module',
},
});
});
return;
return {
details: {
assetContent: null,
code,
file: filename,
hasteID: annotations.providesModule || null,
transformed,
type: options.polyfill ? 'script' : 'module',
},
type: 'code',
};
}
function transformJSON(json, options, callback) {
function transformJSON(json, options): TransformedSourceFile {
const value = JSON.parse(json);
const {filename} = options;
const code = `__d(function(${JsFileWrapping.MODULE_FACTORY_PARAMETERS.join(
@ -151,24 +128,23 @@ function transformJSON(json, options, callback) {
'react-native': value['react-native'],
};
}
callback(null, {type: 'code', details: result});
return {type: 'code', details: result};
}
function transformAsset(
content: Buffer,
options: TransformOptions,
callback: Callback<TransformedSourceFile>,
) {
callback(null, {
): TransformedSourceFile {
return {
details: {
assetContentBase64: content.toString('base64'),
filePath: options.filename,
},
type: 'asset',
});
};
}
function makeResult(ast, filename, sourceCode, isPolyfill = false) {
function makeResult(ast: Ast, filename, sourceCode, isPolyfill = false) {
let dependencies, dependencyMapName, file;
if (isPolyfill) {
dependencies = [];