├── .eslintignore ├── dev-project-vue3 ├── .eslintignore ├── src │ ├── pages │ │ ├── watchref │ │ │ ├── VueBox.vue │ │ │ ├── react_app │ │ │ │ └── Box.js │ │ │ └── index.vue │ │ ├── vueMissReact │ │ │ ├── react_app │ │ │ │ └── Test1.js │ │ │ └── index.vue │ │ ├── basic │ │ │ ├── react_app │ │ │ │ ├── CC.js │ │ │ │ ├── BB.js │ │ │ │ ├── AA.js │ │ │ │ ├── UnMount.js │ │ │ │ └── Basic.js │ │ │ └── index.vue │ │ ├── slots │ │ │ ├── react_app │ │ │ │ ├── ReactCom.js │ │ │ │ └── Slots.js │ │ │ ├── Custom1.vue │ │ │ └── index.vue │ │ ├── pureReactInVue │ │ │ └── react_app │ │ │ │ ├── Box.js │ │ │ │ ├── BB.js │ │ │ │ ├── CC.js │ │ │ │ └── AA.js │ │ ├── routerView │ │ │ ├── Sub.vue │ │ │ ├── react_app │ │ │ │ └── Basic.js │ │ │ └── index.vue │ │ ├── context │ │ │ ├── react_app │ │ │ │ └── Basic.js │ │ │ ├── Custom1.vue │ │ │ └── index.vue │ │ ├── lazyReactInVue │ │ │ ├── react_app │ │ │ │ └── Basic.js │ │ │ └── index.vue │ │ ├── useInjectPropsFromWrapper │ │ │ ├── index.vue │ │ │ └── react_app │ │ │ │ └── BasicVueFromReact.js │ │ ├── events │ │ │ ├── react_app │ │ │ │ └── Button.js │ │ │ └── index.vue │ │ ├── crossProvider │ │ │ ├── vueRouterAndVuexCrossingProvider.js │ │ │ ├── vueRouterAndVuexCrossingProviderPure.js │ │ │ ├── index.vue │ │ │ └── react_app │ │ │ │ ├── Basic.js │ │ │ │ └── BasicPure.js │ │ └── introduce │ │ │ └── index.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── A1.vue │ │ ├── Test1.vue │ │ └── HelloWorld.vue │ ├── App.vue │ ├── main.js │ ├── store │ │ └── index.js │ └── router │ │ └── index.js ├── public │ ├── favicon.ico │ └── index.html ├── tailwind.config.js ├── .gitignore ├── vue.config.js ├── babel.config.js ├── package.json └── README.md ├── tests ├── mock │ └── empty.js ├── cases │ ├── applyReactInVue │ │ ├── ReactComponent.js │ │ ├── AA.js │ │ ├── FunctionalChildren.js │ │ ├── 3-getReactNode-test.js │ │ ├── 3-getReactNode.vue │ │ ├── 2-scopedSlots-vnode-test.js │ │ ├── 4-reactUnMount.vue │ │ ├── react_app │ │ │ └── ReactUnMount.js │ │ ├── LazyReactInVue.vue │ │ ├── 4-reactUnMount-test.js │ │ ├── 2-scopedSlot-vnode.vue │ │ ├── Basic.vue │ │ └── 1-test.js │ ├── applyVueInReact │ │ ├── VueComponent.vue │ │ ├── 3-globalVueComponent.vue │ │ ├── 2-getVNode.vue │ │ ├── AA.vue │ │ ├── Basic.js │ │ ├── 3-globalVueComponent-test.js │ │ ├── 1-test.js │ │ └── 2-getVNode-test.js │ ├── vueInReactSlot │ │ └── 1-test.js │ ├── getVNodeAndRenderVNode │ │ └── 1-test.js │ ├── reactInVueGetRef │ │ └── 1-test.js │ ├── reactInVueSlot │ │ └── 1-test.js │ ├── reactMissVue │ │ └── 1-test.js │ ├── crossingProviderInReact │ │ └── 1-test.js │ ├── babel │ │ └── 1-test.js │ ├── useInjectPropsFromWrapperInReact │ │ └── 1-test.js │ ├── crossingProviderInVue │ │ └── 1-test.js │ ├── vModelInReact │ │ └── 1-test.js │ ├── pureReactInVue │ │ └── 1-test.js │ ├── utils │ │ └── 1-test.js │ └── vitePlugin │ │ └── 1-test.js ├── anyEmpty.js ├── setup.js └── package.json ├── .github └── FUNDING.yml ├── dev-project-react ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── App.js │ ├── router │ │ ├── index.js │ │ └── config.js │ ├── setupTests.js │ ├── App.test.js │ ├── components │ │ ├── Context │ │ │ ├── Basic.vue │ │ │ └── index.js │ │ ├── LazyVueInReact │ │ │ ├── Basic.vue │ │ │ └── index.js │ │ ├── pureVueInReact │ │ │ ├── style.css │ │ │ ├── AA.vue │ │ │ └── index.js │ │ ├── Basic │ │ │ ├── AA.vue │ │ │ ├── Basic.vue │ │ │ └── index.js │ │ ├── useInjectPropsFromWrapper │ │ │ ├── index.js │ │ │ ├── Basic.vue │ │ │ └── ReactBasic.js │ │ ├── Events │ │ │ ├── Basic.vue │ │ │ └── index.js │ │ ├── getVNodeAndRenderVNode │ │ │ ├── AA.vue │ │ │ └── index.js │ │ ├── Slots │ │ │ ├── Basic.vue │ │ │ └── index.js │ │ ├── CrossProvider │ │ │ ├── reactRouterCrossingProvider.js │ │ │ ├── Basic.vue │ │ │ ├── reactRouterCrossingProviderPure.js │ │ │ ├── BasicPure.vue │ │ │ └── index.js │ │ ├── VModel │ │ │ ├── Basic1.vue │ │ │ ├── Basic.vue │ │ │ ├── Basic2.vue │ │ │ └── index.js │ │ ├── Introduce.js │ │ └── reactMissVue │ │ │ ├── index.js │ │ │ └── defineReactMissVue.js │ ├── index.css │ ├── reportWebVitals.js │ ├── index.js │ ├── App.css │ └── logo.svg ├── config │ ├── webpack │ │ └── persistentCache │ │ │ └── createEnvironmentHash.js │ ├── jest │ │ ├── cssTransform.js │ │ ├── babelTransform.js │ │ └── fileTransform.js │ ├── getHttpsConfig.js │ └── paths.js ├── .gitignore ├── babel.config.js ├── scripts │ └── test.js └── README.md ├── vite ├── index.js ├── esm │ └── index.mjs └── cjs │ └── index.cjs ├── webpack ├── VeauryVuePlugin.js ├── VeauryVuePlugin.cjs └── VeauryVuePlugin.mjs ├── src ├── pureReactInVue │ ├── isTextChild.js │ ├── injectSyncUpdateForPureReactInVue.js │ ├── addScopeId.js │ ├── transformer.js │ ├── getReactNode.js │ ├── RenderReactNode.js │ ├── interceptProps.js │ ├── index.js │ ├── getChildInfo.js │ ├── takeVueDomInReact.js │ ├── resolveRef.js │ └── getDistinguishReactOrVue.js ├── utils │ ├── toCamelCase.js │ ├── getRandomId.js │ ├── setChildKey.js │ ├── couldBeClass.js │ ├── styleClassTransformer.js │ └── parseVModel.js ├── pureVueInReact │ ├── transformer.js │ ├── resolveRef.js │ ├── getVNode.js │ ├── index.js │ ├── interceptProps.js │ ├── getChildInfo.js │ ├── takeReactDomInVue.js │ └── getDistinguishReactOrVue.js ├── lazyVueInReact.js ├── lazyPureVueInReact.js ├── injectPropsFromWrapper.js ├── lookupReactWrapperRef.js ├── lazyReactInVue.js ├── lazyPureReactInVue.js ├── lookupVueWrapperRef.js ├── createCrossingProviderForReactInVue.js ├── createCrossingProviderForPureReactInVue.js ├── createCrossingProviderForVueInReact.js ├── createCrossingProviderForPureVueInReact.js ├── createReactMissVue.js ├── reactAllHandles.js ├── index.js ├── overrideDom.js └── options.js ├── .yarnrc ├── .gitignore ├── babel.config.js ├── babel └── ReactPreset.js ├── LICENSE ├── rollup.config.js ├── pure_mode.md └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | *.* 2 | -------------------------------------------------------------------------------- /dev-project-vue3/.eslintignore: -------------------------------------------------------------------------------- 1 | *.* 2 | -------------------------------------------------------------------------------- /tests/mock/empty.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [devilwjp] 4 | -------------------------------------------------------------------------------- /dev-project-react/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/watchref/VueBox.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vite/index.js: -------------------------------------------------------------------------------- 1 | const veauryVitePlugins = require('./cjs/index.cjs') 2 | 3 | module.exports = veauryVitePlugins 4 | -------------------------------------------------------------------------------- /dev-project-vue3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriasoft/veaury/HEAD/dev-project-vue3/public/favicon.ico -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/ReactComponent.js: -------------------------------------------------------------------------------- 1 | export default function (props) { 2 | return props.children 3 | } 4 | -------------------------------------------------------------------------------- /dev-project-react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriasoft/veaury/HEAD/dev-project-react/public/favicon.ico -------------------------------------------------------------------------------- /dev-project-react/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriasoft/veaury/HEAD/dev-project-react/public/logo192.png -------------------------------------------------------------------------------- /dev-project-react/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriasoft/veaury/HEAD/dev-project-react/public/logo512.png -------------------------------------------------------------------------------- /dev-project-vue3/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriasoft/veaury/HEAD/dev-project-vue3/src/assets/logo.png -------------------------------------------------------------------------------- /webpack/VeauryVuePlugin.js: -------------------------------------------------------------------------------- 1 | const VeauryVuePlugin = require('./VeauryVuePlugin.cjs') 2 | 3 | module.exports = VeauryVuePlugin; 4 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/VueComponent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/pureReactInVue/isTextChild.js: -------------------------------------------------------------------------------- 1 | import { Text } from 'vue' 2 | 3 | export function isTextOwner(child) { 4 | return child.type === Text 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/AA.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function () { 4 | return
React in Vue
5 | } 6 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | yarn-path ".yarn/releases/yarn-1.22.22.cjs" 6 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/vueMissReact/react_app/Test1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function () { 4 | return
1212
5 | } 6 | -------------------------------------------------------------------------------- /src/utils/toCamelCase.js: -------------------------------------------------------------------------------- 1 | export default function toCamelCase(val) { 2 | const reg = /-(\w)/g 3 | return val.replace(reg, ($, $1) => $1.toUpperCase()) 4 | } 5 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/react_app/CC.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function (props) { 4 | return props.children?.('DDDDD') 5 | } 6 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/FunctionalChildren.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function (props) { 4 | return props.children('DDDDD') 5 | } 6 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/slots/react_app/ReactCom.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function (props) { 4 | return
{props.children}
5 | } 6 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/watchref/react_app/Box.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Box(props) { 4 | return
5 | {props.children} 6 |
7 | } 8 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/3-globalVueComponent.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/pureReactInVue/react_app/Box.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Box(props) { 4 | return
5 | {props.children} 6 |
7 | } 8 | -------------------------------------------------------------------------------- /dev-project-vue3/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | 10 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/2-getVNode.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /dev-project-vue3/src/components/A1.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /dev-project-react/src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import Router from './router' 3 | import React from 'react' 4 | function App() { 5 | return (
); 6 | } 7 | 8 | export default App; 9 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/AA.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/Basic.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { applyVueInReact } from 'veaury' 3 | import AA from './AA' 4 | const AAReact = applyVueInReact(AA) 5 | 6 | export default function () { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/pureReactInVue/react_app/BB.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function BB(props) { 4 | return <> 5 | {props.children} 6 | {props.renderProps?.()} 7 | {props.reactNode} 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/pureVueInReact/transformer.js: -------------------------------------------------------------------------------- 1 | import applyVueInReact from '../applyVueInReact' 2 | 3 | export default function transformer (ReactComponent, {globalName, combinedOption, transparentApi} = {}) { 4 | return applyVueInReact(ReactComponent, combinedOption || {}) 5 | } 6 | -------------------------------------------------------------------------------- /tests/anyEmpty.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | process() { 5 | return { 6 | code: 'module.exports = {};' 7 | } 8 | }, 9 | getCacheKey() { 10 | // The output is always the same. 11 | return 'anyEmpty'; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /dev-project-react/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { HashRouter, useRoutes } from "react-router-dom"; 2 | import config from './config' 3 | const Routes = () => { 4 | return useRoutes(config); 5 | }; 6 | export default function() { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /dev-project-react/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /dev-project-react/config/webpack/persistentCache/createEnvironmentHash.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { createHash } = require('crypto'); 3 | 4 | module.exports = env => { 5 | const hash = createHash('md5'); 6 | hash.update(JSON.stringify(env)); 7 | 8 | return hash.digest('hex'); 9 | }; 10 | -------------------------------------------------------------------------------- /dev-project-react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Context/Basic.vue: -------------------------------------------------------------------------------- 1 | 7 | 12 | 15 | -------------------------------------------------------------------------------- /src/lazyVueInReact.js: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | import applyVueInReact from './applyVueInReact' 3 | export default function lazyVueInReact (asyncImport, useVueOptions) { 4 | return lazy(() => asyncImport().then((mod) => { 5 | return { default: applyVueInReact(mod.default, useVueOptions) } 6 | })) 7 | } 8 | -------------------------------------------------------------------------------- /src/lazyPureVueInReact.js: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react' 2 | import applyPureVueInReact from './pureVueInReact' 3 | export default function lazyVueInReact (asyncImport, useVueOptions) { 4 | return lazy(() => asyncImport().then((mod) => { 5 | return { default: applyPureVueInReact(mod.default, useVueOptions) } 6 | })) 7 | } 8 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/react_app/BB.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class BB extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | test() { 8 | console.log(11111) 9 | } 10 | render() { 11 | return
React Component BB
12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dev-project-react/src/components/LazyVueInReact/Basic.vue: -------------------------------------------------------------------------------- 1 | 8 | 13 | 16 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/routerView/Sub.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/pureVueInReact/resolveRef.js: -------------------------------------------------------------------------------- 1 | export default function resolveRef(child) { 2 | 3 | let ref = child.ref 4 | if (ref) { 5 | if (typeof ref === 'object') { 6 | ref = (r) => { child.ref.current = r } 7 | return ref 8 | } 9 | if (typeof ref === 'function') { 10 | return ref 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/pureReactInVue/injectSyncUpdateForPureReactInVue.js: -------------------------------------------------------------------------------- 1 | export default function injectSyncUpdateForPureReactInVue(ReactComponent, syncUpdateHooks) { 2 | if (!ReactComponent.__syncUpdateForPureReactInVue) { 3 | ReactComponent.__syncUpdateForPureReactInVue = {} 4 | } 5 | Object.assign(ReactComponent.__syncUpdateForPureReactInVue, syncUpdateHooks) 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/getRandomId.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | pool = new Set() 3 | getRandomId (prefix) { 4 | const id = prefix + (Math.random() + '').substr(2) 5 | // Recreate random numbers if duplicate data is generated 6 | if (this.pool.has(id)) { 7 | return this.getRandomId(prefix) 8 | } 9 | this.pool.add(id) 10 | return id 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/setChildKey.js: -------------------------------------------------------------------------------- 1 | export default function setChildKey(child, children, topIndex) { 2 | if (child instanceof Array && child.length === 1) { 3 | child = child[0] 4 | } 5 | if (!(child instanceof Array) && child.key == null && children.length > 1) { 6 | child = {...child} 7 | child.key = `_key_${topIndex}` 8 | } 9 | return child 10 | } 11 | -------------------------------------------------------------------------------- /dev-project-react/src/components/pureVueInReact/style.css: -------------------------------------------------------------------------------- 1 | .slot { 2 | background: aquamarine; 3 | padding: 10px; 4 | margin: 10px; 5 | } 6 | .flex-sub { 7 | width: 50px; 8 | height: 50px; 9 | background: seagreen; 10 | line-height: 50px; 11 | margin: 5px; 12 | } 13 | .flex-sub-in-bb { 14 | background: seagreen; 15 | color: white; 16 | } 17 | -------------------------------------------------------------------------------- /dev-project-vue3/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | yarn.lock 5 | package-lock.json 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/pureReactInVue/react_app/CC.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function CC(props) { 4 | console.log('AAAAAA', props) 5 | return <> 6 | {props.renderProps1?.()} 7 | {props.renderProps2?.(
ReactNode
)} 8 | {props.reactNode} 9 | {props.children} 10 | {props.stringNode} 11 | {props.objectNode} 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/3-getReactNode-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/vue'; 3 | import VueComponent from './3-getReactNode'; 4 | 5 | test('test getReactNode', async () => { 6 | render(VueComponent); 7 | const linkElement = await screen.findByText(/test getReactNode/); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | yarn.lock 5 | package-lock.json 6 | /coverage 7 | .coveralls.yml 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /dev-project-react/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Basic/AA.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Basic/Basic.vue: -------------------------------------------------------------------------------- 1 | 8 | 16 | 19 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/react_app/AA.js: -------------------------------------------------------------------------------- 1 | import React, {useImperativeHandle, forwardRef} from 'react' 2 | export default forwardRef(function (props, ref) { 3 | useImperativeHandle(ref, () => { 4 | return { 5 | aaa:1, 6 | ddd: null 7 | } 8 | }) 9 | return
The React component will disappear in {props.disappearTime}s
10 | }) 11 | -------------------------------------------------------------------------------- /dev-project-react/src/components/useInjectPropsFromWrapper/index.js: -------------------------------------------------------------------------------- 1 | import { applyVueInReact } from 'veaury' 2 | import BasicReact from './ReactBasic' 3 | export default function () { 4 | 5 | return
6 |

This example shows the basic usage of `useInjectPropsFromWrapper`.

7 |

Get the state of the current React app's react-router in a Vue component.

8 | 9 |
10 | } 11 | -------------------------------------------------------------------------------- /src/pureReactInVue/addScopeId.js: -------------------------------------------------------------------------------- 1 | function addScopeId(child, hashList) { 2 | if (!hashList || (hashList instanceof Array && hashList.length === 0)) return child 3 | if (typeof hashList === 'string') hashList = [hashList] 4 | child = {...child} 5 | child.props = {...child.props} 6 | hashList.forEach((val) => { 7 | child.props[val] = '' 8 | }) 9 | return child 10 | } 11 | 12 | export default addScopeId 13 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/slots/Custom1.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /dev-project-react/src/components/pureVueInReact/AA.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 21 | -------------------------------------------------------------------------------- /dev-project-react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/pureReactInVue/react_app/AA.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const containerStyle = { 4 | background: '#91e7fc', 5 | width: 800, 6 | margin: 'auto', 7 | padding: 10, 8 | display: 'flex', 9 | justifyContent: 'space-around' 10 | } 11 | export default function AA(props) { 12 | return
13 | {props.children} 14 | {props.numberNode} 15 |
16 | } 17 | -------------------------------------------------------------------------------- /dev-project-react/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | import * as Vue from 'vue' 2 | import * as VueCompilerDOM from '@vue/compiler-dom' 3 | import React from 'react' 4 | global.Vue = Vue 5 | global.VueCompilerDOM = VueCompilerDOM 6 | global.React = React 7 | 8 | if (!process.env.TEST_TEMP) { 9 | // drop console.log and console.warn 10 | jest.spyOn(console, 'warn').mockImplementation(() => {}); 11 | jest.spyOn(console, 'log').mockImplementation(() => {}); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /dev-project-vue3/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /dev-project-vue3/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | 6 | // react-dom >= 19 7 | import { createRoot } from 'react-dom/client' 8 | import { setVeauryOptions } from 'veaury' 9 | setVeauryOptions({ 10 | react: { 11 | createRoot 12 | } 13 | }) 14 | 15 | const app = createApp(App) 16 | app.use(router) 17 | app.use(store) 18 | app.mount('#app') 19 | -------------------------------------------------------------------------------- /dev-project-vue3/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state () { 5 | return { 6 | count: 0 7 | } 8 | }, 9 | mutations: { 10 | increment (state) { 11 | state.count++ 12 | } 13 | }, 14 | actions: { 15 | increment (context) { 16 | context.commit('increment') 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /tests/cases/vueInReactSlot/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/slots') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | expect(await findByText(/To React Component Node/)).toBeInTheDocument() 9 | }) 10 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/context/react_app/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | export default function(props) { 4 | const style = useRef({ 5 | background: '#917fc', 6 | width: 500, 7 | margin: 'auto', 8 | padding: 10 9 | }) 10 | return ( 11 |
12 | This is the React Component 13 | {props.children} 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /dev-project-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | /dist 3 | yarn.lock 4 | package-lock.json 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /tests/cases/getVNodeAndRenderVNode/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/getVNodeAndRenderVNode') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | expect(await findByTestId('VueContainerTest')).toHaveTextContent('This is a VNode of Foo!') 9 | }) 10 | -------------------------------------------------------------------------------- /dev-project-react/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | function resolve (dir) { 3 | return path.join(__dirname, dir) 4 | } 5 | 6 | module.exports = { 7 | // for dev only 8 | overrides: [ 9 | { 10 | test:function(filename) { 11 | if (filename?.startsWith(resolve('../src'))) return filename 12 | }, 13 | presets: [ 14 | 'babel-preset-react-app' 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/react_app/UnMount.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | 3 | 4 | const myReactComponent = class A extends React.Component { 5 | componentWillUnmount() { 6 | console.log(1111, document.querySelector(".aaa")) 7 | console.log(2222, document.querySelector(".bbb")) 8 | } 9 | render() { 10 | return (
11 |
unMountTest
12 |
) 13 | } 14 | } 15 | 16 | export default myReactComponent -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/lazyReactInVue/react_app/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | export default function(props) { 4 | const style = useRef({ 5 | background: '#91e7fc', 6 | width: 300, 7 | margin: 'auto', 8 | padding: 10 9 | }) 10 | return (
11 | This is the React Component 12 |

13 | received foo's value: {props.foo} 14 |

15 | {props.children} 16 |
) 17 | } 18 | -------------------------------------------------------------------------------- /src/injectPropsFromWrapper.js: -------------------------------------------------------------------------------- 1 | export default function(injectionHook, Component) { 2 | console.warn(`[veaury warn]: HOC injectPropsFromWrapper is deprecated! Try using 'useInjectPropsFromWrapper' in the options of 'applyReactInVue' or 'applyVueInReact'!`) 3 | if (typeof injectionHook !== 'function') { 4 | console.warn(`[veaury warn]: parameter 'injectionHook' is not a function`) 5 | return Component 6 | } 7 | Component.__veauryInjectPropsFromWrapper__ = injectionHook 8 | return Component 9 | } 10 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/react_app/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | export default function(props) { 4 | const style = useRef({ 5 | background: '#91e7fc', 6 | width: 500, 7 | margin: 'auto', 8 | padding: 10 9 | }) 10 | return (
11 | This is the React Component 12 |

13 | received foo's value: {props.foo} 14 |

15 | {props.children} 16 |
) 17 | } 18 | -------------------------------------------------------------------------------- /src/lookupReactWrapperRef.js: -------------------------------------------------------------------------------- 1 | // let parentInstance = this.$parent 2 | // // Look up the React encapsulation layer 3 | // while (parentInstance) { 4 | // if (parentInstance.parentReactWrapperRef) { 5 | // reactWrapperRef = parentInstance.parentReactWrapperRef 6 | // break 7 | // } 8 | // if (parentInstance.reactWrapperRef) { 9 | // reactWrapperRef = parentInstance.reactWrapperRef 10 | // break 11 | // } 12 | // parentInstance = parentInstance.$parent 13 | // } 14 | -------------------------------------------------------------------------------- /src/pureReactInVue/transformer.js: -------------------------------------------------------------------------------- 1 | import applyReactInVue from '../applyReactInVue' 2 | 3 | export default function transformer (ReactComponent, {globalName, combinedOption, transparentApi} = {}) { 4 | const Component = applyReactInVue(ReactComponent, combinedOption || {}) 5 | Component.install = function(app, {globalName: newGlobalName} = {}) { 6 | if (globalName) { 7 | app.component(newGlobalName || globalName, Component); 8 | } 9 | return Component 10 | } 11 | return Component; 12 | } 13 | -------------------------------------------------------------------------------- /src/lazyReactInVue.js: -------------------------------------------------------------------------------- 1 | import applyReactInVue from './applyReactInVue' 2 | import { defineAsyncComponent } from 'vue' 3 | export default function lazyReactInVue (asyncImport, useReactOptions) { 4 | let loader = asyncImport 5 | if (typeof asyncImport === 'object') { 6 | loader = asyncImport.loader 7 | } 8 | const resolveLoader = () => loader().then((mod) => { 9 | return applyReactInVue(mod.default, useReactOptions) 10 | }) 11 | return defineAsyncComponent(typeof asyncImport === 'object' ? {...asyncImport, loader: resolveLoader}: resolveLoader) 12 | } 13 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/3-getReactNode.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 19 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Events/Basic.vue: -------------------------------------------------------------------------------- 1 | 10 | 18 | 21 | -------------------------------------------------------------------------------- /src/lazyPureReactInVue.js: -------------------------------------------------------------------------------- 1 | import applyPureReactInVue from './pureReactInVue' 2 | import { defineAsyncComponent } from 'vue' 3 | export default function lazyReactInVue (asyncImport, useReactOptions) { 4 | let loader = asyncImport 5 | if (typeof asyncImport === 'object') { 6 | loader = asyncImport.loader 7 | } 8 | const resolveLoader = () => loader().then((mod) => { 9 | return applyPureReactInVue(mod.default, useReactOptions) 10 | }) 11 | return defineAsyncComponent(typeof asyncImport === 'object' ? {...asyncImport, loader: resolveLoader}: resolveLoader) 12 | } 13 | -------------------------------------------------------------------------------- /src/pureReactInVue/getReactNode.js: -------------------------------------------------------------------------------- 1 | import {VueContainer} from '../applyVueInReact' 2 | import React from 'react' 3 | import getDistinguishReactOrVue from "./getDistinguishReactOrVue"; 4 | 5 | const NoWrapFunction = getDistinguishReactOrVue({reactComponents: 'all', domTags: 'all'}) 6 | 7 | function getReactNode(vnode) { 8 | if (typeof vnode === 'function') { 9 | vnode = vnode() 10 | } 11 | vnode = [vnode] 12 | vnode = vnode.flat(Infinity) 13 | 14 | return NoWrapFunction(vnode, (vnode) => ) 15 | } 16 | 17 | export default getReactNode 18 | -------------------------------------------------------------------------------- /src/lookupVueWrapperRef.js: -------------------------------------------------------------------------------- 1 | export default function (reactInstance, fiberNode) { 2 | fiberNode = reactInstance?._reactInternals || reactInstance?._reactInternalFiber || fiberNode 3 | let parentInstance = fiberNode?.return 4 | let vueWrapperRef 5 | // Look up the vueWrapperRef 6 | while (parentInstance) { 7 | const parentFiberNode = parentInstance.stateNode 8 | vueWrapperRef = parentFiberNode?.parentVueWrapperRef || parentFiberNode?.__veauryVueWrapperRef__ 9 | if (vueWrapperRef) return vueWrapperRef 10 | parentInstance = parentInstance.return 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /dev-project-react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /dev-project-react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = createRoot(document.getElementById('root')); 8 | root.render( 9 | // 10 | 11 | // 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/2-scopedSlots-vnode-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import React from 'react'; 3 | import { render, screen } from '@testing-library/vue'; 4 | import Event from "./2-scopedSlot-vnode" 5 | 6 | test('test scoped slots', async () => { 7 | render(Event); 8 | const linkElement = await screen.findByText(/vnode-/); 9 | expect(linkElement).toBeInTheDocument() 10 | const linkElement1 = await screen.findByText(/scopedSlotA-attr1/); 11 | expect(linkElement1).toBeInTheDocument() 12 | const linkElement2 = await screen.findByText(/slotA/); 13 | expect(linkElement2).toBeInTheDocument() 14 | }); 15 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/context/Custom1.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/routerView/react_app/Basic.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { VueContainer } from 'veaury' 3 | 4 | export default function(props) { 5 | const style = useRef({ 6 | background: '#91e7fc', 7 | width: 500, 8 | margin: 'auto', 9 | padding: 10 10 | }) 11 | return (
12 | This is the React Component 13 |

14 | use the 'router-view' of 'vue-router' 15 |

16 | {/* Similar to the global component 'router-view' in Vue */} 17 | 18 |
) 19 | } 20 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/useInjectPropsFromWrapper/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 30 | -------------------------------------------------------------------------------- /dev-project-react/config/jest/babelTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const babelJest = require('babel-jest').default; 4 | 5 | const hasJsxRuntime = (() => { 6 | if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { 7 | return false; 8 | } 9 | 10 | try { 11 | require.resolve('react/jsx-runtime'); 12 | return true; 13 | } catch (e) { 14 | return false; 15 | } 16 | })(); 17 | 18 | module.exports = babelJest.createTransformer({ 19 | presets: [ 20 | [ 21 | require.resolve('babel-preset-react-app'), 22 | { 23 | runtime: hasJsxRuntime ? 'automatic' : 'classic', 24 | }, 25 | ], 26 | ], 27 | babelrc: false, 28 | configFile: false, 29 | }); 30 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/events/react_app/Button.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | 3 | export default function(props) { 4 | const style = useRef({ 5 | background: '#91e7fc', 6 | width: 500, 7 | margin: 'auto', 8 | padding: 10, 9 | lineHeight: '30px' 10 | }) 11 | return (
12 | This is the React Component 13 |

14 | received foo's value: {props.foo} 15 |

16 | Click the button can change the value of the prop 'foo'
17 | 18 | {props.children} 19 |
) 20 | } 21 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/routerView/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /dev-project-vue3/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/crossProvider/vueRouterAndVuexCrossingProvider.js: -------------------------------------------------------------------------------- 1 | // Create a Provider that can get hooks of vue 2 | // This Provider will be exported as a vue component, 3 | // and the react components in this Provider can get the status of vue hooks 4 | 5 | import {useStore} from 'vuex' 6 | import {useRouter, useRoute} from 'vue-router' 7 | import {createCrossingProviderForReactInVue} from 'veaury' 8 | 9 | const [useVueHooksInReact, ProviderInVue] = createCrossingProviderForReactInVue(function() { 10 | return { 11 | vuex: useStore(), 12 | vueRoute: useRoute(), 13 | vueRouter: useRouter() 14 | } 15 | }) 16 | 17 | export { 18 | useVueHooksInReact, 19 | ProviderInVue 20 | } 21 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/crossProvider/vueRouterAndVuexCrossingProviderPure.js: -------------------------------------------------------------------------------- 1 | // Create a Provider that can get hooks of vue 2 | // This Provider will be exported as a vue component, 3 | // and the react components in this Provider can get the status of vue hooks 4 | 5 | import {useStore} from 'vuex' 6 | import {useRouter, useRoute} from 'vue-router' 7 | import {createCrossingProviderForPureReactInVue} from 'veaury' 8 | 9 | const [useVueHooksInReact, ProviderInVuePure] = createCrossingProviderForPureReactInVue(function() { 10 | return { 11 | vuex: useStore(), 12 | vueRoute: useRoute(), 13 | vueRouter: useRouter() 14 | } 15 | }) 16 | 17 | export { 18 | useVueHooksInReact, 19 | ProviderInVuePure 20 | } 21 | -------------------------------------------------------------------------------- /dev-project-react/src/components/getVNodeAndRenderVNode/AA.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/pureVueInReact/getVNode.js: -------------------------------------------------------------------------------- 1 | import {h} from 'vue'; 2 | import RenderReactNode from '../pureReactInVue/RenderReactNode'; 3 | import getDistinguishReactOrVue from "./getDistinguishReactOrVue"; 4 | 5 | const NoWrapFunction = getDistinguishReactOrVue({reactComponents: 'all', domTags: 'all'}) 6 | 7 | function getVNode(reactNode) { 8 | if (typeof reactNode === 'function') { 9 | reactNode = reactNode() 10 | } 11 | 12 | reactNode = [reactNode] 13 | reactNode = reactNode.flat(Infinity) 14 | if (reactNode.length === 1) { 15 | reactNode = reactNode[0] 16 | } 17 | 18 | return NoWrapFunction(reactNode, (reactNode) => h(RenderReactNode, {node: reactNode})) 19 | } 20 | 21 | export default getVNode 22 | -------------------------------------------------------------------------------- /src/createCrossingProviderForReactInVue.js: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { createContext, useContext } from "react" 3 | import applyReactInVue from "./applyReactInVue" 4 | 5 | export default function createCrossingProviderForReactInVue(vueInjection) { 6 | const reactContext = createContext({}) 7 | const ProviderInVue = applyReactInVue(function ({children, ...props}) { 8 | return 11 | {children} 12 | 13 | }, { 14 | useInjectPropsFromWrapper: vueInjection 15 | }) 16 | function useVueHooksInReact() { 17 | return useContext(reactContext) 18 | } 19 | return [useVueHooksInReact, ProviderInVue, reactContext] 20 | } 21 | -------------------------------------------------------------------------------- /src/createCrossingProviderForPureReactInVue.js: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { createContext, useContext } from "react" 3 | import applyPureReactInVue from "./pureReactInVue" 4 | 5 | export default function createCrossingProviderForReactInVue(vueInjection) { 6 | const reactContext = createContext({}) 7 | const ProviderInVue = applyPureReactInVue(function ({children, ...props}) { 8 | return 11 | {children} 12 | 13 | }, { 14 | useInjectPropsFromWrapper: vueInjection 15 | }) 16 | function useVueHooksInReact() { 17 | return useContext(reactContext) 18 | } 19 | return [useVueHooksInReact, ProviderInVue, reactContext] 20 | } 21 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/4-reactUnMount.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /src/createCrossingProviderForVueInReact.js: -------------------------------------------------------------------------------- 1 | import { provide, inject, h } from "vue" 2 | import applyVueInReact from "./applyVueInReact" 3 | import Random from "./utils/getRandomId" 4 | 5 | const random = new Random() 6 | export default function createCrossingProviderForVueInReact(reactInjection, providerName) { 7 | providerName = providerName || random.getRandomId('veauryCrossingProvide_') 8 | const ProviderInReact = applyVueInReact({ 9 | setup(props, context) { 10 | provide(providerName, context.attrs) 11 | return () => h(context.slots.default) 12 | } 13 | }, { 14 | useInjectPropsFromWrapper: reactInjection 15 | }) 16 | function useReactHooksInVue() { 17 | return inject(providerName) 18 | } 19 | return [useReactHooksInVue, ProviderInReact] 20 | } 21 | -------------------------------------------------------------------------------- /src/createCrossingProviderForPureVueInReact.js: -------------------------------------------------------------------------------- 1 | import { provide, inject, h } from "vue" 2 | import applyPureVueInReact from "./pureVueInReact" 3 | import Random from "./utils/getRandomId" 4 | 5 | const random = new Random() 6 | export default function createCrossingProviderForVueInReact(reactInjection, providerName) { 7 | providerName = providerName || random.getRandomId('veauryCrossingProvide_') 8 | const ProviderInReact = applyPureVueInReact({ 9 | setup(props, context) { 10 | provide(providerName, context.attrs) 11 | return () => h(context.slots.default) 12 | } 13 | }, { 14 | useInjectPropsFromWrapper: reactInjection 15 | }) 16 | function useReactHooksInVue() { 17 | return inject(providerName) 18 | } 19 | return [useReactHooksInVue, ProviderInReact] 20 | } 21 | -------------------------------------------------------------------------------- /src/pureVueInReact/index.js: -------------------------------------------------------------------------------- 1 | // This HOC is used to solve the problem that when React components are used in Vue components, 2 | // passing children is encapsulated by a div with `style="all:unset"`. 3 | // This HOC will convert the VNode passed to the React component proportionally to the ReactNode. 4 | 5 | import transformer from "./transformer"; 6 | import getDistinguishReactOrVue from "./getDistinguishReactOrVue"; 7 | 8 | const NoWrapFunction = getDistinguishReactOrVue({vueComponents: 'all', domTags: 'all'}) 9 | export default function applyPureVueInReact(ReactComponent, combinedOption) { 10 | return transformer(ReactComponent, { 11 | combinedOption: { 12 | pureTransformer: true, 13 | // Custom slot handler 14 | defaultSlotsFormatter: NoWrapFunction, 15 | ...combinedOption 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /dev-project-vue3/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); 3 | 4 | module.exports = { 5 | publicPath: './', 6 | devServer: { 7 | overlay: { 8 | warnings: false, 9 | errors: false 10 | } 11 | }, 12 | transpileDependencies:['tailwind-merge'], 13 | configureWebpack: { 14 | resolve: { 15 | alias: { 16 | ...(process.env.BUILD_TYPE === 'remote'? {}: { 17 | veaury: path.resolve(__dirname, '../src') 18 | }), 19 | src: path.resolve(__dirname, './src'), 20 | react_app: path.resolve(__dirname, './src/react_app'), 21 | } 22 | }, 23 | plugins: [ 24 | new ReactRefreshWebpackPlugin({ 25 | overlay: false, 26 | }) 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Slots/Basic.vue: -------------------------------------------------------------------------------- 1 | 15 | 27 | 30 | -------------------------------------------------------------------------------- /src/createReactMissVue.js: -------------------------------------------------------------------------------- 1 | // Sometimes some features and plugins of Vue are really more useful than React. 2 | // Such as beforeEach of vue-router, and pinia. 3 | // So I implemented a factory function called createReactMissVue that returns a React Provider Component and a React hooks. 4 | // With ReactMissVue, you can use Vue's plugins directly in React applications. 5 | // Enjoy it! 6 | 7 | import createCrossingProviderForReactInVue from './createCrossingProviderForReactInVue' 8 | import applyVueInReact from './applyVueInReact' 9 | export default function createReactMissVue({useVueInjection, beforeVueAppMount}) { 10 | let [useReactMissVue, ReactMissVue, ReactMissVueContext] = createCrossingProviderForReactInVue(useVueInjection) 11 | ReactMissVue = applyVueInReact(ReactMissVue, { beforeVueAppMount }) 12 | 13 | return [useReactMissVue, ReactMissVue, ReactMissVueContext] 14 | } 15 | -------------------------------------------------------------------------------- /dev-project-react/src/components/CrossProvider/reactRouterCrossingProvider.js: -------------------------------------------------------------------------------- 1 | // Create a Provider that can get react hooks 2 | // This Provider will be exported as a react component, 3 | // and all of the vue components in this Provider can get the status of react hooks 4 | 5 | import { useLocation, useNavigate } from 'react-router-dom' 6 | import { createCrossingProviderForVueInReact } from 'veaury' 7 | 8 | // Execute 'useReactRouterForVue' in the setup function of the vue component to get the object returned by the incoming function 9 | const [useReactRouterForVue, ReactRouterProviderForVue] = createCrossingProviderForVueInReact( 10 | // This incoming function can execute react hooks 11 | function() { 12 | return { 13 | location: useLocation(), 14 | navigate: useNavigate() 15 | } 16 | } 17 | ) 18 | 19 | export { 20 | useReactRouterForVue, 21 | ReactRouterProviderForVue 22 | } 23 | -------------------------------------------------------------------------------- /dev-project-react/src/components/CrossProvider/Basic.vue: -------------------------------------------------------------------------------- 1 | 8 | 26 | 29 | -------------------------------------------------------------------------------- /dev-project-react/src/components/VModel/Basic1.vue: -------------------------------------------------------------------------------- 1 | 9 | 25 | 28 | -------------------------------------------------------------------------------- /dev-project-react/src/components/CrossProvider/reactRouterCrossingProviderPure.js: -------------------------------------------------------------------------------- 1 | // Create a Provider that can get react hooks 2 | // This Provider will be exported as a react component, 3 | // and all of the vue components in this Provider can get the status of react hooks 4 | 5 | import { useLocation, useNavigate } from 'react-router-dom' 6 | import { createCrossingProviderForPureVueInReact } from 'veaury' 7 | 8 | // Execute 'useReactRouterForVue' in the setup function of the vue component to get the object returned by the incoming function 9 | const [useReactRouterForVue, ReactRouterProviderForVuePure] = createCrossingProviderForPureVueInReact( 10 | // This incoming function can execute react hooks 11 | function() { 12 | return { 13 | location: useLocation(), 14 | navigate: useNavigate() 15 | } 16 | } 17 | ) 18 | 19 | export { 20 | useReactRouterForVue, 21 | ReactRouterProviderForVuePure 22 | } 23 | -------------------------------------------------------------------------------- /dev-project-react/src/components/CrossProvider/BasicPure.vue: -------------------------------------------------------------------------------- 1 | 8 | 26 | 29 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/3-globalVueComponent-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/react'; 3 | import React from 'react'; 4 | import VueComponent from './3-globalVueComponent' 5 | import {h} from 'vue' 6 | import {applyVueInReact, getVNode, VueContainer} from 'veaury' 7 | 8 | function VueFC(props, context) { 9 | return h('div', context.attrs, context.slots) 10 | } 11 | const ReactComponent = applyVueInReact(VueFC, {beforeVueAppMount(app) { 12 | app.component('GlobalVueComponent', VueComponent) 13 | }}) 14 | const ReactNode =
test getVNode
15 | test('test global vue component', async () => { 16 | render( 17 | 18 | ); 19 | const linkElement = await screen.findByText(/test global vue component/); 20 | expect(linkElement).toBeInTheDocument(); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "veaury-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": "true", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@testing-library/jest-dom": "^5.16.5", 14 | "@testing-library/react": "^16.1.0", 15 | "@testing-library/vue": "^6.6.1", 16 | "@vitejs/plugin-react": "^2.1.0", 17 | "@vitejs/plugin-vue": "^3.1.0", 18 | "@vitejs/plugin-vue-jsx": "^2.0.1", 19 | "babel-jest": "^29.0.1", 20 | "jest-environment-jsdom": "^29.0.1", 21 | "pinia": "^2.0.21", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-router-dom": "^6.3.0", 25 | "veaury": "^2.6.3-beta.0", 26 | "vite": "^3.1.0", 27 | "vue": "^3.2.38", 28 | "vue-router": "^4.1.5", 29 | "vuex": "^4.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/slots/react_app/Slots.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react' 2 | 3 | export default function(props) { 4 | const style = useRef({ 5 | background: '#91e7fc', 6 | width: 500, 7 | margin: 'auto', 8 | padding: 10, 9 | lineHeight: '30px' 10 | }) 11 | const [foo, setFoo] = useState(Math.random()) 12 | const timer = useRef(null) 13 | useEffect(() => { 14 | timer.current = setInterval(() => { 15 | setFoo(Math.random()) 16 | }, 1000) 17 | return () => { 18 | clearTimeout(timer.current) 19 | } 20 | }, []) 21 | return (
22 | This is the React Component 23 | {props.slot1 && props.slot1()} 24 | {props.slot2 && props.slot2(foo)} 25 | {props.slot3} 26 | {props.children} 27 |
) 28 | } 29 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/react_app/ReactUnMount.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | export default class A extends React.Component { 3 | componentWillUnmount() { 4 | const foundDom = document.querySelector(".aaa") 5 | const notFoundDom = document.querySelector(".bbb") 6 | if (foundDom && !notFoundDom) { 7 | document.body.appendChild(document.createTextNode("test-result-1")) 8 | } 9 | 10 | const foundIdDom = document.getElementById("unmountId") 11 | const notFoundIdDom = document.getElementById("unmountId-notexists") 12 | if (foundIdDom && !notFoundIdDom) { 13 | document.body.appendChild(document.createTextNode("test-result-2")) 14 | } 15 | } 16 | render() { 17 | return (
18 |
unMountTest
19 |
unMountTest
20 |
) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dev-project-react/src/components/CrossProvider/index.js: -------------------------------------------------------------------------------- 1 | import {applyPureVueInReact} from 'veaury' 2 | import BasicVue from './Basic' 3 | import BasicVuePure from './BasicPure' 4 | import { ReactRouterProviderForVue } from './reactRouterCrossingProvider' 5 | import { ReactRouterProviderForVuePure } from './reactRouterCrossingProviderPure' 6 | 7 | const Basic = applyPureVueInReact(BasicVue) 8 | const BasicPure = applyPureVueInReact(BasicVuePure) 9 | export default function () { 10 | 11 | return
12 |

This example shows the basic usage of `createCrossingProviderForVueInReact`.

13 |

Get the state of the current React app's react-router in a Vue component.

14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | } 22 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/LazyReactInVue.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /tests/cases/reactInVueGetRef/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/vue'; 3 | import routerPlugin from 'dev-project-vue3/src/router' 4 | import storePlugin from 'dev-project-vue3/src/store' 5 | import App from 'dev-project-vue3/src/App' 6 | 7 | function getGlobalProperties(targetOject) { 8 | return function(app) { 9 | Object.assign(targetOject, app.config.globalProperties) 10 | } 11 | } 12 | 13 | test('Test ref in applyReactInVue', async () => { 14 | const globalProperties = {} 15 | const { findByText, getByTestId, findByTestId } = render(App, { 16 | global: { 17 | plugins: [ 18 | routerPlugin, 19 | storePlugin, 20 | getGlobalProperties(globalProperties) 21 | ] 22 | } 23 | }) 24 | await globalProperties.$router.push({ 25 | name: 'basic' 26 | }) 27 | expect(await findByText(/React Component BB/)).toBeInTheDocument() 28 | }) 29 | -------------------------------------------------------------------------------- /tests/cases/reactInVueSlot/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/vue'; 3 | import routerPlugin from 'dev-project-vue3/src/router' 4 | import storePlugin from 'dev-project-vue3/src/store' 5 | import App from 'dev-project-vue3/src/App' 6 | 7 | function getGlobalProperties(targetOject) { 8 | return function(app) { 9 | Object.assign(targetOject, app.config.globalProperties) 10 | } 11 | } 12 | 13 | test('Test pureReactInVue', async () => { 14 | const globalProperties = {} 15 | const { findByText, getByTestId, findByTestId } = render(App, { 16 | global: { 17 | plugins: [ 18 | routerPlugin, 19 | storePlugin, 20 | getGlobalProperties(globalProperties) 21 | ] 22 | } 23 | }) 24 | await globalProperties.$router.push({ 25 | name: 'slots' 26 | }) 27 | expect(await findByText(/This is the Custom1 Vue Component./)).toBeInTheDocument(); 28 | }) 29 | -------------------------------------------------------------------------------- /tests/cases/reactMissVue/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/ReactMissVue') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | expect(await findByText(/This example shows the basic usage of `ReactMissVue`/)).toBeInTheDocument() 9 | 10 | // A click triggers an asynchronous update of the state 11 | await act(async () => { 12 | fireEvent.click(await findByTestId('jumpAAA')) 13 | fireEvent.change(getByTestId('fooValue'), { 14 | target: { 15 | value: 'abc' 16 | } 17 | }) 18 | }) 19 | 20 | await waitFor(async () => { 21 | expect(await findByTestId('routePath')).toHaveTextContent('path: /aaa') 22 | expect(await findByTestId('fooValueShow')).toHaveTextContent('abc') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /dev-project-react/src/components/useInjectPropsFromWrapper/Basic.vue: -------------------------------------------------------------------------------- 1 | 8 | 29 | 32 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | function vueJsxInclude(filename) { 2 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 3 | if (filename.match(/\.(vue|vue\.js)$/i) && filename.match(/dev-project-/)){ 4 | return filename 5 | } 6 | if (filename.match(/[/\\]vue_app[\\/$]+/)) return filename 7 | } 8 | 9 | module.exports = { 10 | "presets": [ 11 | "@babel/preset-env", 12 | "@babel/preset-react" 13 | ], 14 | "plugins": ["@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-class-properties"], 15 | "env": { 16 | "rollup": { 17 | "plugins": ["@babel/plugin-external-helpers"] 18 | }, 19 | "test": { 20 | "presets": ["@babel/preset-env"], 21 | "plugins": ["@babel/plugin-proposal-class-properties"] 22 | } 23 | }, 24 | overrides: [ 25 | { 26 | test: vueJsxInclude, 27 | plugins: ['@vue/babel-plugin-jsx'] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tests/cases/crossingProviderInReact/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/CrossingProvider') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | expect(await findByText(/createCrossingProviderForVueInReact/)).toBeInTheDocument() 9 | 10 | // A click triggers an asynchronous update of the state 11 | await act(async () => { 12 | fireEvent.click(await findByTestId('changeQuery')) 13 | }) 14 | 15 | await waitFor(() => { 16 | const targetFullPath = document.location.hash.replace('#', '') 17 | if (!targetFullPath.match(/\?a=/)) expect().toThrowError('Hash has no parameters!') 18 | expect(getByTestId('pathnameSearch')).toHaveTextContent(targetFullPath) 19 | expect(getByTestId('pathnameSearchPure')).toHaveTextContent(targetFullPath) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /babel/ReactPreset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | module.exports = function(context, options = {}) { 6 | let presets1 = [] 7 | let presets2 = [] 8 | try { 9 | presets1.push([require('@vue/cli-plugin-babel/preset'), { 10 | // Turn off jsx compilation of Vue 11 | jsx: false 12 | }]) 13 | presets2.push(require('@vue/cli-plugin-babel/preset')) 14 | } catch(e) {} 15 | presets1.push('babel-preset-react-app') 16 | return { 17 | presets: presets2, 18 | overrides: [ 19 | { 20 | test (filename) { 21 | // default ignore node_modules 22 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 23 | // default pass react_app path 24 | if (filename.match(/[/\\]react_app[\\/$]+/)) return filename 25 | }, 26 | ...options, 27 | // plugins: [ 28 | // // Compile with React's jsx 29 | // '@babel/plugin-transform-react-jsx' 30 | // ], 31 | presets: presets1 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/introduce/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 28 | -------------------------------------------------------------------------------- /dev-project-react/src/components/VModel/Basic.vue: -------------------------------------------------------------------------------- 1 | 10 | 29 | 32 | -------------------------------------------------------------------------------- /src/reactAllHandles.js: -------------------------------------------------------------------------------- 1 | export default new Set(['onClick', 'onContextMenu', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOut', 'onMouseOver', 'onMouseUp', 'onChange', 'onInput', 'onInvalid', 'onReset', 'onSubmit', 'onError', 'onLoad', 'onPointerDown', 'onPointerMove', 'onPointerUp', 'onPointerCancel', 'onGotPointerCapture', 'onLostPointerCapture', 'onPointerEnter', 'onPointerLeave', 'onPointerOver', 'onPointerOut', 'onSelect', 'onTouchCancel', 'onTouchEnd', 'onTouchMove', 'onTouchStart', 'onScroll', 'onWheel', 'onAbort', 'onCanPlay', 'onCanPlayThrough', 'onDurationChange', 'onEmptied', 'onEncrypted', 'onEnded', 'onError', 'onLoadedData', 'onLoadedMetadata', 'onLoadStart', 'onPause', 'onPlay', 'onPlaying', 'onProgress', 'onRateChange', 'onSeeked', 'onSeeking', 'onStalled', 'onSuspend', 'onTimeUpdate', 'onVolumeChange', 'onWaiting', 'onLoad', 'onError', 'onAnimationStart', 'onAnimationEnd', 'onAnimationIteration', 'onTransitionEnd', 'onToggle']) 2 | -------------------------------------------------------------------------------- /src/pureReactInVue/RenderReactNode.js: -------------------------------------------------------------------------------- 1 | import applyReactInVue from '../applyReactInVue' 2 | import React, {forwardRef} from 'react' 3 | import {h} from 'vue' 4 | 5 | function RenderReactNode(props, ref) { 6 | let reactNode = props.node 7 | if (typeof reactNode === 'function') { 8 | reactNode = reactNode() 9 | } 10 | 11 | if (!ref?.current && typeof ref !== 'function' && !ref?.toString().match(/^function/)) { 12 | ref = null 13 | } 14 | 15 | if (['string', 'number'].indexOf(typeof reactNode) > -1) { 16 | return reactNode 17 | } 18 | 19 | if (reactNode instanceof Array) { 20 | if (reactNode.length === 1) { 21 | reactNode = reactNode[0] 22 | } else { 23 | return reactNode 24 | } 25 | } 26 | 27 | return {...reactNode, ref} 28 | } 29 | 30 | const Bridge = applyReactInVue(RenderReactNode) 31 | 32 | function WrapVue(props) { 33 | return h(Bridge, { 34 | node: () => props.node 35 | }) 36 | } 37 | 38 | WrapVue.originReactComponent = forwardRef(RenderReactNode) 39 | 40 | export default WrapVue 41 | -------------------------------------------------------------------------------- /src/pureVueInReact/interceptProps.js: -------------------------------------------------------------------------------- 1 | // TODO 2 | // This function will solve the timing problem of affecting external vue components after modifying the state inside some special react components 3 | const reactComponents = [] 4 | 5 | export function pureInterceptProps(target = {}, child, ReactComponent) { 6 | return target 7 | 8 | // if (reactComponents.indexOf(ReactComponent) > -1) { 9 | // if (target.onChange && child.__top__) { 10 | // const oldChange = target.onChange 11 | // target.onChange = function (...args) { 12 | // child.__extraData = { 13 | // value: args[0].target.value 14 | // } 15 | // child.__top__.__veaurySyncUpdateProps__({}) 16 | // child.__top__.macroTaskUpdate = true; 17 | // oldChange.apply(this, args) 18 | // if (child.__top__) { 19 | // Promise.resolve().then(() => { 20 | // child.__extraData = null 21 | // child.__top__.__veauryMountReactComponent__(true) 22 | // }) 23 | // } 24 | // } 25 | // } 26 | // } 27 | // return target 28 | } 29 | -------------------------------------------------------------------------------- /src/pureReactInVue/interceptProps.js: -------------------------------------------------------------------------------- 1 | // This function will solve the timing problem of affecting external vue components after modifying the state inside some special react components 2 | export function pureInterceptProps(target = {}, child, ReactComponent) { 3 | if (ReactComponent.__syncUpdateForPureReactInVue) { 4 | Object.keys(ReactComponent.__syncUpdateForPureReactInVue).map((key) => { 5 | if (target[key] && typeof target[key] === 'function' && child.__top__) { 6 | const oldFun = target[key] 7 | target[key] = function(...args) { 8 | child.__extraData = ReactComponent.__syncUpdateForPureReactInVue[key].apply(this, args) 9 | child.__top__.__veaurySyncUpdateProps__({}) 10 | child.__top__.macroTaskUpdate = true; 11 | oldFun.apply(this, args) 12 | if (child.__top__) { 13 | Promise.resolve().then(() => { 14 | child.__extraData = null 15 | child.__top__.__veauryMountReactComponent__(true) 16 | }) 17 | } 18 | } 19 | } 20 | }) 21 | } 22 | return target 23 | } 24 | -------------------------------------------------------------------------------- /tests/cases/babel/1-test.js: -------------------------------------------------------------------------------- 1 | import preset from "root/babel/ReactPreset"; 2 | 3 | describe('Test babel/ReactPreset.js', () => { 4 | test('Test default options', () => { 5 | const result = preset() 6 | const testFunction = result.overrides[0].test 7 | expect(result.presets.length === 1).toBe(true) 8 | expect(testFunction('/node_modules/xxxx') === undefined).toBe(true) 9 | expect(testFunction('node_modules/xxxx') === undefined).toBe(true) 10 | expect(testFunction('/react_app/xxxx') === '/react_app/xxxx').toBe(true) 11 | }) 12 | 13 | test('Test custom options', () => { 14 | const result = preset({},{ 15 | test (filename) { 16 | // default ignore node_modules 17 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 18 | // default pass abc path 19 | if (filename.match(/[/\\]abc[\\/$]+/)) return filename 20 | }, 21 | }) 22 | const testFunction = result.overrides[0].test 23 | expect(result.presets.length === 1).toBe(true) 24 | expect(testFunction('/abc/xxxx') === '/abc/xxxx').toBe(true) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/cases/useInjectPropsFromWrapperInReact/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/useInjectPropsFromWrapper') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | const targetFullPath = document.location.hash.replace('#', '') 9 | expect(await findByTestId('fullPath')).toHaveTextContent(targetFullPath) 10 | 11 | // A click triggers an asynchronous update of the state 12 | await (new Promise((resolve) => { 13 | setTimeout(async () => { 14 | fireEvent.click(await findByTestId('changeQuery')) 15 | resolve() 16 | }) 17 | })) 18 | 19 | await waitFor(async () => { 20 | const targetFullPath = document.location.hash.replace('#', '') 21 | if (!targetFullPath.match(/\?a=/)) expect().toThrowError('Hash has no parameters!') 22 | expect(await findByTestId('fullPath')).toHaveTextContent(targetFullPath) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /dev-project-react/src/App.css: -------------------------------------------------------------------------------- 1 | .vue-component { 2 | background: aquamarine; 3 | padding: 10px; 4 | width: 500px; 5 | margin: auto; 6 | } 7 | 8 | .react-component { 9 | background: #0ae4fd; 10 | padding: 10px; 11 | width: 500px; 12 | margin: auto; 13 | } 14 | 15 | .slot { 16 | background: #0ae4fd; 17 | padding: 10px; 18 | margin: 10px; 19 | } 20 | 21 | .App { 22 | text-align: center; 23 | margin-top: 60px; 24 | } 25 | 26 | .App-logo { 27 | height: 40vmin; 28 | pointer-events: none; 29 | } 30 | 31 | @media (prefers-reduced-motion: no-preference) { 32 | .App-logo { 33 | animation: App-logo-spin infinite 20s linear; 34 | } 35 | } 36 | 37 | .App-header { 38 | background-color: #282c34; 39 | min-height: 100vh; 40 | display: flex; 41 | flex-direction: column; 42 | align-items: center; 43 | justify-content: center; 44 | font-size: calc(10px + 2vmin); 45 | color: white; 46 | } 47 | 48 | .App-link { 49 | color: #61dafb; 50 | } 51 | 52 | @keyframes App-logo-spin { 53 | from { 54 | transform: rotate(0deg); 55 | } 56 | to { 57 | transform: rotate(360deg); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 天堂里的花大咩 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/4-reactUnMount-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import React from 'react'; 3 | import { render, screen } from '@testing-library/vue'; 4 | import Component from "./4-reactUnMount.vue" 5 | 6 | test('test reactUnMount', async () => { 7 | render(Component); 8 | await new Promise(resolve => { 9 | setTimeout(resolve, 300); 10 | }) 11 | const linkElement = await screen.findByText(/test-result-1/); 12 | expect(linkElement).toBeInTheDocument() 13 | }); 14 | test('test reactUnMount getElementById', async () => { 15 | render(Component); 16 | await new Promise(resolve => { 17 | setTimeout(resolve, 300); 18 | }) 19 | const linkElement = await screen.findByText(/test-result-2/); 20 | expect(linkElement).toBeInTheDocument() 21 | }); 22 | // test('test reactUnMount querySelector', async () => { 23 | // render(Component); 24 | // await new Promise(resolve => { 25 | // setTimeout(resolve, 300); 26 | // }) 27 | // const linkElement = await screen.findByText(/reactUnMountQuerySelectorSuccess/); 28 | // expect(linkElement).toBeInTheDocument() 29 | // }); 30 | -------------------------------------------------------------------------------- /src/utils/couldBeClass.js: -------------------------------------------------------------------------------- 1 | export default function couldBeClass(obj, strict) { 2 | if (typeof obj != "function") return false; 3 | 4 | const str = obj.toString(); 5 | 6 | // async function or arrow function 7 | if (obj.prototype === undefined) return false; 8 | // generator function or malformed definition 9 | if (obj.prototype.constructor !== obj) return false; 10 | // ES6 class 11 | if (str.slice(0, 5) == "class") return true; 12 | // has own prototype properties 13 | if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true; 14 | // anonymous function 15 | if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false; 16 | // ES5 class without `this` in the body and the name's first character 17 | // upper-cased. 18 | if (strict && /^function\s+[A-Z]/.test(str)) return true; 19 | // has `this` in the body 20 | if (/\b\(this\b|\bthis[\.\[]\b/.test(str)) { 21 | // not strict or ES5 class generated by babel 22 | if (!strict || /classCallCheck\(this/.test(str)) return true; 23 | 24 | return /^function\sdefault_\d+\s*\(/.test(str); 25 | } 26 | 27 | return false; 28 | } 29 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/2-scopedSlot-vnode.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /src/utils/styleClassTransformer.js: -------------------------------------------------------------------------------- 1 | import toCamelCase from './toCamelCase' 2 | 3 | export function formatStyle(val) { 4 | if (!val) return {} 5 | if (typeof val === 'string') { 6 | val = val.trim() 7 | return val.split(/\s*;\s*/).reduce((prev, cur) => { 8 | if (!cur) { 9 | return prev 10 | } 11 | cur = cur.split(/\s*:\s*/) 12 | if (cur.length !== 2) return prev 13 | Object.assign(prev, { 14 | [toCamelCase(cur[0])]: cur[1], 15 | }) 16 | return prev 17 | }, {}) 18 | } 19 | if (typeof val === 'object') { 20 | const newVal = {} 21 | Object.keys(val).forEach((v) => { 22 | newVal[toCamelCase(v)] = val[v] 23 | }) 24 | return newVal 25 | } 26 | return {} 27 | } 28 | 29 | export function formatClass(val) { 30 | if (!val) return [] 31 | if (val instanceof Array) return val 32 | if (typeof val === 'string') { 33 | val = val.trim() 34 | return val.split(/\s+/) 35 | } 36 | if (typeof val === 'object') { 37 | return Object.keys(val).filter((v)=>!!val[v]) 38 | } 39 | return [] 40 | } 41 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Events/index.js: -------------------------------------------------------------------------------- 1 | import {applyVueInReact} from 'veaury' 2 | import BasicVue from './Basic' 3 | import {useState} from 'react' 4 | 5 | const Basic = applyVueInReact(BasicVue) 6 | export default function () { 7 | const [state, setState] = useState({ 8 | foo: Math.random(), 9 | currentTime: Date.now() 10 | }) 11 | 12 | function onClickForVue() { 13 | setState((prevState) => ({ 14 | ...prevState, 15 | foo: Math.random() 16 | })) 17 | } 18 | 19 | function onClickForReact() { 20 | setState((prevState) => ({ 21 | ...prevState, 22 | currentTime: Date.now() 23 | })) 24 | } 25 | 26 | return
27 |

This example shows how to pass events when using `applyVueInReact`.

28 | 29 |
30 | This is the Vue component Slot from React
31 | current timestamp: {state.currentTime}
32 | Click the button can refresh current timestamp
33 | 34 |
35 |
36 |
37 | } 38 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/crossProvider/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 45 | -------------------------------------------------------------------------------- /dev-project-react/src/components/VModel/Basic2.vue: -------------------------------------------------------------------------------- 1 | 10 | 29 | 32 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/crossProvider/react_app/Basic.js: -------------------------------------------------------------------------------- 1 | import React, {useRef} from 'react' 2 | import { useVueHooksInReact } from '../vueRouterAndVuexCrossingProvider' 3 | 4 | export default function (props) { 5 | const { vuex, vueRoute, vueRouter } = useVueHooksInReact() 6 | function changeQuery() { 7 | vueRouter.replace({ 8 | query: { 9 | a: Math.random() 10 | } 11 | }) 12 | } 13 | function incrementCount() { 14 | vuex.dispatch('increment') 15 | } 16 | 17 | const style = useRef({ 18 | background: '#91e7fc', 19 | width: 500, 20 | margin: 'auto', 21 | padding: 10, 22 | lineHeight: '30px' 23 | }) 24 | return (
25 | This is the React Component
26 | 27 | the path info from 'vue-router': {vueRoute.fullPath}
28 | the count from 'vuex': {vuex.state.count} 29 |

30 | 31 |
) 32 | } 33 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Context/index.js: -------------------------------------------------------------------------------- 1 | import {applyVueInReact} from 'veaury' 2 | import BasicVue from './Basic' 3 | import {useEffect, useState, useRef, createContext, useContext} from 'react' 4 | 5 | const Basic = applyVueInReact(BasicVue) 6 | const Context = createContext({}) 7 | 8 | function SubReactComponent() { 9 | const {bossName} = useContext(Context) 10 | return
bossName from Context: {bossName}
11 | } 12 | 13 | export default function () { 14 | const [bossName, setBossName] = useState(Math.random) 15 | const timer = useRef(null) 16 | useEffect(() => { 17 | timer.current = setInterval(() => { 18 | setBossName(Math.random()) 19 | }, 1000) 20 | return () => { 21 | clearInterval(timer.current) 22 | } 23 | }, []) 24 | return
25 |

This example shows the basic usage of `applyVueInReact`.

26 |

Using React components in Vue components.

27 | 28 | 29 |
30 | This is the Vue component Slot from React
31 | 32 |
33 |
34 |
35 |
36 | } 37 | -------------------------------------------------------------------------------- /dev-project-react/src/components/LazyVueInReact/index.js: -------------------------------------------------------------------------------- 1 | import { lazyVueInReact } from 'veaury' 2 | import { useEffect, useState, useRef } from 'react' 3 | 4 | const AsyncBasic = lazyVueInReact(() => import('./Basic')) 5 | console.log(AsyncBasic) 6 | export default function () { 7 | const [state, setState] = useState({ 8 | foo: Math.random(), 9 | currentTime: new Date().toLocaleString() 10 | }) 11 | const timer = useRef(null) 12 | useEffect(() => { 13 | timer.current = setInterval(() => { 14 | setState({ 15 | foo: Math.random(), 16 | currentTime: new Date().toLocaleString() 17 | }) 18 | }, 1000) 19 | return () => { 20 | clearInterval(timer.current) 21 | } 22 | }, []) 23 | 24 | return
25 |

This example shows the basic usage of `lazyVueInReact`.

26 |

Using React components in Vue components.

27 | 28 |
29 | This is the Vue component Slot from React
30 | current time: {state.currentTime} 31 |
32 |
33 |
34 | } 35 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Slots/index.js: -------------------------------------------------------------------------------- 1 | import {applyVueInReact} from 'veaury' 2 | import BasicVue from './Basic' 3 | 4 | const Basic = applyVueInReact(BasicVue) 5 | export default function () { 6 | return
7 |

Pass Slots to Vue Components.

8 |

The usage of 'slots' is similar to the usage of 'v-slots' of vue's jsx.

9 | {/*just send children*/} 10 | 13 |
this is children
14 |
15 | {/*send v-slots*/} 16 | this is slot1(namedSlot)
, 18 | slot2: ({value}) =>
this is slot2(scopedSlot), and receive value: {value}
, 19 | default:
this is children
20 | }}/> 21 | {/*another usage*/} 22 | 23 | {{ 24 | slot1:
this is slot1(namedSlot)
, 25 | slot2: ({value}) =>
this is slot2(scopedSlot), and receive value: {value}
, 26 | default:
this is children
27 | }} 28 |
29 | 30 | } 31 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/crossProvider/react_app/BasicPure.js: -------------------------------------------------------------------------------- 1 | import React, {useRef} from 'react' 2 | import { useVueHooksInReact } from '../vueRouterAndVuexCrossingProviderPure' 3 | 4 | export default function (props) { 5 | const { vuex, vueRoute, vueRouter } = useVueHooksInReact() 6 | function changeQuery() { 7 | vueRouter.replace({ 8 | query: { 9 | a: Math.random() 10 | } 11 | }) 12 | } 13 | function incrementCount() { 14 | vuex.dispatch('increment') 15 | } 16 | 17 | const style = useRef({ 18 | background: '#91e7fc', 19 | width: 500, 20 | margin: 'auto', 21 | padding: 10, 22 | lineHeight: '30px' 23 | }) 24 | return (
25 | This is the React Component
26 | 27 | the path info from 'vue-router': {vueRoute.fullPath}
28 | the count from 'vuex': {vuex.state.count} 29 |

30 | 31 |
) 32 | } 33 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Introduce.js: -------------------------------------------------------------------------------- 1 | import {Fragment} from 'react' 2 | import { NavLink } from 'react-router-dom' 3 | export default function introduce() { 4 | const style = { 5 | marginRight: 10 6 | } 7 | return 8 |

9 |

The React development environment for Veaury

10 |

Examples

11 | basic 12 | events 13 | slots 14 | v-model 15 | context 16 | useInjectPropsFromWrapper 17 | CrossingProvider 18 | lazyVueInReact 19 | ReactMissVue 20 | Pure mode 21 | getVNodeAndRenderVNode 22 |
23 | } 24 | -------------------------------------------------------------------------------- /dev-project-react/src/components/useInjectPropsFromWrapper/ReactBasic.js: -------------------------------------------------------------------------------- 1 | import { applyVueInReact, injectPropsFromWrapper } from 'veaury' 2 | import { useLocation, useNavigate } from 'react-router-dom' 3 | import BasicVue from './Basic' 4 | 5 | // Deprecated 6 | // Just for unit test coverage 7 | const Com = injectPropsFromWrapper( function useInjectPropsFromWrapper(reactProps) { 8 | // React hooks can be used in this function 9 | // Use the hooks of react-router-dom 10 | const location = useLocation() 11 | const navigate = useNavigate() 12 | 13 | // The returned object will be passed to the Vue component as props 14 | return { 15 | reactRouter: { 16 | navigate, 17 | location 18 | } 19 | } 20 | }, BasicVue) 21 | // Deprecated 22 | // Just for unit test coverage 23 | const com1 = injectPropsFromWrapper(null , BasicVue) 24 | 25 | export default applyVueInReact(BasicVue, { 26 | useInjectPropsFromWrapper(reactProps) { 27 | // React hooks can be used in this function 28 | // Use the hooks of react-router-dom 29 | const location = useLocation() 30 | const navigate = useNavigate() 31 | 32 | // The returned object will be passed to the Vue component as props 33 | return { 34 | reactRouter: { 35 | navigate, 36 | location 37 | } 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/context/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import babel from 'rollup-plugin-babel' 3 | import {uglify} from 'rollup-plugin-uglify' 4 | import commonjs from 'rollup-plugin-commonjs' 5 | 6 | const shared = { 7 | input: 'src/index.js', 8 | plugins: [ 9 | resolve({ 10 | customResolveOptions: { 11 | moduleDirectory: 'node_modules', 12 | }, 13 | }), 14 | babel({ 15 | exclude: 'node-modules/**', 16 | }), 17 | commonjs(), 18 | uglify({ 19 | compress: { 20 | // pure_getters: true, 21 | // unsafe: true, 22 | // unsafe_comps: true 23 | } 24 | }) 25 | ], 26 | external: ['react', 'react-dom', 'vue'], 27 | } 28 | 29 | export default [ 30 | Object.assign({}, shared, { 31 | output: { 32 | file: 'dist/veaury.umd.js', 33 | format: 'umd', 34 | name: 'veaury', 35 | globals: { 36 | react: 'React', 37 | 'react-dom': 'ReactDOM', 38 | vue: 'Vue' 39 | }, 40 | }, 41 | }), 42 | Object.assign({}, shared, { 43 | output: { 44 | file: 'dist/veaury.esm.js', 45 | format: 'esm', 46 | name: 'veaury', 47 | globals: { 48 | react: 'React', 49 | 'react-dom': 'ReactDOM', 50 | vue: 'Vue' 51 | }, 52 | }, 53 | }), 54 | ] 55 | -------------------------------------------------------------------------------- /dev-project-react/src/components/pureVueInReact/index.js: -------------------------------------------------------------------------------- 1 | import AAVue from './AA.vue' 2 | import './style.css' 3 | import {applyPureVueInReact, getReactNode, applyVueInReact, getVNode} from 'veaury' 4 | 5 | const AAWithPure = applyPureVueInReact(AAVue) 6 | const AAWithNormal = applyVueInReact(AAVue) 7 | 8 | export default function (props) { 9 | return <> 10 |

