Open hoperyy opened 5 years ago
const parse = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');
const { traverseTemplate, traverseScript, splitSFC, genConstructor, genSFCRenderMethod } = require('../src/index.js');
const sfc = require('./index.vue');
const state = {
name: undefined,
data: {},
props: {},
computeds: {},
components: {},
};
// 分割 .vue 单文件(SFC)
const parseCode = splitSFC(sfc.file, true);
// traverse template
const renderArgument = traverseTemplate(parseCode.template);
// traverse script
traverseScript(parseCode.js, state);
// vue --> react
const tpl = `export default class myComponent extends Component {}`;
// 编译ast
const rast = parse(tpl, {
sourceType: 'module',
});
// 转换ast
traverse(rast, {
ClassBody(path) {
genConstructor(path, state);
genSFCRenderMethod(path, state, renderArgument);
},
});
// 重新生成ast
const { code } = generate(rast, {
quotes: 'single',
retainLines: true,
});
// 转化后的代码
console.log(code);
<template>
<div>
<p class="title">
{{title}}
</p>
<p class="name">
{{name}}
</p>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
name: 'name',
};
},
};
</script>
export default class myComponent extends Component {
constructor(props) {
super(props);
this.state = {
show: true,
name: 'name',
};
}
render() {
return (
<div>
<p className="title">{title}</p>
<p className="name">{name}</p>
</div>
);
}
}
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
// 解析data
const analysisData = (body, data, isObject) => {
let propNodes = [];
if (isObject) {
propNodes = body;
data._statements = [].concat(body);
} else {
body.forEach(child => {
if (t.isReturnStatement(child)) {
propNodes = child.argument.properties;
data._statements = [].concat(child.argument.properties);
}
});
}
propNodes.forEach(propNode => {
data[propNode.key.name] = propNode;
});
};
// 解析props
const analysisProps = {
ObjectProperty(path) {
const parent = path.parentPath.parent;
if (parent.key && parent.key.name === this.childName) {
const key = path.node.key;
const node = path.node.value;
if (key.name === 'type') {
if (t.isIdentifier(node)) {
this.result.props[this.childName].type = node.name.toLowerCase();
} else if (t.isArrayExpression(node)) {
let elements = [];
node.elements.forEach(child => {
elements.push(child.name.toLowerCase());
});
this.result.props[this.childName].type = elements.length > 1 ? 'array' : elements[0] ? elements[0] : elements;
this.result.props[this.childName].value = elements.length === 1 ? elements[0] : elements;
}
}
if (t.isLiteral(node)) {
if (key.name === 'default') {
this.result.props[this.childName].defaultValue = node.value;
}
if (key.name === 'required') {
this.result.props[this.childName].required = node.value;
}
}
}
}
};
// life-cycle
const cycle = {
'created': 'componentWillMount',
'mounted': 'componentDidMount',
'beforeUpdated': 'componentWillUpdate',
'updated': 'componentDidUpdate',
'beforeDestroy': 'componentWillUnmount'
}
const parseScript = (ast) => {
let result = {
data: {},
props: {},
methods: [],
cycle: []
};
traverse(ast, {
/**
* 对象方法
* data() {return {}}
*/
ObjectMethod(path) {
const parent = path.parentPath.parent;
const name = path.node.key.name;
if (parent && t.isExportDefaultDeclaration(parent)) {
if (name === 'data') {
const body = path.node.body.body;
analysisData(body, result.data);
} else if (name && Object.keys(cycle).indexOf(name) >= 0) {
let expressions = path.node.body.body;
let newExpressions = [];
if (expressions.length > 0) {
expressions.forEach(node => {
let args = node.expression.arguments;
let newArgs = [];
if (args.length > 0) {
args.forEach(arg => {
if (t.isMemberExpression(arg)) {
// data.name
if (result.data[arg.property.name]) {
newArgs.push(t.memberExpression(
t.memberExpression(
t.thisExpression(),
t.identifier('state')
),
t.identifier(arg.property.name)
))
}
} else {
newArgs.push(arg);
}
});
}
newExpressions.push(t.expressionStatement(
t.callExpression(t.memberExpression(
t.identifier('console'),
t.identifier('log')
), newArgs)
));
});
}
path.replaceWith(t.objectMethod(
'method',
t.identifier(name),
[],
t.blockStatement(newExpressions)
));
result.cycle.push(path.node);
// 防止超过最大堆栈内存
path.remove();
}
}
},
/**
* 对象属性、箭头函数
* data: () => {return {}}
* data: () => ({})
* props: []
* props: {
* name: String
* }
* props: {
* name: {
* type: String
* }
* }
*/
ObjectProperty(path) {
const parent = path.parentPath.parent;
if (parent && t.isExportDefaultDeclaration(parent)) {
const name = path.node.key.name;
const node = path.node.value;
if (name === 'data') {
if (t.isArrowFunctionExpression(node)) {
if (node.body.body) {
// return {}
analysisData(node.body.body, result.data);
} else {
// {}
analysisData(node.body.properties, result.data, true);
}
}
} else if (name === 'props') {
if (t.isArrayExpression(node)) {
node.elements.forEach(child => {
result.props[child.value] = {
type: undefined,
value: undefined,
required: false,
validator: false
}
});
} else if (t.isObjectExpression(node)) {
const childs = node.properties;
if (childs.length > 0) {
path.traverse({
ObjectProperty(propPath) {
const propParent = propPath.parentPath.parent;
if (propParent.key && propParent.key.name === name) {
const childName = propPath.node.key.name;
const childVal = propPath.node.value;
// console.log(childVal.type);
if (t.isIdentifier(childVal)) {
result.props[childName] = {
type: childVal.name.toLowerCase(),
value: undefined,
required: false,
validator: false
}
} else if (t.isArrayExpression(childVal)) {
let elements = [];
childVal.elements.forEach(child => {
elements.push(child.name.toLowerCase());
});
result.props[childName] = {
type: elements.length > 1 ? 'array' : elements[0] ? elements[0] : elements,
value: elements.length === 1 ? elements[0] : elements,
required: false,
validator: false
}
} else if (t.isObjectExpression(childVal)) {
result.props[childName] = {
type: '',
value: undefined,
required: false,
validator: false
}
path.traverse(analysisProps, {
result,
childName
});
}
}
}
});
}
}
} else if (name === 'methods') {
const properties = node.properties;
if (properties.length > 0) {
result.methods = [].concat(properties);
}
}
}
}
});
return result;
};
const genConstructor = (path, state) => {
const blocks = [
t.expressionStatement(
t.callExpression(
t.super(),
[t.identifier('props')]
)
)
];
if (state._statements && state._statements.length > 0) {
let propArr = [];
state._statements.forEach(node => {
if (t.isObjectProperty(node)) {
// state.key = value;
// let nodeStatement = t.expressionStatement(
// t.assignmentExpression('=', t.memberExpression(
// t.identifier('state'),
// t.identifier(node.key.name)
// ), t.isBooleanLiteral(node.value) ?
// t.booleanLiteral(node.value.value) :
// t.stringLiteral(node.value.value)
// )
// );
// state = { key: value };
propArr.push(t.objectProperty(
t.stringLiteral(node.key.name),
t.isBooleanLiteral(node.value) ?
t.booleanLiteral(node.value.value) :
t.stringLiteral(node.value.value)
))
}
});
let nodeStatement = t.expressionStatement(
t.assignmentExpression('=', t.identifier('state'),
t.objectExpression(propArr)
)
);
blocks.push(nodeStatement);
}
const constructor = t.classMethod(
'constructor', // kind
t.identifier('constructor'), // 方法名
[t.identifier('props')], // 参数
t.blockStatement(blocks) // body
);
path.node.body.push(constructor);
};
const genMethods = (path, arr) => {
const methods = [];
if (arr.length > 0) {
arr.forEach(node => {
methods.push(t.classMethod(
'method',
t.identifier(node.key.name),
node.params,
node.body
))
});
}
path.node.body = path.node.body.concat(methods);
};
const genCycle = (path, arr) => {
const cycles = [];
if (arr.length > 0) {
arr.forEach(node => {
cycles.push(t.classMethod(
'method',
t.identifier(cycle[node.key.name]),
[],
node.body
))
});
}
path.node.body = path.node.body.concat(cycles);
};
module.exports = {
parseScript,
genConstructor,
genMethods,
genCycle
};
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const parseTemplate = (ast, json) => {
let argument = null;
function identifier(value) {
let flag = json.props[value] ? t.identifier('props') : (json.data[value] ? t.identifier('state') : null);
if (!flag) return null;
return t.memberExpression(
t.memberExpression(t.thisExpression(), flag),
t.identifier(value)
);
}
traverse(ast, {
ExpressionStatement: {
enter(path) {},
exit(path) {
argument = path.node.expression;
}
},
JSXAttribute(path) {
const node = path.node;
if (node.name.name === 'class') {
path.replaceWith(
t.jsxAttribute(t.jsxIdentifier('className'), node.value)
);
return;
} else if (node.name.name === 'v-if') {
let parentPath = path.parentPath.parentPath;
let expression = identifier(node.value.value);
if (!expression) {
path.remove();
return;
}
parentPath.replaceWith(
t.jSXExpressionContainer( // 条件 ? success : false
t.conditionalExpression(
expression,
parentPath.node,
t.nullLiteral()
)
)
);
path.remove();
} else if (t.isJSXNamespacedName(node.name)) {
if (node.name.namespace.name === 'v-on') {
path.replaceWith(
t.jsxAttribute(t.jsxIdentifier('onClick'), t.jsxExpressionContainer(
t.memberExpression(
t.thisExpression(),
t.identifier(node.value.value)
)
))
);
}
}
},
JSXExpressionContainer(path) {
const name = path.node.expression.name;
if (name && path.container) {
let expression = identifier(name);
if (!expression) return;
path.replaceWith(
t.jSXExpressionContainer(expression)
);
}
}
});
return argument;
};
const genTemplate = (path, args) => {
// template->render
const render = t.classMethod(
"method",
t.identifier("render"),
[],
t.blockStatement(
[].concat(t.returnStatement(args))
)
);
path.node.body.push(render);
};
module.exports = {
parseTemplate,
genTemplate
};
const fs = require('fs');
const path = require('path');
// SFC(single-file component or *.vue file)
const compiler = require('vue-template-compiler');
const parse = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const jsMethod = require('./parse-script');
const templateMethod = require('./parse-template');
const generate = require('@babel/generator').default;
/**
* vue-template-compiler提取vue代码里的template、style、script
* @param file
* @return Object
* { template: null,
* script: null,
* styles: [],
* customBlocks: [],
* errors: [] }
*/
function getSFCComponent(file) {
let source = fs.readFileSync(path.resolve(__dirname, file));
let result = compiler.parseComponent(source.toString(), {
pad: "line"
});
let cssContent = '';
result.styles.forEach(style => {
cssContent += '' + style.content;
})
return {
template: result.template.content.replace(/{{/g, '{').replace(/}}/g, '}'),
js: result.script.content.replace(/\/\/.*/g, ''),
css: cssContent
};
}
let app = Object.create(null);
// 解析vue文件
let component = getSFCComponent('./source.vue');
// 复用style
app.style = component.css;
// 解析script
let script_ast = parse(component.js, {
sourceType: 'module'
});
let jsObj = jsMethod.parseScript(script_ast);
app.script = {
ast: script_ast,
components: null,
computed: null,
data: jsObj.data,
props: jsObj.props,
methods: jsObj.methods,
cycle: jsObj.cycle
};
// 解析template
const template_ast = parse(component.template, {
sourceType: "module",
plugins: ["jsx"]
});
const renderArgument = templateMethod.parseTemplate(template_ast, jsObj);
// vue->react
const tpl = `
import { createElement, Component } from 'React';
export default class myComponent extends Component {}
`;
const final_ast = parse(tpl, {
sourceType: 'module'
});
traverse(final_ast, {
ClassBody(path) {
jsMethod.genConstructor(path, app.script.data)
jsMethod.genMethods(path, app.script.methods)
jsMethod.genCycle(path, app.script.cycle)
templateMethod.genTemplate(path, renderArgument)
}
});
const result = generate(final_ast);
console.log(result.code);
<template>
<div>
<p class="title" v-on:click="handleClick">{{title}}</p>
<p class="name" v-if="show">{{name}}</p>
</div>
</template>
<style>
body {
background-color: #fef6fc;
}
</style>
<style>
.title {font-size: 28px; color: #333;}
.name {font-size: 32px; color: #999;}
</style>
<script>
// script文件
export default {
// props: ['title'],
props: {
// title: String,
// title2: [String],
// title3: [String, Number],
title: {
type: String,
default: 'title'
}
// title5: {
// type: [String],
// default: 'title5'
// },
// title6: {
// type: [String, Number],
// default: 'title6',
// required: true
// }
},
data() {
return {
show: true,
name: 'name'
}
},
created() {},
mounted() {
console.log(this.name);
},
methods: {
handleClick() {},
handleClick2(a, b) {
comsole.log(1)
}
}
};
</script>
import { createElement, Component } from 'React';
export default class myComponent extends Component {
constructor(props) {
super(props);
state = {
"show": true,
"name": "name"
};
}
handleClick() {}
handleClick2(a, b) {
comsole.log(1);
}
componentWillMount() {}
componentDidMount() {
console.log(this.state.name);
}
render() {
return
<div>
<p className="title" onClick={this.handleClick}>{this.props.title}</p>
{this.state.show ?
<p className="name">{this.state.name}</p> : null}
</div>;
}
}
代码有点乱,没有抽离公共方法,凑合看 附两个最常用查询网址: 看ast结构:https://astexplorer.net/ 插件使用@babel/types:https://www.babeljs.cn/docs/babel-types
index.vue
index.js