Explanation
"version": "0.1.3"
Run in react and vue at the same time.
-
see more
Two situations
First said first:
import Vue from 'vue'
import {VuePlugin} from 'vuera'
Vue.use (VuePlugin)
/* ... */Now, use your React components like you will normally use your Vue components!
<template>
<div>
<h1> I'm a Vue component </h1>
<my-react-component: message = "message" @ reset = "reset" />
</div>
</template>
<script>
import MyReactComponent from './MyReactComponent'
export default {
data () {
message: 'Hello from React!',
},
methods: {
reset () {
this.message = ''
}
},
components: {'my-react-component': MyReactComponent},
}
</script>Use React as a Vue plugin
vuera/src/VuePlugin.js
// Determine if React's component
import isReactComponent from './utils/isReactComponent'
// React Component -> Vue Component
import VueResolver from './resolvers/Vue'
/**
* vue plugin
*/
export default {
install (Vue, options) {
/**
* Custom merge strategy, this strategy is really just
* Wraps all React components, while retaining the Vue components.
*/
const originalComponentsMergeStrategy = Vue.config.optionMergeStrategies.components
Vue.config.optionMergeStrategies.components = function (parent, ... args) {
// value set before <- return Object.assign (parent, wrappedComponents)
const mergedValue = originalComponentsMergeStrategy (parent, ... args)
//
const wrappedComponents = mergedValue
Object.entries (mergedValue) .reduce (
(acc, [k, v]) => ({
... acc,
[k]: isReactComponent (v)? VueResolver (v): v,
}),
{}
)
: mergedValue
//merge
return Object.assign (parent, wrappedComponents)
}
},
}Which appeared
const obj = {foo: 'bar', baz: 42};
console.log (Object.entries (obj)); // [['foo', 'bar'], ['baz', 42]]var flattened = [[0, 1], [2, 3], [4, 5]]. reduce (function (a, b) {
return a.concat (b);
}, []);
// flattened is [0, 1, 2, 3, 4, 5]- VueResolver
vuera/src/resolvers/Vue.js
React Component -> Vue Component
So you can see that the use of traversing the side of the Vue component, if found, React component looks like it is transformed into Vue.
Say the second:
Add vuera/babel to your .babelrc plugins option
.babelrc
{
"presets": "react",
"plugins": ["vuera/babel"]
}use
import React from 'react'
import MyVueComponent from './MyVueComponent.vue'
export default () => (
<div>
<h1> I'm a react component </h1>
<div>
<MyVueComponent message = 'Hello from Vue!' />
</div>
</div>
)From the way of adding .babelrc, is it possible to use babel?
vuera/babel.js
/* eslint-env node */
function processCreateElement (maybeReactComponent, args, file, path, types) {
// If the first argument is a string (built-in React component), return
if (maybeReactComponent.type === 'StringLiteral') return
if (! file.insertedVueraImport) {
file.path.node.body.unshift (
types.importDeclaration (
[
types.importSpecifier (
types.identifier ('__ vueraReactResolver'),
types.identifier ('__ vueraReactResolver')
),
],
types.stringLiteral ('vuera')
)
)
}
// Prevent duplicate imports
file.insertedVueraImport = true
// Replace React.createElement (component, props) with our helper function
path.replaceWith (
types.callExpression (types.identifier ('__ vueraReactResolver'), [maybeReactComponent, ... args])
)
}
// types is the babel plugin API? One of the modules
module.exports = function ({types}) {
return {
visitor: {
CallExpression (path, {file}) {
const callee = path.node.callee
const [maybeReactComponent, ... args] = path.node.arguments
// If there is a react module, reactImport
const reactImport = file.path.node.body.find (
x => x.type === 'ImportDeclaration' && x.source.value === 'react'
)
if (! reactImport) return
// if CallExpression is react.createElement
if (callee.type === 'MemberExpression') {
/**
* Get the default import name Examples:
* import React from 'react' => "React"
* import hahaLOL from 'react' => "hahaLOL"
*/
const defaultImport = reactImport.specifiers.find (
x => x.type === 'ImportDefaultSpecifier'
)
if (! defaultImport) return
const reactName = defaultImport.local.name
const {object, property} = callee
if (! (object.name === reactName && property.name === 'createElement')) {
return
}
processCreateElement (maybeReactComponent, args, file, path, types)
}
// Check CallExpression is react's 'createElement'
if (callee.type === 'Identifier' && callee.name! == '__vueraReactResolver') {
// Return unless createElement was imported
const createElementImport = reactImport.specifiers.find (
x => x.type === 'ImportSpecifier' && x.imported.name === 'createElement'
)
if (! createElementImport) return
processCreateElement (maybeReactComponent, args, file, path, types)
}
},
},
}
}This part of the need to understand the AST syntax tree, the above link can help you to be simple and clear babel plugins plug-in ImportSpecifiermember problems?
babel.js in general, is to turn
react.createElementinto a `__vueraReactResolver`` built-in function
export function babelReactResolver (component, props, children) {
return isReactComponent (component)
? React.createElement (component, props, children)
: React.createElement (VueWrapper, Object.assign ({component}, props), children)
}
// babelReactResolver as __vueraReactResolverThe above is only the last thing babel does to downgrade js
VueWrapper The above code is the most important
vuera/src/wrapper/vue.js
import React from 'react'
import Vue from 'vue'
import ReactWrapper from './React'
const VUE_COMPONENT_NAME = 'vuera-internal-component-name'
const wrapReactChildren = (createElement, children) =>
createElement ('vuera-internal-react-wrapper', {
props: {
component: () => <div> {children} </div>,
},
})
export default class VueContainer extends React.Component {
constructor (props) {
super (props)
/**
* Incoming and redefinition of real components
* `component` prop.
*/
this.currentVueComponent = props.component
/**
* Modify the createVueInstance function to pass this binding correctly. Doing this
* Constructor Avoid rendering functions in the rendering.
*// I feel a bit difficult to understand: translator said
*/
const createVueInstance = this.createVueInstance
const self = this
this.createVueInstance = function (element, component, prevComponent) {
createVueInstance (element, self, component, prevComponent)
}
}
componentWillReceiveProps (nextProps) {
const {component, ... props} = nextProps
if (this.currentVueComponent! == component) {
this.updateVueComponent (this.props.component, component)
}
/**
* NOTE: Did not compare props and nextprops, because I did not want to write a
function for deep object comparison. I do not know if this hurts performance a lot, maybe
* we do need to compare those objects.
*/
Object.assign (this.vueInstance. $ Data, props)
}
componentWillUnmount () {
this.vueInstance. $ destroy ()
}
/**
* Create and load VueInstance interface
* NOTE: VueInstance inside VueContainer
* We can not bind createVueInstance to this VueContainer object and need to be explicit
Pass the binding object
* @param {HTMLElement} targetElement - element to attact the Vue instance to
* @param {ReactInstance} reactThisBinding - current instance of VueContainer
*/
createVueInstance (targetElement, reactThisBinding) {
const {component, ... props} = reactThisBinding.props
// `this` refers to Vue instance in the constructor
reactThisBinding.vueInstance = new Vue ({
// targetElement is VueContainer render () element
el: targetElement,
data: props,
render (createElement) {
return createElement (
VUE_COMPONENT_NAME,
{
props: this. $ data,
},
[wrapReactChildren (createElement, this.children)]
)
},
components: {
[VUE_COMPONENT_NAME]: component,
'vuera-internal-react-wrapper': ReactWrapper,
},
})
}
updateVueComponent (prevComponent, nextComponent) {
this.currentVueComponent = nextComponent
/**
* Replace the component in the Vue instance and update it.
*/
this.vueInstance. $ options.components [VUE_COMPONENT_NAME] = nextComponent
this.vueInstance. $ forceUpdate ()
}
render () {
return <div ref = {this.createVueInstance} />
}
}The props.component component is wrapped with React.Component, and then the Vue instance reactThisBinding.vueInstance is created internally.
- Equivalent to say that this part of the
<div ref = {this.createVueInstance} />to the Vue processing, and then part of Vue react.component component event handler call.
export default function isReactComponent (component) {
if (typeof component === 'object') {
return false // no object == no react
} else if (
typeof component === 'function' &&
component.prototype.constructor.super &&
component.prototype.constructor.super.name.startsWith('Vue')
) {
return false // is vue
} else {
return true // is react
}
}