-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
step2: 实现 vue 转 react #3
Comments
网上搜寻各种资料,写了一个乞丐版主要源码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>
);
}
} |
部分功能版vue转react解析script.jsconst 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
}; 解析template.jsconst 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
}; 入口index.jsconst 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); 源vue文件<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> 转换后的react文件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>;
}
} |
代码有点乱,没有抽离公共方法,凑合看 |
Open
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
index.vue
index.js
The text was updated successfully, but these errors were encountered: