Pass local paths to transformers

Summary:
Pass `localPath` to transformers to allow usage of plugins like `transform-react-jsx-source` that don’t conflict with x-machine caches, e.g. Buck or our own global cache.
These caches don’t work properly if paths local to a specific machine are part of cached outputs.

Reviewed By: jeanlauliac

Differential Revision: D5044147

fbshipit-source-id: 1177afb48d6dd9351738fcd4da8f9f6357e25e81
This commit is contained in:
David Aurelio 2017-05-11 16:37:07 -07:00 committed by Facebook Github Bot
parent 143beaf95d
commit 2fbe3b799e
9 changed files with 71 additions and 32 deletions

View File

@ -36,6 +36,7 @@ module.exports = {
return transformer.transform({ return transformer.transform({
filename: file, filename: file,
localPath: file,
options: { options: {
dev: true, dev: true,
inlineRequires: true, inlineRequires: true,

View File

@ -1,5 +1,5 @@
{ {
"version": "0.6.0", "version": "0.6.1",
"name": "react-native-packager", "name": "react-native-packager",
"description": "Build native apps with React!", "description": "Build native apps with React!",
"repository": { "repository": {

View File

@ -224,6 +224,7 @@ class Bundler {
transformCode: transformCode:
(module, code, transformCodeOptions) => this._transformer.transformFile( (module, code, transformCodeOptions) => this._transformer.transformFile(
module.path, module.path,
module.localPath,
code, code,
transformCodeOptions, transformCodeOptions,
), ),

View File

@ -30,6 +30,7 @@ const {Readable} = require('stream');
describe('Transformer', function() { describe('Transformer', function() {
let workers, Cache; let workers, Cache;
const fileName = '/an/arbitrary/file.js'; const fileName = '/an/arbitrary/file.js';
const localPath = 'arbitrary/file.js';
const transformModulePath = __filename; const transformModulePath = __filename;
beforeEach(function() { beforeEach(function() {
@ -49,10 +50,11 @@ describe('Transformer', function() {
' to the worker farm when transforming', () => { ' to the worker farm when transforming', () => {
const transformOptions = {arbitrary: 'options'}; const transformOptions = {arbitrary: 'options'};
const code = 'arbitrary(code)'; const code = 'arbitrary(code)';
new Transformer(transformModulePath).transformFile(fileName, code, transformOptions); new Transformer(transformModulePath).transformFile(fileName, localPath, code, transformOptions);
expect(workers.transformAndExtractDependencies).toBeCalledWith( expect(workers.transformAndExtractDependencies).toBeCalledWith(
transformModulePath, transformModulePath,
fileName, fileName,
localPath,
code, code,
transformOptions, transformOptions,
any(Function), any(Function),
@ -65,7 +67,7 @@ describe('Transformer', function() {
var snippet = 'snippet'; var snippet = 'snippet';
workers.transformAndExtractDependencies.mockImplementation( workers.transformAndExtractDependencies.mockImplementation(
function(transformPath, filename, code, opts, callback) { function(transformPath, filename, localPth, code, opts, callback) {
var babelError = new SyntaxError(message); var babelError = new SyntaxError(message);
babelError.type = 'SyntaxError'; babelError.type = 'SyntaxError';
babelError.description = message; babelError.description = message;
@ -78,7 +80,8 @@ describe('Transformer', function() {
}, },
); );
return transformer.transformFile(fileName, '', {}) expect.assertions(7);
return transformer.transformFile(fileName, localPath, '', {})
.catch(function(error) { .catch(function(error) {
expect(error.type).toEqual('TransformError'); expect(error.type).toEqual('TransformError');
expect(error.message).toBe('SyntaxError ' + message); expect(error.message).toBe('SyntaxError ' + message);

View File

@ -14,14 +14,21 @@
const Logger = require('../Logger'); const Logger = require('../Logger');
const debug = require('debug')('RNP:JStransformer'); const debug = require('debug')('RNP:JStransformer');
const denodeify = require('denodeify'); const denodeify: Denodeify = require('denodeify');
const invariant = require('fbjs/lib/invariant'); const invariant = require('fbjs/lib/invariant');
const path = require('path'); const path = require('path');
const util = require('util'); const util = require('util');
const workerFarm = require('../worker-farm'); const workerFarm = require('../worker-farm');
import type {Data as TransformData, Options as WorkerOptions} from './worker/worker'; import type {Data as TransformData, Options as WorkerOptions} from './worker/worker';
import type {LocalPath} from '../node-haste/lib/toLocalPath';
import type {MappingsMap} from '../lib/SourceMap'; import type {MappingsMap} from '../lib/SourceMap';
import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker/worker';
type CB<T> = (?Error, ?T) => mixed;
type Denodeify =
& (<A, B, C, T>((A, B, C, CB<T>) => void) => (A, B, C) => Promise<T>)
& (<A, B, C, D, E, T>((A, B, C, D, E, CB<T>) => void) => (A, B, C, D, E) => Promise<T>);
// Avoid memory leaks caused in workers. This number seems to be a good enough number // Avoid memory leaks caused in workers. This number seems to be a good enough number
// to avoid any memory leak while not slowing down initial builds. // to avoid any memory leak while not slowing down initial builds.
@ -62,8 +69,9 @@ class Transformer {
_transform: ( _transform: (
transform: string, transform: string,
filename: string, filename: string,
localPath: LocalPath,
sourceCode: string, sourceCode: string,
options: ?WorkerOptions, options: WorkerOptions,
) => Promise<TransformData>; ) => Promise<TransformData>;
minify: ( minify: (
filename: string, filename: string,
@ -71,7 +79,11 @@ class Transformer {
sourceMap: MappingsMap, sourceMap: MappingsMap,
) => Promise<{code: string, map: MappingsMap}>; ) => Promise<{code: string, map: MappingsMap}>;
constructor(transformModulePath: string, maxWorkerCount: number, reporters: Reporters) { constructor(
transformModulePath: string,
maxWorkerCount: number,
reporters: Reporters,
) {
invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute'); invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute');
this._transformModulePath = transformModulePath; this._transformModulePath = transformModulePath;
@ -89,21 +101,31 @@ class Transformer {
}); });
this._workers = farm.methods; this._workers = farm.methods;
this._transform = denodeify(this._workers.transformAndExtractDependencies); this._transform = denodeify((this._workers.transformAndExtractDependencies: TransformAndExtractDependencies));
this.minify = denodeify(this._workers.minify); this.minify = denodeify((this._workers.minify: Minify));
} }
kill() { kill() {
this._workers && workerFarm.end(this._workers); this._workers && workerFarm.end(this._workers);
} }
transformFile(fileName: string, code: string, options: WorkerOptions) { transformFile(
fileName: string,
localPath: LocalPath,
code: string,
options: WorkerOptions) {
if (!this._transform) { if (!this._transform) {
return Promise.reject(new Error('No transform module')); return Promise.reject(new Error('No transform module'));
} }
debug('transforming file', fileName); debug('transforming file', fileName);
return this return this
._transform(this._transformModulePath, fileName, code, options) ._transform(
this._transformModulePath,
fileName,
localPath,
code,
options,
)
.then(data => { .then(data => {
Logger.log(data.transformFileStartLogEntry); Logger.log(data.transformFileStartLogEntry);
Logger.log(data.transformFileEndLogEntry); Logger.log(data.transformFileEndLogEntry);

View File

@ -35,11 +35,13 @@ describe('code transformation worker:', () => {
it('calls the transform with file name, source code, and transform options', function() { it('calls the transform with file name, source code, and transform options', function() {
const filename = 'arbitrary/file.js'; const filename = 'arbitrary/file.js';
const localPath = `local/${filename}`;
const sourceCode = 'arbitrary(code)'; const sourceCode = 'arbitrary(code)';
const transformOptions = {arbitrary: 'options'}; const transformOptions = {arbitrary: 'options'};
transformCode(transformer, filename, sourceCode, {transform: transformOptions}, () => {}); transformCode(transformer, filename, localPath, sourceCode, {transform: transformOptions}, () => {});
expect(transformer.transform).toBeCalledWith({ expect(transformer.transform).toBeCalledWith({
filename, filename,
localPath,
options: transformOptions, options: transformOptions,
src: sourceCode, src: sourceCode,
}); });
@ -47,10 +49,12 @@ describe('code transformation worker:', () => {
it('prefixes JSON files with an assignment to module.exports to make the code valid', function() { it('prefixes JSON files with an assignment to module.exports to make the code valid', function() {
const filename = 'arbitrary/file.json'; const filename = 'arbitrary/file.json';
const localPath = `local/${filename}`;
const sourceCode = '{"arbitrary":"property"}'; const sourceCode = '{"arbitrary":"property"}';
transformCode(transformer, filename, sourceCode, {}, () => {}); transformCode(transformer, filename, localPath, sourceCode, {}, () => {});
expect(transformer.transform).toBeCalledWith({ expect(transformer.transform).toBeCalledWith({
filename, filename,
localPath,
options: undefined, options: undefined,
src: `module.exports=${sourceCode}`, src: `module.exports=${sourceCode}`,
}); });
@ -62,7 +66,7 @@ describe('code transformation worker:', () => {
map: {}, map: {},
}; };
transformCode(transformer, 'filename', result.code, {}, (error, data) => { transformCode(transformer, 'filename', 'local/filename', result.code, {}, (error, data) => {
expect(error).toBeNull(); expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(result)); expect(data.result).toEqual(objectContaining(result));
done(); done();
@ -75,7 +79,7 @@ describe('code transformation worker:', () => {
done => { done => {
const code = '{a:1,b:2}'; const code = '{a:1,b:2}';
const filePath = 'arbitrary/file.json'; const filePath = 'arbitrary/file.json';
transformCode(transformer, filePath, code, {}, (error, data) => { transformCode(transformer, filePath, filePath, code, {}, (error, data) => {
expect(error).toBeNull(); expect(error).toBeNull();
expect(data.result.code).toEqual(code); expect(data.result.code).toEqual(code);
done(); done();
@ -90,7 +94,7 @@ describe('code transformation worker:', () => {
code: `${shebang} \n arbitrary(code)`, code: `${shebang} \n arbitrary(code)`,
}; };
const filePath = 'arbitrary/file.js'; const filePath = 'arbitrary/file.js';
transformCode(transformer, filePath, result.code, {}, (error, data) => { transformCode(transformer, filePath, filePath, result.code, {}, (error, data) => {
expect(error).toBeNull(); expect(error).toBeNull();
const {code} = data.result; const {code} = data.result;
expect(code).not.toContain(shebang); expect(code).not.toContain(shebang);
@ -105,7 +109,7 @@ describe('code transformation worker:', () => {
throw new Error(message); throw new Error(message);
}); });
transformCode(transformer, 'filename', 'code', {}, error => { transformCode(transformer, 'filename', 'local/filename', 'code', {}, error => {
expect(error.message).toBe(message); expect(error.message).toBe(message);
done(); done();
}); });
@ -115,7 +119,7 @@ describe('code transformation worker:', () => {
it('passes the transformed code the `extractDependencies`', done => { it('passes the transformed code the `extractDependencies`', done => {
const code = 'arbitrary(code)'; const code = 'arbitrary(code)';
transformCode(transformer, 'filename', code, {}, error => { transformCode(transformer, 'filename', 'local/filename', code, {}, error => {
expect(error).toBeNull(); expect(error).toBeNull();
expect(extractDependencies).toBeCalledWith(code); expect(extractDependencies).toBeCalledWith(code);
done(); done();
@ -132,7 +136,7 @@ describe('code transformation worker:', () => {
}; };
extractDependencies.mockReturnValue(dependencyData); extractDependencies.mockReturnValue(dependencyData);
transformCode(transformer, 'filename', 'code', {}, (error, data) => { transformCode(transformer, 'filename', 'local/filename', 'code', {}, (error, data) => {
expect(error).toBeNull(); expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(dependencyData)); expect(data.result).toEqual(objectContaining(dependencyData));
done(); done();
@ -142,7 +146,7 @@ describe('code transformation worker:', () => {
it('does not extract requires of JSON files', done => { it('does not extract requires of JSON files', done => {
const jsonStr = '{"arbitrary":"json"}'; const jsonStr = '{"arbitrary":"json"}';
transformCode(transformer, 'arbitrary.json', jsonStr, {}, (error, data) => { transformCode(transformer, 'arbitrary.json', 'local/arbitrary.json', jsonStr, {}, (error, data) => {
expect(error).toBeNull(); expect(error).toBeNull();
const {dependencies, dependencyOffsets} = data.result; const {dependencies, dependencyOffsets} = data.result;
expect(extractDependencies).not.toBeCalled(); expect(extractDependencies).not.toBeCalled();
@ -181,7 +185,7 @@ describe('code transformation worker:', () => {
it('passes the transform result to `inline` for constant inlining', done => { it('passes the transform result to `inline` for constant inlining', done => {
transformResult = {map: {version: 3}, code: 'arbitrary(code)'}; transformResult = {map: {version: 3}, code: 'arbitrary(code)'};
transformCode(transformer, filename, 'code', options, () => { transformCode(transformer, filename, filename, 'code', options, () => {
expect(inline).toBeCalledWith(filename, transformResult, options); expect(inline).toBeCalledWith(filename, transformResult, options);
done(); done();
}); });
@ -190,21 +194,21 @@ describe('code transformation worker:', () => {
it('passes the result obtained from `inline` on to `constant-folding`', done => { it('passes the result obtained from `inline` on to `constant-folding`', done => {
const inlineResult = {map: {version: 3, sources: []}, ast: {}}; const inlineResult = {map: {version: 3, sources: []}, ast: {}};
inline.mockReturnValue(inlineResult); inline.mockReturnValue(inlineResult);
transformCode(transformer, filename, 'code', options, () => { transformCode(transformer, filename, filename, 'code', options, () => {
expect(constantFolding).toBeCalledWith(filename, inlineResult); expect(constantFolding).toBeCalledWith(filename, inlineResult);
done(); done();
}); });
}); });
it('Uses the code obtained from `constant-folding` to extract dependencies', done => { it('Uses the code obtained from `constant-folding` to extract dependencies', done => {
transformCode(transformer, filename, 'code', options, () => { transformCode(transformer, filename, filename, 'code', options, () => {
expect(extractDependencies).toBeCalledWith(foldedCode); expect(extractDependencies).toBeCalledWith(foldedCode);
done(); done();
}); });
}); });
it('uses the dependencies obtained from the optimized result', done => { it('uses the dependencies obtained from the optimized result', done => {
transformCode(transformer, filename, 'code', options, (_, data) => { transformCode(transformer, filename, filename, 'code', options, (_, data) => {
const result = data.result; const result = data.result;
expect(result.dependencies).toEqual(dependencyData.dependencies); expect(result.dependencies).toEqual(dependencyData.dependencies);
done(); done();
@ -212,7 +216,7 @@ describe('code transformation worker:', () => {
}); });
it('uses data produced by `constant-folding` for the result', done => { it('uses data produced by `constant-folding` for the result', done => {
transformCode(transformer, 'filename', 'code', options, (_, data) => { transformCode(transformer, 'filename', 'local/filename', 'code', options, (_, data) => {
expect(data.result) expect(data.result)
.toEqual(objectContaining({code: foldedCode, map: foldedMap})); .toEqual(objectContaining({code: foldedCode, map: foldedMap}));
done(); done();

View File

@ -19,8 +19,9 @@ const invariant = require('fbjs/lib/invariant');
const minify = require('./minify'); const minify = require('./minify');
import type {LogEntry} from '../../Logger/Types'; import type {LogEntry} from '../../Logger/Types';
import type {MappingsMap} from '../../lib/SourceMap';
import type {LocalPath} from '../../node-haste/lib/toLocalPath'; import type {LocalPath} from '../../node-haste/lib/toLocalPath';
import type {Ast, Plugins as BabelPlugins, SourceMap as MappingsMap} from 'babel-core'; import type {Ast, Plugins as BabelPlugins} from 'babel-core';
export type TransformedCode = { export type TransformedCode = {
code: string, code: string,
@ -32,6 +33,7 @@ export type TransformedCode = {
export type Transformer<ExtraOptions: {} = {}> = { export type Transformer<ExtraOptions: {} = {}> = {
transform: ({| transform: ({|
filename: string, filename: string,
localPath: string,
options: ExtraOptions & TransformOptions, options: ExtraOptions & TransformOptions,
plugins?: BabelPlugins, plugins?: BabelPlugins,
src: string, src: string,
@ -71,17 +73,18 @@ export type Data = {
transformFileEndLogEntry: LogEntry, transformFileEndLogEntry: LogEntry,
}; };
type Callback = ( type Callback<T> = (
error: ?Error, error: ?Error,
data: ?Data, data: ?T,
) => mixed; ) => mixed;
function transformCode( function transformCode(
transformer: Transformer<*>, transformer: Transformer<*>,
filename: string, filename: string,
localPath: LocalPath,
sourceCode: string, sourceCode: string,
options: Options, options: Options,
callback: Callback, callback: Callback<Data>,
) { ) {
invariant( invariant(
!options.minify || options.transform.generateSourceMaps, !options.minify || options.transform.generateSourceMaps,
@ -105,6 +108,7 @@ function transformCode(
try { try {
transformed = transformer.transform({ transformed = transformer.transform({
filename, filename,
localPath,
options: options.transform, options: options.transform,
src: sourceCode, src: sourceCode,
}); });
@ -158,21 +162,22 @@ function transformCode(
exports.transformAndExtractDependencies = ( exports.transformAndExtractDependencies = (
transform: string, transform: string,
filename: string, filename: string,
localPath: LocalPath,
sourceCode: string, sourceCode: string,
options: Options, options: Options,
callback: Callback, callback: Callback<Data>,
) => { ) => {
babelRegisterOnly([transform]); babelRegisterOnly([transform]);
/* $FlowFixMe: impossible to type a dynamic require */ /* $FlowFixMe: impossible to type a dynamic require */
const transformModule: Transformer<*> = require(transform); const transformModule: Transformer<*> = require(transform);
transformCode(transformModule, filename, sourceCode, options, callback); transformCode(transformModule, filename, localPath, sourceCode, options, callback);
}; };
exports.minify = ( exports.minify = (
filename: string, filename: string,
code: string, code: string,
sourceMap: MappingsMap, sourceMap: MappingsMap,
callback: (error: ?Error, result: mixed) => mixed, callback: Callback<{code: string, map: MappingsMap}>,
) => { ) => {
var result; var result;
try { try {

View File

@ -97,11 +97,13 @@ describe('transforming JS modules:', () => {
transformModule(sourceCode, options(variants), () => { transformModule(sourceCode, options(variants), () => {
expect(transformer.transform).toBeCalledWith({ expect(transformer.transform).toBeCalledWith({
filename, filename,
localPath: filename,
options: {...defaults, ...variants.dev}, options: {...defaults, ...variants.dev},
src: sourceCode, src: sourceCode,
}); });
expect(transformer.transform).toBeCalledWith({ expect(transformer.transform).toBeCalledWith({
filename, filename,
localPath: filename,
options: {...defaults, ...variants.prod}, options: {...defaults, ...variants.prod},
src: sourceCode, src: sourceCode,
}); });

View File

@ -72,6 +72,7 @@ function transformModule(
Object.keys(variants).forEach(name => { Object.keys(variants).forEach(name => {
tasks[name] = asyncify(() => transformer.transform({ tasks[name] = asyncify(() => transformer.transform({
filename, filename,
localPath: filename,
options: {...defaultTransformOptions, ...variants[name]}, options: {...defaultTransformOptions, ...variants[name]},
src: code, src: code,
}) })