11 | This example shows the basic usage of `applyPureVueInReact`. 12 |

13 |

14 | The style attribute display is set to 'flex' inside the AA component. 15 |

16 |

17 | Pure mode
18 | The divs in the children will no longer be placed in an additional container, so the divs will be directly 19 | affected by the flex style. 20 |

21 | 22 |
A
23 |
B
24 |
C
25 |
26 |
27 |

28 | Normal mode
29 | The divs in the children will be placed in a container styled 'all:unset', so the flex setting in the AA component 30 | has no effect on the divs. 31 |

32 | 33 |
A
34 |
B
35 |
C
36 |
37 | 38 | } 39 | -------------------------------------------------------------------------------- /dev-project-vue3/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | function resolve (dir) { 3 | return path.join(__dirname, dir) 4 | } 5 | 6 | module.exports = { 7 | presets: [ 8 | // Turn off '@vue/cli-plugin-babel/preset' 9 | // '@vue/cli-plugin-babel/preset', 10 | ['veaury/babel/ReactPreset', { 11 | // for dev only 12 | test: function(filename) { 13 | // The files in the following paths are compiled with React's jsx 14 | if (filename?.startsWith(resolve('src')) && filename.match(/[/\\]react_app[\\/$]+/) || filename?.startsWith(resolve('../src'))) return filename 15 | } 16 | }] 17 | ], 18 | overrides: [ 19 | { 20 | include: [resolve(""), resolve("node_modules")], 21 | exclude: (file) => /react_app[\/\\]+/.test(file) || (/node_modules[\/\\]+/.test(file) && !(/node_modules[\/\\]+((webpack-dev-server[\/\\]+client)([\/\\]|$))/.test(file))), 22 | presets: [ 23 | ["@babel/preset-env", { 24 | // "modules": "false", 25 | targets: { 26 | browsers: ["> 1%", "last 2 versions", "not ie <= 8"], 27 | }, 28 | }], 29 | ], 30 | plugins: [ 31 | // "transform-vue-jsx", 32 | "@babel/plugin-proposal-class-properties", 33 | "@babel/plugin-syntax-dynamic-import", 34 | "@babel/plugin-transform-runtime", 35 | ], 36 | // "sourceType":"unambiguous" 37 | } 38 | ], 39 | } 40 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/Basic.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 40 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/events/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /dev-project-react/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /dev-project-vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev-project-vue3", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "babel-plugin-transform-react-jsx": "^6.24.1", 12 | "babel-preset-react-app": "^10.0.1", 13 | "core-js": "^3.6.5", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "tailwindcss": "^3.4.3", 17 | "veaury": "^2.6.3-beta.0", 18 | "vue": "^3.0.0", 19 | "vue-router": "^4.0.12", 20 | "vuex": "^4.0.2" 21 | }, 22 | "devDependencies": { 23 | "@babel/plugin-transform-react-jsx": "^7.18.10", 24 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", 25 | "@vue/cli-plugin-babel": "^4.5.19", 26 | "@vue/cli-plugin-eslint": "^4.5.19", 27 | "@vue/cli-service": "^4.5.19", 28 | "@vue/compiler-sfc": "^3.0.0", 29 | "babel-eslint": "^10.1.0", 30 | "eslint": "^6.7.2", 31 | "eslint-plugin-vue": "^7.0.0", 32 | "react-refresh": "^0.14.0" 33 | }, 34 | "eslintConfig": { 35 | "root": true, 36 | "env": { 37 | "node": true 38 | }, 39 | "extends": [ 40 | "plugin:vue/vue3-essential", 41 | "eslint:recommended" 42 | ], 43 | "parserOptions": { 44 | "parser": "babel-eslint" 45 | }, 46 | "rules": {} 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions", 51 | "not dead" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/lazyReactInVue/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /vite/esm/index.mjs: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import react from '@vitejs/plugin-react' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | 5 | function veauryVitePlugins({isNuxt, type, vueJsxInclude, vueJsxExclude, vueOptions = {}, vueJsxOptions: initVueJsxOptions = {}, reactOptions = {}}) { 6 | 7 | let vueJsxOptions = {...initVueJsxOptions} 8 | if (type === 'react') { 9 | vueJsxOptions.include = [/vue&type=script&lang\.[tj]sx$/i, /vue&type=script&setup=true&lang\.[tj]sx$/i, /[/\\]vue_app[\\/][\w\W]+\.[tj]sx$/] 10 | } 11 | if (type === 'vue') { 12 | vueJsxOptions.exclude = [/[/\\]react_app[\\/$]+/] 13 | } 14 | if (type === 'custom') { 15 | if (vueJsxInclude) { 16 | vueJsxOptions.include = vueJsxInclude 17 | } 18 | if (vueJsxExclude) { 19 | vueJsxOptions.exclude = vueJsxExclude 20 | } 21 | } 22 | 23 | return [ 24 | // ReactDOMTransformPlugin(), 25 | // requireTransform({ 26 | 27 | // fileRegex: /veaury/ 28 | // }), 29 | isNuxt === true? {}: vue(vueOptions), 30 | // Make vueJsx plugin run time earlier 31 | { 32 | ...vueJsx(vueJsxOptions), 33 | enforce: 'pre' 34 | }, 35 | // recover esbuild include 36 | { 37 | config(){ 38 | return { 39 | esbuild: { 40 | include: /\.[jt]sx?$/ 41 | } 42 | } 43 | } 44 | }, 45 | react({ 46 | jsxImportSource: 'react', 47 | ...reactOptions 48 | }) 49 | ] 50 | } 51 | 52 | export default veauryVitePlugins 53 | -------------------------------------------------------------------------------- /tests/cases/applyReactInVue/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/vue'; 3 | import Basic from './Basic'; 4 | import LazyReactInVue from './LazyReactInVue' 5 | 6 | test('renders a React component In Vue', async () => { 7 | render(Basic); 8 | let linkElement = await screen.findByText(/React in Vue/); 9 | expect(linkElement).toBeInTheDocument(); 10 | linkElement = await screen.findByText(/test ref/); 11 | expect(linkElement).toBeInTheDocument(); 12 | linkElement = await screen.findByText((content, element) => content.match(/test pure style/) && element.style.color === 'red' && element.classList.contains('AAA')); 13 | expect(linkElement).toBeInTheDocument(); 14 | linkElement = await screen.findByText(/normal DDDDD/) 15 | expect(linkElement).toBeInTheDocument(); 16 | linkElement = await screen.findByText(/pure DDDDD/) 17 | expect(linkElement).toBeInTheDocument(); 18 | }); 19 | 20 | test('test lazyReactInVue', async () => { 21 | render(LazyReactInVue); 22 | let linkElement = await screen.findByText(/test lazyReactInVue/); 23 | expect(linkElement).toBeInTheDocument(); 24 | linkElement = await screen.findByText(/test lazyPureReactInVue/); 25 | expect(linkElement).toBeInTheDocument(); 26 | linkElement = await screen.findByText(/lazyReactInVue loader options/); 27 | expect(linkElement).toBeInTheDocument(); 28 | linkElement = await screen.findByText(/lazyPureReactInVue loader options/); 29 | expect(linkElement).toBeInTheDocument(); 30 | }) 31 | -------------------------------------------------------------------------------- /dev-project-vue3/src/components/Test1.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 70 | 71 | 74 | -------------------------------------------------------------------------------- /dev-project-react/src/components/Basic/index.js: -------------------------------------------------------------------------------- 1 | import { applyVueInReact } from 'veaury' 2 | import BasicVue from './Basic' 3 | import AAVue from './AA' 4 | import { useEffect, useState, useRef } from 'react' 5 | 6 | const Basic = applyVueInReact(BasicVue) 7 | const AA = applyVueInReact(AAVue) 8 | export default function () { 9 | const [state, setState] = useState({ 10 | foo: Math.random(), 11 | currentTime: new Date().toLocaleString() 12 | }) 13 | const [aa, setAa] = useState(true) 14 | const timer = useRef(null) 15 | const timer1 = useRef(null) 16 | useEffect(() => { 17 | timer.current = setInterval(() => { 18 | setState({ 19 | foo: Math.random(), 20 | currentTime: new Date().toLocaleString() 21 | }) 22 | }, 1000) 23 | timer1.current = setTimeout(() => { 24 | setAa(false) 25 | }, 5000) 26 | return () => { 27 | clearInterval(timer.current) 28 | clearInterval(timer1.current) 29 | } 30 | }, []) 31 | 32 | return
33 |

This example shows the basic usage of `applyVueInReact`.

34 |

Using React components in Vue components.

35 | 36 | {aa && } 37 |
38 | This is the Vue component Slot from React
39 | current time: {state.currentTime} 40 |
41 |
42 |
43 | } 44 | -------------------------------------------------------------------------------- /dev-project-react/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const execSync = require('child_process').execSync; 20 | let argv = process.argv.slice(2); 21 | 22 | function isInGitRepository() { 23 | try { 24 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 25 | return true; 26 | } catch (e) { 27 | return false; 28 | } 29 | } 30 | 31 | function isInMercurialRepository() { 32 | try { 33 | execSync('hg --cwd . root', { stdio: 'ignore' }); 34 | return true; 35 | } catch (e) { 36 | return false; 37 | } 38 | } 39 | 40 | // Watch unless on CI or explicitly running all tests 41 | if ( 42 | !process.env.CI && 43 | argv.indexOf('--watchAll') === -1 && 44 | argv.indexOf('--watchAll=false') === -1 45 | ) { 46 | // https://github.com/facebook/create-react-app/issues/5210 47 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 48 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 49 | } 50 | 51 | 52 | jest.run(argv); 53 | -------------------------------------------------------------------------------- /src/pureVueInReact/getChildInfo.js: -------------------------------------------------------------------------------- 1 | import {formatClass, formatStyle} from '../utils/styleClassTransformer' 2 | import { transferSlots } from '../applyVueInReact' 3 | import parseVModel from '../utils/parseVModel' 4 | import options from "../options"; 5 | 6 | export default function getChildInfo(child, index, reactInVueCall, defaultSlotsFormatter, hashList) { 7 | 8 | // Filter out ref 9 | let {ref, children, 'v-slots': slots = {}, ...props} = child.props || {} 10 | 11 | if (children) { 12 | if (typeof children === 'object' && !(children instanceof Array) && !children.$$typeof) { 13 | slots = children 14 | } else { 15 | slots.default = children 16 | } 17 | // slots = transferSlots(slots) 18 | } 19 | 20 | 21 | let vueSlots = null 22 | Object.keys(slots || {}).forEach((key) => { 23 | let slot = slots[key] 24 | if (!vueSlots) vueSlots = {} 25 | vueSlots[key] = function (...args) { 26 | if (typeof slot === 'function') { 27 | slot = slot.apply(this, args) 28 | } 29 | // slot.__vueArgs = args 30 | return defaultSlotsFormatter(slot, reactInVueCall, hashList) 31 | } 32 | }) 33 | 34 | const newProps = {} 35 | const style = formatStyle(props.style) 36 | const className = Array.from(new Set(formatClass(props.className))).join(' ') 37 | if (Object.keys(style).length > 0) newProps.style = style 38 | if (className !== '') newProps.class = className 39 | 40 | Object.assign(props, { 41 | ...newProps 42 | }) 43 | delete props.className 44 | 45 | props = parseVModel(props) 46 | return { props, slots: vueSlots } 47 | } 48 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as lazyVueInReact} from './lazyVueInReact' 2 | export {default as lazyReactInVue} from './lazyReactInVue' 3 | export {default as applyReactInVue} from './applyReactInVue' 4 | export {default as applyVueInReact, VueContainer } from './applyVueInReact' 5 | export {default as REACT_ALL_HANDLERS} from './reactAllHandles' 6 | export {default as injectPropsFromWrapper} from './injectPropsFromWrapper' 7 | export {default as veauryOptions, setOptions as setVeauryOptions} from './options' 8 | export {default as createCrossingProviderForReactInVue} from './createCrossingProviderForReactInVue' 9 | export {default as createCrossingProviderForVueInReact} from './createCrossingProviderForVueInReact' 10 | export {default as createReactMissVue} from './createReactMissVue' 11 | export {default as applyPureReactInVue} from './pureReactInVue' 12 | export {default as RenderReactNode} from './pureReactInVue/RenderReactNode' 13 | export {default as getReactNode} from './pureReactInVue/getReactNode' 14 | export {default as applyPureVueInReact} from './pureVueInReact' 15 | export {default as getVNode} from './pureVueInReact/getVNode' 16 | export {default as lazyPureReactInVue} from './lazyPureReactInVue' 17 | export {default as lazyPureVueInReact} from './lazyPureVueInReact' 18 | export {default as createCrossingProviderForPureReactInVue} from './createCrossingProviderForPureReactInVue' 19 | export {default as createCrossingProviderForPureVueInReact} from './createCrossingProviderForPureVueInReact' 20 | export {default as injectSyncUpdateForPureReactInVue} from './pureReactInVue/injectSyncUpdateForPureReactInVue' 21 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen, act } from '@testing-library/react'; 3 | import React from 'react'; 4 | import Basic from './Basic'; 5 | import {lazyVueInReact, lazyPureVueInReact} from 'veaury' 6 | import {transferSlots} from "src/applyVueInReact"; 7 | 8 | test('renders a Vue component in React', () => { 9 | render(); 10 | const linkElement = screen.getByText(/Vue in React/); 11 | expect(linkElement).toBeInTheDocument(); 12 | }); 13 | 14 | 15 | test('test lazyVueInReact', async () => { 16 | const VueComponentInReact = lazyVueInReact(() => import('./VueComponent')) 17 | const PureVueComponentInReact = lazyPureVueInReact(() => import('./VueComponent')) 18 | await act(async () => { 19 | render(
20 | test lazyVueInReact 21 | test lazyPureVueInReact 22 |
) 23 | }) 24 | 25 | let linkElement = await screen.findByText(/test lazyVueInReact/); 26 | expect(linkElement).toBeInTheDocument(); 27 | linkElement = await screen.findByText(/test lazyPureVueInReact/); 28 | expect(linkElement).toBeInTheDocument(); 29 | }) 30 | 31 | describe('Test transferSlots', () => { 32 | test('Without passing in $slots', () => { 33 | expect(transferSlots()).toBe(undefined) 34 | let slot = { 35 | a: null 36 | } 37 | expect(transferSlots(slot) === slot).toBe(true) 38 | slot = { 39 | a: { 40 | vueFunction: () => {} 41 | } 42 | } 43 | expect(transferSlots(slot) === slot).toBe(true) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /tests/cases/crossingProviderInVue/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen, fireEvent, waitFor } from '@testing-library/vue'; 3 | import routerPlugin from 'dev-project-vue3/src/router' 4 | import storePlugin from 'dev-project-vue3/src/store' 5 | import App from 'dev-project-vue3/src/App' 6 | 7 | function getGlobalProperties(targetOject) { 8 | return function(app) { 9 | Object.assign(targetOject, app.config.globalProperties) 10 | } 11 | } 12 | test('test crossingProvider', async () => { 13 | const globalProperties = {} 14 | const { findByText, getByTestId, findByTestId } = render(App, { 15 | global: { 16 | plugins: [ 17 | routerPlugin, 18 | storePlugin, 19 | getGlobalProperties(globalProperties) 20 | ] 21 | } 22 | }) 23 | await globalProperties.$router.push({ 24 | name: 'crossingProvider' 25 | }) 26 | expect(await findByTestId('fullPath')).toHaveTextContent(/\/crossingProvider/); 27 | 28 | await fireEvent.click(await findByTestId('changeQuery')) 29 | // await waitFor(async () => await findByText(/a=/)) 30 | await waitFor(() => { 31 | const targetFullPath = document.location.hash.replace('#', '') 32 | expect(getByTestId('fullPath')).toHaveTextContent(targetFullPath) 33 | expect(getByTestId('fullPathPure')).toHaveTextContent(targetFullPath) 34 | }) 35 | 36 | const lastCount = globalProperties.$store.state.count 37 | await fireEvent.click(getByTestId('increment count')) 38 | await waitFor(() => expect(getByTestId('stateCount')).toHaveTextContent(lastCount + 1)) 39 | await waitFor(() => expect(getByTestId('stateCountPure')).toHaveTextContent(lastCount + 1)) 40 | }) 41 | -------------------------------------------------------------------------------- /tests/cases/applyVueInReact/2-getVNode-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen, waitFor } from '@testing-library/react'; 3 | import React, { Fragment } from 'react'; 4 | import VueComponent from './2-getVNode' 5 | import {applyPureVueInReact, getVNode} from 'veaury' 6 | import {h} from 'vue' 7 | 8 | const ReactComponent = applyPureVueInReact(VueComponent) 9 | const TempVueComponentInReact = applyPureVueInReact(function(props, context) { 10 | return h('div', context.attrs, context.slots) 11 | }) 12 | function TempReactComponent(props) { 13 | return
{props.children}
14 | } 15 | const ReactNode =
test getVNodeTempReactComponent
16 | test('test getVNode', async () => { 17 | let ref1 18 | let ref2 = {current: null} 19 | render( ReactNode)}> 20 |
{ref1 = r}}>children1
21 |
children2
22 | AABBCC 23 |
aaaa
24 | {{ 25 | default: () =>
bbbb
26 | }}
27 |
); 28 | await waitFor(() => { 29 | expect(screen.getByText(/test getVNode/)).toBeInTheDocument(); 30 | expect(screen.getByText(/TempReactComponent/)).toBeInTheDocument(); 31 | expect(screen.getByText(/aaaa/)).toBeInTheDocument(); 32 | expect(screen.getByText(/bbbb/)).toBeInTheDocument(); 33 | expect(!!ref1).toBe(true) 34 | expect(!!ref2.current).toBe(true) 35 | }) 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /dev-project-react/src/router/config.js: -------------------------------------------------------------------------------- 1 | import {lazy, Suspense} from 'react' 2 | 3 | const asyncElement = (asyncImport) => { 4 | const Component = lazy(asyncImport) 5 | return loading...}> 6 | } 7 | 8 | export default [ 9 | { 10 | path: '/basic', 11 | element: asyncElement(() => import('../components/Basic')), 12 | }, 13 | { 14 | path: '/events', 15 | element: asyncElement(() => import('../components/Events')), 16 | }, 17 | { 18 | path: '/slots', 19 | element: asyncElement(() => import('../components/Slots')), 20 | }, 21 | { 22 | path: '/v-model', 23 | element: asyncElement(() => import('../components/VModel')), 24 | }, 25 | { 26 | path: '/context', 27 | element: asyncElement(() => import('../components/Context')), 28 | }, 29 | { 30 | path: '/useInjectPropsFromWrapper', 31 | element: asyncElement(() => import('../components/useInjectPropsFromWrapper')), 32 | }, 33 | { 34 | path: '/CrossingProvider', 35 | element: asyncElement(() => import('../components/CrossProvider')), 36 | }, 37 | { 38 | path: '/lazyVueInReact', 39 | element: asyncElement(() => import('../components/LazyVueInReact')), 40 | }, 41 | { 42 | path: '/ReactMissVue/*', 43 | element: asyncElement(() => import('../components/reactMissVue')), 44 | }, 45 | { 46 | path: '/pureVueInReact/*', 47 | element: asyncElement(() => import('../components/pureVueInReact')), 48 | }, 49 | { 50 | path: '/getVNodeAndRenderVNode/*', 51 | element: asyncElement(() => import('../components/getVNodeAndRenderVNode')), 52 | }, 53 | { 54 | path: '*', 55 | element: asyncElement(() => import('../components/Introduce')), 56 | }, 57 | ] 58 | -------------------------------------------------------------------------------- /tests/cases/vModelInReact/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, fireEvent, waitFor, act } from '@testing-library/react'; 3 | import App from 'dev-project-react/src/App'; 4 | 5 | test('test crossingProvider', async () => { 6 | window.history.pushState({}, '', '/#/v-model') 7 | const {findByText, getByTestId, findByTestId} = render() 8 | expect(await findByTestId('reactModelShow')).toBeVisible() 9 | expect(await findByTestId('reactModelShow1')).toBeVisible() 10 | 11 | await act(async () => { 12 | fireEvent.click(await findByTestId('changeModel')) 13 | fireEvent.click(await findByTestId('changeModel1')) 14 | }) 15 | 16 | await waitFor(async () => { 17 | expect(await findByTestId('vModelShow')).toHaveTextContent(getByTestId('reactModelShow').textContent) 18 | expect(await findByTestId('vModelShow1')).toHaveTextContent(getByTestId('reactModelShow1').textContent) 19 | expect(await findByTestId('zooValueShow2_0')).toHaveTextContent(getByTestId('reactModelShow1').textContent) 20 | expect(await findByTestId('zooValueShow2_1')).toHaveTextContent(getByTestId('reactModelShow1').textContent) 21 | expect(await findByTestId('modelValueShow2_2')).toHaveTextContent(getByTestId('reactModelShow1').textContent) 22 | }) 23 | 24 | await act(async () => { 25 | fireEvent.click(await findByTestId('changeModel2_0')) 26 | fireEvent.click(await findByTestId('changeModel2_1')) 27 | fireEvent.click(await findByTestId('changeModel2_2')) 28 | }) 29 | 30 | await waitFor(async () => { 31 | expect(await findByTestId('modelType0')).toHaveTextContent('string') 32 | expect(await findByTestId('modelType1')).toHaveTextContent('number') 33 | expect(await findByTestId('modelType2')).toHaveTextContent('number') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /dev-project-react/src/components/reactMissVue/index.js: -------------------------------------------------------------------------------- 1 | import { VueContainer } from 'veaury' 2 | import { ReactMissVue, useReactMissVue } from './defineReactMissVue' 3 | 4 | function TestReactComponent() { 5 | // use pinia store 6 | const { fooStore } = useReactMissVue() 7 | return
8 | Foo's name: {fooStore.name} 9 |
10 | } 11 | 12 | function TestReactComponent1() { 13 | // use pinia store 14 | const { barStore } = useReactMissVue() 15 | return
16 | Bar's name: {barStore.name} 17 |
18 | } 19 | 20 | function Demo() { 21 | const { vueRouter, fooStore, barStore } = useReactMissVue() 22 | function jump(path) { 23 | vueRouter.push(path) 24 | } 25 | 26 | return
27 |

This example shows the basic usage of `ReactMissVue`.

28 |

Sometimes some features and plugins of Vue are really more useful than React.

29 |

Such as beforeEach of vue-router and pinia.

30 | 31 | 32 | {/* Use the global component router-view */} 33 | 34 |
35 |
36 | change the name of 'Foo' store from pina: fooStore.changeName(ele.target.value)} value={fooStore.name} data-testid="fooValue"/>
37 | change the name of 'Bar' store from pina: barStore.changeName(ele.target.value)} value={barStore.name}/>
38 |
39 | } 40 | 41 | 42 | 43 | export default function () { 44 | return 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /tests/cases/pureReactInVue/1-test.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render, screen } from '@testing-library/vue'; 3 | import routerPlugin from 'dev-project-vue3/src/router' 4 | import storePlugin from 'dev-project-vue3/src/store' 5 | import App from 'dev-project-vue3/src/App' 6 | import addScopeId from "src/pureReactInVue/addScopeId"; 7 | import resolveRef from "src/pureReactInVue/resolveRef"; 8 | 9 | function getGlobalProperties(targetOject) { 10 | return function(app) { 11 | Object.assign(targetOject, app.config.globalProperties) 12 | } 13 | } 14 | 15 | test('Test pureReactInVue', async () => { 16 | const globalProperties = {} 17 | const { findByText, getByTestId, findByTestId } = render(App, { 18 | global: { 19 | plugins: [ 20 | routerPlugin, 21 | storePlugin, 22 | getGlobalProperties(globalProperties) 23 | ] 24 | } 25 | }) 26 | await globalProperties.$router.push({ 27 | name: 'pureReactInVue' 28 | }) 29 | expect(await findByText(/applyPureReactInVue/)).toBeInTheDocument(); 30 | expect((await findByTestId('directiveTest')).style.color).toBe('red') 31 | expect(await findByTestId('random')).toBeVisible() 32 | expect(await findByTestId('ccRenderProps1')).toHaveTextContent('PPPPP') 33 | expect(await findByTestId('ccReactNode')).toHaveTextContent('RRRRR') 34 | expect(await findByTestId('ccDefault')).toHaveTextContent('YYYYY') 35 | expect(await findByText(/8888/)).toBeInTheDocument('8888') 36 | expect(await findByText(/6666/)).toBeInTheDocument('6666') 37 | }) 38 | 39 | test('Test hashList', () => { 40 | const child = {} 41 | expect(addScopeId(child, [])).toBe(child) 42 | expect(addScopeId(child, ['aaaa']).props.aaaa).toBe('') 43 | }) 44 | 45 | // Test('Test resolveRef', () => { 46 | // const child = {} 47 | // expect().toBe() 48 | // }) 49 | -------------------------------------------------------------------------------- /src/pureVueInReact/takeReactDomInVue.js: -------------------------------------------------------------------------------- 1 | import {h} from 'vue'; 2 | import {Fragment} from "react"; 3 | import {formatClass, formatStyle} from '../utils/styleClassTransformer' 4 | import resolveRef from "./resolveRef"; 5 | 6 | function takeReactDomInVue(child, tags, reactInVueCall, division, slotsFormatter, __top__) { 7 | if (tags !== 'all' && !(tags instanceof Array)) { 8 | tags = tags ? [tags] : [] 9 | } 10 | 11 | if (child.type === Fragment) { 12 | return slotsFormatter(child.props?.children, reactInVueCall) 13 | } 14 | if (typeof child.type === 'string' && (tags === 'all' || tags.indexOf(child.type) > -1)) { 15 | 16 | // Resolve ref 17 | let ref = resolveRef(child) 18 | 19 | const {style, class: className, children, ...otherProps} = child.props || {} 20 | const cleanClassName = Array.from(new Set(formatClass(className))).join(' ') 21 | const cleanStyle = formatStyle(style) 22 | let props = { 23 | ...otherProps, 24 | ...Object.keys(cleanStyle).length === 0 ? {}: {style: cleanStyle}, 25 | ...cleanClassName? {className: cleanClassName}: {}, 26 | ...(ref ? {ref} : {}) 27 | } 28 | if (Object.keys(props).length === 0) { 29 | props = null 30 | } 31 | let newChildren = children 32 | if (newChildren) { 33 | if (["string", "number"].indexOf(typeof newChildren) > -1) { 34 | newChildren = [newChildren] 35 | } else { 36 | if (newChildren instanceof Array) { 37 | newChildren = [...newChildren] 38 | } else { 39 | newChildren = {...newChildren} 40 | } 41 | } 42 | newChildren.__top__ = __top__ 43 | } 44 | 45 | return h(child.type, props, slotsFormatter(newChildren, reactInVueCall)) 46 | } 47 | return reactInVueCall([child], null, division) 48 | } 49 | 50 | export default takeReactDomInVue 51 | -------------------------------------------------------------------------------- /src/pureReactInVue/index.js: -------------------------------------------------------------------------------- 1 | // This HOC is used to solve the problem that when React components are used in Vue components, 2 | // passing children is encapsulated by a div with `style="all:unset"`. 3 | // This HOC will convert the VNode passed to the React component proportionally to the ReactNode. 4 | 5 | import transformer from "./transformer"; 6 | import getDistinguishReactOrVue from "./getDistinguishReactOrVue"; 7 | 8 | const NoWrapFunction = getDistinguishReactOrVue({reactComponents: 'all', domTags: 'all'}) 9 | export default function applyPureReactInVue (ReactComponent, combinedOption) { 10 | return transformer(ReactComponent, {combinedOption: { 11 | pureTransformer: true, 12 | // Custom slot handler 13 | defaultSlotsFormatter: NoWrapFunction, 14 | // This function will be called by the react container component using call(this,...) 15 | defaultPropsFormatter(props, vueInReactCall, hashList) { 16 | const newProps = {} 17 | Object.keys(props).forEach((key) => { 18 | let slot = props[key] 19 | if (!slot) return 20 | if (slot.vueFunction) { 21 | 22 | newProps[key] = function (...args) { 23 | return NoWrapFunction(slot.vueFunction.apply(this, args), vueInReactCall, hashList) 24 | } 25 | // Override the length of the input parameter of the function 26 | Object.defineProperty(newProps[key], 'length', { 27 | get() { 28 | return slot.vueFunction.length 29 | } 30 | }) 31 | return 32 | } 33 | if (slot.vueSlot) { 34 | newProps[key] = NoWrapFunction(slot.vueSlot, vueInReactCall, hashList) 35 | } 36 | }) 37 | return Object.assign(props, newProps) 38 | }, 39 | ...combinedOption 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /dev-project-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/pureReactInVue/getChildInfo.js: -------------------------------------------------------------------------------- 1 | import {formatClass, formatStyle} from '../utils/styleClassTransformer' 2 | import options from '../options' 3 | // import RenderReactNode from './RenderReactNode' 4 | 5 | export default function getChildInfo(child, index, vueInReactCall, defaultSlotsFormatter, hashList) { 6 | 7 | // Filter out ref 8 | const {ref, ...props} = child.props || {} 9 | 10 | const reactScoped = {} 11 | Object.keys(child.children || {}).forEach((key) => { 12 | const fn = child.children[key] 13 | // get reactNode and children 14 | const prefix = options.react.vueNamedSlotsKey.find((prefix) => key.indexOf(prefix) === 0) 15 | if (prefix || key === 'default') { 16 | // replace slot's name to react props key name 17 | const newKey = key.replace(new RegExp(`^${prefix}`), '').replace(/^default$/, 'children') 18 | reactScoped[newKey] = defaultSlotsFormatter.call(child.__top__, fn(), vueInReactCall, hashList) 19 | return 20 | } 21 | 22 | if (typeof fn === 'function') { 23 | // react render props 24 | reactScoped[key] = function (...args) { 25 | fn.__reactArgs = args 26 | return defaultSlotsFormatter(fn.apply(this, args), vueInReactCall, hashList) 27 | } 28 | } 29 | }) 30 | 31 | const newProps = {} 32 | const style = formatStyle(props.style) 33 | const className = Array.from(new Set(formatClass(props.class))).join(' ') 34 | if (Object.keys(style).length > 0) newProps.style = style 35 | if (className !== '') newProps.className = className 36 | 37 | Object.assign(props, { 38 | ...newProps, 39 | ...reactScoped, 40 | }) 41 | delete props.class 42 | 43 | // if (child.type === RenderReactNode) { 44 | // const reactNode = props.node 45 | // props.node = () => reactNode 46 | // } 47 | 48 | // remove ref_for 49 | if (typeof props.ref_for === "boolean") delete props.ref_for 50 | 51 | return props 52 | } 53 | -------------------------------------------------------------------------------- /src/pureReactInVue/takeVueDomInReact.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Fragment} from 'vue'; 3 | import {formatClass, formatStyle} from '../utils/styleClassTransformer' 4 | import DirectiveHOC from "./FakeDirective"; 5 | import resolveRef from "./resolveRef"; 6 | import addScopeId from "./addScopeId"; 7 | 8 | function takeVueDomInReact(child, tags, vueInReactCall, division, slotsFormatter, hashList, __top__) { 9 | if (tags !== 'all' && ! (tags instanceof Array)) { 10 | tags = tags ? [tags]: [] 11 | } 12 | 13 | if (child.type === Fragment) { 14 | return slotsFormatter.call(__top__, child.children, vueInReactCall, hashList) 15 | } 16 | 17 | if (typeof child.type === 'string' && (tags === 'all' || tags.indexOf(child.type) > -1)) { 18 | 19 | // Resolve ref 20 | let ref = resolveRef(child) 21 | 22 | const {style, class: className, ...otherProps} = child.props || {} 23 | const props = { 24 | ...otherProps, 25 | style: formatStyle(style), 26 | className: Array.from(new Set(formatClass(className))).join(' '), 27 | ...(ref? {ref}: {}) 28 | } 29 | // const directives = child.dirs 30 | let newChildren = child.children || props.children 31 | if (newChildren) { 32 | if (["string", "number"].indexOf(typeof newChildren) > -1) { 33 | newChildren = [newChildren] 34 | } else { 35 | newChildren = [...newChildren] 36 | } 37 | newChildren.__top__ = __top__ 38 | } 39 | const reactNode = addScopeId({slotsFormatter.call(__top__, newChildren, vueInReactCall, hashList)}, child.scopeId) 40 | return DirectiveHOC(child, reactNode) 41 | } 42 | return vueInReactCall([child], null, division) 43 | } 44 | 45 | export default takeVueDomInReact 46 | -------------------------------------------------------------------------------- /src/overrideDom.js: -------------------------------------------------------------------------------- 1 | const domMethods = ["getElementById", "getElementsByClassName", "getElementsByTagName", "getElementsByTagNameNS", "querySelector", "querySelectorAll"] 2 | const domTopObject = { Document: {}, Element: {} } 3 | /** 4 | * Override the native method of finding DOM objects. 5 | * In order to ensure that React can obtain the DOM before destruction, 6 | * and the DOM has been unloaded in the beforeDestroy phase of Vue 7 | **/ 8 | export function overwriteDomMethods(refDom) { 9 | Object.keys(domTopObject).forEach((key) => { 10 | domMethods.forEach((method) => { 11 | const old = domTopObject[key][method] || window[key].prototype[method] 12 | if (!old) return 13 | domTopObject[key][method] = old 14 | window[key].prototype[method] = function (...args) { 15 | const oldResult = old.apply(this, args) 16 | if (oldResult && (oldResult.constructor !== NodeList || (oldResult.constructor === NodeList && oldResult.length > 0))) return oldResult 17 | // If each function of Document is called using apply, an error will occur. Here you need to use the native function of Element. 18 | let currentMethod = method 19 | if (currentMethod === 'getElementById'){ 20 | currentMethod = 'querySelector' 21 | args = ['#' + args[0]] 22 | } 23 | const nativeElementFn = domTopObject.Element[currentMethod] || Element.prototype[currentMethod] 24 | return nativeElementFn.apply(refDom, args) 25 | } 26 | }) 27 | }) 28 | } 29 | // Restore native method 30 | export function recoverDomMethods() { 31 | Object.keys(domTopObject).forEach((key) => { 32 | domMethods.forEach((method) => { 33 | window[key].prototype[method] = domTopObject[key][method] 34 | }) 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /dev-project-react/README.md: -------------------------------------------------------------------------------- 1 | # The React development environment for Veaury 2 | The React project created by `create-react-app`, and ran the `npm run eject` command in the project. 3 | This project is only used for the development of `veaury` in the main React environment, not a standard usage scenario. 4 | ## Dependency of Vue3 environment 5 | To enable React projects to develop Vue3 files, some additional dependencies need to be installed. In actual usage scenarios, it is not necessary to deploy two front-end frameworks in one project. 6 | - `vue` —— It is recommended to install the latest version. 7 | ## configuration 8 | - First install `vue` , `veaury` , `vue-loader` and `@vue/babel-plugin-jsx`. 9 | - Add Veaury's webpack plugin to the plugins of `config/webpack.config.js` 10 | ```js 11 | // webpack.config.js 12 | // ... 13 | module.exports = { 14 | // ... 15 | plugins: [ 16 | new (require('veaury/webpack/VeauryVuePlugin')), 17 | // ... 18 | ] 19 | // ... 20 | } 21 | ``` 22 | By default, all vue files and js files in the vue_app directory can use the vue type jsx. 23 | The way to customize the file range supported by vue type jsx: 24 | ```js 25 | // webpack.config.js 26 | // ... 27 | const VeauryVuePlugin = require('veaury/webpack/VeauryVuePlugin') 28 | module.exports = { 29 | // ... 30 | plugins: [ 31 | new VeauryVuePlugin({ 32 | babelLoader: { 33 | // Set all vue files and js files in the 'abc' directory to support vue type jsx 34 | include(filename) { 35 | // ignore node_modules 36 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 37 | // pass all vue file 38 | if (filename.match(/\.(vue|vue\.js)$/i)){ 39 | return filename 40 | } 41 | // pass abc path 42 | if (filename.match(/[/\\]abc[\\/$]+/)) return filename 43 | }, 44 | // exclude() {} 45 | } 46 | }), 47 | // ... 48 | ] 49 | // ... 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /dev-project-vue3/README.md: -------------------------------------------------------------------------------- 1 | # The Vue3 development environment for Veaury 2 | The vue3 project created by `@vue/cli`, and through the configuration of `babel.config.js`, the files in the specified path support React's jsx compilation. 3 | This project is only used for the development of `veaury` in the main Vue3 environment, not a standard usage scenario. 4 | 5 | ## Dependency of React environment 6 | To enable Vue3 project to develop React JS files, some additional dependencies need to be installed. In actual usage scenarios, it is not necessary to deploy two front-end frameworks in one project. 7 | 1. `react` —— It is recommended to install the latest version. 8 | 2. `react-dom` —— It is recommended to install the latest version. 9 | 10 | ## configuration 11 | It is relatively easy to configure the Vue3 project to support React JSX compilation, just configure the `babel.config.js` of the local project. 12 | - First install `react` , `react-dom` , `veaury` and `babel-preset-react-app`. 13 | - Comment `@vue/cli-plugin-babel/preset`. 14 | - Add Veaury's babel preset to the `babel.config.js` 15 | ```js 16 | module.exports = { 17 | presets: [ 18 | // Turn off '@vue/cli-plugin-babel/preset' 19 | // '@vue/cli-plugin-babel/preset', 20 | // veaury babel preset 21 | 'veaury/babel/ReactPreset' 22 | ] 23 | } 24 | ``` 25 | By default, js files in the 'react_app' directory can use the react type jsx. 26 | The way to customize the file range supported by react type jsx: 27 | ```js 28 | module.exports = { 29 | presets: [ 30 | // Turn off '@vue/cli-plugin-babel/preset' 31 | // '@vue/cli-plugin-babel/preset', 32 | // veaury babel preset 33 | ['veaury/babel/ReactPreset', { 34 | test (filename) { 35 | // default ignore node_modules 36 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 37 | // default pass abc path 38 | if (filename.match(/[/\\]abc[\\/$]+/)) return filename 39 | }, 40 | }] 41 | ] 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /src/pureReactInVue/resolveRef.js: -------------------------------------------------------------------------------- 1 | // The VNode internal properties, ref: {i: instance, r: ref_key} 2 | // TODO: ref for 3 | 4 | import couldBeClass from "../utils/couldBeClass"; 5 | 6 | export default function resolveRef(child, children) { 7 | if (typeof child.type?.originReactComponent === 'function' && !couldBeClass(child.type?.originReactComponent)) { 8 | return null 9 | } 10 | 11 | let ref 12 | let refProxy 13 | // vue ENV production 14 | if (child.ref?.k) { 15 | ref = child.ref?.k 16 | refProxy = child.ref?.r 17 | } else { 18 | ref = child.ref?.r 19 | } 20 | if (ref && typeof ref === 'string') { 21 | const refKey = ref 22 | ref = (reactRef) => { 23 | if (child.ref?.i?.refs) { 24 | // object is not extensible, so reassign the whole object 25 | const $refs = {...child.ref.i.refs} 26 | $refs[refKey] = reactRef 27 | child.ref.i.refs = $refs 28 | } 29 | 30 | if (refProxy) { 31 | refProxy.value = reactRef 32 | // In vue ENV development, composition api ref variable exists 33 | } else if (child.ref.i.setupState && refKey in child.ref.i.setupState){ 34 | child.ref.i.setupState[refKey] = reactRef 35 | } 36 | 37 | if (!reactRef) return 38 | reactRef.__syncUpdateProps = function (newExtraData = {}) { 39 | if (!children.__top__) return 40 | child.__extraData = newExtraData 41 | children.__top__.__syncUpdateProps({}) 42 | } 43 | } 44 | const oldRef= ref 45 | ref = new Proxy(oldRef, { 46 | get(target, key) { 47 | return target[key] 48 | }, 49 | set(target, key, value) { 50 | if (child.ref?.i?.refs && refKey in child.ref?.i?.refs) { 51 | const $refs = {...child.ref.i.refs} 52 | $refs[key] = value 53 | child.ref.i.refs = $refs 54 | } 55 | return value 56 | } 57 | }) 58 | } 59 | return ref 60 | } 61 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/vueMissReact/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | -------------------------------------------------------------------------------- /tests/cases/utils/1-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import couldBeClass from "src/utils/couldBeClass"; 6 | import { formatClass, formatStyle } from "src/utils/styleClassTransformer"; 7 | import parseVModel from "src/utils/parseVModel"; 8 | 9 | describe('Test utils', () => { 10 | 11 | test('Test couldBeClass.js', () => { 12 | expect(couldBeClass({})).toBe(false) 13 | expect(couldBeClass(112)).toBe(false) 14 | expect(couldBeClass('1212')).toBe(false) 15 | expect(couldBeClass(null)).toBe(false) 16 | expect(couldBeClass(undefined)).toBe(false) 17 | expect(couldBeClass(function () {})).toBe(false) 18 | expect(couldBeClass(() => {})).toBe(false) 19 | expect(couldBeClass(async function () {})).toBe(false) 20 | expect(couldBeClass(class{})).toBe(true) 21 | 22 | let fun = () => {} 23 | fun.prototype = undefined 24 | expect(couldBeClass(fun)).toBe(false) 25 | 26 | fun = function() {} 27 | fun.prototype.constructor = null 28 | expect(couldBeClass(fun)).toBe(false) 29 | }) 30 | 31 | test('Test styleClassTransformer.js', () => { 32 | expect(formatClass({ 33 | 'AAA': true, 34 | 'BBB': false 35 | }).toString()).toBe('AAA') 36 | expect(formatClass('aaaa bbbb').toString()).toBe('aaaa,bbbb') 37 | expect(formatClass([]).toString()).toBe('') 38 | expect(formatClass(1212).toString()).toBe('') 39 | 40 | expect(JSON.stringify(formatStyle('color: red; font-weight: bold ; aaa; '))).toBe('{"color":"red","fontWeight":"bold"}') 41 | expect(JSON.stringify(formatStyle(1212))).toBe('{}') 42 | }) 43 | 44 | test('Test parseVModel\'s exception', () => { 45 | expect(() => { 46 | parseVModel({ 47 | 'v-model': [1, null] 48 | }) 49 | }).toThrow() 50 | expect(() => { 51 | parseVModel({ 52 | 'v-model': null 53 | }) 54 | }).toThrow() 55 | expect(() => { 56 | parseVModel({ 57 | 'v-models': 1 58 | }) 59 | }).toThrow() 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /tests/cases/vitePlugin/1-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import vitePlugin from "root/vite/index"; 6 | 7 | describe('Test babel/ReactPreset.js', () => { 8 | test('Test type vue', () => { 9 | const result = vitePlugin({ 10 | type: 'vue' 11 | }) 12 | expect(result.length === 4).toBe(true) 13 | expect(!!result[2].config()).toBe(true) 14 | }) 15 | test('Test type react', () => { 16 | const result = vitePlugin({ 17 | type: 'react' 18 | }) 19 | expect(result.length === 4).toBe(true) 20 | }) 21 | test('Test type custom', () => { 22 | let result = vitePlugin({ 23 | type: 'custom', 24 | // The jsx in .vue files and in the directory named 'vue_app' will be parsed with vue jsx. 25 | vueJsxInclude: [/vue&type=script&lang\.[tj]sx?$/, /vue&type=script&setup=true&lang\.[tj]sx?$/, /[/\\]vue_app[\\/$]+/], 26 | vueJsxExclude: [] 27 | }) 28 | expect(result.length === 4).toBe(true) 29 | 30 | result = vitePlugin({ 31 | type: 'custom' 32 | }) 33 | expect(result.length === 4).toBe(true) 34 | }) 35 | // test('Test react-dom/client', async () => { 36 | // const result = vitePlugin({ 37 | // type: 'vue' 38 | // }) 39 | // const resolveId = result[0].resolveId 40 | // // fake this.resolve 41 | // let fakeThis = { 42 | // resolve: () => true 43 | // } 44 | // let resolution 45 | // resolution = await resolveId.call(fakeThis, 'react-dom/client', '') 46 | // expect(resolution).toBe(undefined) 47 | // resolution = await resolveId.call(fakeThis, 'aabbcc', '') 48 | // expect(resolution).toBe(undefined) 49 | // fakeThis = { 50 | // resolve: () => null 51 | // } 52 | // resolution = await resolveId.call(fakeThis, 'react-dom/client', '') 53 | // expect(resolution.id === 'veaury-fake-react-dom-client').toBe(true) 54 | // 55 | // const load = result[0].load 56 | // expect(!!load('veaury-fake-react-dom-client')).toBe(true) 57 | // expect(!!load('asasas')).toBe(false) 58 | // }) 59 | }) 60 | -------------------------------------------------------------------------------- /dev-project-react/src/components/getVNodeAndRenderVNode/index.js: -------------------------------------------------------------------------------- 1 | import { applyPureVueInReact, VueContainer, getVNode, getReactNode } from 'veaury' 2 | import { useRef } from 'react' 3 | import { h } from 'vue' 4 | import AAVue from './AA.vue' 5 | const AA = applyPureVueInReact(AAVue) 6 | 7 | export default function () { 8 | const vSlots = useRef({ 9 | // The scoped slot of the vue component AA 10 | // The v-slots property will automatically convert reactNode to VNode, so there is no need to manually use getVNode for conversion. 11 | renderSomething(VNode) { 12 | return <> 13 |

Render scoped slots

14 | {/* There are two ways to consume VNode in reactNode. */} 15 |
16 | rendered with VueContainer 17 | {/* The first way is to use VueContainer */} 18 | 19 |
20 |
21 | rendered with getReactNode 22 | {/* The second way is to directly convert VNode to ReactNode. */} 23 | {getReactNode(VNode)} 24 |
25 | 26 | } 27 | }) 28 | // `VNodeBar` is a property of type VNode, so use getVNode to convert reactNode to VNode. 29 | const VNodeBar = useRef(getVNode( 30 |
31 |
rendered with a property
32 |
This is Bar's VNode
33 |
34 | )) 35 | const VNode = h('div', null, 'simple vnode') 36 | return <> 37 |

