Stronger typing for transformers

Reviewed By: jeanlauliac

Differential Revision: D5006679

fbshipit-source-id: 795c60db363fb53bc74697e4befe50995e9b97a7
This commit is contained in:
David Aurelio 2017-05-09 08:02:08 -07:00 committed by Facebook Github Bot
parent 3dfed2e865
commit 73fc439bc0
12 changed files with 102 additions and 61 deletions

View File

@ -51,13 +51,14 @@ type GeneratorOptions = {
};
type InlinePlugin = string | {} | () => {};
type _Plugins = Array<string | Object | [InlinePlugin] | [InlinePlugin, mixed]>;
// based on https://babeljs.io/docs/usage/options/ -- 2016-11-11
type __TransformOptions = {
filename?: string,
filenameRelative?: string,
presets?: Array<string | Object>,
plugins?: Array<string | Object | [InlinePlugin] | [InlinePlugin, mixed]>,
plugins?: _Plugins,
parserOpts?: BabylonOptions,
generatorOpts?: GeneratorOptions,
highlightCode?: boolean,
@ -92,11 +93,13 @@ declare class _Ast {}
type TransformResult = {
ast: _Ast,
code: ?string,
ignored: boolean,
map: ?_SourceMap,
};
type VisitFn = <State>(path: Object, state: State) => any;
declare module 'babel-core' {
declare type Plugins = _Plugins;
declare type SourceMap = _SourceMap;
declare type Ast = _Ast;
declare type TransformOptions = _TransformOptions;

View File

@ -5,6 +5,8 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
@ -24,7 +26,7 @@ babelRegisterOnly([]);
const transformer = require('../packager/transformer.js');
module.exports = {
process(src, file) {
process(src/*: string*/, file/*: string*/) {
if (nodeFiles.test(file)) { // node specific transforms only
return babel.transform(
src,
@ -32,7 +34,12 @@ module.exports = {
).code;
}
return transformer.transform(src, file, {inlineRequires: true}).code;
return transformer.transform(src, file, {
dev: true,
inlineRequires: true,
platform: '',
projectRoot: '',
}).code;
},
getCacheKey: createCacheKeyFunction([

View File

@ -138,6 +138,8 @@ type Options = {|
+watch: boolean,
|};
const {hasOwnProperty} = Object;
class Bundler {
_opts: Options;
@ -673,7 +675,7 @@ class Bundler {
const preloaded =
module.path === entryFilePath ||
isPolyfill ||
preloadedModules && preloadedModules.hasOwnProperty(module.path);
preloadedModules && hasOwnProperty.call(preloadedModules, module.path);
return new ModuleTransport({
name,

View File

@ -20,7 +20,7 @@ const path = require('path');
const util = require('util');
const workerFarm = require('../worker-farm');
import type {Data as TransformData, Options as TransformOptions} from './worker/worker';
import type {Data as TransformData, Options as WorkerOptions} from './worker/worker';
import type {MappingsMap} from '../lib/SourceMap';
// Avoid memory leaks caused in workers. This number seems to be a good enough number
@ -63,7 +63,7 @@ class Transformer {
transform: string,
filename: string,
sourceCode: string,
options: ?TransformOptions,
options: ?WorkerOptions,
) => Promise<TransformData>;
minify: (
filename: string,
@ -97,7 +97,7 @@ class Transformer {
this._workers && workerFarm.end(this._workers);
}
transformFile(fileName: string, code: string, options: TransformOptions) {
transformFile(fileName: string, code: string, options: WorkerOptions) {
if (!this._transform) {
return Promise.reject(new Error('No transform module'));
}

View File

@ -11,6 +11,7 @@
'use strict';
const babelRegisterOnly = require('../../../babelRegisterOnly');
const constantFolding = require('./constant-folding');
const extractDependencies = require('./extract-dependencies');
const inline = require('./inline');
@ -18,7 +19,7 @@ const invariant = require('fbjs/lib/invariant');
const minify = require('./minify');
import type {LogEntry} from '../../Logger/Types';
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
import type {Ast, Plugins as BabelPlugins, SourceMap as MappingsMap} from 'babel-core';
export type TransformedCode = {
code: string,
@ -27,15 +28,18 @@ export type TransformedCode = {
map?: ?MappingsMap,
};
type Transformer = {
export type Transformer<ExtraOptions: {} = {}> = {
transform: (
filename: string,
sourceCode: string,
options: ?{},
) => {ast: ?Ast, code: string, map: ?MappingsMap}
options: ExtraOptions & TransformOptions,
plugins?: BabelPlugins,
) => {ast: ?Ast, code: string, map: ?MappingsMap},
getCacheKey: TransformOptionsStrict => string,
};
export type TransformOptions = {|
export type TransformOptionsStrict = {|
+dev: boolean,
+generateSourceMaps: boolean,
+hot: boolean,
@ -44,11 +48,20 @@ export type TransformOptions = {|
+projectRoot: string,
|};
export type TransformOptions = {
+dev?: boolean,
+generateSourceMaps?: boolean,
+hot?: boolean,
+inlineRequires?: {+blacklist: {[string]: true}} | boolean,
+platform: string,
+projectRoot: string,
};
export type Options = {|
+dev: boolean,
+minify: boolean,
+platform: string,
+transform: TransformOptions,
+transform: TransformOptionsStrict,
|};
export type Data = {
@ -63,7 +76,7 @@ type Callback = (
) => mixed;
function transformCode(
transformer: Transformer,
transformer: Transformer<*>,
filename: string,
sourceCode: string,
options: Options,
@ -144,8 +157,9 @@ exports.transformAndExtractDependencies = (
options: Options,
callback: Callback,
) => {
babelRegisterOnly([transform]);
/* $FlowFixMe: impossible to type a dynamic require */
const transformModule = require(transform);
const transformModule: Transformer<*> = require(transform);
transformCode(transformModule, filename, sourceCode, options, callback);
};

View File

@ -13,6 +13,7 @@
import type {MappingsMap, SourceMap} from '../lib/SourceMap';
import type {Ast} from 'babel-core';
import type {Console} from 'console';
export type {Transformer} from '../JSTransformer/worker/worker.js';
export type Callback<A = void, B = void>
= (Error => void)
@ -105,15 +106,6 @@ export type TransformerResult = {|
map: ?MappingsMap,
|};
export type Transformer = {
transform: (
sourceCode: string,
filename: string,
options: ?{},
plugins?: Array<string | Object | [string | Object, any]>,
) => {ast: ?Ast, code: string, map: ?MappingsMap}
};
export type TransformResult = {|
code: string,
dependencies: Array<string>,

View File

@ -80,6 +80,15 @@ describe('transforming JS modules:', () => {
});
});
const defaults = {
dev: false,
generateSourceMaps: true,
hot: false,
inlineRequires: false,
platform: '',
projectRoot: '',
};
it('calls the passed-in transform function with code, file name, and options ' +
'for all passed in variants',
done => {
@ -87,9 +96,9 @@ describe('transforming JS modules:', () => {
transformModule(sourceCode, options(variants), () => {
expect(transformer.transform)
.toBeCalledWith(sourceCode, filename, variants.dev);
.toBeCalledWith(sourceCode, filename, {...defaults, ...variants.dev});
expect(transformer.transform)
.toBeCalledWith(sourceCode, filename, variants.prod);
.toBeCalledWith(sourceCode, filename, {...defaults, ...variants.prod});
done();
});
},

View File

@ -12,6 +12,7 @@
const JsFileWrapping = require('./JsFileWrapping');
const asyncify = require('async/asyncify');
const collectDependencies = require('./collect-dependencies');
const defaults = require('../../../defaults');
const docblock = require('../../node-haste/DependencyGraph/docblock');
@ -34,10 +35,18 @@ import type {
export type TransformOptions = {|
filename: string,
polyfill?: boolean,
transformer: Transformer,
transformer: Transformer<*>,
variants?: TransformVariants,
|};
const defaultTransformOptions = {
dev: true,
generateSourceMaps: true,
hot: false,
inlineRequires: false,
platform: '',
projectRoot: '',
};
const defaultVariants = {default: {}};
const ASSET_EXTENSIONS = new Set(defaults.assetExts);
@ -61,17 +70,12 @@ function transformModule(
const {filename, transformer, variants = defaultVariants} = options;
const tasks = {};
Object.keys(variants).forEach(name => {
tasks[name] = cb => {
try {
cb(null, transformer.transform(
code,
filename,
variants[name],
));
} catch (error) {
cb(error, null);
}
};
tasks[name] = asyncify(() => transformer.transform(
code,
filename,
{...defaultTransformOptions, ...variants[name]},
)
);
});
series(tasks, (error, results: {[key: string]: TransformerResult}) => {

View File

@ -24,7 +24,7 @@ const throat = require('throat');
import type {
Options as TransformWorkerOptions,
TransformOptions,
TransformOptionsStrict,
} from '../JSTransformer/worker/worker';
import type {CachedResult, GetTransformCacheKey} from './TransformCache';
@ -381,7 +381,7 @@ class OptionsHasher {
* of the cache key as they should not affect the transformation of a single
* particular file.
*/
hashTransformOptions(hash: crypto$Hash, options: TransformOptions): crypto$Hash {
hashTransformOptions(hash: crypto$Hash, options: TransformOptionsStrict): crypto$Hash {
const {
generateSourceMaps, dev, hot, inlineRequires, platform, projectRoot,
...unknowns,

View File

@ -20,7 +20,7 @@ const rimraf = require('rimraf');
const terminal = require('../lib/terminal');
const writeFileAtomicSync = require('write-file-atomic').sync;
import type {Options as TransformOptions} from '../JSTransformer/worker/worker';
import type {Options as WorkerOptions} from '../JSTransformer/worker/worker';
import type {MappingsMap} from './SourceMap';
import type {Reporter} from './reporting';
@ -58,7 +58,7 @@ function hashSourceCode(props: {
filePath: string,
sourceCode: string,
getTransformCacheKey: GetTransformCacheKey,
transformOptions: TransformOptions,
transformOptions: WorkerOptions,
transformOptionsKey: string,
}): string {
return crypto.createHash('sha1')
@ -134,7 +134,7 @@ function writeSync(props: {
filePath: string,
sourceCode: string,
getTransformCacheKey: GetTransformCacheKey,
transformOptions: TransformOptions,
transformOptions: WorkerOptions,
transformOptionsKey: string,
result: CachedResult,
}): void {
@ -326,7 +326,7 @@ function readMetadataFileSync(
export type ReadTransformProps = {
filePath: string,
sourceCode: string,
transformOptions: TransformOptions,
transformOptions: WorkerOptions,
transformOptionsKey: string,
getTransformCacheKey: GetTransformCacheKey,
cacheOptions: CacheOptions,

View File

@ -22,7 +22,7 @@ const jsonStableStringify = require('json-stable-stringify');
const {join: joinPath, relative: relativePath, extname} = require('path');
import type {TransformedCode, Options as TransformOptions} from '../JSTransformer/worker/worker';
import type {TransformedCode, Options as WorkerOptions} from '../JSTransformer/worker/worker';
import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
import type {MappingsMap} from '../lib/SourceMap';
import type {GetTransformCacheKey} from '../lib/TransformCache';
@ -47,7 +47,7 @@ export type CachedReadResult = {|
export type TransformCode = (
module: Module,
sourceCode: string,
transformOptions: TransformOptions,
transformOptions: WorkerOptions,
) => Promise<TransformedCode>;
export type HasteImpl = {
@ -131,11 +131,11 @@ class Module {
return Promise.resolve().then(() => this._getHasteName() != null);
}
getCode(transformOptions: TransformOptions) {
getCode(transformOptions: WorkerOptions) {
return this.read(transformOptions).then(({code}) => code);
}
getMap(transformOptions: TransformOptions) {
getMap(transformOptions: WorkerOptions) {
return this.read(transformOptions).then(({map}) => map);
}
@ -168,7 +168,7 @@ class Module {
return this._moduleCache.getPackageForModule(this);
}
getDependencies(transformOptions: TransformOptions) {
getDependencies(transformOptions: WorkerOptions) {
return this.read(transformOptions).then(({dependencies}) => dependencies);
}
@ -324,7 +324,7 @@ class Module {
* Shorthand for reading both from cache or from fresh for all call sites that
* are asynchronous by default.
*/
read(transformOptions: TransformOptions): Promise<ReadResult> {
read(transformOptions: WorkerOptions): Promise<ReadResult> {
return Promise.resolve().then(() => {
const cached = this.readCached(transformOptions);
if (cached.result != null) {
@ -339,7 +339,7 @@ class Module {
* the file from source. This has the benefit of being synchronous. As a
* result it is possible to read many cached Module in a row, synchronously.
*/
readCached(transformOptions: TransformOptions): CachedReadResult {
readCached(transformOptions: WorkerOptions): CachedReadResult {
const key = stableObjectHash(transformOptions || {});
let result = this._readResultsByOptionsKey.get(key);
if (result != null) {
@ -355,7 +355,7 @@ class Module {
* so it's faster in case the results are already in memory.
*/
_readFromTransformCache(
transformOptions: TransformOptions,
transformOptions: WorkerOptions,
transformOptionsKey: string,
): CachedReadResult {
const cacheProps = this._getCacheProps(transformOptions, transformOptionsKey);
@ -375,7 +375,7 @@ class Module {
* scratch. We don't repeat the same work as `readCached` because we assume
* call sites have called it already.
*/
readFresh(transformOptions: TransformOptions): Promise<ReadResult> {
readFresh(transformOptions: WorkerOptions): Promise<ReadResult> {
const key = stableObjectHash(transformOptions || {});
const promise = this._readPromises.get(key);
if (promise != null) {
@ -404,7 +404,7 @@ class Module {
return freshPromise;
}
_getCacheProps(transformOptions: TransformOptions, transformOptionsKey: string) {
_getCacheProps(transformOptions: WorkerOptions, transformOptionsKey: string) {
const sourceCode = this._readSourceCode();
const getTransformCacheKey = this._getTransformCacheKey;
return {

View File

@ -7,6 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* Note: This is a fork of the fb-specific transform.js
*
* @flow
*/
'use strict';
@ -23,6 +25,9 @@ const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins');
const {compactMapping} = require('./src/Bundler/source-map');
import type {Plugins as BabelPlugins} from 'babel-core';
import type {Transformer, TransformOptions} from './src/JSTransformer/worker/worker';
const cacheKeyParts = [
fs.readFileSync(__filename),
require('babel-plugin-external-helpers/package.json').version,
@ -36,14 +41,14 @@ const cacheKeyParts = [
* default RN babelrc file and uses that.
*/
const getBabelRC = (function() {
let babelRC = null;
let babelRC: ?{extends?: string, plugins: BabelPlugins} = null;
return function _getBabelRC(projectRoot) {
if (babelRC !== null) {
return babelRC;
}
babelRC = {plugins: []}; // empty babelrc
babelRC = {plugins: []};
// Let's look for the .babelrc in the project root.
// In the future let's look into adding a command line option to specify
@ -62,6 +67,7 @@ const getBabelRC = (function() {
);
// Require the babel-preset's listed in the default babel config
// $FlowFixMe: dynamic require can't be avoided
babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset));
babelRC.plugins = resolvePlugins(babelRC.plugins);
} else {
@ -91,7 +97,7 @@ function buildBabelConfig(filename, options) {
const extraPlugins = [externalHelpersPlugin];
var inlineRequires = options.inlineRequires;
var blacklist = inlineRequires && inlineRequires.blacklist;
var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null;
if (inlineRequires && !(blacklist && filename in blacklist)) {
extraPlugins.push(inlineRequiresPlugin);
}
@ -106,7 +112,11 @@ function buildBabelConfig(filename, options) {
return Object.assign({}, babelRC, config);
}
function transform(src, filename, options) {
function transform(
src: string,
filename: string,
options,
) {
options = options || {};
const OLD_BABEL_ENV = process.env.BABEL_ENV;
@ -144,13 +154,13 @@ function transform(src, filename, options) {
}
}
function getCacheKey(options) {
function getCacheKey(options: TransformOptions) {
var key = crypto.createHash('md5');
cacheKeyParts.forEach(part => key.update(part));
return key.digest('hex');
}
module.exports = {
module.exports = ({
transform,
getCacheKey,
};
}: Transformer<>);