Skip to content

Commit 099719f

Browse files
authored
refactor: extract common code in common package (#443)
1 parent f26c213 commit 099719f

File tree

17 files changed

+358
-1108
lines changed

17 files changed

+358
-1108
lines changed

packages/common/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './refresh-utils'
2+
export * from './warning'

packages/common/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "@vitejs/react-common",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"private": true,
6+
"exports": {
7+
".": "./index.ts",
8+
"./refresh-runtime": "./refresh-runtime.js"
9+
},
10+
"peerDependencies": {
11+
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
12+
},
13+
"devDependencies": {
14+
"vite": "^6.2.2"
15+
}
16+
}

packages/plugin-react-swc/src/refresh-runtime.js renamed to packages/common/refresh-runtime.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ export function validateRefreshBoundaryAndEnqueueUpdate(
624624
if (hasExports && allExportsAreComponentsOrUnchanged === true) {
625625
enqueueUpdate()
626626
} else {
627-
return `Could not Fast Refresh ("${allExportsAreComponentsOrUnchanged}" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react-swc#consistent-components-exports`
627+
return `Could not Fast Refresh ("${allExportsAreComponentsOrUnchanged}" export is incompatible). Learn more at __README_URL__#consistent-components-exports`
628628
}
629629
}
630630

packages/common/refresh-utils.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
export const runtimePublicPath = '/@react-refresh'
2+
3+
const reactCompRE = /extends\s+(?:React\.)?(?:Pure)?Component/
4+
const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/
5+
6+
// NOTE: this is exposed publicly via plugin-react
7+
export const preambleCode = `import { injectIntoGlobalHook } from "__BASE__${runtimePublicPath.slice(
8+
1,
9+
)}"
10+
injectIntoGlobalHook(window);
11+
window.$RefreshReg$ = () => {};
12+
window.$RefreshSig$ = () => (type) => type;`
13+
14+
export const getPreambleCode = (base: string): string =>
15+
preambleCode.replace('__BASE__', base)
16+
17+
export function addRefreshWrapper<M extends { mappings: string } | undefined>(
18+
code: string,
19+
map: M | string,
20+
pluginName: string,
21+
id: string,
22+
): { code: string; map: M | string } {
23+
const hasRefresh = refreshContentRE.test(code)
24+
const onlyReactComp = !hasRefresh && reactCompRE.test(code)
25+
if (!hasRefresh && !onlyReactComp) return { code, map }
26+
27+
const newMap = typeof map === 'string' ? (JSON.parse(map) as M) : map
28+
let newCode = code
29+
if (hasRefresh) {
30+
newCode = `let prevRefreshReg;
31+
let prevRefreshSig;
32+
33+
if (import.meta.hot && !inWebWorker) {
34+
if (!window.$RefreshReg$) {
35+
throw new Error(
36+
"${pluginName} can't detect preamble. Something is wrong."
37+
);
38+
}
39+
40+
prevRefreshReg = window.$RefreshReg$;
41+
prevRefreshSig = window.$RefreshSig$;
42+
window.$RefreshReg$ = RefreshRuntime.getRefreshReg(${JSON.stringify(id)});
43+
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
44+
}
45+
46+
${newCode}
47+
48+
if (import.meta.hot && !inWebWorker) {
49+
window.$RefreshReg$ = prevRefreshReg;
50+
window.$RefreshSig$ = prevRefreshSig;
51+
}
52+
`
53+
if (newMap) {
54+
newMap.mappings = ';'.repeat(17) + newMap.mappings
55+
}
56+
}
57+
58+
newCode = `import * as RefreshRuntime from "${runtimePublicPath}";
59+
const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
60+
61+
${newCode}
62+
63+
if (import.meta.hot && !inWebWorker) {
64+
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
65+
RefreshRuntime.registerExportsForReactRefresh(${JSON.stringify(
66+
id,
67+
)}, currentExports);
68+
import.meta.hot.accept((nextExports) => {
69+
if (!nextExports) return;
70+
const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate(${JSON.stringify(
71+
id,
72+
)}, currentExports, nextExports);
73+
if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
74+
});
75+
});
76+
}
77+
`
78+
if (newMap) {
79+
newMap.mappings = ';;;' + newMap.mappings
80+
}
81+
82+
return { code: newCode, map: newMap }
83+
}