This example shows how to transform and render directly in reactNode and VNode.

38 | 39 | {/* Just for testing */} 40 | 41 | {/* Just for testing */} 42 | 43 | {/* Just for testing */} 44 | 45 | {/* Just for testing */} 46 | 4444}/> 47 | 48 | } 49 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/slots/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /dev-project-react/config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /vite/cjs/index.cjs: -------------------------------------------------------------------------------- 1 | const vue = require('@vitejs/plugin-vue') 2 | const react = require('@vitejs/plugin-react') 3 | const vueJsx = require('@vitejs/plugin-vue-jsx') 4 | // const requireTransform = require('vite-plugin-require-transform').default 5 | // function ReactDOMTransformPlugin() { 6 | // return { 7 | // async resolveId(source, importer, options) { 8 | // if (source.match(/react-dom\/client/)) { 9 | // const resolution = await this.resolve(source, importer, { skipSelf: true, ...options }) 10 | // if (!resolution) { 11 | // return { id: 'veaury-fake-react-dom-client', moduleSideEffects: true } 12 | // } 13 | // } 14 | // }, 15 | // load(id) { 16 | // if (id === 'veaury-fake-react-dom-client') { 17 | // return `export * from 'react-dom'; export {default} from 'react-dom';` 18 | // } 19 | // } 20 | // } 21 | // } 22 | 23 | function veauryVitePlugins({isNuxt, type, vueJsxInclude, vueJsxExclude, vueOptions = {}, vueJsxOptions: initVueJsxOptions = {}, reactOptions = {}}) { 24 | 25 | let vueJsxOptions = {...initVueJsxOptions} 26 | if (type === 'react') { 27 | vueJsxOptions.include = [/vue&type=script&lang\.[tj]sx$/i, /vue&type=script&setup=true&lang\.[tj]sx$/i, /[/\\]vue_app[\\/][\w\W]+\.[tj]sx$/] 28 | } 29 | if (type === 'vue') { 30 | vueJsxOptions.exclude = [/[/\\]react_app[\\/$]+/] 31 | } 32 | if (type === 'custom') { 33 | if (vueJsxInclude) { 34 | vueJsxOptions.include = vueJsxInclude 35 | } 36 | if (vueJsxExclude) { 37 | vueJsxOptions.exclude = vueJsxExclude 38 | } 39 | } 40 | 41 | return [ 42 | // ReactDOMTransformPlugin(), 43 | // requireTransform({ 44 | 45 | // fileRegex: /veaury/ 46 | // }), 47 | isNuxt === true? {}: vue(vueOptions), 48 | // Make vueJsx plugin run time earlier 49 | { 50 | ...vueJsx(vueJsxOptions), 51 | enforce: 'pre' 52 | }, 53 | // recover esbuild include 54 | { 55 | config(){ 56 | return { 57 | esbuild: { 58 | include: /\.[jt]sx?$/ 59 | } 60 | } 61 | } 62 | }, 63 | react({ 64 | jsxImportSource: 'react', 65 | ...reactOptions 66 | }) 67 | ] 68 | } 69 | 70 | module.exports = veauryVitePlugins 71 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import {Fragment} from "react"; 3 | 4 | const originOptions = { 5 | react: { 6 | componentWrap: 'div', 7 | slotWrap: 'div', 8 | componentWrapAttrs: { 9 | __use_react_component_wrap: '', 10 | style: { 11 | all: 'unset' 12 | } 13 | }, 14 | slotWrapAttrs: { 15 | __use_react_slot_wrap: '', 16 | style: { 17 | all: 'unset' 18 | } 19 | }, 20 | vueNamedSlotsKey: ['node:'] 21 | }, 22 | vue: { 23 | // wrapper 24 | componentWrapHOC: (VueComponentMountAt, nativeProps = []) => { 25 | // portals 26 | return function ({ portals = [] } = {}) { 27 | return ({VueComponentMountAt}{portals.map(({ Portal, key }) => )}) 28 | } 29 | }, 30 | componentWrapAttrs: { 31 | 'data-use-vue-component-wrap': '', 32 | style: { 33 | all: 'unset', 34 | } 35 | }, 36 | slotWrapAttrs: { 37 | 'data-use-vue-slot-wrap': '', 38 | style: { 39 | all: 'unset' 40 | } 41 | } 42 | } 43 | } 44 | 45 | export function setOptions (newOptions = { 46 | react: {}, 47 | vue: {} 48 | }, options = originOptions, clone) { 49 | if (!newOptions.vue) { 50 | newOptions.vue = {} 51 | } 52 | if (!newOptions.react) { 53 | newOptions.react = {} 54 | } 55 | const params = [options, { 56 | ...newOptions, 57 | react: { 58 | ...options.react, 59 | ...newOptions.react, 60 | componentWrapAttrs: { 61 | ...options.react.componentWrapAttrs, 62 | ...newOptions.react.componentWrapAttrs 63 | }, 64 | slotWrapAttrs: { 65 | ...options.react.slotWrapAttrs, 66 | ...newOptions.react.slotWrapAttrs 67 | } 68 | }, 69 | vue: { 70 | ...options.vue, 71 | ...newOptions.vue, 72 | componentWrapAttrs: { 73 | ...options.vue.componentWrapAttrs, 74 | ...newOptions.vue.componentWrapAttrs 75 | }, 76 | slotWrapAttrs: { 77 | ...options.vue.slotWrapAttrs, 78 | ...newOptions.vue.slotWrapAttrs 79 | } 80 | } 81 | }] 82 | if (clone) { 83 | params.unshift({}) 84 | } 85 | 86 | return Object.assign.apply(this, params) 87 | } 88 | 89 | export default originOptions 90 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/watchref/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 76 | -------------------------------------------------------------------------------- /dev-project-react/src/components/VModel/index.js: -------------------------------------------------------------------------------- 1 | import { applyVueInReact } from 'veaury' 2 | import BasicVue from './Basic' 3 | import Basic1Vue from './Basic1' 4 | import Basic2Vue from './Basic2' 5 | import { useState } from 'react' 6 | 7 | const Basic = applyVueInReact(BasicVue) 8 | const Basic1 = applyVueInReact(Basic1Vue) 9 | const Basic2 = applyVueInReact(Basic2Vue) 10 | export default function () { 11 | const [foo, setFoo] = useState(Math.random()) 12 | const [bar, setBar] = useState(Math.random()) 13 | const [zoo, setZoo] = useState(Math.random()) 14 | const [type0, setType0] = useState('') 15 | const [type1, setType1] = useState('') 16 | const [type2, setType2] = useState('') 17 | 18 | return
19 |

