saven
Version:
395 lines • 17.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const babel_traverse_1 = require("babel-traverse");
const babel_generator_1 = require("babel-generator");
const fs = require("fs");
const class_1 = require("./class");
const html_1 = require("html");
const utils_1 = require("./utils");
const t = require("babel-types");
const constant_1 = require("./constant");
const babel_core_1 = require("babel-core");
const ts = require("typescript");
const template = require('babel-template');
function getIdsFromMemberProps(member) {
let ids = [];
const { object, property } = member;
if (t.isMemberExpression(object)) {
ids = ids.concat(getIdsFromMemberProps(object));
}
if (t.isThisExpression(object)) {
ids.push('this');
}
if (t.isIdentifier(object)) {
ids.push(object.name);
}
if (t.isIdentifier(property)) {
ids.push(property.name);
}
return ids;
}
/**
* TS 编译器会把 class property 移到构造器,
* 而小程序要求 `config` 和所有函数在初始化(after new Class)之后就收集到所有的函数和 config 信息,
* 所以当如构造器里有 this.func = () => {...} 的形式,就给他转换成普通的 classProperty function
* 如果有 config 就给他还原
*/
function resetTSClassProperty(body) {
for (const method of body) {
if (t.isClassMethod(method) && method.kind === 'constructor') {
if (t.isBlockStatement(method.body)) {
method.body.body = method.body.body.filter(statement => {
if (t.isExpressionStatement(statement) && t.isAssignmentExpression(statement.expression)) {
const expr = statement.expression;
const { left, right } = expr;
if (t.isMemberExpression(left) &&
t.isThisExpression(left.object) &&
t.isIdentifier(left.property)) {
if ((t.isArrowFunctionExpression(right) || t.isFunctionExpression(right))
||
(left.property.name === 'config' && t.isObjectExpression(right))) {
const classProp = t.classProperty(left.property, right);
body.push(classProp);
handleThirdPartyComponent(classProp);
return false;
}
}
}
return true;
});
}
}
}
}
function findDeclarationScope(path, id) {
const scopePath = path.findParent(p => !!p.scope.getOwnBindingIdentifier(id.name));
if (scopePath) {
return scopePath;
}
throw utils_1.codeFrameError(path.node, '该引用从未被定义');
}
function buildFullPathThisPropsRef(id, memberIds, path) {
const scopePath = findDeclarationScope(path, id);
const binding = scopePath.scope.getOwnBinding(id.name);
if (binding) {
const bindingPath = binding.path;
if (bindingPath.isVariableDeclarator()) {
const dclId = bindingPath.get('id');
const dclInit = bindingPath.get('init');
let dclInitIds = [];
if (dclInit.isMemberExpression()) {
dclInitIds = getIdsFromMemberProps(dclInit.node);
if (dclId.isIdentifier()) {
memberIds.shift();
}
if (dclInitIds[0] === 'this' && dclInitIds[1] === 'props') {
return template(dclInitIds.concat(memberIds).join('.'))().expression;
}
}
}
}
}
function handleThirdPartyComponent(expr) {
if (t.isClassProperty(expr) && expr.key.name === 'config' && t.isObjectExpression(expr.value)) {
const properties = expr.value.properties;
for (const prop of properties) {
if (t.isObjectProperty(prop) &&
(t.isIdentifier(prop.key, { name: 'usingComponents' }) || t.isStringLiteral(prop.key, { value: 'usingComponents' })) &&
t.isObjectExpression(prop.value)) {
for (const value of prop.value.properties) {
if (t.isObjectProperty(value)) {
if (t.isStringLiteral(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.value);
}
if (t.isIdentifier(value.key)) {
constant_1.THIRD_PARTY_COMPONENTS.add(value.key.name);
}
}
}
}
}
}
}
function transform(options) {
const code = options.isTyped
? ts.transpile(options.code, {
jsx: ts.JsxEmit.Preserve,
target: ts.ScriptTarget.ESNext,
importHelpers: true,
noEmitHelpers: true
})
: options.code;
options.env = Object.assign({ TARO_ENV: 'weapp' }, options.env || {});
const taroEnv = options.env.TARO_ENV;
utils_1.setting.sourceCode = code;
// babel-traverse 无法生成 Hub
// 导致 Path#getSource|buildCodeFrameError 都无法直接使用
// 原因大概是 babylon.parse 没有生成 File 实例导致 scope 和 path 原型上都没有 `file`
// 将来升级到 babel@7 可以直接用 parse 而不是 transform
const ast = babel_core_1.transform(code, {
parserOpts: {
sourceType: 'module',
plugins: [
'classProperties',
'jsx',
'flow',
'flowComment',
'trailingFunctionCommas',
'asyncFunctions',
'exponentiationOperator',
'asyncGenerators',
'objectRestSpread',
'decorators',
'dynamicImport'
]
},
plugins: [
require('babel-plugin-transform-flow-strip-types'),
[require('babel-plugin-transform-define').default, {
'process.env.TARO_ENV': taroEnv
}]
].concat((process.env.NODE_ENV === 'test') ? [] : require('babel-plugin-remove-dead-code').default)
}).ast;
if (options.isNormal) {
return { ast };
}
// transformFromAst(ast, code)
let result;
const componentSourceMap = new Map();
const imageSource = new Set();
let componentProperies = [];
let mainClass;
let storeName;
let renderMethod;
let isImportTaro = false;
babel_traverse_1.default(ast, {
ClassDeclaration(path) {
mainClass = path;
const superClass = path.node.superClass;
if (t.isIdentifier(superClass)) {
const binding = path.scope.getBinding(superClass.name);
if (binding && binding.kind === 'module') {
const bindingPath = binding.path.parentPath;
if (bindingPath.isImportDeclaration()) {
const source = bindingPath.node.source;
try {
const p = fs.existsSync(source.value + '.js') ? source.value + '.js' : source.value + '.tsx';
const code = fs.readFileSync(p, 'utf8');
componentProperies = transform({
isRoot: false,
isApp: false,
code,
isTyped: true,
sourcePath: source.value,
outputPath: source.value
}).componentProperies;
}
catch (error) {
//
}
}
}
}
},
ClassExpression(path) {
mainClass = path;
},
ClassMethod(path) {
if (t.isIdentifier(path.node.key) && path.node.key.name === 'render') {
renderMethod = path;
}
},
IfStatement(path) {
const consequent = path.get('consequent');
if (!consequent.isBlockStatement()) {
consequent.replaceWith(t.blockStatement([
consequent.node
]));
}
},
CallExpression(path) {
const callee = path.get('callee');
if (utils_1.isContainJSXElement(path)) {
return;
}
if (callee.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(callee.node);
const calleeIds = getIdsFromMemberProps(callee.node);
if (t.isIdentifier(id) && id.name.startsWith('on')) {
const fullPath = buildFullPathThisPropsRef(id, calleeIds, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
if (callee.isReferencedIdentifier()) {
const id = callee.node;
const ids = [id.name];
if (t.isIdentifier(id) && id.name.startsWith('on')) {
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
path.replaceWith(t.callExpression(fullPath, path.node.arguments));
}
}
}
},
AwaitExpression() {
const isAsyncImported = ast.program.body.some(statement => {
return t.isImportDeclaration(statement) && statement.source.value === constant_1.ASYNC_PACKAGE_NAME;
});
if (!isAsyncImported) {
ast.program.body.unshift(t.importDeclaration([], t.stringLiteral(constant_1.ASYNC_PACKAGE_NAME)));
}
},
// JSXIdentifier (path) {
// const parentPath = path.parentPath
// if (!parentPath.isJSXAttribute()) {
// return
// }
// const element = parentPath.parentPath
// if (!element.isJSXOpeningElement()) {
// return
// }
// const elementName = element.get('name')
// if (!elementName.isJSXIdentifier()) {
// return
// }
// if (DEFAULT_Component_SET.has(elementName.node.name)) {
// return
// }
// const expr = parentPath.get('value.expression')
// },
JSXOpeningElement(path) {
const { name } = path.node.name;
if (name === 'Provider') {
const modules = path.scope.getAllBindings('module');
const providerBinding = Object.values(modules).some((m) => m.identifier.name === 'Provider');
if (providerBinding) {
path.node.name = t.jSXIdentifier('View');
const store = path.node.attributes.find(attr => attr.name.name === 'store');
if (store && t.isJSXExpressionContainer(store.value) && t.isIdentifier(store.value.expression)) {
storeName = store.value.expression.name;
}
path.node.attributes = [];
}
}
if (constant_1.IMAGE_COMPONENTS.has(name)) {
for (const attr of path.node.attributes) {
if (attr.name.name === 'src') {
if (t.isStringLiteral(attr.value)) {
imageSource.add(attr.value.value);
}
else if (t.isJSXExpressionContainer(attr.value)) {
if (t.isStringLiteral(attr.value.expression)) {
imageSource.add(attr.value.expression.value);
}
}
}
}
}
},
JSXAttribute(path) {
const { name, value } = path.node;
if (!t.isJSXIdentifier(name) || value === null || t.isStringLiteral(value) || t.isJSXElement(value)) {
return;
}
const expr = value.expression;
const exprPath = path.get('value.expression');
if (!t.isBinaryExpression(expr, { operator: '+' }) && !t.isLiteral(expr) && name.name === 'style') {
const jsxID = path.findParent(p => p.isJSXOpeningElement()).get('name');
if (jsxID && jsxID.isJSXIdentifier() && constant_1.DEFAULT_Component_SET.has(jsxID.node.name)) {
exprPath.replaceWith(t.callExpression(t.identifier(constant_1.INTERNAL_INLINE_STYLE), [expr]));
}
}
if (name.name.startsWith('on')) {
if (exprPath.isReferencedIdentifier()) {
const ids = [expr.name];
const fullPath = buildFullPathThisPropsRef(expr, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
if (exprPath.isReferencedMemberExpression()) {
const id = utils_1.findFirstIdentifierFromMemberExpression(expr);
const ids = getIdsFromMemberProps(expr);
if (t.isIdentifier(id)) {
const fullPath = buildFullPathThisPropsRef(id, ids, path);
if (fullPath) {
exprPath.replaceWith(fullPath);
}
}
}
// @TODO: bind 的处理待定
}
},
ImportDeclaration(path) {
const source = path.node.source.value;
const names = [];
if (source === constant_1.TARO_PACKAGE_NAME) {
isImportTaro = true;
path.node.specifiers.push(t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)));
}
if (source === constant_1.REDUX_PACKAGE_NAME) {
path.node.specifiers.forEach((s, index, specs) => {
if (s.local.name === 'Provider') {
specs.splice(index, 1);
specs.push(t.importSpecifier(t.identifier('setStore'), t.identifier('setStore')));
}
});
}
path.traverse({
ImportDefaultSpecifier(path) {
const name = path.node.local.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
},
ImportSpecifier(path) {
const name = path.node.imported.name;
constant_1.DEFAULT_Component_SET.has(name) || names.push(name);
if (source === constant_1.TARO_PACKAGE_NAME && name === 'Component') {
path.node.local = t.identifier('__BaseComponent');
}
}
});
componentSourceMap.set(source, names);
}
});
if (!isImportTaro) {
ast.program.body.unshift(t.importDeclaration([
t.importDefaultSpecifier(t.identifier('Saven')),
t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)),
t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE))
], t.stringLiteral('@savenjs/saven')));
}
if (!mainClass) {
throw new Error('未找到 Saven.Component 的类定义');
}
mainClass.node.body.body.forEach(handleThirdPartyComponent);
const storeBinding = mainClass.scope.getBinding(storeName);
mainClass.scope.rename('Component', '__BaseComponent');
if (storeBinding) {
const statementPath = storeBinding.path.getStatementParent();
if (statementPath) {
ast.program.body.forEach((node, index, body) => {
if (node === statementPath.node) {
body.splice(index + 1, 0, t.expressionStatement(t.callExpression(t.identifier('setStore'), [
t.identifier(storeName)
])));
}
});
}
}
resetTSClassProperty(mainClass.node.body.body);
if (options.isApp) {
renderMethod.replaceWith(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([])));
return { ast };
}
result = new class_1.Transformer(mainClass, options.sourcePath, componentProperies).result;
result.code = babel_generator_1.default(ast).code;
result.ast = ast;
result.template = html_1.prettyPrint(result.template);
result.imageSrcs = Array.from(imageSource);
return result;
}
exports.default = transform;
//# sourceMappingURL=index.js.map