14 |
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 |
2 |
3 |
This is the Vue Component.
4 | the path info from 'react-router': {{pathname + search}}
8 |
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 |
2 |
3 |
This is the Vue Component.
4 | the path info from 'react-router': {{pathname + search}}
4 | received modelValue's value: {{$attrs.modelValue}}
5 | received bar's value: {{$attrs.bar}}
6 |
7 |
8 |
9 |
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 |
2 |
3 |
6 | scopedSlotA-{{attr1}}
7 |
8 |
11 | slotA
12 |
13 |
14 |
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 |
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 |
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 |
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 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 |
30 |
Pure mode
31 |
32 |
A
33 |
B
34 |
C
35 |
36 |
37 |
Normal mode
38 |
39 |
A
40 |
B
41 |
C
42 |
43 |
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 |
2 |
3 | This example shows the basic usage of `applyReactInVue`.
4 |
5 |
6 | Using React components in Vue components.
7 |
8 |
9 |
10 |
11 | This is the Vue default slot
12 |
13 |
14 | current time: {{currentTime}}
15 |
16 |
17 |
18 | This is a react functional children in Vue, and received {{value}}
19 |
20 |
)
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 |