packages/common/warning.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { BuildOptions, UserConfig } from 'vite'
2+
3+
export const silenceUseClientWarning = (
4+
userConfig: UserConfig,
5+
): BuildOptions => ({
6+
rollupOptions: {
7+
onwarn(warning, defaultHandler) {
8+
if (
9+
warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
10+
warning.message.includes('use client')
11+
) {
12+
return
13+
}
14+
// https://github.com/vitejs/vite/issues/15012
15+
if (
16+
warning.code === 'SOURCEMAP_ERROR' &&
17+
warning.message.includes('resolve original location') &&
18+
warning.pos === 0
19+
) {
20+
return
21+
}
22+
if (userConfig.build?.rollupOptions?.onwarn) {
23+
userConfig.build.rollupOptions.onwarn(warning, defaultHandler)
24+
} else {
25+
defaultHandler(warning)
26+
}
27+
},
28+
},
29+
})

packages/plugin-react-swc/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"devDependencies": {
3939
"@playwright/test": "^1.51.1",
4040
"@types/fs-extra": "^11.0.4",
41-
"@types/node": "22.13.10",
41+
"@types/node": "^22.13.15",
42+
"@vitejs/react-common": "workspace:*",
4243
"@vitejs/release-scripts": "^1.3.3",
4344
"esbuild": "^0.25.1",
4445
"fs-extra": "^11.3.0",

packages/plugin-react-swc/playground/hmr/__tests__/hmr.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('HMR invalidate', async ({ page }) => {
3535
// Edit export
3636
editFile('src/TitleWithExport.tsx', ['React', 'React!'])
3737
await waitForLogs(
38-
'[vite] invalidate /src/TitleWithExport.tsx: Could not Fast Refresh ("framework" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react-swc#consistent-components-exports',
38+
'[vite] invalidate /src/TitleWithExport.tsx: Could not Fast Refresh ("framework" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#consistent-components-exports',
3939
'[vite] hot updated: /src/App.tsx',
4040
)
4141
await expect(page.locator('h1')).toHaveText('Vite * React!')

packages/plugin-react-swc/playground/react-18/__tests__/react-18.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('HMR invalidate', async ({ page }) => {
3535
// Edit export
3636
editFile('src/TitleWithExport.tsx', ['React', 'React!'])
3737
await waitForLogs(
38-
'[vite] invalidate /src/TitleWithExport.tsx: Could not Fast Refresh ("framework" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react-swc#consistent-components-exports',
38+
'[vite] invalidate /src/TitleWithExport.tsx: Could not Fast Refresh ("framework" export is incompatible). Learn more at https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#consistent-components-exports',
3939
'[vite] hot updated: /src/App.tsx',
4040
)
4141
await expect(page.locator('h1')).toHaveText('Vite * React!')

packages/plugin-react-swc/scripts/bundle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const buildOrWatch = async (options: BuildOptions) => {
2727

2828
Promise.all([
2929
buildOrWatch({
30-
entryPoints: ['src/refresh-runtime.js'],
30+
entryPoints: ['@vitejs/react-common/refresh-runtime'],
3131
outdir: 'dist',
3232
platform: 'browser',
3333
format: 'esm',

packages/plugin-react-swc/src/index.ts

Lines changed: 18 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import {
1111
type Options as SWCOptions,
1212
transform,
1313
} from '@swc/core'
14-
import type { BuildOptions, PluginOption, UserConfig } from 'vite'
15-
16-
const runtimePublicPath = '/@react-refresh'
17-
18-
const preambleCode = `import { injectIntoGlobalHook } from "__PATH__";
19-
injectIntoGlobalHook(window);
20-
window.$RefreshReg$ = () => {};
21-
window.$RefreshSig$ = () => (type) => type;`
14+
import type { PluginOption } from 'vite'
15+
import {
16+
addRefreshWrapper,
17+
getPreambleCode,
18+
runtimePublicPath,
19+
silenceUseClientWarning,
20+
} from '@vitejs/react-common'
2221

2322
/* eslint-disable no-restricted-globals */
2423
const _dirname =
@@ -30,9 +29,6 @@ const resolve = createRequire(
3029
).resolve
3130
/* eslint-enable no-restricted-globals */
3231

33-
const reactCompRE = /extends\s+(?:React\.)?(?:Pure)?Component/
34-
const refreshContentRE = /\$Refresh(?:Reg|Sig)\$\(/
35-
3632
type Options = {
3733
/**
3834
* Control where the JSX factory is imported from.
@@ -94,7 +90,10 @@ const react = (_options?: Options): PluginOption[] => {
9490
resolveId: (id) => (id === runtimePublicPath ? id : undefined),
9591
load: (id) =>
9692
id === runtimePublicPath
97-
? readFileSync(join(_dirname, 'refresh-runtime.js'), 'utf-8')
93+
? readFileSync(join(_dirname, 'refresh-runtime.js'), 'utf-8').replace(
94+
/__README_URL__/g,
95+
'https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc',
96+
)
9897
: undefined,
9998
},
10099
{
@@ -126,10 +125,7 @@ const react = (_options?: Options): PluginOption[] => {
126125
{
127126
tag: 'script',
128127
attrs: { type: 'module' },
129-
children: preambleCode.replace(
130-
'__PATH__',
131-
config.server!.config.base + runtimePublicPath.slice(1),
132-
),
128+
children: getPreambleCode(config.server!.config.base),
133129
},
134130
],
135131
async transform(code, _id, transformOptions) {
@@ -151,43 +147,12 @@ const react = (_options?: Options): PluginOption[] => {
151147
if (!result) return
152148
if (!refresh) return result
153149

154-
const hasRefresh = refreshContentRE.test(result.code)
155-
if (!hasRefresh && !reactCompRE.test(result.code)) return result
156-
157-
const sourceMap: SourceMapPayload = JSON.parse(result.map!)
158-
sourceMap.mappings = ';;' + sourceMap.mappings
159-
160-
result.code = `import * as RefreshRuntime from "${runtimePublicPath}";
161-
162-
${result.code}`
163-
164-
if (hasRefresh) {
165-
sourceMap.mappings = ';;;;;;' + sourceMap.mappings
166-
result.code = `if (!window.$RefreshReg$) throw new Error("React refresh preamble was not loaded. Something is wrong.");
167-
const prevRefreshReg = window.$RefreshReg$;
168-
const prevRefreshSig = window.$RefreshSig$;
169-
window.$RefreshReg$ = RefreshRuntime.getRefreshReg("${id}");
170-
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
171-
172-
${result.code}
173-
174-
window.$RefreshReg$ = prevRefreshReg;
175-
window.$RefreshSig$ = prevRefreshSig;
176-
`
177-
}
178-
179-
result.code += `
180-
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
181-
RefreshRuntime.registerExportsForReactRefresh("${id}", currentExports);
182-
import.meta.hot.accept((nextExports) => {
183-
if (!nextExports) return;
184-
const invalidateMessage = RefreshRuntime.validateRefreshBoundaryAndEnqueueUpdate("${id}", currentExports, nextExports);
185-
if (invalidateMessage) import.meta.hot.invalidate(invalidateMessage);
186-
});
187-
});
188-
`
189-
190-
return { code: result.code, map: sourceMap }
150+
return addRefreshWrapper<SourceMapPayload>(
151+
result.code,
152+
result.map!,
153+
'@vitejs/plugin-react-swc',
154+
id,
155+
)
191156
},
192157
},
193158
options.plugins
@@ -280,30 +245,4 @@ const transformWithOptions = async (
280245
return result
281246
}
282247

283-
const silenceUseClientWarning = (userConfig: UserConfig): BuildOptions => ({
284-
rollupOptions: {
285-
onwarn(warning, defaultHandler) {
286-
if (
287-
warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
288-
warning.message.includes('use client')
289-
) {
290-
return
291-
}
292-
// https://github.com/vitejs/vite/issues/15012
293-
if (
294-
warning.code === 'SOURCEMAP_ERROR' &&
295-
warning.message.includes('resolve original location') &&
296-
warning.pos === 0
297-
) {
298-
return
299-
}
300-
if (userConfig.build?.rollupOptions?.onwarn) {
301-
userConfig.build.rollupOptions.onwarn(warning, defaultHandler)
302-
} else {
303-
defaultHandler(warning)
304-
}
305-
},
306-
},
307-
})
308-
309248
export default react

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy