Skip to content

Commit 55dec04

Browse files
sapphi-redunderfin
andauthored
feat: add plugin-react-oxc (#439)
Co-authored-by: underfin <[email protected]>
1 parent 099719f commit 55dec04

File tree

25 files changed

+994
-163
lines changed

25 files changed

+994
-163
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ body:
1717
[plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react)
1818
- label: |
1919
[plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc)
20+
- label: |
21+
[plugin-react-oxc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-oxc)
2022
- type: textarea
2123
id: bug-description
2224
attributes:

.github/ISSUE_TEMPLATE/feature_request.yml

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ body:
1717
[plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react)
1818
- label: |
1919
[plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc)
20+
- label: |
21+
[plugin-react-oxc](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-oxc)
2022
- type: textarea
2123
id: feature-description
2224
attributes:

packages/common/refresh-utils.ts

+33-10
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,30 @@ window.$RefreshSig$ = () => (type) => type;`
1414
export const getPreambleCode = (base: string): string =>
1515
preambleCode.replace('__BASE__', base)
1616

17-
export function addRefreshWrapper<M extends { mappings: string } | undefined>(
17+
export const avoidSourceMapOption = Symbol()
18+
19+
export function addRefreshWrapper<M extends { mappings: string }>(
1820
code: string,
19-
map: M | string,
21+
map: M | string | typeof avoidSourceMapOption,
2022
pluginName: string,
2123
id: string,
22-
): { code: string; map: M | string } {
24+
): { code: string; map: M | null | string } {
2325
const hasRefresh = refreshContentRE.test(code)
2426
const onlyReactComp = !hasRefresh && reactCompRE.test(code)
25-
if (!hasRefresh && !onlyReactComp) return { code, map }
27+
const normalizedMap = map === avoidSourceMapOption ? null : map
28+
29+
if (!hasRefresh && !onlyReactComp) return { code, map: normalizedMap }
30+
31+
const avoidSourceMap = map === avoidSourceMapOption
32+
const newMap =
33+
typeof normalizedMap === 'string'
34+
? (JSON.parse(normalizedMap) as M)
35+
: normalizedMap
2636

27-
const newMap = typeof map === 'string' ? (JSON.parse(map) as M) : map
2837
let newCode = code
2938
if (hasRefresh) {
30-
newCode = `let prevRefreshReg;
39+
const refreshHead = removeLineBreaksIfNeeded(
40+
`let prevRefreshReg;
3141
let prevRefreshSig;
3242
3343
if (import.meta.hot && !inWebWorker) {
@@ -43,22 +53,31 @@ if (import.meta.hot && !inWebWorker) {
4353
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
4454
}
4555
46-
${newCode}
56+
`,
57+
avoidSourceMap,
58+
)
59+
60+
newCode = `${refreshHead}${newCode}
4761
4862
if (import.meta.hot && !inWebWorker) {
4963
window.$RefreshReg$ = prevRefreshReg;
5064
window.$RefreshSig$ = prevRefreshSig;
5165
}
5266
`
5367
if (newMap) {
54-
newMap.mappings = ';'.repeat(17) + newMap.mappings
68+
newMap.mappings = ';'.repeat(16) + newMap.mappings
5569
}
5670
}
5771

