mirror of https://github.com/status-im/metro.git
Export the hmr plugin directly
Summary: This will allow to configure the HMR plugin directly from `.babelrc` again, instead of having to create a bridge file: https://github.com/rafeca/metro-sample-app/blob/master/metro-babel7-plugin-react-transform.js#L3 Reviewed By: davidaurelio Differential Revision: D7707314 fbshipit-source-id: 4c5612e1e5d27874807f2dce50d99ec0f6354bbc
This commit is contained in:
parent
a8ce776044
commit
a6b61554ec
|
@ -18,7 +18,7 @@ const path = require('path');
|
||||||
|
|
||||||
/*eslint-disable import/no-extraneous-dependencies*/
|
/*eslint-disable import/no-extraneous-dependencies*/
|
||||||
const {transformSync} = require('@babel/core');
|
const {transformSync} = require('@babel/core');
|
||||||
const reactPlugin = require('../lib/index.js').default;
|
const reactPlugin = require('../lib/index.js');
|
||||||
|
|
||||||
describe('finds React components', () => {
|
describe('finds React components', () => {
|
||||||
const fixturesDir = path.join(__dirname, '__fixtures__');
|
const fixturesDir = path.join(__dirname, '__fixtures__');
|
||||||
|
|
|
@ -28,71 +28,70 @@ const find = require('lodash/find');
|
||||||
|
|
||||||
const {addDefault} = require('@babel/helper-module-imports');
|
const {addDefault} = require('@babel/helper-module-imports');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = function({types: t, template}) {
|
||||||
default: function({types: t, template}) {
|
function matchesPatterns(path, patterns) {
|
||||||
function matchesPatterns(path, patterns) {
|
return !!find(patterns, pattern => {
|
||||||
return !!find(patterns, pattern => {
|
|
||||||
return (
|
|
||||||
t.isIdentifier(path.node, {name: pattern}) ||
|
|
||||||
path.matchesPattern(pattern)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isReactLikeClass(node) {
|
|
||||||
return !!find(node.body.body, classMember => {
|
|
||||||
return (
|
|
||||||
t.isClassMethod(classMember) &&
|
|
||||||
t.isIdentifier(classMember.key, {name: 'render'})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isReactLikeComponentObject(node) {
|
|
||||||
return (
|
return (
|
||||||
t.isObjectExpression(node) &&
|
t.isIdentifier(path.node, {name: pattern}) ||
|
||||||
!!find(node.properties, objectMember => {
|
path.matchesPattern(pattern)
|
||||||
return (
|
|
||||||
(t.isObjectProperty(objectMember) ||
|
|
||||||
t.isObjectMethod(objectMember)) &&
|
|
||||||
(t.isIdentifier(objectMember.key, {name: 'render'}) ||
|
|
||||||
t.isStringLiteral(objectMember.key, {value: 'render'}))
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// `foo({ displayName: 'NAME' });` => 'NAME'
|
function isReactLikeClass(node) {
|
||||||
function getDisplayName(node) {
|
return !!find(node.body.body, classMember => {
|
||||||
const property = find(
|
return (
|
||||||
node.arguments[0].properties,
|
t.isClassMethod(classMember) &&
|
||||||
_node => _node.key.name === 'displayName',
|
t.isIdentifier(classMember.key, {name: 'render'})
|
||||||
);
|
);
|
||||||
return property && property.value.value;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasParentFunction(path) {
|
function isReactLikeComponentObject(node) {
|
||||||
return !!path.findParent(parentPath => parentPath.isFunction());
|
return (
|
||||||
}
|
t.isObjectExpression(node) &&
|
||||||
|
!!find(node.properties, objectMember => {
|
||||||
|
return (
|
||||||
|
(t.isObjectProperty(objectMember) ||
|
||||||
|
t.isObjectMethod(objectMember)) &&
|
||||||
|
(t.isIdentifier(objectMember.key, {name: 'render'}) ||
|
||||||
|
t.isStringLiteral(objectMember.key, {value: 'render'}))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// wrapperFunction("componentId")(node)
|
// `foo({ displayName: 'NAME' });` => 'NAME'
|
||||||
function wrapComponent(node, componentId, wrapperFunctionId) {
|
function getDisplayName(node) {
|
||||||
return t.callExpression(
|
const property = find(
|
||||||
t.callExpression(wrapperFunctionId, [t.stringLiteral(componentId)]),
|
node.arguments[0].properties,
|
||||||
[node],
|
_node => _node.key.name === 'displayName',
|
||||||
);
|
);
|
||||||
}
|
return property && property.value.value;
|
||||||
|
}
|
||||||
|
|
||||||
// `{ name: foo }` => Node { type: "ObjectExpression", properties: [...] }
|
function hasParentFunction(path) {
|
||||||
function toObjectExpression(object) {
|
return !!path.findParent(parentPath => parentPath.isFunction());
|
||||||
const properties = Object.keys(object).map(key => {
|
}
|
||||||
return t.objectProperty(t.identifier(key), object[key]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return t.objectExpression(properties);
|
// wrapperFunction("componentId")(node)
|
||||||
}
|
function wrapComponent(node, componentId, wrapperFunctionId) {
|
||||||
|
return t.callExpression(
|
||||||
|
t.callExpression(wrapperFunctionId, [t.stringLiteral(componentId)]),
|
||||||
|
[node],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const wrapperFunctionTemplate = template(`
|
// `{ name: foo }` => Node { type: "ObjectExpression", properties: [...] }
|
||||||
|
function toObjectExpression(object) {
|
||||||
|
const properties = Object.keys(object).map(key => {
|
||||||
|
return t.objectProperty(t.identifier(key), object[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return t.objectExpression(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperFunctionTemplate = template(`
|
||||||
function WRAPPER_FUNCTION_ID(ID_PARAM) {
|
function WRAPPER_FUNCTION_ID(ID_PARAM) {
|
||||||
return function(COMPONENT_PARAM) {
|
return function(COMPONENT_PARAM) {
|
||||||
return EXPRESSION;
|
return EXPRESSION;
|
||||||
|
@ -100,342 +99,334 @@ module.exports = {
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const VISITED_KEY = 'react-transform-' + Date.now();
|
const VISITED_KEY = 'react-transform-' + Date.now();
|
||||||
|
|
||||||
const componentVisitor = {
|
const componentVisitor = {
|
||||||
Class(path) {
|
Class(path) {
|
||||||
if (
|
if (
|
||||||
path.node[VISITED_KEY] ||
|
path.node[VISITED_KEY] ||
|
||||||
!matchesPatterns(path.get('superClass'), this.superClasses) ||
|
!matchesPatterns(path.get('superClass'), this.superClasses) ||
|
||||||
!isReactLikeClass(path.node)
|
!isReactLikeClass(path.node)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
path.node[VISITED_KEY] = true;
|
path.node[VISITED_KEY] = true;
|
||||||
|
|
||||||
const componentName = (path.node.id && path.node.id.name) || null;
|
const componentName = (path.node.id && path.node.id.name) || null;
|
||||||
const componentId =
|
const componentId = componentName || path.scope.generateUid('component');
|
||||||
componentName || path.scope.generateUid('component');
|
const isInFunction = hasParentFunction(path);
|
||||||
const isInFunction = hasParentFunction(path);
|
|
||||||
|
|
||||||
this.components.push({
|
this.components.push({
|
||||||
id: componentId,
|
id: componentId,
|
||||||
name: componentName,
|
name: componentName,
|
||||||
isInFunction: isInFunction,
|
isInFunction: isInFunction,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Can't wrap ClassDeclarations
|
// Can't wrap ClassDeclarations
|
||||||
const isStatement = t.isStatement(path.node);
|
const isStatement = t.isStatement(path.node);
|
||||||
const isExport = t.isExportDefaultDeclaration(path.parent);
|
const isExport = t.isExportDefaultDeclaration(path.parent);
|
||||||
|
|
||||||
if (isStatement && !isExport) {
|
if (isStatement && !isExport) {
|
||||||
// class decl
|
// class decl
|
||||||
// need to work around Babel 7 detecting duplicate decls here
|
// need to work around Babel 7 detecting duplicate decls here
|
||||||
|
|
||||||
path.insertAfter(
|
path.insertAfter(
|
||||||
t.expressionStatement(
|
t.expressionStatement(
|
||||||
t.assignmentExpression(
|
t.assignmentExpression(
|
||||||
'=',
|
'=',
|
||||||
|
t.identifier(componentId),
|
||||||
|
wrapComponent(
|
||||||
t.identifier(componentId),
|
t.identifier(componentId),
|
||||||
wrapComponent(
|
componentId,
|
||||||
t.identifier(componentId),
|
this.wrapperFunctionId,
|
||||||
componentId,
|
|
||||||
this.wrapperFunctionId,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const expression = t.toExpression(path.node);
|
|
||||||
|
|
||||||
// wrapperFunction("componentId")(node)
|
|
||||||
let wrapped = wrapComponent(
|
|
||||||
expression,
|
|
||||||
componentId,
|
|
||||||
this.wrapperFunctionId,
|
|
||||||
);
|
|
||||||
let constId;
|
|
||||||
|
|
||||||
if (isStatement) {
|
|
||||||
// wrapperFunction("componentId")(class Foo ...) => const Foo = wrapperFunction("componentId")(class Foo ...)
|
|
||||||
constId = t.identifier(componentName || componentId);
|
|
||||||
wrapped = t.variableDeclaration('const', [
|
|
||||||
t.variableDeclarator(constId, wrapped),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isExport) {
|
|
||||||
path.parentPath.insertBefore(wrapped);
|
|
||||||
path.parent.declaration = constId;
|
|
||||||
} else {
|
|
||||||
path.replaceWith(wrapped);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
CallExpression(path) {
|
|
||||||
if (
|
|
||||||
path.node[VISITED_KEY] ||
|
|
||||||
!matchesPatterns(path.get('callee'), this.factoryMethods) ||
|
|
||||||
!isReactLikeComponentObject(path.node.arguments[0])
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
path.node[VISITED_KEY] = true;
|
|
||||||
|
|
||||||
// `foo({ displayName: 'NAME' });` => 'NAME'
|
|
||||||
const componentName = getDisplayName(path.node);
|
|
||||||
const componentId =
|
|
||||||
componentName || path.scope.generateUid('component');
|
|
||||||
const isInFunction = hasParentFunction(path);
|
|
||||||
|
|
||||||
this.components.push({
|
|
||||||
id: componentId,
|
|
||||||
name: componentName,
|
|
||||||
isInFunction: isInFunction,
|
|
||||||
});
|
|
||||||
|
|
||||||
path.replaceWith(
|
|
||||||
wrapComponent(path.node, componentId, this.wrapperFunctionId),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
class ReactTransformBuilder {
|
|
||||||
constructor(file, options) {
|
|
||||||
this.file = file;
|
|
||||||
this.program = file.path;
|
|
||||||
this.options = this.normalizeOptions(options);
|
|
||||||
|
|
||||||
// @todo: clean this shit up
|
|
||||||
this.configuredTransformsIds = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
static validateOptions(options) {
|
|
||||||
return typeof options === 'object' && Array.isArray(options.transforms);
|
|
||||||
}
|
|
||||||
|
|
||||||
static assertValidOptions(options) {
|
|
||||||
if (!ReactTransformBuilder.validateOptions(options)) {
|
|
||||||
throw new Error(
|
|
||||||
'babel-plugin-react-transform requires that you specify options ' +
|
|
||||||
'in .babelrc or from the Babel Node API, and that it is an object ' +
|
|
||||||
'with a transforms property which is an array.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeOptions(options) {
|
|
||||||
return {
|
|
||||||
factoryMethods: options.factoryMethods || ['React.createClass'],
|
|
||||||
superClasses: options.superClasses || [
|
|
||||||
'React.Component',
|
|
||||||
'React.PureComponent',
|
|
||||||
'Component',
|
|
||||||
'PureComponent',
|
|
||||||
],
|
|
||||||
transforms: options.transforms.map(opts => {
|
|
||||||
return {
|
|
||||||
transform: opts.transform,
|
|
||||||
locals: opts.locals || [],
|
|
||||||
imports: opts.imports || [],
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
build(path) {
|
|
||||||
const componentsDeclarationId = this.file.scope.generateUidIdentifier(
|
|
||||||
'components',
|
|
||||||
);
|
|
||||||
const wrapperFunctionId = this.file.scope.generateUidIdentifier(
|
|
||||||
'wrapComponent',
|
|
||||||
);
|
|
||||||
|
|
||||||
const components = this.collectAndWrapComponents(wrapperFunctionId);
|
|
||||||
|
|
||||||
if (!components.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentsDeclaration = this.initComponentsDeclaration(
|
|
||||||
componentsDeclarationId,
|
|
||||||
components,
|
|
||||||
);
|
|
||||||
const configuredTransforms = this.initTransformers(
|
|
||||||
path,
|
|
||||||
componentsDeclarationId,
|
|
||||||
);
|
|
||||||
const wrapperFunction = this.initWrapperFunction(wrapperFunctionId);
|
|
||||||
|
|
||||||
const body = this.program.node.body;
|
|
||||||
|
|
||||||
body.unshift(wrapperFunction);
|
|
||||||
configuredTransforms.reverse().forEach(node => body.unshift(node));
|
|
||||||
body.unshift(componentsDeclaration);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* const Foo = _wrapComponent('Foo')(class Foo extends React.Component {});
|
|
||||||
* ...
|
|
||||||
* const Bar = _wrapComponent('Bar')(React.createClass({
|
|
||||||
* displayName: 'Bar'
|
|
||||||
* }));
|
|
||||||
*/
|
|
||||||
collectAndWrapComponents(wrapperFunctionId) {
|
|
||||||
const components = [];
|
|
||||||
|
|
||||||
this.file.path.traverse(componentVisitor, {
|
|
||||||
wrapperFunctionId: wrapperFunctionId,
|
|
||||||
components: components,
|
|
||||||
factoryMethods: this.options.factoryMethods,
|
|
||||||
superClasses: this.options.superClasses,
|
|
||||||
currentlyInFunction: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* const _components = {
|
|
||||||
* Foo: {
|
|
||||||
* displayName: "Foo"
|
|
||||||
* }
|
|
||||||
* };
|
|
||||||
*/
|
|
||||||
initComponentsDeclaration(componentsDeclarationId, components) {
|
|
||||||
const props = components.map(component => {
|
|
||||||
const componentId = component.id;
|
|
||||||
const componentProps = [];
|
|
||||||
|
|
||||||
if (component.name) {
|
|
||||||
componentProps.push(
|
|
||||||
t.objectProperty(
|
|
||||||
t.identifier('displayName'),
|
|
||||||
t.stringLiteral(component.name),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (component.isInFunction) {
|
|
||||||
componentProps.push(
|
|
||||||
t.objectProperty(
|
|
||||||
t.identifier('isInFunction'),
|
|
||||||
t.booleanLiteral(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let objectKey;
|
|
||||||
|
|
||||||
if (t.isValidIdentifier(componentId)) {
|
|
||||||
objectKey = t.identifier(componentId);
|
|
||||||
} else {
|
|
||||||
objectKey = t.stringLiteral(componentId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return t.objectProperty(
|
|
||||||
objectKey,
|
|
||||||
t.objectExpression(componentProps),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return t.variableDeclaration('const', [
|
|
||||||
t.variableDeclarator(
|
|
||||||
componentsDeclarationId,
|
|
||||||
t.objectExpression(props),
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = t.toExpression(path.node);
|
||||||
|
|
||||||
|
// wrapperFunction("componentId")(node)
|
||||||
|
let wrapped = wrapComponent(
|
||||||
|
expression,
|
||||||
|
componentId,
|
||||||
|
this.wrapperFunctionId,
|
||||||
|
);
|
||||||
|
let constId;
|
||||||
|
|
||||||
|
if (isStatement) {
|
||||||
|
// wrapperFunction("componentId")(class Foo ...) => const Foo = wrapperFunction("componentId")(class Foo ...)
|
||||||
|
constId = t.identifier(componentName || componentId);
|
||||||
|
wrapped = t.variableDeclaration('const', [
|
||||||
|
t.variableDeclarator(constId, wrapped),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (isExport) {
|
||||||
* import _transformLib from "transform-lib";
|
path.parentPath.insertBefore(wrapped);
|
||||||
* ...
|
path.parent.declaration = constId;
|
||||||
* const _transformLib2 = _transformLib({
|
} else {
|
||||||
* filename: "filename",
|
path.replaceWith(wrapped);
|
||||||
* components: _components,
|
}
|
||||||
* locals: [],
|
},
|
||||||
* imports: []
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
initTransformers(path, componentsDeclarationId) {
|
|
||||||
return this.options.transforms.map(transform => {
|
|
||||||
const transformName = transform.transform;
|
|
||||||
const transformImportId = addDefault(path, transformName, {
|
|
||||||
nameHint: transformName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const transformLocals = transform.locals.map(local => {
|
CallExpression(path) {
|
||||||
return t.identifier(local);
|
if (
|
||||||
});
|
path.node[VISITED_KEY] ||
|
||||||
|
!matchesPatterns(path.get('callee'), this.factoryMethods) ||
|
||||||
const transformImports = transform.imports.map(importName => {
|
!isReactLikeComponentObject(path.node.arguments[0])
|
||||||
return addDefault(path, importName, {hint: importName});
|
) {
|
||||||
});
|
return;
|
||||||
|
|
||||||
const configuredTransformId = this.file.scope.generateUidIdentifier(
|
|
||||||
transformName,
|
|
||||||
);
|
|
||||||
const configuredTransform = t.variableDeclaration('const', [
|
|
||||||
t.variableDeclarator(
|
|
||||||
configuredTransformId,
|
|
||||||
t.callExpression(transformImportId, [
|
|
||||||
toObjectExpression({
|
|
||||||
filename: t.stringLiteral(
|
|
||||||
this.file.opts.filename || 'unknown',
|
|
||||||
),
|
|
||||||
components: componentsDeclarationId,
|
|
||||||
locals: t.arrayExpression(transformLocals),
|
|
||||||
imports: t.arrayExpression(transformImports),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.configuredTransformsIds.push(configuredTransformId);
|
|
||||||
|
|
||||||
return configuredTransform;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
path.node[VISITED_KEY] = true;
|
||||||
* function _wrapComponent(id) {
|
|
||||||
* return function (Component) {
|
|
||||||
* return _transformLib2(Component, id);
|
|
||||||
* };
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
initWrapperFunction(wrapperFunctionId) {
|
|
||||||
const idParam = t.identifier('id');
|
|
||||||
const componentParam = t.identifier('Component');
|
|
||||||
|
|
||||||
const expression = this.configuredTransformsIds
|
// `foo({ displayName: 'NAME' });` => 'NAME'
|
||||||
.reverse()
|
const componentName = getDisplayName(path.node);
|
||||||
.reduce((memo, transformId) => {
|
const componentId = componentName || path.scope.generateUid('component');
|
||||||
return t.callExpression(transformId, [memo, idParam]);
|
const isInFunction = hasParentFunction(path);
|
||||||
}, componentParam);
|
|
||||||
|
|
||||||
return wrapperFunctionTemplate({
|
this.components.push({
|
||||||
WRAPPER_FUNCTION_ID: wrapperFunctionId,
|
id: componentId,
|
||||||
ID_PARAM: idParam,
|
name: componentName,
|
||||||
COMPONENT_PARAM: componentParam,
|
isInFunction: isInFunction,
|
||||||
EXPRESSION: expression,
|
});
|
||||||
});
|
|
||||||
|
path.replaceWith(
|
||||||
|
wrapComponent(path.node, componentId, this.wrapperFunctionId),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class ReactTransformBuilder {
|
||||||
|
constructor(file, options) {
|
||||||
|
this.file = file;
|
||||||
|
this.program = file.path;
|
||||||
|
this.options = this.normalizeOptions(options);
|
||||||
|
|
||||||
|
// @todo: clean this shit up
|
||||||
|
this.configuredTransformsIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static validateOptions(options) {
|
||||||
|
return typeof options === 'object' && Array.isArray(options.transforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
static assertValidOptions(options) {
|
||||||
|
if (!ReactTransformBuilder.validateOptions(options)) {
|
||||||
|
throw new Error(
|
||||||
|
'babel-plugin-react-transform requires that you specify options ' +
|
||||||
|
'in .babelrc or from the Babel Node API, and that it is an object ' +
|
||||||
|
'with a transforms property which is an array.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
normalizeOptions(options) {
|
||||||
visitor: {
|
return {
|
||||||
Program(path, {file, opts}) {
|
factoryMethods: options.factoryMethods || ['React.createClass'],
|
||||||
ReactTransformBuilder.assertValidOptions(opts);
|
superClasses: options.superClasses || [
|
||||||
const builder = new ReactTransformBuilder(file, opts);
|
'React.Component',
|
||||||
builder.build(path);
|
'React.PureComponent',
|
||||||
},
|
'Component',
|
||||||
|
'PureComponent',
|
||||||
|
],
|
||||||
|
transforms: options.transforms.map(opts => {
|
||||||
|
return {
|
||||||
|
transform: opts.transform,
|
||||||
|
locals: opts.locals || [],
|
||||||
|
imports: opts.imports || [],
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
build(path) {
|
||||||
|
const componentsDeclarationId = this.file.scope.generateUidIdentifier(
|
||||||
|
'components',
|
||||||
|
);
|
||||||
|
const wrapperFunctionId = this.file.scope.generateUidIdentifier(
|
||||||
|
'wrapComponent',
|
||||||
|
);
|
||||||
|
|
||||||
|
const components = this.collectAndWrapComponents(wrapperFunctionId);
|
||||||
|
|
||||||
|
if (!components.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentsDeclaration = this.initComponentsDeclaration(
|
||||||
|
componentsDeclarationId,
|
||||||
|
components,
|
||||||
|
);
|
||||||
|
const configuredTransforms = this.initTransformers(
|
||||||
|
path,
|
||||||
|
componentsDeclarationId,
|
||||||
|
);
|
||||||
|
const wrapperFunction = this.initWrapperFunction(wrapperFunctionId);
|
||||||
|
|
||||||
|
const body = this.program.node.body;
|
||||||
|
|
||||||
|
body.unshift(wrapperFunction);
|
||||||
|
configuredTransforms.reverse().forEach(node => body.unshift(node));
|
||||||
|
body.unshift(componentsDeclaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* const Foo = _wrapComponent('Foo')(class Foo extends React.Component {});
|
||||||
|
* ...
|
||||||
|
* const Bar = _wrapComponent('Bar')(React.createClass({
|
||||||
|
* displayName: 'Bar'
|
||||||
|
* }));
|
||||||
|
*/
|
||||||
|
collectAndWrapComponents(wrapperFunctionId) {
|
||||||
|
const components = [];
|
||||||
|
|
||||||
|
this.file.path.traverse(componentVisitor, {
|
||||||
|
wrapperFunctionId: wrapperFunctionId,
|
||||||
|
components: components,
|
||||||
|
factoryMethods: this.options.factoryMethods,
|
||||||
|
superClasses: this.options.superClasses,
|
||||||
|
currentlyInFunction: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* const _components = {
|
||||||
|
* Foo: {
|
||||||
|
* displayName: "Foo"
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
initComponentsDeclaration(componentsDeclarationId, components) {
|
||||||
|
const props = components.map(component => {
|
||||||
|
const componentId = component.id;
|
||||||
|
const componentProps = [];
|
||||||
|
|
||||||
|
if (component.name) {
|
||||||
|
componentProps.push(
|
||||||
|
t.objectProperty(
|
||||||
|
t.identifier('displayName'),
|
||||||
|
t.stringLiteral(component.name),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.isInFunction) {
|
||||||
|
componentProps.push(
|
||||||
|
t.objectProperty(
|
||||||
|
t.identifier('isInFunction'),
|
||||||
|
t.booleanLiteral(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let objectKey;
|
||||||
|
|
||||||
|
if (t.isValidIdentifier(componentId)) {
|
||||||
|
objectKey = t.identifier(componentId);
|
||||||
|
} else {
|
||||||
|
objectKey = t.stringLiteral(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.objectProperty(objectKey, t.objectExpression(componentProps));
|
||||||
|
});
|
||||||
|
|
||||||
|
return t.variableDeclaration('const', [
|
||||||
|
t.variableDeclarator(
|
||||||
|
componentsDeclarationId,
|
||||||
|
t.objectExpression(props),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* import _transformLib from "transform-lib";
|
||||||
|
* ...
|
||||||
|
* const _transformLib2 = _transformLib({
|
||||||
|
* filename: "filename",
|
||||||
|
* components: _components,
|
||||||
|
* locals: [],
|
||||||
|
* imports: []
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
initTransformers(path, componentsDeclarationId) {
|
||||||
|
return this.options.transforms.map(transform => {
|
||||||
|
const transformName = transform.transform;
|
||||||
|
const transformImportId = addDefault(path, transformName, {
|
||||||
|
nameHint: transformName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformLocals = transform.locals.map(local => {
|
||||||
|
return t.identifier(local);
|
||||||
|
});
|
||||||
|
|
||||||
|
const transformImports = transform.imports.map(importName => {
|
||||||
|
return addDefault(path, importName, {hint: importName});
|
||||||
|
});
|
||||||
|
|
||||||
|
const configuredTransformId = this.file.scope.generateUidIdentifier(
|
||||||
|
transformName,
|
||||||
|
);
|
||||||
|
const configuredTransform = t.variableDeclaration('const', [
|
||||||
|
t.variableDeclarator(
|
||||||
|
configuredTransformId,
|
||||||
|
t.callExpression(transformImportId, [
|
||||||
|
toObjectExpression({
|
||||||
|
filename: t.stringLiteral(this.file.opts.filename || 'unknown'),
|
||||||
|
components: componentsDeclarationId,
|
||||||
|
locals: t.arrayExpression(transformLocals),
|
||||||
|
imports: t.arrayExpression(transformImports),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.configuredTransformsIds.push(configuredTransformId);
|
||||||
|
|
||||||
|
return configuredTransform;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* function _wrapComponent(id) {
|
||||||
|
* return function (Component) {
|
||||||
|
* return _transformLib2(Component, id);
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
initWrapperFunction(wrapperFunctionId) {
|
||||||
|
const idParam = t.identifier('id');
|
||||||
|
const componentParam = t.identifier('Component');
|
||||||
|
|
||||||
|
const expression = this.configuredTransformsIds
|
||||||
|
.reverse()
|
||||||
|
.reduce((memo, transformId) => {
|
||||||
|
return t.callExpression(transformId, [memo, idParam]);
|
||||||
|
}, componentParam);
|
||||||
|
|
||||||
|
return wrapperFunctionTemplate({
|
||||||
|
WRAPPER_FUNCTION_ID: wrapperFunctionId,
|
||||||
|
ID_PARAM: idParam,
|
||||||
|
COMPONENT_PARAM: componentParam,
|
||||||
|
EXPRESSION: expression,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
visitor: {
|
||||||
|
Program(path, {file, opts}) {
|
||||||
|
ReactTransformBuilder.assertValidOptions(opts);
|
||||||
|
const builder = new ReactTransformBuilder(file, opts);
|
||||||
|
builder.build(path);
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
},
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -135,7 +135,7 @@ function makeMakeHMRConfig7() {
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
require('metro-babel7-plugin-react-transform').default,
|
require('metro-babel7-plugin-react-transform'),
|
||||||
{
|
{
|
||||||
transforms: [
|
transforms: [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue