You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
9.2 KiB
276 lines
9.2 KiB
import { join } from 'path';
|
|
import { addSideEffect, addDefault, addNamed } from '@babel/helper-module-imports';
|
|
|
|
function transCamel(_str, symbol) {
|
|
const str = _str[0].toLowerCase() + _str.substr(1);
|
|
return str.replace(/([A-Z])/g, ($1) => `${symbol}${$1.toLowerCase()}`);
|
|
}
|
|
|
|
function winPath(path) {
|
|
return path.replace(/\\/g, '/');
|
|
}
|
|
|
|
function normalizeCustomName(originCustomName) {
|
|
// If set to a string, treat it as a JavaScript source file path.
|
|
if (typeof originCustomName === 'string') {
|
|
const customNameExports = require(originCustomName);
|
|
return typeof customNameExports === 'function'
|
|
? customNameExports : customNameExports.default;
|
|
}
|
|
|
|
return originCustomName;
|
|
}
|
|
|
|
export default class Plugin {
|
|
constructor(
|
|
libraryName,
|
|
libraryDirectory,
|
|
style,
|
|
styleLibraryDirectory,
|
|
customStyleName,
|
|
camel2DashComponentName,
|
|
camel2UnderlineComponentName,
|
|
fileName,
|
|
customName,
|
|
transformToDefaultImport,
|
|
types,
|
|
index = 0
|
|
) {
|
|
this.libraryName = libraryName;
|
|
this.libraryDirectory = typeof libraryDirectory === 'undefined'
|
|
? 'lib'
|
|
: libraryDirectory;
|
|
this.camel2DashComponentName = typeof camel2DashComponentName === 'undefined'
|
|
? true
|
|
: camel2DashComponentName;
|
|
this.camel2UnderlineComponentName = camel2UnderlineComponentName;
|
|
this.style = style || false;
|
|
this.styleLibraryDirectory = styleLibraryDirectory;
|
|
this.customStyleName = normalizeCustomName(customStyleName);
|
|
this.fileName = fileName || '';
|
|
this.customName = normalizeCustomName(customName);
|
|
this.transformToDefaultImport = typeof transformToDefaultImport === 'undefined'
|
|
? true
|
|
: transformToDefaultImport;
|
|
this.types = types;
|
|
this.pluginStateKey = `importPluginState${index}`;
|
|
}
|
|
|
|
getPluginState(state) {
|
|
if (!state[this.pluginStateKey]) {
|
|
state[this.pluginStateKey] = {}; // eslint-disable-line
|
|
}
|
|
return state[this.pluginStateKey];
|
|
}
|
|
|
|
importMethod(methodName, file, pluginState) {
|
|
if (!pluginState.selectedMethods[methodName]) {
|
|
const libraryDirectory = this.libraryDirectory;
|
|
const style = this.style;
|
|
const transformedMethodName = this.camel2UnderlineComponentName // eslint-disable-line
|
|
? transCamel(methodName, '_')
|
|
: this.camel2DashComponentName
|
|
? transCamel(methodName, '-')
|
|
: methodName;
|
|
const path = winPath(
|
|
this.customName ? this.customName(transformedMethodName) : join(this.libraryName, libraryDirectory, transformedMethodName, this.fileName) // eslint-disable-line
|
|
);
|
|
pluginState.selectedMethods[methodName] = this.transformToDefaultImport // eslint-disable-line
|
|
? addDefault(file.path, path, { nameHint: methodName })
|
|
: addNamed(file.path, methodName, path);
|
|
if (this.customStyleName) {
|
|
const stylePath = winPath(this.customStyleName(transformedMethodName));
|
|
addSideEffect(file.path, `${stylePath}`);
|
|
} else if (this.styleLibraryDirectory) {
|
|
const stylePath = winPath(
|
|
join(this.libraryName, this.styleLibraryDirectory, transformedMethodName, this.fileName)
|
|
);
|
|
addSideEffect(file.path, `${stylePath}`);
|
|
} else if (style === true) {
|
|
addSideEffect(file.path, `${path}/style`);
|
|
} else if (style === 'css') {
|
|
addSideEffect(file.path, `${path}/style/css`);
|
|
} else if (typeof style === 'function') {
|
|
const stylePath = style(path, file);
|
|
if (stylePath) {
|
|
addSideEffect(file.path, stylePath);
|
|
}
|
|
}
|
|
}
|
|
return Object.assign({}, pluginState.selectedMethods[methodName]);
|
|
}
|
|
|
|
buildExpressionHandler(node, props, path, state) {
|
|
const file = (path && path.hub && path.hub.file) || (state && state.file);
|
|
const types = this.types;
|
|
const pluginState = this.getPluginState(state);
|
|
props.forEach(prop => {
|
|
if (!types.isIdentifier(node[prop])) return;
|
|
if (
|
|
pluginState.specified[node[prop].name] &&
|
|
types.isImportSpecifier(path.scope.getBinding(node[prop].name).path)
|
|
) {
|
|
node[prop] = this.importMethod(pluginState.specified[node[prop].name], file, pluginState); // eslint-disable-line
|
|
}
|
|
});
|
|
}
|
|
|
|
buildDeclaratorHandler(node, prop, path, state) {
|
|
const file = (path && path.hub && path.hub.file) || (state && state.file);
|
|
const types = this.types;
|
|
const pluginState = this.getPluginState(state);
|
|
if (!types.isIdentifier(node[prop])) return;
|
|
if (pluginState.specified[node[prop].name] &&
|
|
path.scope.hasBinding(node[prop].name) &&
|
|
path.scope.getBinding(node[prop].name).path.type === 'ImportSpecifier') {
|
|
node[prop] = this.importMethod(pluginState.specified[node[prop].name], file, pluginState); // eslint-disable-line
|
|
}
|
|
}
|
|
|
|
ProgramEnter(path, state) {
|
|
const pluginState = this.getPluginState(state);
|
|
pluginState.specified = Object.create(null);
|
|
pluginState.libraryObjs = Object.create(null);
|
|
pluginState.selectedMethods = Object.create(null);
|
|
pluginState.pathsToRemove = [];
|
|
}
|
|
|
|
ProgramExit(path, state) {
|
|
this.getPluginState(state).pathsToRemove.forEach(p => !p.removed && p.remove());
|
|
}
|
|
|
|
ImportDeclaration(path, state) {
|
|
const { node } = path;
|
|
|
|
// path maybe removed by prev instances.
|
|
if (!node) return;
|
|
|
|
const { value } = node.source;
|
|
const libraryName = this.libraryName;
|
|
const types = this.types;
|
|
const pluginState = this.getPluginState(state);
|
|
if (value === libraryName) {
|
|
node.specifiers.forEach(spec => {
|
|
if (types.isImportSpecifier(spec)) {
|
|
pluginState.specified[spec.local.name] = spec.imported.name;
|
|
} else {
|
|
pluginState.libraryObjs[spec.local.name] = true;
|
|
}
|
|
});
|
|
pluginState.pathsToRemove.push(path);
|
|
}
|
|
}
|
|
|
|
CallExpression(path, state) {
|
|
const { node } = path;
|
|
const file = (path && path.hub && path.hub.file) || (state && state.file);
|
|
const { name } = node.callee;
|
|
const types = this.types;
|
|
const pluginState = this.getPluginState(state);
|
|
|
|
if (types.isIdentifier(node.callee)) {
|
|
if (pluginState.specified[name]) {
|
|
node.callee = this.importMethod(pluginState.specified[name], file, pluginState);
|
|
}
|
|
}
|
|
|
|
node.arguments = node.arguments.map(arg => {
|
|
const { name: argName } = arg;
|
|
if (pluginState.specified[argName] &&
|
|
path.scope.hasBinding(argName) &&
|
|
path.scope.getBinding(argName).path.type === 'ImportSpecifier') {
|
|
return this.importMethod(pluginState.specified[argName], file, pluginState);
|
|
}
|
|
return arg;
|
|
});
|
|
}
|
|
|
|
MemberExpression(path, state) {
|
|
const { node } = path;
|
|
const file = (path && path.hub && path.hub.file) || (state && state.file);
|
|
const pluginState = this.getPluginState(state);
|
|
|
|
// multiple instance check.
|
|
if (!node.object || !node.object.name) return;
|
|
|
|
if (pluginState.libraryObjs[node.object.name]) {
|
|
// antd.Button -> _Button
|
|
path.replaceWith(this.importMethod(node.property.name, file, pluginState));
|
|
} else if (pluginState.specified[node.object.name] && path.scope.hasBinding(node.object.name)) {
|
|
const scope = path.scope.getBinding(node.object.name).scope;
|
|
// global variable in file scope
|
|
if (scope.path.parent.type === 'File') {
|
|
node.object = this.importMethod(
|
|
pluginState.specified[node.object.name],
|
|
file,
|
|
pluginState
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Property(path, state) {
|
|
const { node } = path;
|
|
this.buildDeclaratorHandler(node, 'value', path, state);
|
|
}
|
|
|
|
VariableDeclarator(path, state) {
|
|
const { node } = path;
|
|
this.buildDeclaratorHandler(node, 'init', path, state);
|
|
}
|
|
|
|
ArrayExpression(path, state) {
|
|
const { node } = path;
|
|
const props = node.elements.map((_, index) => index);
|
|
this.buildExpressionHandler(node.elements, props, path, state);
|
|
}
|
|
|
|
LogicalExpression(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['left', 'right'], path, state);
|
|
}
|
|
|
|
ConditionalExpression(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['test', 'consequent', 'alternate'], path, state);
|
|
}
|
|
|
|
IfStatement(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['test'], path, state);
|
|
this.buildExpressionHandler(node.test, ['left', 'right'], path, state);
|
|
}
|
|
|
|
ExpressionStatement(path, state) {
|
|
const { node } = path;
|
|
const { types } = this;
|
|
if (types.isAssignmentExpression(node.expression)) {
|
|
this.buildExpressionHandler(node.expression, ['right'], path, state);
|
|
}
|
|
}
|
|
|
|
ReturnStatement(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['argument'], path, state);
|
|
}
|
|
|
|
ExportDefaultDeclaration(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['declaration'], path, state);
|
|
}
|
|
|
|
BinaryExpression(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['left', 'right'], path, state);
|
|
}
|
|
|
|
NewExpression(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['callee', 'arguments'], path, state);
|
|
}
|
|
|
|
ClassDeclaration(path, state) {
|
|
const { node } = path;
|
|
this.buildExpressionHandler(node, ['superClass'], path, state);
|
|
}
|
|
}
|
|
|