58-
newCode = `import * as RefreshRuntime from "${runtimePublicPath}";
72+
const sharedHead = removeLineBreaksIfNeeded(
73+
`import * as RefreshRuntime from "${runtimePublicPath}";
5974
const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
6075
61-
${newCode}
76+
`,
77+
avoidSourceMap,
78+
)
79+
80+
newCode = `${sharedHead}${newCode}
6281
6382
if (import.meta.hot && !inWebWorker) {
6483
RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
@@ -81,3 +100,7 @@ if (import.meta.hot && !inWebWorker) {
81100

82101
return { code: newCode, map: newMap }
83102
}
103+
104+
function removeLineBreaksIfNeeded(code: string, enabled: boolean): string {
105+
return enabled ? code.replace(/\n/g, '') : code
106+
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Create Oxc plugin

packages/plugin-react-oxc/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors
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.

packages/plugin-react-oxc/README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# @vitejs/plugin-react-oxc [![npm](https://img.shields.io/npm/v/@vitejs/plugin-react-oxc.svg)](https://npmjs.com/package/@vitejs/plugin-react-oxc)
2+
3+
The future default Vite plugin for React projects.
4+
5+
- enable [Fast Refresh](https://www.npmjs.com/package/react-refresh) in development
6+
- use the [automatic JSX runtime](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html)
7+
- small installation size
8+
9+
```js
10+
// vite.config.js
11+
import { defineConfig } from 'vite'
12+
import react from '@vitejs/plugin-react-oxc'
13+
14+
export default defineConfig({
15+
plugins: [react()],
16+
})
17+
```
18+
19+
## Caveats
20+
21+
- `jsx runtime` is always `automatic`
22+
- this plugin only works with [`rolldown-vite`](https://vitejs.dev/guide/rolldown)
23+
24+
## Options
25+
26+
### include/exclude
27+
28+
Includes `.js`, `.jsx`, `.ts` & `.tsx` by default. This option can be used to add fast refresh to `.mdx` files:
29+
30+
```js
31+
import { defineConfig } from 'vite'
32+
import react from '@vitejs/plugin-react'
33+
import mdx from '@mdx-js/rollup'
34+
35+
export default defineConfig({
36+
plugins: [
37+
{ enforce: 'pre', ...mdx() },
38+
react({ include: /\.(mdx|js|jsx|ts|tsx)$/ }),
39+
],
40+
})
41+
```
42+
43+
> `node_modules` are never processed by this plugin (but Oxc will)
44+
45+
### jsxImportSource
46+
47+
Control where the JSX factory is imported from. Default to `'react'`
48+
49+
```js
50+
react({ jsxImportSource: '@emotion/react' })
51+
```
52+
53+
## Middleware mode
54+
55+
In [middleware mode](https://vite.dev/config/server-options.html#server-middlewaremode), you should make sure your entry `index.html` file is transformed by Vite. Here's an example for an Express server:
56+
57+
```js
58+
app.get('/', async (req, res, next) => {
59+
try {
60+
let html = fs.readFileSync(path.resolve(root, 'index.html'), 'utf-8')
61+
62+
// Transform HTML using Vite plugins.
63+
html = await viteServer.transformIndexHtml(req.url, html)
64+
65+
res.send(html)
66+
} catch (e) {
67+
return next(e)
68+
}
69+
})
70+
```
71+
72+
Otherwise, you'll probably get this error:
73+
74+
```
75+
Uncaught Error: @vitejs/plugin-react-oxc can't detect preamble. Something is wrong.
76+
```
77+
78+
## Consistent components exports
79+
80+
For React refresh to work correctly, your file should only export React components. You can find a good explanation in the [Gatsby docs](https://www.gatsbyjs.com/docs/reference/local-development/fast-refresh/#how-it-works).
81+
82+
If an incompatible change in exports is found, the module will be invalidated and HMR will propagate. To make it easier to export simple constants alongside your component, the module is only invalidated when their value changes.
83+
84+
You can catch mistakes and get more detailed warning with this [eslint rule](https://github.com/ArnaudBarre/eslint-plugin-react-refresh).
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineBuildConfig } from 'unbuild'
2+
3+
export default defineBuildConfig({
4+
entries: ['src/index'],
5+
externals: ['vite'],
6+
clean: true,
7+
declaration: true,
8+
rollup: {
9+
inlineDependencies: true,
10+
},
11+
})
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@vitejs/plugin-react-oxc",
3+
"version": "0.1.0",
4+
"license": "MIT",
5+
"author": "Evan You",
6+
"contributors": [
7+
"Alec Larson",
8+
"Arnaud Barré"
9+
],
10+
"description": "The future default Vite plugin for React projects",
11+
"keywords": [
12+
"vite",
13+
"vite-plugin",
14+
"react",
15+
"oxc",
16+
"react-refresh",
17+
"fast refresh"
18+
],
19+
"files": [
20+
"dist"
21+
],
22+
"type": "module",
23+
"types": "./dist/index.d.mts",
24+
"exports": "./dist/index.mjs",
25+
"scripts": {
26+
"dev": "unbuild --stub",
27+
"build": "unbuild && tsx scripts/copyRefreshRuntime.ts",
28+
"prepublishOnly": "npm run build"
29+
},
30+
"engines": {
31+
"node": ">=20.0.0"
32+
},
33+
"repository": {
34+
"type": "git",
35+
"url": "git+https://github.com/vitejs/vite-plugin-react.git",
36+
"directory": "packages/plugin-react-oxc"
37+
},
38+
"bugs": {
39+
"url": "https://github.com/vitejs/vite-plugin-react/issues"
40+
},
41+
"homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme",
42+
"peerDependencies": {
43+
"vite": "^6.3.0"
44+
},
45+
"devDependencies": {
46+
"@vitejs/react-common": "workspace:*",
47+
"unbuild": "^3.5.0",
48+
"vite": "catalog:rolldown-vite"
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { copyFileSync } from 'node:fs'
2+
3+
copyFileSync(
4+
'node_modules/@vitejs/react-common/refresh-runtime.js',
5+
'dist/refresh-runtime.js',
6+
)

0 commit comments

Comments
 (0)