diff --git a/packages/remix-dev/package.json b/packages/remix-dev/package.json index 2b990a5a524..780f4231265 100644 --- a/packages/remix-dev/package.json +++ b/packages/remix-dev/package.json @@ -103,6 +103,7 @@ "strip-ansi": "^6.0.1", "tiny-invariant": "^1.2.0", "vite": "5.1.3", + "@vitejs/plugin-react-swc": "^3.6.0", "wrangler": "^3.28.2" }, "peerDependencies": { @@ -110,6 +111,7 @@ "@remix-run/serve": "^2.9.1", "typescript": "^5.1.0", "vite": "^5.1.0", + "@vitejs/plugin-react-swc": "^3.6.0", "wrangler": "^3.28.2" }, "peerDependenciesMeta": { @@ -122,6 +124,9 @@ "vite": { "optional": true }, + "@vitejs/plugin-react-swc": { + "optional": true + }, "wrangler": { "optional": true } diff --git a/packages/remix-dev/vite/plugin.ts b/packages/remix-dev/vite/plugin.ts index 7ff22795066..540046fe55d 100644 --- a/packages/remix-dev/vite/plugin.ts +++ b/packages/remix-dev/vite/plugin.ts @@ -5,7 +5,7 @@ import { type BinaryLike, createHash } from "node:crypto"; import * as path from "node:path"; import * as url from "node:url"; import * as fse from "fs-extra"; -import babel from "@babel/core"; +import reactPlugin from "@vitejs/plugin-react-swc"; import { type ServerBuild, unstable_setDevServerHooks as setDevServerHooks, @@ -289,7 +289,6 @@ let serverBuildId = VirtualModule.id("server-build"); let serverManifestId = VirtualModule.id("server-manifest"); let browserManifestId = VirtualModule.id("browser-manifest"); let hmrRuntimeId = VirtualModule.id("hmr-runtime"); -let injectHmrRuntimeId = VirtualModule.id("inject-hmr-runtime"); const resolveRelativeRouteFilePath = ( route: ConfigRoute, @@ -599,7 +598,9 @@ let deepFreeze = (o: any) => { return o; }; -export type RemixVitePlugin = (config?: VitePluginConfig) => Vite.Plugin[]; +export type RemixVitePlugin = ( + config?: VitePluginConfig +) => Vite.PluginOption[]; export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { // Prevent mutations to the user config remixUserConfig = deepFreeze(remixUserConfig); @@ -984,7 +985,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { hmr: { runtime: path.posix.join( ctx.remixConfig.publicPath, - VirtualModule.url(injectHmrRuntimeId) + VirtualModule.url(hmrRuntimeId) ), }, entry: { @@ -1585,6 +1586,7 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { } }, }, + ...reactPlugin(), { name: "remix-route-exports", async transform(code, id, options) { @@ -1630,93 +1632,28 @@ export const remixVitePlugin: RemixVitePlugin = (remixUserConfig = {}) => { }); }, }, + // TODO: combine HMR plugins { - name: "remix-inject-hmr-runtime", + name: "remix-hmr", enforce: "pre", resolveId(id) { - if (id === injectHmrRuntimeId) - return VirtualModule.resolve(injectHmrRuntimeId); + if (id === hmrRuntimeId) return VirtualModule.resolve(hmrRuntimeId); }, async load(id) { - if (id !== VirtualModule.resolve(injectHmrRuntimeId)) return; + if (id !== VirtualModule.resolve(hmrRuntimeId)) return; return [ - `import RefreshRuntime from "${hmrRuntimeId}"`, + // preamble + `import * as RefreshRuntime from "/@react-refresh"`, "RefreshRuntime.injectIntoGlobalHook(window)", "window.$RefreshReg$ = () => {}", "window.$RefreshSig$ = () => (type) => type", "window.__vite_plugin_react_preamble_installed__ = true", + // framework integration + `window.__registerBeforePerformReactRefresh(() => console.log('[remix] before react refresh'))`, + `window.__getReactRefreshIgnoredExports = ({id}) => {console.log('[remix] ignored exports: ', id); return ['meta', 'links', 'handle', 'clientLoader'];}`, ].join("\n"); }, - }, - { - name: "remix-hmr-runtime", - enforce: "pre", - resolveId(id) { - if (id === hmrRuntimeId) return VirtualModule.resolve(hmrRuntimeId); - }, - async load(id) { - if (id !== VirtualModule.resolve(hmrRuntimeId)) return; - - let reactRefreshDir = path.dirname( - require.resolve("react-refresh/package.json") - ); - let reactRefreshRuntimePath = path.join( - reactRefreshDir, - "cjs/react-refresh-runtime.development.js" - ); - - return [ - "const exports = {}", - await fse.readFile(reactRefreshRuntimePath, "utf8"), - await fse.readFile( - require.resolve("./static/refresh-utils.cjs"), - "utf8" - ), - "export default exports", - ].join("\n"); - }, - }, - { - name: "remix-react-refresh-babel", - async transform(code, id, options) { - if (viteCommand !== "serve") return; - if (id.includes("/node_modules/")) return; - - let [filepath] = id.split("?"); - let extensionsRE = /\.(jsx?|tsx?|mdx?)$/; - if (!extensionsRE.test(filepath)) return; - - let devRuntime = "react/jsx-dev-runtime"; - let ssr = options?.ssr === true; - let isJSX = filepath.endsWith("x"); - let useFastRefresh = !ssr && (isJSX || code.includes(devRuntime)); - if (!useFastRefresh) return; - - let result = await babel.transformAsync(code, { - configFile: false, - babelrc: false, - filename: id, - sourceFileName: filepath, - parserOpts: { - sourceType: "module", - allowAwaitOutsideFunction: true, - }, - plugins: [[require("react-refresh/babel"), { skipEnvCheck: true }]], - sourceMaps: true, - }); - if (result === null) return; - - code = result.code!; - let refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/; - if (refreshContentRE.test(code)) { - code = addRefreshWrapper(ctx.remixConfig, code, id); - } - return { code, map: result.map }; - }, - }, - { - name: "remix-hmr-updates", async handleHotUpdate({ server, file, modules, read }) { let route = getRoute(ctx.remixConfig, file); @@ -1777,68 +1714,6 @@ function isEqualJson(v1: unknown, v2: unknown) { return JSON.stringify(v1) === JSON.stringify(v2); } -function addRefreshWrapper( - remixConfig: ResolvedVitePluginConfig, - code: string, - id: string -): string { - let route = getRoute(remixConfig, id); - let acceptExports = route - ? [ - "clientAction", - "clientLoader", - "handle", - "meta", - "links", - "shouldRevalidate", - ] - : []; - return ( - REACT_REFRESH_HEADER.replaceAll("__SOURCE__", JSON.stringify(id)) + - code + - REACT_REFRESH_FOOTER.replaceAll("__SOURCE__", JSON.stringify(id)) - .replaceAll("__ACCEPT_EXPORTS__", JSON.stringify(acceptExports)) - .replaceAll("__ROUTE_ID__", JSON.stringify(route?.id)) - ); -} - -const REACT_REFRESH_HEADER = ` -import RefreshRuntime from "${hmrRuntimeId}"; - -const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; -let prevRefreshReg; -let prevRefreshSig; - -if (import.meta.hot && !inWebWorker) { - if (!window.__vite_plugin_react_preamble_installed__) { - throw new Error( - "Remix Vite plugin can't detect preamble. Something is wrong." - ); - } - - prevRefreshReg = window.$RefreshReg$; - prevRefreshSig = window.$RefreshSig$; - window.$RefreshReg$ = (type, id) => { - RefreshRuntime.register(type, __SOURCE__ + " " + id) - }; - window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; -}`.replace(/\n+/g, ""); - -const REACT_REFRESH_FOOTER = ` -if (import.meta.hot && !inWebWorker) { - window.$RefreshReg$ = prevRefreshReg; - window.$RefreshSig$ = prevRefreshSig; - RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => { - RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports); - import.meta.hot.accept((nextExports) => { - if (!nextExports) return; - __ROUTE_ID__ && window.__remixRouteModuleUpdates.set(__ROUTE_ID__, nextExports); - const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(currentExports, nextExports, __ACCEPT_EXPORTS__); - if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage); - }); - }); -}`; - function getRoute( pluginConfig: ResolvedVitePluginConfig, file: string diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34bdab5023c..f1d774a10f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1063,6 +1063,9 @@ importers: '@types/ws': specifier: ^7.4.1 version: 7.4.7 + '@vitejs/plugin-react-swc': + specifier: ^3.6.0 + version: 3.6.0(vite@5.1.3) esbuild-register: specifier: ^3.3.2 version: 3.5.0(esbuild@0.17.6) @@ -4476,6 +4479,131 @@ packages: dependencies: '@sinonjs/commons': 3.0.0 + /@swc/core-darwin-arm64@1.4.8: + resolution: {integrity: sha512-hhQCffRTgzpTIbngSnC30vV6IJVTI9FFBF954WEsshsecVoCGFiMwazBbrkLG+RwXENTrMhgeREEFh6R3KRgKQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.4.8: + resolution: {integrity: sha512-P3ZBw8Jr8rKhY/J8d+6WqWriqngGTgHwtFeJ8MIakQJTbdYbFgXSZxcvDiERg3psbGeFXaUaPI0GO6BXv9k/OQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.4.8: + resolution: {integrity: sha512-PP9JIJt19bUWhAGcQW6qMwTjZOcMyzkvZa0/LWSlDm0ORYVLmDXUoeQbGD3e0Zju9UiZxyulnpjEN0ZihJgPTA==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.4.8: + resolution: {integrity: sha512-HvEWnwKHkoVUr5iftWirTApFJ13hGzhAY2CMw4lz9lur2m+zhPviRRED0FCI6T95Knpv7+8eUOr98Z7ctrG6DQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.4.8: + resolution: {integrity: sha512-kY8+qa7k/dEeBq9p0Hrta18QnJPpsiJvDQSLNaTIFpdM3aEM9zbkshWz8gaX5VVGUEALowCBUWqmzO4VaqM+2w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.4.8: + resolution: {integrity: sha512-0WWyIw432wpO/zeGblwq4f2YWam4pn8Z/Ig4KzHMgthR/KmiLU3f0Z7eo45eVmq5vcU7Os1zi/Zb65OOt09q/w==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.4.8: + resolution: {integrity: sha512-p4yxvVS05rBNCrBaSTa20KK88vOwtg8ifTW7ec/yoab0bD5EwzzB8KbDmLLxE6uziFa0sdjF0dfRDwSZPex37Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.4.8: + resolution: {integrity: sha512-jKuXihxAaqUnbFfvPxtmxjdJfs87F1GdBf33il+VUmSyWCP4BE6vW+/ReDAe8sRNsKyrZ3UH1vI5q1n64csBUA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.4.8: + resolution: {integrity: sha512-O0wT4AGHrX8aBeH6c2ADMHgagAJc5Kf6W48U5moyYDAkkVnKvtSc4kGhjWhe1Yl0sI0cpYh2In2FxvYsb44eWw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.4.8: + resolution: {integrity: sha512-C2AYc3A2o+ECciqsJWRgIpp83Vk5EaRzHe7ed/xOWzVd0MsWR+fweEsyOjlmzHfpUxJSi46Ak3/BIZJlhZbXbg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.4.8: + resolution: {integrity: sha512-uY2RSJcFPgNOEg12RQZL197LZX+MunGiKxsbxmh22VfVxrOYGRvh4mPANFlrD1yb38CgmW1wI6YgIi8LkIwmWg==} + engines: {node: '>=10'} + requiresBuild: true + peerDependencies: + '@swc/helpers': ^0.5.0 + peerDependenciesMeta: + '@swc/helpers': + optional: true + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.6 + optionalDependencies: + '@swc/core-darwin-arm64': 1.4.8 + '@swc/core-darwin-x64': 1.4.8 + '@swc/core-linux-arm-gnueabihf': 1.4.8 + '@swc/core-linux-arm64-gnu': 1.4.8 + '@swc/core-linux-arm64-musl': 1.4.8 + '@swc/core-linux-x64-gnu': 1.4.8 + '@swc/core-linux-x64-musl': 1.4.8 + '@swc/core-win32-arm64-msvc': 1.4.8 + '@swc/core-win32-ia32-msvc': 1.4.8 + '@swc/core-win32-x64-msvc': 1.4.8 + dev: true + + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: true + + /@swc/types@0.1.6: + resolution: {integrity: sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==} + dependencies: + '@swc/counter': 0.1.3 + dev: true + /@testing-library/cypress@8.0.2(cypress@9.6.0): resolution: {integrity: sha512-KVdm7n37sg/A4e3wKMD4zUl0NpzzVhx06V9Tf0hZHZ7nrZ4yFva6Zwg2EFF1VzHkEfN/ahUzRtT1qiW+vuWnJw==} engines: {node: '>=12', npm: '>=6'} @@ -5296,6 +5424,17 @@ packages: - terser - ts-node + /@vitejs/plugin-react-swc@3.6.0(vite@5.1.3): + resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==} + peerDependencies: + vite: ^4 || ^5 + dependencies: + '@swc/core': 1.4.8 + vite: 5.1.3(@types/node@18.17.1) + transitivePeerDependencies: + - '@swc/helpers' + dev: true + /@web3-storage/multipart-parser@1.0.0: resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} dev: false