babel-plugin-transform-react-to-vue
Advanced tools
Comparing version 0.0.0 to 0.1.0
311
index.js
@@ -1,78 +0,275 @@ | ||
module.exports = function ({ types: t }) { | ||
const mapKey = key => { | ||
const map = { | ||
componentDidMount: t.identifier('mounted'), | ||
componentWillMount: t.identifier('beforeMount'), | ||
componentWillUnmount: t.identifier('beforeDestroy'), | ||
state: t.identifier('data') | ||
const isSpecialMethod = (() => { | ||
const specialMethods = new Set(['render', 'componentDidMount', 'componentWillMount', 'componentWillUnmount']) | ||
return methodName => specialMethods.has(methodName) | ||
})() | ||
const mapMethodName = (() => { | ||
let map = null | ||
return (t, key) => { | ||
if (!map) { | ||
map = { | ||
componentDidMount: t.identifier('mounted'), | ||
componentWillMount: t.identifier('beforeMount'), | ||
componentWillUnmount: t.identifier('beforeDestroy') | ||
} | ||
} | ||
return map[key.name] || key | ||
} | ||
})() | ||
return { | ||
inherits: require('babel-plugin-syntax-class-properties'), | ||
visitor: { | ||
ClassDeclaration(path) { | ||
if (!path.node.superClass || path.node.superClass.name !== 'Component') { | ||
const removeReactImport = (t, path) => { | ||
path.traverse({ | ||
ImportDeclaration(path) { | ||
const specifiers = path.get('specifiers') | ||
const source = path.get('source') | ||
if (!t.isStringLiteral(source) || source.node.value !== 'react') { | ||
return | ||
} | ||
specifiers.forEach(specifier => { | ||
if (t.isImportDefaultSpecifier(specifier)) { | ||
specifier.remove() | ||
if (specifiers.length === 1) { | ||
path.remove() | ||
} | ||
} | ||
}) | ||
} | ||
}) | ||
} | ||
const getReactComponentIdentifier = (t, path) => { | ||
let result = null | ||
path.traverse({ | ||
ImportDeclaration(path) { | ||
const specifiers = path.get('specifiers') | ||
const source = path.get('source') | ||
if ( | ||
specifiers.length === 1 && | ||
t.isStringLiteral(source) && | ||
source.node.value === 'react-dom' && | ||
t.isImportDefaultSpecifier(specifiers[0]) && | ||
specifiers[0].node.local.name === 'ReactDOM' | ||
) { | ||
path.replaceWith(t.importDeclaration([t.importDefaultSpecifier(t.identifier('Vue'))], t.stringLiteral('vue'))) | ||
return | ||
} | ||
if (!t.isStringLiteral(source) || source.node.value !== 'react') { | ||
return | ||
} | ||
specifiers.forEach(specifier => { | ||
if (!t.isImportSpecifier(specifier)) { | ||
return | ||
} | ||
const body = [] | ||
const imported = specifier.get('imported') | ||
const local = specifier.get('local') | ||
path.node.body.body.forEach(exp => { | ||
if (exp.type === 'ClassMethod') { | ||
if (exp.kind === 'method') { | ||
exp.type = 'ObjectMethod' | ||
exp.key = mapKey(exp.key) | ||
body.push(exp) | ||
} | ||
} else if (exp.type === 'ClassProperty') { | ||
if (exp.key.name === 'state') { | ||
exp.key = mapKey(exp.key) | ||
body.push(t.objectMethod( | ||
'method', | ||
t.identifier('data'), | ||
[], | ||
t.blockStatement([ | ||
t.returnStatement(exp.value) | ||
]) | ||
)) | ||
} | ||
if (t.isIdentifier(imported) && imported.node.name === 'Component' && t.isIdentifier(local)) { | ||
result = local.node.name | ||
specifier.remove() | ||
if (specifiers.length === 1) { | ||
path.remove() | ||
} | ||
} | ||
}) | ||
} | ||
}) | ||
return result | ||
} | ||
const convertReactBody = (t, path) => { | ||
path.traverse({ | ||
// this.state.* => this.$data.* and this.props.* => this.$attrs.* | ||
MemberExpression(path) { | ||
const object = path.get('object') | ||
const property = path.get('property') | ||
if (t.isThisExpression(object) && t.isIdentifier(property) && property.node.name === 'state') { | ||
property.replaceWith(t.identifier('$data')) | ||
} else if (t.isThisExpression(object) && t.isIdentifier(property) && property.node.name === 'props') { | ||
property.replaceWith(t.identifier('$attrs')) | ||
} | ||
}, | ||
// className => class | ||
JSXAttribute(path) { | ||
const name = path.get('name') | ||
if (t.isJSXIdentifier(name) && name.node.name === 'className') { | ||
path.replaceWith(t.jSXAttribute(t.jSXIdentifier('class'), path.get('value').node)) | ||
} | ||
}, | ||
// this.setState({...this.state, newProps: newVals}) | ||
CallExpression(path) { | ||
const callee = path.get('callee') | ||
const args = path.get('arguments') | ||
if ( | ||
t.isMemberExpression(callee) && | ||
t.isThisExpression(callee.get('object')) && | ||
t.isIdentifier(callee.get('property')) && | ||
callee.get('property').node.name === 'setState' && | ||
args.length === 1 && | ||
t.isObjectExpression(args[0]) | ||
) { | ||
const statePatch = args[0].get('properties') | ||
const toPatch = [] | ||
statePatch.forEach(property => { | ||
if (t.isSpreadElement(property)) { | ||
return | ||
} | ||
const key = property.get('key') | ||
toPatch.push({ | ||
key, | ||
value: property.get('value') | ||
}) | ||
}) | ||
path.replaceWith( | ||
t.variableDeclaration( | ||
'var', | ||
[ | ||
t.variableDeclarator( | ||
path.node.id, | ||
t.objectExpression(body) | ||
) | ||
] | ||
const assignments = toPatch.map(({ key, value }) => | ||
t.expressionStatement( | ||
t.assignmentExpression( | ||
'=', | ||
t.memberExpression(t.thisExpression(), key.node, !t.isIdentifier(key.node)), | ||
value.node | ||
) | ||
) | ||
) | ||
path.replaceWith(t.blockStatement(assignments)) | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
function looksLike(a, b) { | ||
return ( | ||
a && | ||
b && | ||
Object.keys(b).every(bKey => { | ||
const bVal = b[bKey] | ||
const aVal = a[bKey] | ||
if (typeof bVal === 'function') { | ||
return bVal(aVal) | ||
const convertReactComponent = (t, path, isDefaultExport) => { | ||
const id = path.get('id') | ||
const vueBody = [] | ||
const reactBody = path.get('body') | ||
const methods = [] | ||
reactBody.get('body').forEach(reactProperty => { | ||
const key = reactProperty.get('key') | ||
if ( | ||
// normal methods | ||
t.isClassMethod(reactProperty) && | ||
reactProperty.node.kind === 'method' && | ||
t.isIdentifier(key) | ||
) { | ||
const body = reactProperty.get('body') | ||
const params = reactProperty.node.params | ||
convertReactBody(t, reactProperty.get('body')) | ||
const newMethod = t.objectMethod('method', mapMethodName(t, key.node), params, body.node) | ||
newMethod.async = reactProperty.node.async | ||
newMethod.generator = reactProperty.node.generator | ||
if (isSpecialMethod(key.node.name)) { | ||
vueBody.push(newMethod) | ||
} else { | ||
methods.push(newMethod) | ||
} | ||
return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal) | ||
}) | ||
) | ||
} else if ( | ||
// bound-to-class methods | ||
t.isClassProperty(reactProperty) && | ||
!reactProperty.node.static && | ||
!reactProperty.node.computed && | ||
t.isArrowFunctionExpression(reactProperty.get('value')) | ||
) { | ||
const arrowFn = reactProperty.get('value') | ||
let body = arrowFn.get('body').node | ||
if (!t.isBlockStatement(body)) { | ||
arrowFn.get('body').replaceWith(t.blockStatement([t.returnStatement(body)])) | ||
body = arrowFn.get('body').node | ||
} | ||
const params = arrowFn.node.params | ||
convertReactBody(t, arrowFn.get('body')) | ||
const newMethod = t.objectMethod('method', mapMethodName(t, key.node), params, body) | ||
newMethod.async = arrowFn.node.async | ||
newMethod.generator = arrowFn.node.generator | ||
if (isSpecialMethod(key.node.name)) { | ||
vueBody.push(newMethod) | ||
} else { | ||
methods.push(newMethod) | ||
} | ||
} else if ( | ||
// state | ||
t.isClassProperty(reactProperty) && | ||
!reactProperty.node.static && | ||
!reactProperty.node.computed && | ||
t.isIdentifier(key) && | ||
key.node.name === 'state' | ||
) { | ||
vueBody.push( | ||
t.objectProperty(t.identifier('data'), t.arrowFunctionExpression([], reactProperty.get('value').node)) | ||
) | ||
} | ||
}) | ||
if (methods.length > 0) { | ||
vueBody.push(t.objectProperty(t.identifier('methods'), t.objectExpression(methods))) | ||
} | ||
if (isDefaultExport) { | ||
path.replaceWith(t.objectExpression(vueBody)) | ||
} else { | ||
path.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(id.node, t.objectExpression(vueBody))])) | ||
} | ||
} | ||
function isPrimitive(val) { | ||
// eslint-disable-next-line no-eq-null,eqeqeq | ||
return val == null || /^[sbn]/.test(typeof val) | ||
module.exports = ({ types: t }) => { | ||
return { | ||
visitor: { | ||
Program(path) { | ||
removeReactImport(t, path) | ||
const componentIdentifier = getReactComponentIdentifier(t, path) | ||
let defaultExport = null | ||
path.traverse({ | ||
ExportDefaultDeclaration(path) { | ||
defaultExport = path.get('declaration') | ||
}, | ||
ClassDeclaration(path) { | ||
const superClass = path.get('superClass') | ||
if (superClass && t.isIdentifier(superClass) && superClass.node.name === componentIdentifier) { | ||
convertReactComponent(t, path, path === defaultExport) | ||
} | ||
}, | ||
CallExpression(path) { | ||
const callee = path.get('callee') | ||
const object = callee.get('object') | ||
const property = callee.get('property') | ||
const computed = callee.node.computed | ||
if ( | ||
!computed && | ||
t.isIdentifier(object) && | ||
t.isIdentifier(property) && | ||
object.node.name === 'ReactDOM' && | ||
property.node.name === 'render' | ||
) { | ||
const [jsx, el] = path.get('arguments') | ||
path.replaceWith( | ||
t.newExpression(t.identifier('Vue'), [ | ||
t.objectExpression([ | ||
t.objectProperty(t.identifier('el'), el.node), | ||
t.objectMethod( | ||
'method', | ||
t.identifier('render'), | ||
[], | ||
t.blockStatement([t.returnStatement(jsx.node)]) | ||
) | ||
]) | ||
]) | ||
) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} | ||
} |
{ | ||
"name": "babel-plugin-transform-react-to-vue", | ||
"version": "0.0.0", | ||
"version": "0.1.0", | ||
"description": "my impeccable project", | ||
"repository": { | ||
"url": "egoist/babel-plugin-transform-react-to-vue", | ||
"url": "vueact/babel-plugin-transform-react-to-vue", | ||
"type": "git" | ||
@@ -14,25 +14,22 @@ }, | ||
"scripts": { | ||
"test": "jest && npm run lint", | ||
"lint": "xo" | ||
"test": "ava && npm run lint", | ||
"lint": "xo", | ||
"cov": "nyc --reporter=lcov ava" | ||
}, | ||
"author": "egoist <0x142857@gmail.com>", | ||
"license": "MIT", | ||
"jest": { | ||
"testEnvironment": "node" | ||
}, | ||
"dependencies": { | ||
"babel-plugin-syntax-class-properties": "^6.13.0" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"babel-core": "^6.25.0", | ||
"ava": "^0.21.0", | ||
"babel-core": "next", | ||
"babel-plugin-syntax-class-properties": "next", | ||
"babel-plugin-syntax-jsx": "next", | ||
"babel-plugin-syntax-object-rest-spread": "next", | ||
"eslint-config-rem": "^3.0.0", | ||
"jest-cli": "^19.0.0", | ||
"nyc": "^11.0.3", | ||
"xo": "^0.18.0" | ||
}, | ||
"xo": { | ||
"extends": "rem", | ||
"envs": [ | ||
"jest" | ||
] | ||
"extends": "rem" | ||
} | ||
} |
# babel-plugin-transform-react-to-vue | ||
[![NPM version](https://img.shields.io/npm/v/babel-plugin-transform-react-to-vue.svg?style=flat)](https://npmjs.com/package/babel-plugin-transform-react-to-vue) [![NPM downloads](https://img.shields.io/npm/dm/babel-plugin-transform-react-to-vue.svg?style=flat)](https://npmjs.com/package/babel-plugin-transform-react-to-vue) [![CircleCI](https://circleci.com/gh/egoist/babel-plugin-transform-react-to-vue/tree/master.svg?style=shield)](https://circleci.com/gh/egoist/babel-plugin-transform-react-to-vue/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) | ||
[![NPM version](https://img.shields.io/npm/v/babel-plugin-transform-react-to-vue.svg?style=flat)](https://npmjs.com/package/babel-plugin-transform-react-to-vue) [![NPM downloads](https://img.shields.io/npm/dm/babel-plugin-transform-react-to-vue.svg?style=flat)](https://npmjs.com/package/babel-plugin-transform-react-to-vue) [![CircleCI](https://circleci.com/gh/vueact/babel-plugin-transform-react-to-vue/tree/master.svg?style=shield)](https://circleci.com/gh/vueact/babel-plugin-transform-react-to-vue/tree/master) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) | ||
🚧 **In development...** | ||
## Install | ||
@@ -24,13 +22,30 @@ | ||
```js | ||
class Counter extends Component { | ||
state = { count: 0 } | ||
import ReactDOM from 'react-dom' | ||
import React, { Component } from 'react' | ||
inc = () => this.setState({count: this.state.count + 1}) | ||
class App extends Component { | ||
state = { | ||
hello: 'world' | ||
} | ||
myMethod = () => { | ||
this.setState({ hello: 'not world ;)' }) | ||
} | ||
render() { | ||
return <button onClick={this.inc}> | ||
{this.state.count} | ||
</button> | ||
return ( | ||
<div className="App"> | ||
<div className="App-header" onClick={this.myMethod}> | ||
<h2> | ||
Hello {this.state.hello} | ||
</h2> | ||
</div> | ||
<p className="App-intro"> | ||
To get started, edit <code>src/App.js</code> and save to reload. | ||
</p> | ||
</div> | ||
) | ||
} | ||
componentDidMount = () => console.log(this.state) | ||
} | ||
ReactDOM.render(<App />, document.getElementById('root')) | ||
``` | ||
@@ -41,19 +56,42 @@ | ||
```js | ||
var Counter = { | ||
data() { | ||
return { | ||
count: 0 | ||
} | ||
import Vue from 'vue' | ||
const App = { | ||
data: () => ({ | ||
hello: 'world' | ||
}), | ||
render() { | ||
return ( | ||
<div class="App"> | ||
<div class="App-header" onClick={this.myMethod}> | ||
<h2> | ||
Hello {this.$data.hello} | ||
</h2> | ||
</div> | ||
<p class="App-intro"> | ||
To get started, edit <code>src/App.js</code> and save to reload. | ||
</p> | ||
</div> | ||
) | ||
}, | ||
mounted() { | ||
return console.log(this.$data) | ||
}, | ||
methods: { | ||
inc() { | ||
this.count = this.count + 1 | ||
myMethod() { | ||
this.hello = 'not world ;)' | ||
} | ||
}, | ||
} | ||
} | ||
new Vue({ | ||
el: document.getElementById('root'), | ||
render() { | ||
return <button onClick={this.inc}> | ||
{this.count} | ||
</button> | ||
return <App /> | ||
} | ||
} | ||
}) | ||
``` | ||
@@ -70,7 +108,6 @@ | ||
## Author | ||
## Team | ||
**babel-plugin-transform-react-to-vue** © [egoist](https://github.com/egoist), Released under the [MIT](./LICENSE) License.<br> | ||
Authored and maintained by egoist with help from contributors ([list](https://github.com/egoist/babel-plugin-transform-react-to-vue/contributors)). | ||
> [egoistian.com](https://egoistian.com) · GitHub [@egoist](https://github.com/egoist) · Twitter [@rem_rin_rin](https://twitter.com/rem_rin_rin) | ||
[![EGOIST](https://github.com/egoist.png?size=100)](https://github.com/egoist) | [![Nick Messing](https://github.com/nickmessing.png?size=100)](https://github.com/nickmessing) | ||
---|--- | ||
[EGOIST](http://github.com/egoist) | [Nick Messing](https://github.com/nickmessing) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
13049
0
249
111
8
1
- Removedbabel-plugin-syntax-class-properties@6.13.0(transitive)