From 42b0ed766d47f5b7d84a2bb3dca6b47b842e58cd Mon Sep 17 00:00:00 2001 From: acaldas Date: Wed, 16 Oct 2024 18:22:34 +0100 Subject: [PATCH] fix: use single instance of react dependencies on studio mode --- studio/index.ts | 7 +- studio/vite-plugin.ts | 178 +++++++++++++++++++++++++++------------ vite.renderer.config.mts | 13 +-- 3 files changed, 131 insertions(+), 67 deletions(-) diff --git a/studio/index.ts b/studio/index.ts index d278f907..f87ea483 100644 --- a/studio/index.ts +++ b/studio/index.ts @@ -66,13 +66,18 @@ export async function startServer() { open: true, }, plugins: [ - viteConnectDevStudioPlugin(), + viteConnectDevStudioPlugin(true), viteEnvs({ declarationFile: join(studioDirname, '../.env'), computedEnv: studioConfig, }), runShellScriptPlugin(viteEnvsScript), ], + build: { + rollupOptions: { + input: 'index.html', + }, + }, }; const server = await createServer(config); diff --git a/studio/vite-plugin.ts b/studio/vite-plugin.ts index 48adce04..d6a1868b 100644 --- a/studio/vite-plugin.ts +++ b/studio/vite-plugin.ts @@ -4,10 +4,16 @@ import { Alias, AliasOptions, Plugin, + PluginOption, ViteDevServer, normalizePath, } from 'vite'; +// matches react, react-dom, and all it's sub-imports like react-dom/client +export const externalIds = /^react(-dom)?(\/.*)?$/; +// used to find react imports in the code for text replacement +export const externalImports = /react(-dom)?(\/.*)?/; + export const LOCAL_DOCUMENT_MODELS_IMPORT = 'LOCAL_DOCUMENT_MODELS'; export const LOCAL_DOCUMENT_EDITORS_IMPORT = 'LOCAL_DOCUMENT_EDITORS'; @@ -98,9 +104,66 @@ export function watchLocalFiles( } } +// https://github.com/vitejs/vite/issues/6393#issuecomment-1006819717 +// vite dev server doesn't support setting dependencies as external +// as when building the app. +function viteIgnoreStaticImport(importKeys: (string | RegExp)[]): Plugin { + return { + name: 'vite-plugin-ignore-static-import', + enforce: 'pre', + // vite will still append /@id/ to an external import + // so this will rewrite the 'vite:import-analysis' prefix + configResolved(resolvedConfig) { + const VALID_ID_PREFIX = `/@id/`; + const values = importKeys.map(key => + typeof key === 'string' ? key : key.source, + ); + const reg = new RegExp( + `("|')${VALID_ID_PREFIX}${values.length === 1 ? values[0] : `(${values.join('|')})("|')`}`, + 'g', + ); + + (resolvedConfig.plugins as Plugin[]).push({ + name: 'vite-plugin-ignore-static-import-replace-idprefix', + transform: code => { + const matches = code.matchAll(reg); + for (const match of matches) { + code = code.replaceAll( + match[0], + match[0].replace('/@id/', ''), + ); + } + return code; + }, + }); + }, + // prevents the external import from being transformed to 'node_modules/...' + resolveId: id => { + if ( + importKeys.some(key => + typeof key === 'string' ? key === id : key.test(id), + ) + ) { + return { id, external: true }; + } + }, + // returns empty string to prevent "Pre-transform error: Failed to load url" + load(id) { + if ( + importKeys.some(key => + typeof key === 'string' ? key === id : key.test(id), + ) + ) { + return ''; + } + }, + }; +} + export function viteConnectDevStudioPlugin( + enabled = false, env?: Record, -): Plugin { +): PluginOption[] { const studioConfig = getStudioConfig(env); const importKeys = [ LOCAL_DOCUMENT_MODELS_IMPORT, @@ -110,64 +173,67 @@ export function viteConnectDevStudioPlugin( const localDocumentEditorsPath = studioConfig[LOCAL_DOCUMENT_EDITORS_IMPORT]; - return { - name: 'vite-plugin-connect-dev-studio', - enforce: 'pre', - config(config) { - if (!localDocumentModelsPath && !localDocumentEditorsPath) { - return; - } - - // adds the provided paths to be resolved by vite - const resolve = config.resolve ?? {}; - const alias = resolve.alias; - let resolvedAlias: AliasOptions | undefined; - if (Array.isArray(alias)) { - const arrayAlias = [...(alias as Alias[])]; - - if (localDocumentModelsPath) { - arrayAlias.push({ - find: LOCAL_DOCUMENT_MODELS_IMPORT, - replacement: localDocumentModelsPath, - }); + return [ + enabled && viteIgnoreStaticImport([externalImports]), + { + name: 'vite-plugin-connect-dev-studio', + enforce: 'pre', + config(config) { + if (!localDocumentModelsPath && !localDocumentEditorsPath) { + return; } - if (localDocumentEditorsPath) { - arrayAlias.push({ - find: LOCAL_DOCUMENT_EDITORS_IMPORT, - replacement: localDocumentEditorsPath, - }); + // adds the provided paths to be resolved by vite + const resolve = config.resolve ?? {}; + const alias = resolve.alias; + let resolvedAlias: AliasOptions | undefined; + if (Array.isArray(alias)) { + const arrayAlias = [...(alias as Alias[])]; + + if (localDocumentModelsPath) { + arrayAlias.push({ + find: LOCAL_DOCUMENT_MODELS_IMPORT, + replacement: localDocumentModelsPath, + }); + } + + if (localDocumentEditorsPath) { + arrayAlias.push({ + find: LOCAL_DOCUMENT_EDITORS_IMPORT, + replacement: localDocumentEditorsPath, + }); + } + resolvedAlias = arrayAlias; + } else if (typeof alias === 'object') { + resolvedAlias = { ...alias, ...studioConfig }; + } else if (typeof alias === 'undefined') { + resolvedAlias = { ...studioConfig }; + } else { + console.error('resolve.alias was not recognized'); } - resolvedAlias = arrayAlias; - } else if (typeof alias === 'object') { - resolvedAlias = { ...alias, ...studioConfig }; - } else if (typeof alias === 'undefined') { - resolvedAlias = { ...studioConfig }; - } else { - console.error('resolve.alias was not recognized'); - } - if (resolvedAlias) { - resolve.alias = resolvedAlias; - config.resolve = resolve; - } - }, - configureServer(server) { - watchLocalFiles( - server, - localDocumentModelsPath, - localDocumentEditorsPath, - ); - }, - resolveId: id => { - // if the path was not provided then declares the local - // imports as external so that vite ignores them - if (importKeys.includes(id)) { - return { - id, - external: true, - }; - } + if (resolvedAlias) { + resolve.alias = resolvedAlias; + config.resolve = resolve; + } + }, + configureServer(server) { + watchLocalFiles( + server, + localDocumentModelsPath, + localDocumentEditorsPath, + ); + }, + resolveId: id => { + // if the path was not provided then declares the local + // imports as external so that vite ignores them + if (importKeys.includes(id)) { + return { + id, + external: true, + }; + } + }, }, - }; + ]; } diff --git a/vite.renderer.config.mts b/vite.renderer.config.mts index c9bb2181..067f7e51 100644 --- a/vite.renderer.config.mts +++ b/vite.renderer.config.mts @@ -9,7 +9,7 @@ import { createHtmlPlugin } from 'vite-plugin-html'; import svgr from 'vite-plugin-svgr'; import clientConfig from './client.config'; import pkg from './package.json'; -import { viteConnectDevStudioPlugin } from './studio/vite-plugin'; +import { externalIds, viteConnectDevStudioPlugin } from './studio/vite-plugin'; export default defineConfig(({ mode }) => { const isProd = mode === 'production'; @@ -38,7 +38,7 @@ export default defineConfig(({ mode }) => { const uploadSentrySourcemaps = authToken && org && project; const plugins: PluginOption[] = [ - viteConnectDevStudioPlugin(env), + viteConnectDevStudioPlugin(false, env), react({ include: 'src/**/*.tsx', babel: { @@ -106,14 +106,7 @@ export default defineConfig(({ mode }) => { ? `${chunk.name}.js` : 'assets/[name].[hash].js', }, - external: [ - 'node:crypto', - 'react', - 'react/jsx-runtime', - 'react/jsx-dev-runtime', - 'react-dom', - 'react-dom/client', - ], + external: ['node:crypto', externalIds], }, }, resolve: {