Pass v-model to Vue Components.

20 |

The usage of 'v-model' is similar to the usage of 'v-model' of vue's jsx.

21 | {console.log(`bar update to ${v}`)}} 23 | v-model={[foo, setFoo]} v-model-bar={[bar, setBar]} 24 | onUpdate-modelValue={(v) => {console.log(`modelValue update to ${v}`)}} 25 | > 26 |
27 | This is the Vue component Slot from React
28 | foo's value: {foo}
29 | bar's value: {bar} 30 |
31 |
32 | typeof received v-model zoo: {type0} 33 | {setType0(typeof val); setZoo(val)}, 'zoo']} testId="0"/> 34 | typeof received v-model zoo: {type1} 35 | {setType1(typeof val); setZoo(val)}, 'zoo', ['number']]} testId="1"/> 36 | typeof received v-model value: {type2} 37 | {setType2(typeof val); setZoo(val)}, ['number']]} testId="2"/> 38 | 42 |
43 | This is the Vue component Slot from React
44 | zoo's value: {zoo} 45 |
46 |
47 |
48 | } 49 | -------------------------------------------------------------------------------- /pure_mode.md: -------------------------------------------------------------------------------- 1 | # Pure mode 2 | 3 | ## Example 4 | The example is given to illustrate the difference between `applyReactInVue` and `applyPureReactInVue`. 5 | 6 | The React component AA. 7 | (./react_app/AA) 8 | ```jsx 9 | import React from 'react' 10 | 11 | const containerStyle = { 12 | background: '#91e7fc', 13 | width: 500, 14 | margin: 'auto', 15 | padding: 10, 16 | display: 'flex', 17 | justifyContent: 'space-around' 18 | } 19 | export default function AA(props) { 20 | return
21 | {props.children} 22 |
23 | } 24 | ``` 25 | 26 | The Vue example. 27 | Shows the difference in display of AA components using `applyReactInVue` and `applyPureReactInVue` respectively. 28 | ```vue 29 | 44 | 45 | 55 | 56 | 70 | ``` 71 | ## Preview 72 | 73 | 74 | ## Why does it behave like this? 75 | 76 | 77 | `applyPureReactInVue` will directly convert VNode into ReactNode, and a new container will be created when Vue components are encountered, while `applyReactInVue` will not do any conversion processing on VNode, but will create a container to render VNode uniformly. 78 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/basic/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 73 | 74 | 81 | -------------------------------------------------------------------------------- /dev-project-vue3/src/router/index.js: -------------------------------------------------------------------------------- 1 | import {createRouter, createWebHashHistory} from 'vue-router' 2 | import PureReactInVue from '../pages/pureReactInVue/index' 3 | import watchref from '../pages/watchref/index' 4 | // For unit testing, because jest does not generate scopeId when processing vue files. 5 | if (!PureReactInVue.__scopeId) { 6 | PureReactInVue.__scopeId = 'data-v-5fa1ff81' 7 | } 8 | export default createRouter({ 9 | history: createWebHashHistory(), 10 | routes: [ 11 | { 12 | name: 'VueMissReact', 13 | path: '/VueMissReact', 14 | component: () => import('../pages/vueMissReact') 15 | }, 16 | { 17 | name: 'basic', 18 | path: '/basic', 19 | component: () => import('../pages/basic') 20 | }, 21 | { 22 | name: 'events', 23 | path: '/events', 24 | component: () => import('../pages/events') 25 | }, 26 | { 27 | name: 'slots', 28 | path: '/slots', 29 | component: () => import('../pages/slots') 30 | }, 31 | { 32 | name: 'context', 33 | path: '/context', 34 | component: () => import('../pages/context') 35 | }, 36 | { 37 | name: 'routerView', 38 | path: '/routerView', 39 | component: () => import('../pages/routerView'), 40 | children: [ 41 | { 42 | name: 'routerViewSub', 43 | path: '', 44 | component: () => import('../pages/routerView/Sub') 45 | } 46 | ] 47 | }, 48 | { 49 | name: 'lazyReactInVue', 50 | path: '/lazyReactInVue', 51 | component: () => import('../pages/lazyReactInVue') 52 | }, 53 | { 54 | name: 'injection', 55 | path: '/useInjectPropsFromWrapper', 56 | component: () => import('../pages/useInjectPropsFromWrapper') 57 | }, 58 | { 59 | name: 'crossingProvider', 60 | path: '/crossingProvider', 61 | component: () => import('../pages/crossProvider') 62 | }, 63 | { 64 | name: 'pureReactInVue', 65 | path: '/pureReactInVue', 66 | component: PureReactInVue 67 | // component: () => import('../pages/pureReactInVue') 68 | }, 69 | { 70 | name: 'watchref', 71 | path: '/watchref', 72 | component: watchref, 73 | }, 74 | { 75 | name: 'introduce', 76 | path: '/:default(.*)', 77 | component: () => import('../pages/introduce') 78 | }, 79 | ], 80 | }) 81 | -------------------------------------------------------------------------------- /webpack/VeauryVuePlugin.cjs: -------------------------------------------------------------------------------- 1 | const {VueLoaderPlugin} = require('vue-loader') 2 | let vueJsx = [] 3 | try { 4 | require.resolve('@vue/babel-plugin-jsx') 5 | require.resolve('babel-loader') 6 | vueJsx.push('@vue/babel-plugin-jsx') 7 | } catch(e) { 8 | console.warn(e) 9 | } 10 | 11 | 12 | class VeauryVuePlugin { 13 | constructor(options = {}) { 14 | this.vueLoaderPluginInstance = new VueLoaderPlugin() 15 | this.options = { ...options } 16 | } 17 | apply(compiler) { 18 | function defaultBabelInclude(filename) { 19 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 20 | // // default pass vue file 21 | if (filename.match(/\.(vue|vue\.[jt]sx?)$/i)) return filename 22 | if (filename.match(/vue&type=script&setup=true&lang\.[tj]sx$/i)) return filename 23 | if (filename.match(/vue&type=script&lang\.[tj]sx$/i)) return filename 24 | // default pass vue_app path 25 | if (filename.match(/[/\\]vue_app[\\/$]+/)) return filename 26 | } 27 | const {babelLoader, isNext} = this.options 28 | const extensions = compiler.options.resolve?.extensions 29 | if (extensions && extensions.indexOf('.vue') < 0) { 30 | extensions.push('.vue') 31 | } 32 | 33 | const rules = compiler.options.module.rules 34 | const firstOneOf = rules.find((item) => item.oneOf) 35 | 36 | if (isNext === true) { 37 | // remove error-loader 38 | firstOneOf.oneOf = firstOneOf.oneOf.filter((item) => item?.use?.loader !== 'error-loader') 39 | 40 | firstOneOf.oneOf.push({ 41 | test: /\.css$/, 42 | use: [ 43 | 'style-loader','css-loader' 44 | ] 45 | }) 46 | } 47 | 48 | rules.unshift({ 49 | test: /\.vue$/, 50 | loader: 'vue-loader', 51 | options: { hotReload: false }, 52 | }) 53 | 54 | rules.push({ 55 | test: /\.vue$/, 56 | type: 'javascript/auto' 57 | }, 58 | { 59 | include: defaultBabelInclude, 60 | ...babelLoader, 61 | test: /\.(js|mjs|jsx|ts|tsx)$/, 62 | loader: 'babel-loader', 63 | options: { 64 | cacheDirectory: false, 65 | cacheCompression: false, 66 | configFile: false, 67 | babelrc: false, 68 | plugins: [ 69 | // Compile with Vue's jsx 70 | ...vueJsx 71 | ] 72 | } 73 | } 74 | ) 75 | // apply VueLoaderPlugin 76 | this.vueLoaderPluginInstance.apply(compiler) 77 | } 78 | } 79 | 80 | module.exports = VeauryVuePlugin; 81 | -------------------------------------------------------------------------------- /dev-project-react/src/components/reactMissVue/defineReactMissVue.js: -------------------------------------------------------------------------------- 1 | import { defineStore, createPinia } from 'pinia' 2 | import { createRouter, createWebHashHistory, useRouter, useRoute } from 'vue-router' 3 | import { createReactMissVue, applyPureReactInVue } from 'veaury' 4 | 5 | // create vue-router instance 6 | const router = createRouter({ 7 | // Using vue-router inside route 'ReactMissVue' 8 | history: createWebHashHistory('/#/ReactMissVue'), 9 | routes: [ 10 | { 11 | name: '', 12 | path: '/aaa', 13 | component: applyPureReactInVue(() =>
react use vue-router
path: /aaa
) 14 | }, 15 | { 16 | name: 'empty', 17 | path: '/:default(.*)', 18 | component: applyPureReactInVue(() =>
react use vue-router
empty
) 19 | }, 20 | ], 21 | }) 22 | 23 | // Yes, that's what vue-router has a charm over react-router 24 | router.beforeEach((to, from, next) => { 25 | console.log('The beforeEach hook of vue-router is triggered!', to, from) 26 | next() 27 | }) 28 | 29 | // create a pinia store 30 | const useFooStore = defineStore({ 31 | id: 'foo', 32 | state() { 33 | return { 34 | name: 'Eduardo' 35 | } 36 | }, 37 | actions: { 38 | changeName(name) { 39 | this.$patch({ 40 | name 41 | }) 42 | } 43 | } 44 | }) 45 | // create a pinia store 46 | const useBarStore = defineStore({ 47 | id: 'bar', 48 | state() { 49 | return { 50 | name: 'Italy' 51 | } 52 | }, 53 | actions: { 54 | changeName(name) { 55 | this.$patch({ 56 | name 57 | }) 58 | } 59 | } 60 | }) 61 | 62 | // create a ReactMissVue instance 63 | let [useReactMissVue, ReactMissVue] = createReactMissVue({ 64 | useVueInjection() { 65 | // This object can be obtained by using useReactMissVue in the react component 66 | return { 67 | fooStore: useFooStore(), 68 | barStore: useBarStore(), 69 | vueRouter: useRouter(), 70 | vueRoute: useRoute() 71 | } 72 | }, 73 | // beforeVueAppMount can only be used in the outermost ReactMissVue 74 | // Because veaury will only create a vue application in the outermost layer 75 | beforeVueAppMount(app) { 76 | // register pinia 77 | app.use(createPinia()) 78 | // register vue-router 79 | app.use(router) 80 | } 81 | }) 82 | 83 | export { 84 | useReactMissVue, 85 | ReactMissVue 86 | } 87 | -------------------------------------------------------------------------------- /dev-project-react/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack/VeauryVuePlugin.mjs: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module'; 2 | import {VueLoaderPlugin} from 'vue-loader' 3 | const require = createRequire(import.meta.url); 4 | 5 | let vueJsx = [] 6 | try { 7 | require.resolve('@vue/babel-plugin-jsx') 8 | require.resolve('babel-loader') 9 | vueJsx.push('@vue/babel-plugin-jsx') 10 | } catch(e) { 11 | console.warn(e) 12 | } 13 | 14 | 15 | class VeauryVuePlugin { 16 | constructor(options = {}) { 17 | this.vueLoaderPluginInstance = new VueLoaderPlugin() 18 | this.options = { ...options } 19 | } 20 | apply(compiler) { 21 | function defaultBabelInclude(filename) { 22 | if (filename.match(/[/\\]node_modules[\\/$]+/)) return 23 | // // default pass vue file 24 | if (filename.match(/\.(vue|vue\.[jt]sx?)$/i)) return filename 25 | if (filename.match(/vue&type=script&setup=true&lang\.[tj]sx$/i)) return filename 26 | if (filename.match(/vue&type=script&lang\.[tj]sx$/i)) return filename 27 | // default pass vue_app path 28 | if (filename.match(/[/\\]vue_app[\\/$]+/)) return filename 29 | } 30 | const {babelLoader, isNext} = this.options 31 | const extensions = compiler.options.resolve?.extensions 32 | if (extensions && extensions.indexOf('.vue') < 0) { 33 | extensions.push('.vue') 34 | } 35 | 36 | const rules = compiler.options.module.rules 37 | const firstOneOf = rules.find((item) => item.oneOf) 38 | 39 | if (isNext === true) { 40 | // remove error-loader 41 | firstOneOf.oneOf = firstOneOf.oneOf.filter((item) => item?.use?.loader !== 'error-loader') 42 | 43 | firstOneOf.oneOf.push({ 44 | test: /\.css$/, 45 | use: [ 46 | 'style-loader','css-loader' 47 | ] 48 | }) 49 | } 50 | 51 | rules.unshift({ 52 | test: /\.vue$/, 53 | loader: 'vue-loader', 54 | options: { hotReload: false }, 55 | }) 56 | 57 | rules.push({ 58 | test: /\.vue$/, 59 | type: 'javascript/auto' 60 | }, 61 | { 62 | include: defaultBabelInclude, 63 | ...babelLoader, 64 | test: /\.(js|mjs|jsx|ts|tsx)$/, 65 | loader: 'babel-loader', 66 | options: { 67 | cacheDirectory: false, 68 | cacheCompression: false, 69 | configFile: false, 70 | babelrc: false, 71 | plugins: [ 72 | // Compile with Vue's jsx 73 | ...vueJsx 74 | ] 75 | } 76 | } 77 | ) 78 | // apply VueLoaderPlugin 79 | this.vueLoaderPluginInstance.apply(compiler) 80 | } 81 | } 82 | 83 | export default VeauryVuePlugin; 84 | -------------------------------------------------------------------------------- /src/pureVueInReact/getDistinguishReactOrVue.js: -------------------------------------------------------------------------------- 1 | import {h} from 'vue'; 2 | import getChildInfo from "./getChildInfo"; 3 | import takeReactDomInVue from "./takeReactDomInVue"; 4 | import resolveRef from "./resolveRef"; 5 | import {VueContainer} from "../applyVueInReact"; 6 | 7 | export default function getDistinguishReactOrVue({vueComponents: Component, domTags, division = true}) { 8 | return function defaultSlotsFormatter(children, reactInVueCall) { 9 | if (children == null) return children 10 | if (!(children instanceof Array)) children = [children] 11 | let newChildren = [] 12 | children.forEach((child, topIndex) => { 13 | // if (!child || child.type === Comment) return 14 | if (!child.type?.originVueComponent && child.type !== VueContainer) { 15 | if (child.__v_isVNode || typeof child === 'string' || typeof child === 'number') { 16 | newChildren.push(child) 17 | return 18 | } 19 | if (child.type) { 20 | let newChild = takeReactDomInVue(child, domTags, reactInVueCall, division, defaultSlotsFormatter, children.__top__) 21 | newChildren.push(newChild) 22 | } 23 | return 24 | } 25 | // vue component in react 26 | let VueComponent = child.type.originVueComponent 27 | 28 | if (child.type === VueContainer) { 29 | if (child.props.component) { 30 | VueComponent = child.props.component 31 | child = {...child} 32 | const props = {...child.props} 33 | delete props.component 34 | child.props = props 35 | } else { 36 | newChildren.push(child.props.node) 37 | return 38 | } 39 | } 40 | 41 | let newChild 42 | if (Component !== 'all' && !(Component instanceof Array)) { 43 | Component = [Component] 44 | } 45 | if (Component === 'all' || Component.indexOf(VueComponent) > -1) { 46 | child = {...child} 47 | child.__top__ = children.__top__ 48 | const { props, slots } = getChildInfo(child, `_key_${topIndex}`, reactInVueCall, defaultSlotsFormatter) 49 | 50 | const ref = resolveRef(child) 51 | 52 | if (child.children) { 53 | child.children.__top__ = children.__top__ 54 | } 55 | 56 | newChild = h(VueComponent, {...props}, slots) 57 | } else { 58 | newChild = takeReactDomInVue(child, domTags, reactInVueCall, division, defaultSlotsFormatter) 59 | } 60 | newChildren.push(newChild) 61 | }) 62 | newChildren = newChildren.flat(Infinity) 63 | return newChildren.length === 1 ? newChildren[0] : newChildren 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dev-project-react/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right 103 | 104 | 105 | 121 | -------------------------------------------------------------------------------- /dev-project-vue3/src/pages/useInjectPropsFromWrapper/react_app/BasicVueFromReact.js: -------------------------------------------------------------------------------- 1 | import {toRef} from 'vue' 2 | import {useStore} from 'vuex' 3 | import {useRoute, useRouter} from 'vue-router' 4 | import {applyPureReactInVue} from 'veaury' 5 | import React, {useRef} from 'react' 6 | 7 | // This React component will be used in the Vue app and needs to use the vue-router and vuex hooks 8 | 9 | // setup mode 10 | function VueInjectionHookInSetupMode(vueProps) { 11 | // Vue hooks can be used in this function 12 | // This function will be called in the 'setup' hook of the Vue wrapper component 13 | const store = useStore() 14 | const route = useRoute() 15 | const router = useRouter() 16 | 17 | // The returned object will be passed to the React component as props 18 | return { 19 | // you need to manually convert to proxy with 'setup' mode 20 | // otherwise it will not be responsive 21 | fullPath: toRef(route, 'fullPath'), 22 | count: toRef(store.state, 'count'), 23 | changeQuery: () => router.replace({ 24 | query: { 25 | a: Math.random() 26 | } 27 | }), 28 | incrementCount: () => store.dispatch('increment') 29 | } 30 | } 31 | 32 | // computed mode 33 | function VueInjectionHookInComputedMode(vueProps) { 34 | // The context of the function is binding with the proxy from the 'getCurrentInstance' hook 35 | // Returning a function represents the computed of the options api 36 | // All logic code should be written in this computed function. 37 | // The lifecycle cannot be used in this function. If you want to use the lifecycle, you can only use the 'setup' mode 38 | return function computedFunction() { 39 | return { 40 | fullPath: this.$route.fullPath, 41 | count: this.$store.state.count, 42 | changeQuery: () => this.$router.replace({ 43 | query: { 44 | a: Math.random() 45 | } 46 | }), 47 | incrementCount: () => this.$store.dispatch('increment') 48 | } 49 | } 50 | } 51 | 52 | function ReactComponent (props) { 53 | const style = useRef({ 54 | background: '#91e7fc', 55 | width: 500, 56 | margin: 'auto', 57 | padding: 10, 58 | lineHeight: '30px' 59 | }) 60 | return (
61 | This is the React Component 62 | 63 | the path info from 'vue-router': {props.fullPath}
64 | the count from 'vuex': {props.count} 65 |

66 | 67 | {props.children} 68 |
) 69 | } 70 | 71 | // Vue's injection function has two modes: 'setup' and 'computed'. 72 | // Refer to the case of the above two injection function types. 73 | // Also try replacing the option injectPropsFromWrapper with 'VueInjectionHookInComputedMode' 74 | export default applyPureReactInVue(ReactComponent, { 75 | useInjectPropsFromWrapper: VueInjectionHookInSetupMode 76 | }) 77 | -------------------------------------------------------------------------------- /src/pureReactInVue/getDistinguishReactOrVue.js: -------------------------------------------------------------------------------- 1 | import React, {forwardRef} from 'react'; 2 | import getChildInfo from "./getChildInfo"; 3 | import {isTextOwner} from "./isTextChild"; 4 | import takeVueDomInReact from "./takeVueDomInReact"; 5 | import DirectiveHOC from "./FakeDirective"; 6 | import {pureInterceptProps} from "./interceptProps"; 7 | import resolveRef from "./resolveRef"; 8 | import {Comment} from 'vue' 9 | import addScopeId from "./addScopeId"; 10 | import setChildKey from "../utils/setChildKey"; 11 | 12 | export default function getDistinguishReactOrVue({reactComponents: Component, domTags, division = true}) { 13 | return function defaultSlotsFormatter(children, vueInReactCall, hashList) { 14 | if (children && children.forEach) { 15 | if (!children.__top__) children.__top__ = this 16 | const newChildren = [] 17 | children.forEach((child, topIndex) => { 18 | if (!child || child.type === Comment) return 19 | if (!child.type?.originReactComponent) { 20 | 21 | // reactNode 22 | if (child.$$typeof || typeof child === 'string' || typeof child === 'number') { 23 | newChildren.push(child) 24 | return 25 | } 26 | if (isTextOwner(child)) { 27 | child.children.trim() !== '' && newChildren.push(child.children.trim()) 28 | return 29 | } 30 | if (child.type) { 31 | let newChild = takeVueDomInReact(child, domTags, vueInReactCall, division, defaultSlotsFormatter, hashList, children.__top__) 32 | newChild = setChildKey(newChild, children, topIndex) 33 | addScopeId(newChild, child.scopeId) 34 | newChildren.push(newChild) 35 | } 36 | return 37 | } 38 | // react component in vue 39 | let ReactComponent = child.type.originReactComponent 40 | 41 | let newChild 42 | if (Component !== 'all' && !(Component instanceof Array)) { 43 | Component = [Component] 44 | } 45 | if (Component === 'all' || Component.indexOf(ReactComponent) > -1) { 46 | child.__top__ = children.__top__ 47 | const props = getChildInfo(child, `_key_${topIndex}`, vueInReactCall, defaultSlotsFormatter, hashList) 48 | 49 | const ref = resolveRef(child, children) 50 | 51 | if (child.children) { 52 | child.children.__top__ = children.__top__ 53 | } 54 | 55 | newChild = DirectiveHOC(child, 56 | ) 57 | } else { 58 | newChild = isTextOwner(child) ? child.text : takeVueDomInReact(child, domTags, vueInReactCall, division, defaultSlotsFormatter, hashList) 59 | } 60 | newChild = setChildKey(newChild, children, topIndex) 61 | addScopeId(newChild, child.scopeId) 62 | newChildren.push(newChild) 63 | }) 64 | return newChildren.length === 1 ? newChildren[0] : newChildren 65 | } 66 | return children 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/parseVModel.js: -------------------------------------------------------------------------------- 1 | function createModifiers(VModels, modelKey, modifiers) { 2 | const modifiersObject = {} 3 | modifiers.forEach((key) => { 4 | modifiersObject[key] = true 5 | }) 6 | return VModels[(modelKey === 'modelValue'? 'model': modelKey) + 'Modifiers'] = modifiersObject 7 | } 8 | 9 | function setVModel(VModels, originValue, modelKey, errorFrom = 'v-model') { 10 | const modelMix = originValue 11 | if (modelMix instanceof Array) { 12 | if (typeof modelMix[1] !== 'function') { 13 | throw Error(`[error:veaury] Parameter type error from '${errorFrom}', a single v-model is an array, the second element of the array must be a setter function`) 14 | } 15 | const setter = modelMix[1] 16 | if (typeof modelMix[2] === 'string') { 17 | modelKey = modelMix[2] 18 | if (modelMix[3] instanceof Array) { 19 | createModifiers(VModels, modelKey, modelMix[3]) 20 | } 21 | } else if (modelMix[2] instanceof Array) { 22 | createModifiers(VModels, modelKey, modelMix[2]) 23 | } 24 | const onUpdate = VModels['onUpdate:' + modelKey] 25 | if (typeof onUpdate === 'function') { 26 | VModels['onUpdate:' + modelKey] = (...args) => { 27 | onUpdate.apply(this, args) 28 | setter.apply(this, args) 29 | } 30 | } else { 31 | VModels['onUpdate:' + modelKey] = setter 32 | } 33 | VModels[modelKey] = modelMix[0] 34 | } else { 35 | throw Error(`[error:veaury] Parameter type error from '${errorFrom}', a single v-model is an array, such as [val, setter, argumentKey, modifiers] or [val, setter, modifiers]`) 36 | } 37 | } 38 | 39 | // parse v-model 40 | export default function parseVModel (props) { 41 | const VModels = {} 42 | const newProps = {...props} 43 | 44 | Object.keys(props).forEach((key) => { 45 | // parse onUpdate event 46 | let matcher = key.match(/^onUpdate-([^-]+)/) 47 | if (matcher) { 48 | delete newProps[key] 49 | const onUpdate = VModels[`onUpdate:${matcher[1]}`] 50 | if (typeof onUpdate === 'function') { 51 | VModels[`onUpdate:${matcher[1]}`] = (...args) => { 52 | onUpdate.apply(this, args) 53 | props[key].apply(this, args) 54 | } 55 | } else { 56 | VModels[`onUpdate:${matcher[1]}`] = props[key] 57 | } 58 | return 59 | } 60 | 61 | // single v-model 62 | matcher = key.match(/^v-model($|:([^:]+)|-([^:]+))/) 63 | if (matcher) { 64 | let modelKey = matcher[2] || matcher[3] || 'modelValue' 65 | setVModel(VModels, props[key], modelKey) 66 | delete newProps[key] 67 | return 68 | } 69 | // multiple v-model 70 | if (key === 'v-models') { 71 | if (typeof props[key] === 'object' && !(props[key] instanceof Array)) { 72 | const modelsParam = props[key] 73 | Object.keys(modelsParam).forEach((key) => { 74 | setVModel(VModels, modelsParam[key], key, 'v-models') 75 | }) 76 | delete newProps[key] 77 | } else { 78 | throw Error('[error:veaury] The parameter \'v-models\' must be an object type, such as {[argumentKey]: singleVModel}') 79 | } 80 | } 81 | }) 82 | return {...newProps, ...VModels} 83 | } 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "veaury", 3 | "private": false, 4 | "version": "2.6.3", 5 | "description": "Use React in Vue3 and Vue3 in React, And as perfect as possible!", 6 | "main": "dist/veaury.umd.js", 7 | "module": "dist/veaury.esm.js", 8 | "typings": "types/veaury.d.ts", 9 | "scripts": { 10 | "build": "cross-env BABEL_ENV=rollup rollup -c", 11 | "coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls", 12 | "dev:vue": "npm run serve --prefix dev-project-vue3", 13 | "dev:react": "npm run start --prefix dev-project-react", 14 | "build:vue": "npm run build --prefix dev-project-vue3", 15 | "build:react": "npm run build --prefix dev-project-react", 16 | "remotedev:vue": "cross-env BUILD_TYPE=remote npm run serve --prefix dev-project-vue3", 17 | "remotedev:react": "cross-env BUILD_TYPE=remote npm run start --prefix dev-project-react", 18 | "remotebuild:vue": "cross-env BUILD_TYPE=remote npm run build --prefix dev-project-vue3", 19 | "remotebuild:react": "cross-env BUILD_TYPE=remote npm run build --prefix dev-project-react", 20 | "dev-setup:yarn": "yarn --cwd dev-project-vue3 && yarn --cwd dev-project-react", 21 | "dev-setup:npm": "npm i --prefix dev-project-vue3 && npm i --prefix dev-project-react", 22 | "test-setup:yarn": "yarn --cwd tests", 23 | "test-setup:npm": "npm i --prefix tests", 24 | "setup:yarn": "yarn && npm run dev-setup:yarn && npm run test-setup:yarn", 25 | "setup:npm": "npm i && npm run dev-setup:npm && npm run test-setup:npm", 26 | "test": "jest", 27 | "test:remote": "cross-env TEST_REMOTE=true jest --coverage false", 28 | "test:temp": "cross-env TEST_TEMP=true jest --coverage false", 29 | "remote-beta:yarn": "yarn add veaury@beta --cwd dev-project-vue3 && yarn add veaury@beta --cwd dev-project-react && yarn add veaury@beta --cwd tests", 30 | "remote-latest:yarn": "yarn add veaury --cwd dev-project-vue3 && yarn add veaury --cwd dev-project-react && yarn add veaury --cwd tests", 31 | "remote-beta:npm": "npm i veaury@beta --prefix dev-project-vue3 && npm i veaury@beta --prefix dev-project-react && npm i veaury@beta --prefix tests", 32 | "remote-latest:npm": "npm i veaury --prefix dev-project-vue3 && npm i veaury --prefix dev-project-react && npm i veaury --prefix tests" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/devilwjp/veaury.git" 37 | }, 38 | "author": "天堂里的花大咩", 39 | "license": "ISC", 40 | "bugs": { 41 | "url": "https://github.com/devilwjp/veaury/issues" 42 | }, 43 | "files": [ 44 | "dist", 45 | "types", 46 | "babel", 47 | "webpack", 48 | "vite" 49 | ], 50 | "homepage": "https://github.com/devilwjp/veaury#readme", 51 | "peerDependencies": { 52 | "react": ">= 16.4.0", 53 | "react-dom": ">= 16.4.0" 54 | }, 55 | "keywords": [ 56 | "vue", 57 | "react", 58 | "veaury", 59 | "reactvue", 60 | "vueinreact", 61 | "reactinvue", 62 | "vuereact" 63 | ], 64 | "devDependencies": { 65 | "@babel/core": "^7.7.4", 66 | "@babel/plugin-external-helpers": "^7.7.4", 67 | "@babel/plugin-proposal-class-properties": "^7.7.4", 68 | "@babel/plugin-proposal-object-rest-spread": "^7.7.4", 69 | "@babel/preset-env": "^7.7.4", 70 | "@babel/preset-react": "^7.7.4", 71 | "@vue/babel-plugin-jsx": "^1.1.1", 72 | "@vue/compiler-sfc": "^3.2.38", 73 | "@vue/vue3-jest": "^29.0.0", 74 | "babel-jest": "^29.0.1", 75 | "babel-preset-latest": "^6.24.1", 76 | "coveralls": "^3.1.1", 77 | "cross-env": "^7.0.3", 78 | "jest": "^29.0.1", 79 | "jest-environment-jsdom": "^29.0.1", 80 | "rollup": "^1.27.5", 81 | "rollup-plugin-babel": "^4.3.3", 82 | "rollup-plugin-commonjs": "^10.1.0", 83 | "rollup-plugin-node-resolve": "^5.2.0", 84 | "rollup-plugin-uglify": "^6.0.3" 85 | } 86 | } 87 | --------------------------------------------------------------------------------