From 51b382adba952c0489eb0b57d1fc6567430d9a80 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Tue, 26 Aug 2025 20:14:06 +0900 Subject: [PATCH 1/2] fix: fix bundled react issue --- src/commands/start/web/index.ts | 416 +++++++++++++++--- .../hotReload/widget.proxy.js.template | 8 +- 2 files changed, 369 insertions(+), 55 deletions(-) diff --git a/src/commands/start/web/index.ts b/src/commands/start/web/index.ts index 724f177..1db55c7 100644 --- a/src/commands/start/web/index.ts +++ b/src/commands/start/web/index.ts @@ -38,11 +38,6 @@ const startWebCommand = async () => { const viteServer = await createServer({ ...resultViteConfig, root: PROJECT_DIRECTORY, - optimizeDeps: { - include: ['react', 'react-dom'], - exclude: ['src'], - force: true - }, server: { fs: { strict: false @@ -52,68 +47,381 @@ const startWebCommand = async () => { interval: 100 }, }, + optimizeDeps: { + esbuildOptions: { + plugins: [ + // @note When the React version of Mendix is updated, the following content must also be updated. + // @todo Depending on the React version, we need to consider whether there is a way to handle this automatically rather than manually. + { + name: 'replace-react-with-virtual', + setup(build) { + build.onResolve({ filter: /^react$/ }, (args) => { + return { + path: 'mendix:react', + namespace: 'mendix-react', + external: false + }; + }); + + build.onResolve({ filter: /^react-dom$/ }, (args) => { + return { + path: 'mendix:react-dom', + namespace: 'mendix-react-dom', + external: false + }; + }); + + build.onResolve({ filter: /^react-dom\/client$/ }, (args) => { + return { + path: 'mendix:react-dom/client', + namespace: 'mendix-react-dom-client', + external: false + }; + }); + + build.onResolve({ filter: /^react\/jsx-runtime$/ }, (args) => { + return { + path: 'mendix:react/jsx-runtime', + namespace: 'mendix-react-jsx-runtime', + external: false + }; + }); + + build.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, (args) => { + return { + path: 'mendix:react/jsx-dev-runtime', + namespace: 'mendix-react-jsx-dev-runtime', + external: false + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'mendix-react' }, () => { + return { + contents: ` + const React = window.React; + + export const Children = React.Children; + export const Component = React.Component; + export const Fragment = React.Fragment; + export const Profiler = React.Profiler; + export const PureComponent = React.PureComponent; + export const StrictMode = React.StrictMode; + export const Suspense = React.Suspense; + export const cloneElement = React.cloneElement; + export const createContext = React.createContext; + export const createElement = React.createElement; + export const createFactory = React.createFactory; + export const createRef = React.createRef; + export const forwardRef = React.forwardRef; + export const isValidElement = React.isValidElement; + export const lazy = React.lazy; + export const memo = React.memo; + export const startTransition = React.startTransition; + export const unstable_act = React.unstable_act; + export const useCallback = React.useCallback; + export const useContext = React.useContext; + export const useDebugValue = React.useDebugValue; + export const useDeferredValue = React.useDeferredValue; + export const useEffect = React.useEffect; + export const useId = React.useId; + export const useImperativeHandle = React.useImperativeHandle; + export const useInsertionEffect = React.useInsertionEffect; + export const useLayoutEffect = React.useLayoutEffect; + export const useMemo = React.useMemo; + export const useReducer = React.useReducer; + export const useRef = React.useRef; + export const useState = React.useState; + export const useSyncExternalStore = React.useSyncExternalStore; + export const useTransition = React.useTransition; + export const version = React.version; + + export default React; + `, + loader: 'js', + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'mendix-react-dom' }, () => { + return { + contents: ` + const ReactDOM = window.ReactDOM; + + export const createPortal = ReactDOM.createPortal; + export const createRoot = ReactDOM.createRoot; + export const findDOMNode = ReactDOM.findDOMNode; + export const flushSync = ReactDOM.flushSync; + export const hydrate = ReactDOM.hydrate; + export const hydrateRoot = ReactDOM.hydrateRoot; + export const render = ReactDOM.render; + export const unmountComponentAtNode = ReactDOM.unmountComponentAtNode; + export const unstable_batchedUpdates = ReactDOM.unstable_batchedUpdates; + export const unstable_renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer; + export const version = ReactDOM.version; + + export default ReactDOM; + `, + loader: 'js', + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'mendix-react-dom-client' }, () => { + return { + contents: ` + const ReactDOMClient = window.ReactDOMClient; + + export const createRoot = ReactDOMClient.createRoot; + export const hydrateRoot = ReactDOMClient.hydrateRoot; + + export default ReactDOMClient; + `, + loader: 'js', + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'mendix-react-jsx-runtime' }, () => { + return { + contents: ` + const ReactJSXRuntime = window.ReactJSXRuntime; + + export const Fragment = ReactJSXRuntime.Fragment; + export const jsx = ReactJSXRuntime.jsx; + export const jsxs = ReactJSXRuntime.jsxs; + + export default ReactJSXRuntime; + `, + loader: 'js', + }; + }); + + build.onLoad({ filter: /.*/, namespace: 'mendix-react-jsx-dev-runtime' }, () => { + return { + contents: ` + const ReactJSXDevRuntime = window.ReactJSXDevRuntime; + + export const Fragment = ReactJSXDevRuntime.Fragment; + export const jsxDEV = ReactJSXDevRuntime.jsxDEV; + + export default ReactJSXDevRuntime; + `, + loader: 'js', + }; + }); + + build.onResolve({ filter: /.*node_modules[\\\/]react[\\\/]index\.js$/ }, (args) => { + return { + path: 'mendix:react', + namespace: 'mendix-react', + external: false + }; + }); + + build.onResolve({ filter: /.*node_modules[\\\/]react-dom[\\\/]index\.js$/ }, (args) => { + return { + path: 'mendix:react-dom', + namespace: 'mendix-react-dom', + external: false + }; + }); + + build.onResolve({ filter: /.*node_modules[\\\/]react-dom[\\\/]client\.js$/ }, (args) => { + return { + path: 'mendix:react-dom/client', + namespace: 'mendix-react-dom-client', + external: false + }; + }); + } + } + ], + } + }, plugins: [ ...resultViteConfig.plugins as PluginOption[], + // @note When the React version of Mendix is updated, the following content must also be updated. + // @todo Depending on the React version, we need to consider whether there is a way to handle this automatically rather than manually. { - name: 'mendix-hotreload-react', + name: 'mendix-hotreload-react-18.2.0', enforce: 'pre', - transform(code, id) { - if (!id.includes('node_modules') && /\.(tsx?|jsx?)$/.test(id)) { - let transformedCode = code; + resolveId(id) { + if (id === 'react') { + return { id: 'mendix:react', external: true }; + } + + if (id === 'react-dom') { + return { id: 'mendix:react-dom', external: true }; + } + + if (id === 'react-dom/client') { + return { id: 'mendix:react-dom/client', external: true }; + } + + if (id === 'react/jsx-runtime') { + return { id: 'mendix:react/jsx-runtime', external: true }; + } + + if (id === 'react/jsx-dev-runtime') { + return { id: 'mendix:react/jsx-dev-runtime', external: true }; + } + }, + load(id) { + if (id === 'mendix:react') { + return ` + const React = window.React; + + export const Children = React.Children; + export const Component = React.Component; + export const Fragment = React.Fragment; + export const Profiler = React.Profiler; + export const PureComponent = React.PureComponent; + export const StrictMode = React.StrictMode; + export const Suspense = React.Suspense; + export const cloneElement = React.cloneElement; + export const createContext = React.createContext; + export const createElement = React.createElement; + export const createFactory = React.createFactory; + export const createRef = React.createRef; + export const forwardRef = React.forwardRef; + export const isValidElement = React.isValidElement; + export const lazy = React.lazy; + export const memo = React.memo; + export const startTransition = React.startTransition; + export const unstable_act = React.unstable_act; + export const useCallback = React.useCallback; + export const useContext = React.useContext; + export const useDebugValue = React.useDebugValue; + export const useDeferredValue = React.useDeferredValue; + export const useEffect = React.useEffect; + export const useId = React.useId; + export const useImperativeHandle = React.useImperativeHandle; + export const useInsertionEffect = React.useInsertionEffect; + export const useLayoutEffect = React.useLayoutEffect; + export const useMemo = React.useMemo; + export const useReducer = React.useReducer; + export const useRef = React.useRef; + export const useState = React.useState; + export const useSyncExternalStore = React.useSyncExternalStore; + export const useTransition = React.useTransition; + export const version = React.version; + + export default React; + `; + } + + if (id === 'mendix:react-dom') { + return ` + const ReactDOM = window.ReactDOM; + + export const createPortal = ReactDOM.createPortal; + export const createRoot = ReactDOM.createRoot; + export const findDOMNode = ReactDOM.findDOMNode; + export const flushSync = ReactDOM.flushSync; + export const hydrate = ReactDOM.hydrate; + export const hydrateRoot = ReactDOM.hydrateRoot; + export const render = ReactDOM.render; + export const unmountComponentAtNode = ReactDOM.unmountComponentAtNode; + export const unstable_batchedUpdates = ReactDOM.unstable_batchedUpdates; + export const unstable_renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer; + export const version = ReactDOM.version; + + export default ReactDOM; + `; + } + + if (id === 'mendix:react-dom/client') { + return ` + const ReactDOMClient = window.ReactDOMClient; + + export const createRoot = ReactDOMClient.createRoot; + export const hydrateRoot = ReactDOMClient.hydrateRoot; + + export default ReactDOMClient; + `; + } + + if (id === 'mendix:react/jsx-runtime') { + return ` + const ReactJSXRuntime = window.ReactJSXRuntime; + + export const Fragment = ReactJSXRuntime.Fragment; + export const jsx = ReactJSXRuntime.jsx; + export const jsxs = ReactJSXRuntime.jsxs; + + export default ReactJSXRuntime; + `; + } + + if (id === 'mendix:react/jsx-dev-runtime') { + return ` + const ReactJSXDevRuntime = window.ReactJSXDevRuntime; + + export const Fragment = ReactJSXDevRuntime.Fragment; + export const jsxDEV = ReactJSXDevRuntime.jsxDEV; + + export default ReactJSXDevRuntime; + `; + } + } + }, + // { + // name: 'mendix-hotreload-react', + // enforce: 'pre', + // transform(code, id) { + // if (!id.includes('node_modules') && /\.(tsx?|jsx?)$/.test(id)) { + // let transformedCode = code; - transformedCode = transformedCode.replace( - /import\s+(\w+)\s+from\s+['"]react['"]/g, - 'const $1 = window.React' - ); + // transformedCode = transformedCode.replace( + // /import\s+(\w+)\s+from\s+['"]react['"]/g, + // 'const $1 = window.React' + // ); - transformedCode = transformedCode.replace( - /import\s+\*\s+as\s+(\w+)\s+from\s+['"]react['"]/g, - 'const $1 = window.React' - ); + // transformedCode = transformedCode.replace( + // /import\s+\*\s+as\s+(\w+)\s+from\s+['"]react['"]/g, + // 'const $1 = window.React' + // ); - transformedCode = transformedCode.replace( - /import\s+{([^}]+)}\s+from\s+['"]react['"]/g, - (match, imports) => { - const cleanImports = imports.replace(/\s+/g, ' ').trim(); - return `const { ${cleanImports} } = window.React`; - } - ); + // transformedCode = transformedCode.replace( + // /import\s+{([^}]+)}\s+from\s+['"]react['"]/g, + // (match, imports) => { + // const cleanImports = imports.replace(/\s+/g, ' ').trim(); + // return `const { ${cleanImports} } = window.React`; + // } + // ); - transformedCode = transformedCode.replace( - /import\s+(\w+)\s*,\s*{([^}]+)}\s+from\s+['"]react['"]/g, - (match, defaultImport, namedImports) => { - const cleanImports = namedImports.replace(/\s+/g, ' ').trim(); - return `const ${defaultImport} = window.React;\nconst { ${cleanImports} } = window.React`; - } - ); + // transformedCode = transformedCode.replace( + // /import\s+(\w+)\s*,\s*{([^}]+)}\s+from\s+['"]react['"]/g, + // (match, defaultImport, namedImports) => { + // const cleanImports = namedImports.replace(/\s+/g, ' ').trim(); + // return `const ${defaultImport} = window.React;\nconst { ${cleanImports} } = window.React`; + // } + // ); - transformedCode = transformedCode.replace( - /import\s+(\w+)\s+from\s+['"]react-dom['"]/g, - 'const $1 = window.ReactDOM' - ); + // transformedCode = transformedCode.replace( + // /import\s+(\w+)\s+from\s+['"]react-dom['"]/g, + // 'const $1 = window.ReactDOM' + // ); - transformedCode = transformedCode.replace( - /import\s+{([^}]+)}\s+from\s+['"]react-dom['"]/g, - 'const { $1 } = window.ReactDOM' - ); + // transformedCode = transformedCode.replace( + // /import\s+{([^}]+)}\s+from\s+['"]react-dom['"]/g, + // 'const { $1 } = window.ReactDOM' + // ); - transformedCode = transformedCode.replace( - /import\s+{([^}]+)}\s+from\s+['"]react-dom\/client['"]/g, - 'const { $1 } = window.ReactDOM' - ); + // transformedCode = transformedCode.replace( + // /import\s+{([^}]+)}\s+from\s+['"]react-dom\/client['"]/g, + // 'const { $1 } = window.ReactDOM' + // ); - transformedCode = transformedCode.replace( - /import\s+type\s+{([^}]+)}\s+from\s+['"]react['"]/g, - '// Type import removed: $1' - ); + // transformedCode = transformedCode.replace( + // /import\s+type\s+{([^}]+)}\s+from\s+['"]react['"]/g, + // '// Type import removed: $1' + // ); - return { - code: transformedCode, - map: null - }; - } - }, - } + // return { + // code: transformedCode, + // map: null + // }; + // } + // }, + // }, ] }); diff --git a/src/configurations/hotReload/widget.proxy.js.template b/src/configurations/hotReload/widget.proxy.js.template index ba23216..d4396d8 100644 --- a/src/configurations/hotReload/widget.proxy.js.template +++ b/src/configurations/hotReload/widget.proxy.js.template @@ -14,10 +14,16 @@ if (!window.__vite_plugin_react_preamble_installed__) { import React from 'react'; import * as ReactDOM from 'react-dom'; +import * as ReactDOMClient from 'react-dom/client'; +import * as ReactJSXRuntime from 'react/jsx-runtime'; +import * as ReactJSXDevRuntime from 'react/jsx-dev-runtime'; if (!window.React) { window.React = React; - window.ReactDOM = ReactDOM; + window.ReactDOM = ReactDOM.default; + window.ReactDOMClient = ReactDOMClient.default; + window.ReactJSXRuntime = ReactJSXRuntime.default; + window.ReactJSXDevRuntime = ReactJSXDevRuntime.default; } const DEV_SERVER_URL = '{{ DEV_SERVER_URL }}'; From 5b9418809d6bd6cfc9ce2cba8aa76d856d8cdff4 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Thu, 28 Aug 2025 11:11:29 +0900 Subject: [PATCH 2/2] feat: bump version to 0.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6441b16..f714d3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@repixelcorp/hyper-pwt", - "version": "0.1.3", + "version": "0.1.4", "description": "A faster, more modern, superior alternative for Mendix PWT.", "repository": { "type": "git",