├── .editorconfig
├── .git-blame-ignore-revs
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── PULL_REQUEST_TEMPLATE.md
├── commit-convention.md
├── renovate.json5
└── workflows
│ ├── ci.yml
│ ├── issue-close-require.yml
│ ├── issue-labeled.yml
│ ├── lock-closed-issues.yml
│ ├── publish.yml
│ ├── release-continuous.yml
│ └── semantic-pull-request.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── eslint.config.js
├── package.json
├── packages
├── common
│ ├── index.ts
│ ├── package.json
│ ├── refresh-runtime.js
│ ├── refresh-utils.ts
│ └── warning.ts
├── plugin-react-oxc
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── build.config.ts
│ ├── package.json
│ ├── scripts
│ │ └── copyRefreshRuntime.ts
│ ├── src
│ │ ├── build.d.ts
│ │ └── index.ts
│ └── tsconfig.json
├── plugin-react-swc
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── playground
│ │ ├── base-path
│ │ │ ├── __tests__
│ │ │ │ └── base-path.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── class-components
│ │ │ ├── __tests__
│ │ │ │ └── class-components.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── utils.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── decorators
│ │ │ ├── __tests__
│ │ │ │ └── decorators.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── emotion-plugin
│ │ │ ├── __tests__
│ │ │ │ └── emotion-plugin.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.css
│ │ │ │ ├── App.jsx
│ │ │ │ ├── Button.jsx
│ │ │ │ ├── index.css
│ │ │ │ └── index.jsx
│ │ │ └── vite.config.js
│ │ ├── emotion
│ │ │ ├── __tests__
│ │ │ │ └── emotion.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.css
│ │ │ │ ├── App.tsx
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── index.css
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── hmr
│ │ │ ├── __tests__
│ │ │ │ └── hmr.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.css
│ │ │ │ ├── App.tsx
│ │ │ │ ├── TitleWithExport.tsx
│ │ │ │ ├── index.css
│ │ │ │ ├── index.tsx
│ │ │ │ └── react.svg
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── mdx
│ │ │ ├── __tests__
│ │ │ │ └── mdx.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── Counter.tsx
│ │ │ │ ├── env.d.ts
│ │ │ │ ├── hello.mdx
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── react-18
│ │ │ ├── __tests__
│ │ │ │ └── react-18.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.css
│ │ │ │ ├── App.tsx
│ │ │ │ ├── TitleWithExport.tsx
│ │ │ │ ├── index.css
│ │ │ │ ├── index.tsx
│ │ │ │ └── react.svg
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── shadow-export
│ │ │ ├── __tests__
│ │ │ │ └── shadow-export.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── styled-components
│ │ │ ├── __tests__
│ │ │ │ └── styled-components.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── App.css
│ │ │ │ ├── App.tsx
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── index.css
│ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── ts-lib
│ │ │ ├── __tests__
│ │ │ │ └── ts-lib.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ │ ├── index.tsx
│ │ │ │ └── pages
│ │ │ │ │ ├── 404.tsx
│ │ │ │ │ ├── _app.tsx
│ │ │ │ │ ├── about.tsx
│ │ │ │ │ └── index.tsx
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ ├── utils.ts
│ │ └── worker
│ │ │ ├── __tests__
│ │ │ └── worker.spec.ts
│ │ │ ├── index.html
│ │ │ ├── package.json
│ │ │ ├── public
│ │ │ └── vite.svg
│ │ │ ├── src
│ │ │ ├── App.tsx
│ │ │ ├── index.tsx
│ │ │ ├── worker-via-import.ts
│ │ │ └── worker-via-url.ts
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ ├── playwright.config.ts
│ ├── scripts
│ │ └── bundle.ts
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
└── plugin-react
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── build.config.ts
│ ├── package.json
│ ├── scripts
│ └── copyRefreshRuntime.ts
│ ├── src
│ ├── babel.d.ts
│ ├── build.d.ts
│ └── index.ts
│ └── tsconfig.json
├── playground
├── class-components
│ ├── __tests__
│ │ ├── class-components.spec.ts
│ │ └── oxc
│ │ │ └── class-components.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.tsx
│ │ ├── index.tsx
│ │ └── utils.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── compiler-react-18
│ ├── __tests__
│ │ └── compiler-react-18.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── index.css
│ │ └── main.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── compiler
│ ├── __tests__
│ │ └── compiler.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── index.css
│ │ └── main.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── hook-with-jsx
│ ├── __tests__
│ │ ├── hook-with-jsx.spec.ts
│ │ └── oxc
│ │ │ └── hook-with-jsx.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── App.tsx
│ │ ├── index.tsx
│ │ └── useButtonHook.tsx
│ ├── tsconfig.json
│ └── vite.config.ts
├── mdx
│ ├── __tests__
│ │ ├── mdx.spec.ts
│ │ └── oxc
│ │ │ └── mdx.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── vite.svg
│ ├── src
│ │ ├── demo.mdx
│ │ ├── demo2.md
│ │ ├── main.tsx
│ │ └── vite-env.d.ts
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── package.json
├── react-classic
│ ├── App.jsx
│ ├── __tests__
│ │ └── react.spec.ts
│ ├── index.html
│ ├── package.json
│ └── vite.config.ts
├── react-emotion
│ ├── App.jsx
│ ├── Counter.jsx
│ ├── __tests__
│ │ └── react.spec.ts
│ ├── index.html
│ ├── package.json
│ └── vite.config.ts
├── react-env
│ ├── App.jsx
│ ├── __tests__
│ │ ├── oxc
│ │ │ └── react.spec.ts
│ │ └── react.spec.ts
│ ├── index.html
│ ├── package.json
│ └── vite.config.ts
├── react-sourcemap
│ ├── App.jsx
│ ├── __tests__
│ │ ├── oxc
│ │ │ └── react-sourcemap.spec.ts
│ │ └── react-sourcemap.spec.ts
│ ├── index.html
│ ├── main.jsx
│ ├── package.json
│ └── vite.config.ts
├── react
│ ├── App.jsx
│ ├── __tests__
│ │ ├── oxc
│ │ │ └── react.spec.ts
│ │ └── react.spec.ts
│ ├── components
│ │ └── Dummy.jsx
│ ├── context
│ │ ├── ContextButton.jsx
│ │ └── CountProvider.jsx
│ ├── hmr
│ │ ├── jsx-import-runtime.js
│ │ ├── no-exported-comp.jsx
│ │ └── parent.jsx
│ ├── import-attributes
│ │ ├── data.json
│ │ └── test.jsx
│ ├── index.html
│ ├── jsx-entry
│ │ ├── Button.jsx
│ │ └── package.json
│ ├── package.json
│ └── vite.config.ts
├── shims.d.ts
├── ssr-react
│ ├── __tests__
│ │ ├── oxc
│ │ │ └── ssr-react.spec.ts
│ │ └── ssr-react.spec.ts
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── src
│ │ ├── App.jsx
│ │ ├── add.js
│ │ ├── entry-client.jsx
│ │ ├── entry-server.jsx
│ │ ├── multiply.js
│ │ └── pages
│ │ │ ├── About.jsx
│ │ │ ├── Env.jsx
│ │ │ └── Home.jsx
│ └── vite.config.js
├── test-utils.ts
├── tsconfig.json
├── vitest.config.e2e.ts
├── vitestGlobalSetup.ts
└── vitestSetup.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── scripts
├── patchCJS.ts
├── publishCI.ts
├── release.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # chore: enable prettier trailing commas (#35)
2 | b647e74c38565696bd6fb931b8bd9ac7f3bebe88
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Discord Chat
4 | url: https://chat.vite.dev
5 | about: Ask questions and discuss with other Vite users in real time.
6 | - name: Questions & Discussions
7 | url: https://github.com/vitejs/vite-plugin-react/discussions
8 | about: Use GitHub discussions for message-board style questions and discussions.
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: "\U0001F680 New feature proposal"
2 | description: Propose a new feature
3 | labels: ["pending triage"]
4 | type: Feature
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for your interest in the project and taking the time to fill out this feature report!
10 | - type: checkboxes
11 | id: plugins
12 | attributes:
13 | label: Related plugins
14 | description: Select the plugin which is related
15 | options:
16 | - label: |
17 | [plugin-react](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react)
18 | - label: |
19 | [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)
22 | - type: textarea
23 | id: feature-description
24 | attributes:
25 | label: Description
26 | description: "Clear and concise description of the problem. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks!"
27 | placeholder: As a developer using Vite I want [goal / wish] so that [benefit].
28 | validations:
29 | required: true
30 | - type: textarea
31 | id: suggested-solution
32 | attributes:
33 | label: Suggested solution
34 | description: "In module [xy] we could provide following implementation..."
35 | validations:
36 | required: true
37 | - type: textarea
38 | id: alternative
39 | attributes:
40 | label: Alternative
41 | description: Clear and concise description of any alternative solutions or features you've considered.
42 | - type: textarea
43 | id: additional-context
44 | attributes:
45 | label: Additional context
46 | description: Any other context or screenshots about the feature request here.
47 | - type: checkboxes
48 | id: checkboxes
49 | attributes:
50 | label: Validations
51 | description: Before submitting the issue, please make sure you do the following
52 | options:
53 | - label: Follow our [Code of Conduct](https://github.com/vitejs/vite-plugin-react/blob/main/CODE_OF_CONDUCT.md)
54 | required: true
55 | - label: Read the [Contributing Guidelines](https://github.com/vitejs/vite-plugin-react/blob/main/CONTRIBUTING.md).
56 | required: true
57 | - label: Read the [docs](https://vite.dev/guide).
58 | required: true
59 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
60 | required: true
61 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 |
4 |
5 |
15 |
--------------------------------------------------------------------------------
/.github/commit-convention.md:
--------------------------------------------------------------------------------
1 | ## Git Commit Message Convention
2 |
3 | > This is adapted from [Angular's commit convention](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular).
4 |
5 | #### TL;DR:
6 |
7 | Messages must be matched by the following regex:
8 |
9 |
10 | ```js
11 | /^(revert: )?(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?!?: .{1,50}/
12 | ```
13 |
14 | #### Examples
15 |
16 | ```
17 | feat(dev): add 'comments' option
18 | fix(dev): fix dev error
19 | perf(build)!: remove 'foo' option
20 | revert: feat(compiler): add 'comments' option
21 | ```
22 |
23 | ### Revert
24 |
25 | If the PR reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit
26 |
27 | ### Scope
28 |
29 | The scope could be anything specifying the place of the commit change. For example `dev`, `build`, `workflow`, `cli` etc...
30 |
31 | ### Subject
32 |
33 | The subject contains a succinct description of the change:
34 |
35 | - use the imperative, present tense: "change" not "changed" nor "changes"
36 | - don't capitalize the first letter
37 | - no dot (.) at the end
38 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", "schedule:weekly", "group:allNonMajor"],
4 | "labels": ["dependencies"],
5 | "ignorePaths": ["**/__tests__/**"],
6 | "rangeStrategy": "bump",
7 | "packageRules": [
8 | {
9 | "depTypeList": ["peerDependencies"],
10 | "enabled": false,
11 | },
12 | {
13 | "matchFileNames": ["**/react-18/**", "**/compiler-react-18/**"],
14 | "ignoreDeps": ["react", "react-dom", "@types/react", "@types/react-dom"],
15 | },
16 | // renovate doesn't properly handle x.x.x-beta-hash-yyyymm version schema
17 | {
18 | "matchPackageNames": [
19 | "react-compiler-runtime",
20 | "babel-plugin-react-compiler",
21 | ],
22 | "followTag": "latest",
23 | },
24 | {
25 | "matchDepTypes": ["action"],
26 | "excludePackagePrefixes": ["actions/", "github/"],
27 | "pinDigests": true,
28 | },
29 | ],
30 | "ignoreDeps": [
31 | // manually bumping
32 | "node",
33 |
34 | "generouted", // testing lib shipping JSX (new version ship transpiled JS)
35 |
36 | // breaking changes
37 | "source-map", // `source-map:v0.7.0+` needs more investigation
38 | "kill-port", // `kill-port:^2.0.0 has perf issues (#8392)
39 |
40 | "prettier", // waiting for stable choice on ternaries
41 | ],
42 | }
43 |
--------------------------------------------------------------------------------
/.github/workflows/issue-close-require.yml:
--------------------------------------------------------------------------------
1 | name: Issue Close Require
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | jobs:
8 | close-issues:
9 | if: github.repository == 'vitejs/vite-plugin-react'
10 | runs-on: ubuntu-latest
11 | permissions:
12 | issues: write # for actions-cool/issues-helper to update issues
13 | pull-requests: write # for actions-cool/issues-helper to update PRs
14 | steps:
15 | - name: need reproduction
16 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3
17 | with:
18 | actions: "close-issues"
19 | token: ${{ secrets.GITHUB_TOKEN }}
20 | labels: "need reproduction"
21 | inactive-day: 3
22 |
--------------------------------------------------------------------------------
/.github/workflows/issue-labeled.yml:
--------------------------------------------------------------------------------
1 | name: Issue Labeled
2 |
3 | on:
4 | issues:
5 | types: [labeled]
6 |
7 | jobs:
8 | reply-labeled:
9 | if: github.repository == 'vitejs/vite-plugin-react'
10 | runs-on: ubuntu-latest
11 | permissions:
12 | issues: write # for actions-cool/issues-helper to update issues
13 | pull-requests: write # for actions-cool/issues-helper to update PRs
14 | steps:
15 | - name: contribution welcome
16 | if: github.event.label.name == 'contribution welcome' || github.event.label.name == 'help wanted'
17 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3
18 | with:
19 | actions: "create-comment, remove-labels"
20 | token: ${{ secrets.GITHUB_TOKEN }}
21 | issue-number: ${{ github.event.issue.number }}
22 | body: |
23 | Hello @${{ github.event.issue.user.login }}. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!
24 | labels: "pending triage, need reproduction"
25 |
26 | - name: remove pending
27 | if: (github.event.label.name == 'enhancement' || contains(github.event.label.description, '(priority)')) && contains(github.event.issue.labels.*.name, 'pending triage')
28 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3
29 | with:
30 | actions: "remove-labels"
31 | token: ${{ secrets.GITHUB_TOKEN }}
32 | issue-number: ${{ github.event.issue.number }}
33 | labels: "pending triage"
34 |
35 | - name: need reproduction
36 | if: github.event.label.name == 'need reproduction'
37 | uses: actions-cool/issues-helper@a610082f8ac0cf03e357eb8dd0d5e2ba075e017e # v3
38 | with:
39 | actions: "create-comment, remove-labels"
40 | token: ${{ secrets.GITHUB_TOKEN }}
41 | issue-number: ${{ github.event.issue.number }}
42 | body: |
43 | Hello @${{ github.event.issue.user.login }}. Please provide a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) using a GitHub repository or [StackBlitz](https://vite.new). Issues marked with `need reproduction` will be closed if they have no activity within 3 days.
44 | labels: "pending triage"
45 |
--------------------------------------------------------------------------------
/.github/workflows/lock-closed-issues.yml:
--------------------------------------------------------------------------------
1 | name: Lock Closed Issues
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | permissions:
8 | issues: write
9 |
10 | jobs:
11 | action:
12 | if: github.repository == 'vitejs/vite-plugin-react'
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5
16 | with:
17 | github-token: ${{ secrets.GITHUB_TOKEN }}
18 | issue-inactive-days: "14"
19 | #issue-comment: |
20 | # This issue has been locked since it has been closed for more than 14 days.
21 | #
22 | # If you have found a concrete bug or regression related to it, please open a new [bug report](https://github.com/vitejs/vite-plugin-react/issues/new/choose) with a reproduction against the latest Vite version. If you have any other comments you should join the chat at [Vite Land](https://chat.vite.dev) or create a new [discussion](https://github.com/vitejs/vite-plugin-react/discussions).
23 | issue-lock-reason: ""
24 | process-only: "issues"
25 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package
2 |
3 | on:
4 | push:
5 | tags:
6 | - "plugin-*"
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write # for ArnaudBarre/github-release to create a release
13 | id-token: write # for provenance generation
14 | environment: Release
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Install pnpm
20 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
21 |
22 | - name: Set node version to 20
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: 20
26 | registry-url: https://registry.npmjs.org/
27 | cache: "pnpm"
28 |
29 | - name: Install deps
30 | run: pnpm install
31 | env:
32 | PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
33 |
34 | - name: Get pkgName for tag
35 | id: tag
36 | run: |
37 | # Check if the tag contains "alpha"
38 | if [[ $GITHUB_REF_NAME =~ alpha ]]; then
39 | echo "isAlpha=true" >> $GITHUB_OUTPUT
40 | else
41 | echo "isAlpha=false" >> $GITHUB_OUTPUT
42 | fi
43 |
44 | # `%@*` truncates @ and version number from the right side.
45 | # https://stackoverflow.com/questions/9532654/expression-after-last-specific-character
46 | pkgName=${GITHUB_REF_NAME%@*}
47 | echo "pkgName=$pkgName" >> $GITHUB_OUTPUT
48 |
49 | - if: steps.tag.outputs.pkgName == 'plugin-react-swc'
50 | working-directory: packages/plugin-react-swc
51 | run: pnpm build
52 |
53 | - name: Publish package
54 | run: pnpm run ci-publish ${{ github.ref_name }}
55 | env:
56 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
57 |
58 | - if: steps.tag.outputs.isAlpha == 'false'
59 | uses: ArnaudBarre/github-release@4fa6eafe8e2449c7c1c5a91ae50de4ee34db0b40 # v1.5.0
60 | with:
61 | path: packages/${{ steps.tag.outputs.pkgName }}/CHANGELOG.md
62 | tag-name: ${{ github.ref_name }}
63 |
--------------------------------------------------------------------------------
/.github/workflows/release-continuous.yml:
--------------------------------------------------------------------------------
1 | name: Preview Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | types: [opened, synchronize, labeled]
9 |
10 | permissions: {}
11 |
12 | jobs:
13 | preview:
14 | if: >
15 | github.repository == 'vitejs/vite-plugin-react' &&
16 | (github.event_name == 'push' ||
17 | (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'trigger: preview')))
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout code
21 | uses: actions/checkout@v4
22 |
23 | - name: Install pnpm
24 | uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
25 |
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: lts/*
29 | cache: pnpm
30 |
31 | - name: Install dependencies
32 | run: pnpm install
33 |
34 | - name: Build
35 | run: pnpm build
36 |
37 | - name: Publish
38 | run: pnpm dlx pkg-pr-new@0.0 publish --pnpm --compact './packages/*' './packages/plugin-react-swc/dist'
39 |
--------------------------------------------------------------------------------
/.github/workflows/semantic-pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Semantic Pull Request
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | if: github.repository == 'vitejs/vite-plugin-react'
13 | runs-on: ubuntu-latest
14 | name: Semantic Pull Request
15 | permissions:
16 | pull-requests: read
17 | steps:
18 | - name: Validate PR title
19 | uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
20 | with:
21 | subjectPattern: ^(?![A-Z]).+$
22 | subjectPatternError: |
23 | The subject "{subject}" found in the pull request title "{title}"
24 | didn't match the configured pattern. Please ensure that the subject
25 | doesn't start with an uppercase character.
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | !**/glob-import/dir/node_modules
2 | .DS_Store
3 | .idea
4 | *.cpuprofile
5 | *.local
6 | *.log
7 | /.vscode/
8 | dist
9 | dist-ssr
10 | explorations
11 | node_modules
12 | playground-temp
13 | packages/plugin-react-swc/playground-temp
14 | temp
15 | TODOs.md
16 | .eslintcache
17 | test-results/
18 | .swc/
19 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | hoist-pattern[]=@emotion/* # playground/react-emotion
2 | hoist-pattern[]=*babel*
3 | hoist-pattern[]=@swc/* # packages/plugin-react-swc/playground/emotion-plugin, packages/plugin-react-swc/playground/styled-components
4 | hoist-pattern[]=eslint-import-resolver-*
5 | strict-peer-dependencies=false
6 | shell-emulator=true
7 | auto-install-peers=false
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | packages/*/CHANGELOG.md
2 | playground-temp/
3 | dist/
4 | temp/
5 | LICENSE.md
6 | pnpm-lock.yaml
7 | pnpm-workspace.yaml
8 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "singleQuote": true,
4 | "overrides": [
5 | {
6 | "files": ["*.json5"],
7 | "options": {
8 | "singleQuote": false,
9 | "quoteProps": "preserve"
10 | }
11 | },
12 | {
13 | "files": ["*.yml"],
14 | "options": {
15 | "singleQuote": false
16 | }
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code Of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, political party, or sexual identity and orientation. Note, however, that religion, political party, or other ideological affiliation provide no exemptions for the behavior we outline as unacceptable in this Code of Conduct.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team by DM at [Vite Land](https://chat.vite.dev). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
44 |
45 | [homepage]: https://www.contributor-covenant.org
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | # Vite Plugin React
15 |
16 | See [`@vitejs/plugin-react` documentation](packages/plugin-react/README.md) and [`@vitejs/plugin-react-swc` documentation](packages/plugin-react-swc/README.md)
17 |
18 | ## Packages
19 |
20 | | Package | Version (click for changelogs) |
21 | | ----------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
22 | | [@vitejs/plugin-react](packages/plugin-react) | [](packages/plugin-react/CHANGELOG.md) |
23 | | [@vitejs/plugin-react-oxc](packages/plugin-react-oxc) | [](packages/plugin-react-oxc/CHANGELOG.md) |
24 | | [@vitejs/plugin-react-swc](packages/plugin-react-swc) | [](packages/plugin-react-swc/CHANGELOG.md) |
25 |
26 | ## License
27 |
28 | [MIT](LICENSE).
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/vite-plugin-react-monorepo",
3 | "private": true,
4 | "type": "module",
5 | "engines": {
6 | "node": "^14.18.0 || >=16.0.0"
7 | },
8 | "packageManager": "pnpm@10.11.1",
9 | "homepage": "https://github.com/vitejs/vite-plugin-react/",
10 | "keywords": [
11 | "frontend",
12 | "hmr",
13 | "dev-server",
14 | "build-tool",
15 | "vite",
16 | "react"
17 | ],
18 | "scripts": {
19 | "preinstall": "npx only-allow pnpm",
20 | "postinstall": "simple-git-hooks",
21 | "format": "prettier --write --cache .",
22 | "lint": "eslint --cache .",
23 | "typecheck": "tsc -p scripts && tsc -p playground && tsc -p packages/plugin-react",
24 | "test": "pnpm run test-serve && pnpm run test-build && pnpm --filter ./packages/plugin-react-swc run test",
25 | "test-serve": "vitest run -c playground/vitest.config.e2e.ts",
26 | "test-build": "VITE_TEST_BUILD=1 vitest run -c playground/vitest.config.e2e.ts",
27 | "debug-serve": "VITE_DEBUG_SERVE=1 vitest run -c playground/vitest.config.e2e.ts",
28 | "debug-build": "VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 vitest run -c playground/vitest.config.e2e.ts",
29 | "build": "pnpm -r --filter='./packages/*' run build",
30 | "dev": "pnpm -r --parallel --filter='./packages/*' run dev",
31 | "release": "tsx scripts/release.ts",
32 | "ci-publish": "tsx scripts/publishCI.ts"
33 | },
34 | "devDependencies": {
35 | "@eslint/js": "^9.28.0",
36 | "@types/fs-extra": "^11.0.4",
37 | "@types/node": "^22.15.30",
38 | "@vitejs/release-scripts": "^1.5.0",
39 | "eslint": "^9.28.0",
40 | "eslint-plugin-import-x": "^4.15.1",
41 | "eslint-plugin-n": "^17.19.0",
42 | "eslint-plugin-regexp": "^2.8.0",
43 | "fs-extra": "^11.3.0",
44 | "globals": "^16.2.0",
45 | "lint-staged": "^15.5.2",
46 | "picocolors": "^1.1.1",
47 | "playwright-chromium": "^1.52.0",
48 | "prettier": "^3.0.3",
49 | "simple-git-hooks": "^2.13.0",
50 | "tsx": "^4.19.4",
51 | "typescript": "^5.8.3",
52 | "typescript-eslint": "^8.33.1",
53 | "vite": "^6.3.3",
54 | "vitest": "^3.2.2"
55 | },
56 | "simple-git-hooks": {
57 | "pre-commit": "pnpm exec lint-staged --concurrent false"
58 | },
59 | "lint-staged": {
60 | "*": [
61 | "prettier --write --cache --ignore-unknown"
62 | ],
63 | "packages/*/{src,types}/**/*.ts": [
64 | "eslint --cache --fix"
65 | ],
66 | "packages/**/*.d.ts": [
67 | "eslint --cache --fix"
68 | ],
69 | "playground/**/__tests__/**/*.ts": [
70 | "eslint --cache --fix"
71 | ]
72 | },
73 | "pnpm": {
74 | "packageExtensions": {
75 | "generouted": {
76 | "peerDependencies": {
77 | "react": "*",
78 | "react-router-dom": "*"
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/packages/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './refresh-utils'
2 | export * from './warning'
3 |
--------------------------------------------------------------------------------
/packages/common/package.json:
--------------------------------------------------------------------------------
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.3.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/common/warning.ts:
--------------------------------------------------------------------------------
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 | warning.message.includes('use server'))
12 | ) {
13 | return
14 | }
15 | // https://github.com/vitejs/vite/issues/15012
16 | if (
17 | warning.code === 'SOURCEMAP_ERROR' &&
18 | warning.message.includes('resolve original location') &&
19 | warning.pos === 0
20 | ) {
21 | return
22 | }
23 | if (userConfig.build?.rollupOptions?.onwarn) {
24 | userConfig.build.rollupOptions.onwarn(warning, defaultHandler)
25 | } else {
26 | defaultHandler(warning)
27 | }
28 | },
29 | },
30 | })
31 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Unreleased
4 |
5 | ## 0.2.1 (2025-06-03)
6 |
7 | ### Add explicit semicolon in preambleCode [#485](https://github.com/vitejs/vite-plugin-react/pull/485)
8 |
9 | This fixes an edge case when using HTML minifiers that strips line breaks aggressively.
10 |
11 | ## 0.2.0 (2025-05-23)
12 |
13 | ### Add `filter` for rolldown-vite [#470](https://github.com/vitejs/vite-plugin-react/pull/470)
14 |
15 | Added `filter` so that it is more performant when running this plugin with rolldown-powered version of Vite.
16 |
17 | ### Skip HMR for JSX files with hooks [#480](https://github.com/vitejs/vite-plugin-react/pull/480)
18 |
19 | This removes the HMR warning for hooks with JSX.
20 |
21 | ## 0.1.1 (2025-04-10)
22 |
23 | ## 0.1.0 (2025-04-09)
24 |
25 | - Create Oxc plugin
26 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/LICENSE:
--------------------------------------------------------------------------------
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.
22 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/README.md:
--------------------------------------------------------------------------------
1 | # @vitejs/plugin-react-oxc [](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).
85 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/build.config.ts:
--------------------------------------------------------------------------------
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 | replace: {
12 | 'globalThis.__IS_BUILD__': 'true',
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/plugin-react-oxc",
3 | "version": "0.2.1",
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 | "dependencies": {
51 | "@rolldown/pluginutils": "1.0.0-beta.11"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/scripts/copyRefreshRuntime.ts:
--------------------------------------------------------------------------------
1 | import { copyFileSync } from 'node:fs'
2 |
3 | copyFileSync(
4 | 'node_modules/@vitejs/react-common/refresh-runtime.js',
5 | 'dist/refresh-runtime.js',
6 | )
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/src/build.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | /** replaced by unbuild only in build */
3 | // eslint-disable-next-line no-var --- top level var has to be var
4 | var __IS_BUILD__: boolean | void
5 | }
6 |
7 | export {}
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-oxc/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "scripts"],
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "target": "ES2020",
6 | "module": "ES2020",
7 | "moduleResolution": "bundler",
8 | "strict": true,
9 | "declaration": true,
10 | "sourceMap": true,
11 | "noEmit": true,
12 | "noUnusedLocals": true,
13 | "esModuleInterop": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Arnaud Barré (https://github.com/ArnaudBarre)
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.
22 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/plugin-react-swc",
3 | "version": "3.10.1",
4 | "license": "MIT",
5 | "author": "Arnaud Barré (https://github.com/ArnaudBarre)",
6 | "description": "Speed up your Vite dev server with SWC",
7 | "keywords": [
8 | "vite",
9 | "vite-plugin",
10 | "react",
11 | "swc",
12 | "react-refresh",
13 | "fast refresh"
14 | ],
15 | "type": "module",
16 | "private": true,
17 | "scripts": {
18 | "dev": "tsx scripts/bundle.ts --dev",
19 | "build": "tsx scripts/bundle.ts",
20 | "test": "playwright test"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/vitejs/vite-plugin-react.git",
25 | "directory": "packages/plugin-react-swc"
26 | },
27 | "bugs": {
28 | "url": "https://github.com/vitejs/vite-plugin-react/issues"
29 | },
30 | "homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react-swc#readme",
31 | "dependencies": {
32 | "@rolldown/pluginutils": "1.0.0-beta.11",
33 | "@swc/core": "^1.11.31"
34 | },
35 | "peerDependencies": {
36 | "vite": "^4 || ^5 || ^6"
37 | },
38 | "devDependencies": {
39 | "@playwright/test": "^1.52.0",
40 | "@types/fs-extra": "^11.0.4",
41 | "@types/node": "^22.15.30",
42 | "@vitejs/react-common": "workspace:*",
43 | "esbuild": "^0.25.5",
44 | "fs-extra": "^11.3.0",
45 | "picocolors": "^1.1.1",
46 | "prettier": "^3.0.3",
47 | "typescript": "^5.8.3",
48 | "vite": "^6.3.3"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/__tests__/base-path.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { setupDevServer, setupWaitForLogs } from '../../utils.ts'
3 |
4 | test('Base path HMR', async ({ page }) => {
5 | const { testUrl, server, editFile } = await setupDevServer('base-path')
6 | const waitForLogs = await setupWaitForLogs(page)
7 | await page.goto(testUrl)
8 |
9 | const button = page.locator('button')
10 | await button.click()
11 | await expect(button).toHaveText('count is 1')
12 |
13 | editFile('src/App.tsx', ['{count}', '{count}!'])
14 | await waitForLogs('[vite] hot updated: /src/App.tsx')
15 | await expect(button).toHaveText('count is 1!')
16 |
17 | await server.close()
18 | })
19 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS + base path
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-base-test",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export const App = () => {
4 | const [count, setCount] = useState(0)
5 |
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/base-path/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | base: '/base-test/',
7 | })
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/__tests__/class-components.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { setupDevServer, setupWaitForLogs } from '../../utils.ts'
3 |
4 | test('Class component HMR', async ({ page }) => {
5 | const { testUrl, server, editFile } = await setupDevServer('class-components')
6 | const waitForLogs = await setupWaitForLogs(page)
7 | await page.goto(testUrl)
8 |
9 | await expect(page.locator('body')).toHaveText('Hello World')
10 | editFile('src/App.tsx', ['World', 'class components'])
11 | await waitForLogs('[vite] hot updated: /src/App.tsx')
12 | await expect(page.locator('body')).toHaveText('Hello class components')
13 |
14 | editFile('src/utils.tsx', ['Hello', 'Hi'])
15 | await waitForLogs('[vite] hot updated: /src/App.tsx')
16 | await expect(page.locator('body')).toHaveText('Hi class components')
17 |
18 | await server.close()
19 | })
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + class components
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "class-components",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | import { getGetting } from './utils.tsx'
3 |
4 | export class App extends Component {
5 | render() {
6 | return {getGetting()} World
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/src/utils.tsx:
--------------------------------------------------------------------------------
1 | export const getGetting = () => Hello
2 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/class-components/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/__tests__/decorators.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { setupBuildAndPreview, setupDevServer } from '../../utils.ts'
3 |
4 | test('Decorators build', async ({ page }) => {
5 | const { testUrl, server } = await setupBuildAndPreview('decorators')
6 | await page.goto(testUrl)
7 |
8 | await expect(page.locator('body')).toHaveText('Hello World')
9 |
10 | await server.httpServer.close()
11 | })
12 |
13 | test('Decorators dev', async ({ page }) => {
14 | const { testUrl, server } = await setupDevServer('decorators')
15 | await page.goto(testUrl)
16 |
17 | await expect(page.locator('body')).toHaveText('Hello World')
18 |
19 | await server.close()
20 | })
21 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS + decorators
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-decorators",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/src/App.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentClass } from 'react'
2 | import { Component } from 'react'
3 |
4 | function decorated(target: ComponentClass) {
5 | const original = target.prototype.render
6 |
7 | target.prototype.render = () => {
8 | return Hello {original()}
9 | }
10 | }
11 |
12 | @decorated
13 | export class App extends Component {
14 | render() {
15 | return World
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "experimentalDecorators": true,
13 | "moduleResolution": "bundler",
14 | "allowImportingTsExtensions": true,
15 | "resolveJsonModule": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "useUnknownInCatchVariables": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/decorators/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react({ tsDecorators: true })],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/__tests__/emotion-plugin.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import {
3 | expectColor,
4 | setupBuildAndPreview,
5 | setupDevServer,
6 | setupWaitForLogs,
7 | } from '../../utils.ts'
8 |
9 | test('Emotion plugin build', async ({ page }) => {
10 | const { testUrl, server } = await setupBuildAndPreview('emotion-plugin')
11 | await page.goto(testUrl)
12 |
13 | const button = page.locator('button')
14 | await button.hover()
15 | await expectColor(button, 'color', '#646cff')
16 |
17 | await button.click()
18 | await expect(button).toHaveText('count is 1')
19 |
20 | const code = page.locator('code')
21 | await expectColor(code, 'color', '#646cff')
22 |
23 | await server.httpServer.close()
24 | })
25 |
26 | test('Emotion plugin HMR', async ({ page }) => {
27 | const { testUrl, server, editFile } = await setupDevServer('emotion-plugin')
28 | const waitForLogs = await setupWaitForLogs(page)
29 | await page.goto(testUrl)
30 | await waitForLogs('[vite] connected.')
31 |
32 | const button = page.locator('button')
33 | await button.hover()
34 | await expectColor(button, 'color', '#646cff')
35 |
36 | await button.click()
37 | await expect(button).toHaveText('count is 1')
38 |
39 | const code = page.locator('code')
40 | await expectColor(code, 'color', '#646cff')
41 |
42 | editFile('src/Button.jsx', [
43 | 'background-color: #d26ac2;',
44 | 'background-color: #646cff;',
45 | ])
46 | await waitForLogs('[vite] hot updated: /src/Button.jsx')
47 | await expect(button).toHaveText('count is 1')
48 | await expectColor(button, 'backgroundColor', '#646cff')
49 |
50 | editFile('src/App.jsx', ['color="#646cff"', 'color="#d26ac2"'])
51 | await waitForLogs('[vite] hot updated: /src/App.jsx')
52 | await expect(button).toHaveText('count is 1')
53 | await expectColor(button, 'color', '#d26ac2')
54 |
55 | editFile('src/Button.jsx', ['color: #646cff;', 'color: #d26ac2;'])
56 | await waitForLogs('[vite] hot updated: /src/Button.jsx')
57 | await expect(button).toHaveText('count is 1')
58 | await expectColor(code, 'color', '#d26ac2')
59 |
60 | await server.close()
61 | })
62 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS + Emotion
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-emotion-plugin",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "@emotion/react": "^11.14.0",
12 | "@emotion/styled": "^11.14.0",
13 | "react": "^19.1.0",
14 | "react-dom": "^19.1.0"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^19.1.6",
18 | "@types/react-dom": "^19.1.6",
19 | "@vitejs/plugin-react-swc": "../../dist",
20 | "@swc/plugin-emotion": "^9.0.4"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.emotion:hover {
17 | filter: drop-shadow(0 0 2em #d26ac2aa);
18 | }
19 |
20 | .card {
21 | padding: 2em;
22 | }
23 |
24 | .read-the-docs {
25 | color: #888;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/src/App.jsx:
--------------------------------------------------------------------------------
1 | import './App.css'
2 | import { Button, StyledCode } from './Button.jsx'
3 |
4 | export const App = () => (
5 |
6 |
18 |
19 |
20 |
21 | Edit src/Button.tsx and save to test HMR
22 |
23 |
24 |
25 | Click on the Vite and Emotion logos to learn more
26 |
27 |
28 | )
29 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/src/Button.jsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import { css } from '@emotion/react'
3 | import { useState } from 'react'
4 |
5 | // Ensure HMR of styled component alongside other components
6 | export const StyledCode = styled.code`
7 | color: #646cff;
8 | `
9 |
10 | export const Button = ({ color }) => {
11 | const [count, setCount] = useState(0)
12 |
13 | return (
14 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | body {
19 | margin: 0;
20 | display: flex;
21 | place-items: center;
22 | min-width: 320px;
23 | min-height: 100vh;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/src/index.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.jsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion-plugin/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [
6 | react({
7 | jsxImportSource: '@emotion/react',
8 | plugins: [['@swc/plugin-emotion', {}]],
9 | }),
10 | ],
11 | })
12 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/__tests__/emotion.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import {
3 | expectColor,
4 | setupBuildAndPreview,
5 | setupDevServer,
6 | setupWaitForLogs,
7 | } from '../../utils.ts'
8 |
9 | test('Emotion build', async ({ page }) => {
10 | const { testUrl, server } = await setupBuildAndPreview('emotion')
11 | await page.goto(testUrl)
12 |
13 | const button = page.locator('button')
14 | await button.hover()
15 | await expectColor(button, 'color', '#646cff')
16 |
17 | await button.click()
18 | await expect(button).toHaveText('count is 1')
19 |
20 | const code = page.locator('code')
21 | await expectColor(code, 'color', '#646cff')
22 |
23 | await server.httpServer.close()
24 | })
25 |
26 | test('Emotion HMR', async ({ page }) => {
27 | const { testUrl, server, editFile } = await setupDevServer('emotion')
28 | const waitForLogs = await setupWaitForLogs(page)
29 | await page.goto(testUrl)
30 | await waitForLogs('[vite] connected.')
31 |
32 | const button = page.locator('button')
33 | await button.hover()
34 | await expectColor(button, 'color', '#646cff')
35 |
36 | await button.click()
37 | await expect(button).toHaveText('count is 1')
38 |
39 | const code = page.locator('code')
40 | await expectColor(code, 'color', '#646cff')
41 |
42 | editFile('src/Button.tsx', [
43 | 'background-color: #d26ac2;',
44 | 'background-color: #646cff;',
45 | ])
46 | await waitForLogs('[vite] hot updated: /src/Button.tsx')
47 | await expect(button).toHaveText('count is 1')
48 | await expectColor(button, 'backgroundColor', '#646cff')
49 |
50 | editFile('src/App.tsx', ['color="#646cff"', 'color="#d26ac2"'])
51 | await waitForLogs('[vite] hot updated: /src/App.tsx')
52 | await expect(button).toHaveText('count is 1')
53 | await expectColor(button, 'color', '#d26ac2')
54 |
55 | editFile('src/Button.tsx', ['color: #646cff;', 'color: #d26ac2;'])
56 | await waitForLogs('[vite] hot updated: /src/Button.tsx')
57 | await expect(button).toHaveText('count is 1')
58 | await expectColor(code, 'color', '#d26ac2')
59 |
60 | await server.close()
61 | })
62 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS + Emotion
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-emotion",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "@emotion/react": "^11.14.0",
12 | "@emotion/styled": "^11.14.0",
13 | "react": "^19.1.0",
14 | "react-dom": "^19.1.0"
15 | },
16 | "devDependencies": {
17 | "@types/react": "^19.1.6",
18 | "@types/react-dom": "^19.1.6",
19 | "@vitejs/plugin-react-swc": "../../dist"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.emotion:hover {
17 | filter: drop-shadow(0 0 2em #d26ac2aa);
18 | }
19 |
20 | .card {
21 | padding: 2em;
22 | }
23 |
24 | .read-the-docs {
25 | color: #888;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/src/App.tsx:
--------------------------------------------------------------------------------
1 | import './App.css'
2 | import { Button, StyledCode } from './Button.tsx'
3 |
4 | export const App = () => (
5 |
6 |
18 |
19 |
20 |
21 | Edit src/Button.tsx and save to test HMR
22 |
23 |
24 |
25 | Click on the Vite and Emotion logos to learn more
26 |
27 |
28 | )
29 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/src/Button.tsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import { css } from '@emotion/react'
3 | import { useState } from 'react'
4 |
5 | // Ensure HMR of styled component alongside other components
6 | export const StyledCode = styled.code`
7 | color: #646cff;
8 | `
9 |
10 | export const Button = ({ color }: { color: string }) => {
11 | const [count, setCount] = useState(0)
12 |
13 | return (
14 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | body {
19 | margin: 0;
20 | display: flex;
21 | place-items: center;
22 | min-width: 320px;
23 | min-height: 100vh;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@emotion/react",
9 | "types": ["vite/client", "@emotion/react"],
10 | "noEmit": true,
11 | "isolatedModules": true,
12 | "skipLibCheck": true,
13 | "moduleResolution": "bundler",
14 | "allowImportingTsExtensions": true,
15 | "resolveJsonModule": true,
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "useUnknownInCatchVariables": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/emotion/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react({ jsxImportSource: '@emotion/react' })],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-hmr",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import reactLogo from './react.svg'
3 | import './App.css'
4 | import { TitleWithExport, framework } from './TitleWithExport.tsx'
5 |
6 | export const App = () => {
7 | const [count, setCount] = useState(0)
8 |
9 | return (
10 |
11 |
19 |
20 |
21 |
22 |
23 | Edit src/App.tsx
and save to test HMR
24 |
25 |
26 |
27 | Click on the Vite and {framework} logos to learn more
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/src/TitleWithExport.tsx:
--------------------------------------------------------------------------------
1 | export const framework = 'React'
2 |
3 | export const TitleWithExport = () => Vite + {framework}
4 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/hmr/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/__tests__/mdx.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import {
3 | setupBuildAndPreview,
4 | setupDevServer,
5 | setupWaitForLogs,
6 | } from '../../utils.ts'
7 |
8 | test('MDX build', async ({ page }) => {
9 | const { testUrl, server } = await setupBuildAndPreview('mdx')
10 | await page.goto(testUrl)
11 | await expect(page.getByRole('heading', { name: 'Hello' })).toBeVisible()
12 | await server.httpServer.close()
13 | })
14 |
15 | test('MDX HMR', async ({ page }) => {
16 | const { testUrl, server, editFile } = await setupDevServer('mdx')
17 | const waitForLogs = await setupWaitForLogs(page)
18 | await page.goto(testUrl)
19 | await waitForLogs('[vite] connected.')
20 |
21 | await expect(page.getByRole('heading', { name: 'Hello' })).toBeVisible()
22 |
23 | editFile('src/Counter.tsx', ['{count}', '{count}!'])
24 | await waitForLogs('[vite] hot updated: /src/Counter.tsx')
25 | const button = await page.locator('button')
26 | await button.click()
27 | await expect(button).toHaveText('count is 1!')
28 |
29 | editFile('src/hello.mdx', ['Hello', 'Hello world'])
30 | await waitForLogs('[vite] hot updated: /src/hello.mdx')
31 | await expect(page.getByRole('heading', { name: 'Hello world' })).toBeVisible()
32 | await expect(button).toHaveText('count is 1!')
33 |
34 | await server.close()
35 | })
36 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + MDX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-mdx",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@mdx-js/rollup": "^3.1.0",
16 | "@types/react": "^19.1.6",
17 | "@types/react-dom": "^19.1.6",
18 | "@vitejs/plugin-react-swc": "../../dist"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/src/Counter.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export const Counter = () => {
4 | const [count, setCount] = useState(0)
5 |
6 | return
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/src/env.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.mdx' {
2 | import { JSX } from 'react'
3 | export default () => JSX.Element
4 | }
5 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/src/hello.mdx:
--------------------------------------------------------------------------------
1 | import { Counter } from './Counter.tsx'
2 |
3 | # Hello
4 |
5 | This text is written in Markdown.
6 |
7 | MDX allows Rich React components to be used directly in Markdown:
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import Hello from './hello.mdx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/mdx/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import mdx from '@mdx-js/rollup'
3 | import react from '@vitejs/plugin-react-swc'
4 |
5 | export default defineConfig({
6 | plugins: [mdx(), react()],
7 | })
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-react-18",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^18.3.1",
12 | "react-dom": "^18.3.1"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^18.3.18",
16 | "@types/react-dom": "^18.3.5",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.react:hover {
17 | filter: drop-shadow(0 0 2em #61dafbaa);
18 | }
19 |
20 | @keyframes logo-spin {
21 | from {
22 | transform: rotate(0deg);
23 | }
24 | to {
25 | transform: rotate(360deg);
26 | }
27 | }
28 |
29 | @media (prefers-reduced-motion: no-preference) {
30 | a:nth-of-type(2) .logo {
31 | animation: logo-spin infinite 20s linear;
32 | }
33 | }
34 |
35 | .card {
36 | padding: 2em;
37 | }
38 |
39 | .read-the-docs {
40 | color: #888;
41 | }
42 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import reactLogo from './react.svg'
3 | import './App.css'
4 | import { TitleWithExport, framework } from './TitleWithExport.tsx'
5 |
6 | export const App = () => {
7 | const [count, setCount] = useState(0)
8 |
9 | return (
10 |
11 |
19 |
20 |
21 |
22 |
23 | Edit src/App.tsx
and save to test HMR
24 |
25 |
26 |
27 | Click on the Vite and {framework} logos to learn more
28 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/src/TitleWithExport.tsx:
--------------------------------------------------------------------------------
1 | export const framework = 'React'
2 |
3 | export const TitleWithExport = () => Vite + {framework}
4 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/react-18/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/__tests__/shadow-export.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { setupDevServer, setupWaitForLogs } from '../../utils.ts'
3 |
4 | test('Shadow export HMR', async ({ page }) => {
5 | const { testUrl, server, editFile } = await setupDevServer('shadow-export')
6 | const waitForLogs = await setupWaitForLogs(page)
7 | await page.goto(testUrl)
8 | await waitForLogs('[vite] connected.')
9 | await expect(page.locator('body')).toHaveText('Shadow export')
10 |
11 | editFile('src/App.tsx', ['Shadow export', 'Shadow export updates!'])
12 | await expect(page.locator('body')).toHaveText('Shadow export updates!')
13 |
14 | await server.close()
15 | })
16 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + shadow export
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-shadow-export",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from 'react'
2 |
3 | function App() {
4 | return Shadow export
5 | }
6 |
7 | // For anyone reading this, don't do that
8 | // Use PascalCase for all components and export them directly without rename,
9 | // you're just making grep more complex.
10 | const withMemo = memo(App)
11 | export { withMemo as App }
12 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/shadow-export/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/__tests__/styled-components.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import {
3 | expectColor,
4 | setupBuildAndPreview,
5 | setupDevServer,
6 | setupWaitForLogs,
7 | } from '../../utils.ts'
8 |
9 | test('styled-components build', async ({ page }) => {
10 | const { testUrl, server } = await setupBuildAndPreview('styled-components')
11 | await page.goto(testUrl)
12 |
13 | const button = page.locator('button')
14 | await button.click()
15 | await expect(button).toHaveText('count is 1')
16 | await expectColor(button, 'color', '#ffffff')
17 |
18 | const code = page.locator('code')
19 | await expectColor(code, 'color', '#db7093')
20 | await expect(code).toHaveClass(/Button__StyledCode/)
21 |
22 | await server.httpServer.close()
23 | })
24 |
25 | test('styled-components HMR', async ({ page }) => {
26 | const { testUrl, server, editFile } =
27 | await setupDevServer('styled-components')
28 | const waitForLogs = await setupWaitForLogs(page)
29 | await page.goto(testUrl)
30 | await waitForLogs('[vite] connected.')
31 |
32 | const button = page.locator('button')
33 | await expect(button).toHaveText('count is 0', { timeout: 30000 })
34 | await expectColor(button, 'color', '#ffffff')
35 | await button.click()
36 | await expect(button).toHaveText('count is 1')
37 |
38 | const code = page.locator('code')
39 | await expectColor(code, 'color', '#db7093')
40 | await expect(code).toHaveClass(/Button__StyledCode/)
41 |
42 | editFile('src/App.tsx', ['', ''])
43 | await waitForLogs('[vite] hot updated: /src/App.tsx')
44 | await expect(button).toHaveText('count is 1')
45 | await expectColor(button, 'color', '#000000')
46 |
47 | editFile('src/Button.tsx', ['color: black;', 'color: palevioletred;'])
48 | await waitForLogs('[vite] hot updated: /src/Button.tsx')
49 | await expect(button).toHaveText('count is 1')
50 | await expectColor(button, 'color', '#db7093')
51 |
52 | editFile('src/Button.tsx', ['color: palevioletred;', 'color: white;'])
53 | await waitForLogs('[vite] hot updated: /src/Button.tsx')
54 | await expect(button).toHaveText('count is 1')
55 | await expectColor(code, 'color', '#ffffff')
56 |
57 | await server.close()
58 | })
59 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS + Styled Components
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-styled-components",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0",
13 | "react-is": "^19.1.0",
14 | "styled-components": "^6.1.18"
15 | },
16 | "devDependencies": {
17 | "@swc/plugin-styled-components": "^7.1.5",
18 | "@types/react": "^19.1.6",
19 | "@types/react-dom": "^19.1.6",
20 | "@types/styled-components": "^5.1.34",
21 | "@vitejs/plugin-react-swc": "../../dist"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | }
13 | .logo:hover {
14 | filter: drop-shadow(0 0 2em #646cffaa);
15 | }
16 | .logo.styled-components:hover {
17 | filter: drop-shadow(0 0 2em #db7093aa);
18 | }
19 |
20 | .card {
21 | padding: 2em;
22 | }
23 |
24 | .read-the-docs {
25 | color: #888;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/src/App.tsx:
--------------------------------------------------------------------------------
1 | import './App.css'
2 | import { Counter, StyledCode } from './Button.tsx'
3 |
4 | export const App = () => (
5 |
6 |
18 |
19 |
20 |
21 | Edit src/Button.tsx and save to test HMR
22 |
23 |
24 |
25 | Click on the Vite and Styled Components logos to learn more
26 |
27 |
28 | )
29 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/src/Button.tsx:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components'
2 | import { useState } from 'react'
3 |
4 | // Ensure HMR of styled component alongside other components
5 | export const StyledCode = styled.code`
6 | color: palevioletred;
7 | `
8 |
9 | const Button = styled.button`
10 | border-radius: 3px;
11 | padding: 0.5rem 1rem;
12 | color: white;
13 | background: transparent;
14 | border: 2px solid white;
15 | ${(props: { primary?: boolean }) =>
16 | props.primary &&
17 | css`
18 | background: white;
19 | color: black;
20 | `}
21 | `
22 |
23 | export const Counter = ({ primary }: { primary?: boolean }) => {
24 | const [count, setCount] = useState(0)
25 |
26 | return (
27 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | body {
19 | margin: 0;
20 | display: flex;
21 | place-items: center;
22 | min-width: 320px;
23 | min-height: 100vh;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 | import './index.css'
5 |
6 | createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/styled-components/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react({ plugins: [['@swc/plugin-styled-components', {}]] })],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/__tests__/ts-lib.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from '@playwright/test'
2 | import { setupBuildAndPreview, setupDevServer } from '../../utils.ts'
3 |
4 | test('TS lib build', async ({ page }) => {
5 | const { testUrl, server } = await setupBuildAndPreview('ts-lib')
6 | await page.goto(testUrl)
7 | await expect(page.locator('main')).toHaveText('Home page')
8 |
9 | await page.locator('a', { hasText: 'About' }).click()
10 | await expect(page.locator('main')).toHaveText('About page')
11 |
12 | await server.httpServer.close()
13 | })
14 |
15 | test('TS lib dev', async ({ page }) => {
16 | const { testUrl, server } = await setupDevServer('ts-lib')
17 | await page.goto(testUrl)
18 | await expect(page.locator('main')).toHaveText('Home page')
19 |
20 | await page.locator('a', { hasText: 'About' }).click()
21 | await expect(page.locator('main')).toHaveText('About page')
22 |
23 | await server.close()
24 | })
25 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS lib
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-ts-lib",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "@generouted/react-router": "^1.20.0",
12 | "generouted": "1.11.7",
13 | "react": "^19.1.0",
14 | "react-dom": "^19.1.0",
15 | "react-router-dom": "^7.6.2"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^19.1.6",
19 | "@types/react-dom": "^19.1.6",
20 | "@vitejs/plugin-react-swc": "../../dist"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { Routes } from 'generouted/react-router'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | export default function NotFound() {
2 | return 404
3 | }
4 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Outlet } from 'react-router-dom'
2 |
3 | export default function App() {
4 | return (
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/src/pages/about.tsx:
--------------------------------------------------------------------------------
1 | export default function About() {
2 | return About page
3 | }
4 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from 'react-router-dom'
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
Home page
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 | "noUncheckedIndexedAccess": true,
16 | "noPropertyAccessFromIndexSignature": true,
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "useUnknownInCatchVariables": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/ts-lib/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | optimizeDeps: { include: ['react-router-dom'] },
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/utils.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from 'node:fs'
2 | import { type Locator, type Page, expect } from '@playwright/test'
3 | import {
4 | build,
5 | createServer,
6 | loadConfigFromFile,
7 | mergeConfig,
8 | preview,
9 | } from 'vite'
10 |
11 | export const setupWaitForLogs = async (page: Page) => {
12 | let logs: string[] = []
13 | page.on('console', (log) => {
14 | logs.push(log.text())
15 | })
16 | return (...messages: (string | RegExp)[]) =>
17 | expect
18 | .poll(() => {
19 | if (
20 | messages.every((m) =>
21 | typeof m === 'string'
22 | ? logs.includes(m)
23 | : logs.some((l) => m.test(l)),
24 | )
25 | ) {
26 | logs = []
27 | return true
28 | }
29 | return logs
30 | })
31 | .toBe(true)
32 | }
33 |
34 | let port = 5173
35 | export const setupDevServer = async (name: string) => {
36 | process.env['NODE_ENV'] = 'development'
37 | const root = `playground-temp/${name}`
38 | const res = await loadConfigFromFile(
39 | { command: 'serve', mode: 'development' },
40 | undefined,
41 | root,
42 | )
43 | const testConfig = mergeConfig(res!.config, {
44 | root,
45 | logLevel: 'silent',
46 | configFile: false,
47 | server: { port: port++ },
48 | })
49 | const server = await (await createServer(testConfig)).listen()
50 | return {
51 | testUrl: `http://localhost:${server.config.server.port}${server.config.base}`,
52 | server,
53 | editFile: (
54 | name: string,
55 | ...replacements: [searchValue: string, replaceValue: string][]
56 | ) => {
57 | const path = `${root}/${name}`
58 | let content = readFileSync(path, 'utf-8')
59 | for (const [search, replace] of replacements) {
60 | if (!content.includes(search)) {
61 | throw new Error(`'${search}' not found in ${name}`)
62 | }
63 | content = content.replace(search, replace)
64 | }
65 | writeFileSync(path, content)
66 | },
67 | }
68 | }
69 |
70 | export const setupBuildAndPreview = async (name: string) => {
71 | process.env['NODE_ENV'] = 'production'
72 | const root = `playground-temp/${name}`
73 | const res = await loadConfigFromFile(
74 | { command: 'build', mode: 'production' },
75 | undefined,
76 | root,
77 | )
78 | const testConfig = mergeConfig(
79 | { root, logLevel: 'silent', configFile: false, preview: { port: port++ } },
80 | res!.config,
81 | )
82 | await build(testConfig)
83 | const server = await preview(testConfig)
84 | return {
85 | testUrl: server.resolvedUrls!.local[0],
86 | server,
87 | }
88 | }
89 |
90 | export const expectColor = async (
91 | locator: Locator,
92 | property: 'color' | 'backgroundColor',
93 | color: string,
94 | ) => {
95 | await expect
96 | .poll(async () =>
97 | rgbToHex(
98 | await locator.evaluate(
99 | (el, prop) => getComputedStyle(el)[prop],
100 | property,
101 | ),
102 | ),
103 | )
104 | .toBe(color)
105 | }
106 |
107 | const rgbToHex = (rgb: string): string => {
108 | const [_, rs, gs, bs] = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/)!
109 | return (
110 | '#' +
111 | componentToHex(parseInt(rs, 10)) +
112 | componentToHex(parseInt(gs, 10)) +
113 | componentToHex(parseInt(bs, 10))
114 | )
115 | }
116 |
117 | const componentToHex = (c: number): string => {
118 | const hex = c.toString(16)
119 | return hex.length === 1 ? '0' + hex : hex
120 | }
121 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/__tests__/worker.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from '@playwright/test'
2 | import {
3 | setupBuildAndPreview,
4 | setupDevServer,
5 | setupWaitForLogs,
6 | } from '../../utils.ts'
7 |
8 | test('Worker build', async ({ page }) => {
9 | const { testUrl, server } = await setupBuildAndPreview('worker')
10 | const waitForLogs = await setupWaitForLogs(page)
11 | await page.goto(testUrl)
12 | await waitForLogs('Worker lives!', 'Worker imported!')
13 |
14 | await server.httpServer.close()
15 | })
16 |
17 | test('Worker HMR', async ({ page }) => {
18 | const { testUrl, server, editFile } = await setupDevServer('worker')
19 | const waitForLogs = await setupWaitForLogs(page)
20 | await page.goto(testUrl)
21 | await waitForLogs('Worker lives!', 'Worker imported!')
22 |
23 | editFile('src/worker-via-url.ts', ['Worker lives!', 'Worker updates!'])
24 | await waitForLogs('Worker updates!')
25 |
26 | await server.close()
27 | })
28 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + Worker
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-worker",
3 | "type": "module",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react-swc": "../../dist"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import MyWorker from './worker-via-import.ts?worker&inline'
3 |
4 | new MyWorker()
5 |
6 | export const App = () => {
7 | const [count, setCount] = useState(0)
8 |
9 | return
10 | }
11 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | new Worker(new URL('./worker-via-url.ts', import.meta.url), { type: 'module' })
6 |
7 | createRoot(document.getElementById('root')!).render(
8 |
9 |
10 | ,
11 | )
12 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/src/worker-via-import.ts:
--------------------------------------------------------------------------------
1 | function printAlive(): void {
2 | console.log('Worker imported!')
3 | }
4 |
5 | printAlive()
6 |
7 | export {}
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/src/worker-via-url.ts:
--------------------------------------------------------------------------------
1 | function printAlive(): void {
2 | console.log('Worker lives!')
3 | }
4 |
5 | printAlive()
6 |
7 | export {}
8 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "vite.config.ts"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playground/worker/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | })
7 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { type PlaywrightTestConfig, devices } from '@playwright/test'
3 | import fs from 'fs-extra'
4 |
5 | const tempDir = fileURLToPath(new URL('playground-temp', import.meta.url))
6 | fs.ensureDirSync(tempDir)
7 | fs.emptyDirSync(tempDir)
8 | fs.copySync(fileURLToPath(new URL('playground', import.meta.url)), tempDir, {
9 | filter: (src) => {
10 | src = src.replaceAll('\\', '/')
11 | return (
12 | !src.includes('/__tests__') &&
13 | !src.includes('/.vite') &&
14 | !src.includes('/dist')
15 | )
16 | },
17 | })
18 |
19 | const config: PlaywrightTestConfig = {
20 | forbidOnly: !!process.env['CI'],
21 | workers: 1,
22 | timeout: 10_000,
23 | reporter: process.env['CI'] ? 'github' : 'list',
24 | projects: [{ name: 'chromium', use: devices['Desktop Chrome'] }],
25 | }
26 |
27 | export default config
28 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/scripts/bundle.ts:
--------------------------------------------------------------------------------
1 | import { copyFileSync, rmSync, writeFileSync } from 'node:fs'
2 | import { execSync } from 'node:child_process'
3 | import { type BuildOptions, build, context } from 'esbuild'
4 |
5 | import packageJSON from '../package.json'
6 |
7 | const dev = process.argv.includes('--dev')
8 |
9 | rmSync('dist', { force: true, recursive: true })
10 |
11 | const serverOptions: BuildOptions = {
12 | bundle: true,
13 | platform: 'node',
14 | target: 'node14',
15 | legalComments: 'inline',
16 | external: Object.keys(packageJSON.peerDependencies).concat(
17 | Object.keys(packageJSON.dependencies),
18 | ),
19 | }
20 |
21 | const buildOrWatch = async (options: BuildOptions) => {
22 | if (!dev) return build(options)
23 | const ctx = await context(options)
24 | await ctx.watch()
25 | await ctx.rebuild()
26 | }
27 |
28 | Promise.all([
29 | buildOrWatch({
30 | entryPoints: ['@vitejs/react-common/refresh-runtime'],
31 | outdir: 'dist',
32 | platform: 'browser',
33 | format: 'esm',
34 | target: 'safari13',
35 | legalComments: 'inline',
36 | }),
37 | buildOrWatch({
38 | ...serverOptions,
39 | stdin: {
40 | contents: `import react from "./src";
41 | module.exports = react;
42 | // For backward compatibility with the first broken version
43 | module.exports.default = react;`,
44 | resolveDir: '.',
45 | },
46 | outfile: 'dist/index.cjs',
47 | logOverride: { 'empty-import-meta': 'silent' },
48 | }),
49 | buildOrWatch({
50 | ...serverOptions,
51 | entryPoints: ['src/index.ts'],
52 | format: 'esm',
53 | outfile: 'dist/index.mjs',
54 | }),
55 | ]).then(() => {
56 | copyFileSync('LICENSE', 'dist/LICENSE')
57 | copyFileSync('README.md', 'dist/README.md')
58 |
59 | execSync(
60 | 'tsc src/index.ts --declaration --isolatedDeclarations --noCheck --emitDeclarationOnly --outDir dist --target es2020 --module es2020 --moduleResolution bundler',
61 | { stdio: 'inherit' },
62 | )
63 |
64 | writeFileSync(
65 | 'dist/package.json',
66 | JSON.stringify(
67 | {
68 | ...Object.fromEntries(
69 | Object.entries(packageJSON).filter(
70 | ([key, _val]) =>
71 | key !== 'devDependencies' &&
72 | key !== 'scripts' &&
73 | key !== 'private',
74 | ),
75 | ),
76 | main: 'index.cjs',
77 | types: 'index.d.ts',
78 | module: 'index.mjs',
79 | exports: {
80 | '.': {
81 | types: './index.d.ts',
82 | require: './index.cjs',
83 | import: './index.mjs',
84 | },
85 | },
86 | },
87 | null,
88 | 2,
89 | ),
90 | )
91 | })
92 |
--------------------------------------------------------------------------------
/packages/plugin-react-swc/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src",
4 | "scripts",
5 | "playwright.config.ts",
6 | "playground/utils.ts",
7 | "playground/*/__tests__"
8 | ],
9 | "compilerOptions": {
10 | /* Target node 22 */
11 | "module": "ESNext",
12 | "lib": ["ES2023", "DOM"],
13 | "target": "ES2023",
14 | "skipLibCheck": true,
15 |
16 | /* Bundler mode */
17 | "moduleResolution": "bundler",
18 | "allowImportingTsExtensions": true,
19 | "verbatimModuleSyntax": true,
20 | "noEmit": true,
21 |
22 | /* Linting */
23 | "strict": true,
24 | "noUnusedLocals": true,
25 | "noUnusedParameters": true,
26 | "noFallthroughCasesInSwitch": true,
27 | "useUnknownInCatchVariables": true,
28 | "noUncheckedSideEffectImports": true,
29 | "noPropertyAccessFromIndexSignature": true
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-react/LICENSE:
--------------------------------------------------------------------------------
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.
22 |
--------------------------------------------------------------------------------
/packages/plugin-react/build.config.ts:
--------------------------------------------------------------------------------
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 | emitCJS: true,
10 | inlineDependencies: true,
11 | },
12 | replace: {
13 | 'globalThis.__IS_BUILD__': 'true',
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/packages/plugin-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/plugin-react",
3 | "version": "4.5.1",
4 | "license": "MIT",
5 | "author": "Evan You",
6 | "description": "The default Vite plugin for React projects",
7 | "keywords": [
8 | "vite",
9 | "vite-plugin",
10 | "react",
11 | "babel",
12 | "react-refresh",
13 | "fast refresh"
14 | ],
15 | "contributors": [
16 | "Alec Larson",
17 | "Arnaud Barré"
18 | ],
19 | "files": [
20 | "dist"
21 | ],
22 | "type": "module",
23 | "main": "./dist/index.cjs",
24 | "module": "./dist/index.mjs",
25 | "types": "./dist/index.d.mts",
26 | "exports": {
27 | ".": {
28 | "import": "./dist/index.mjs",
29 | "require": "./dist/index.cjs"
30 | }
31 | },
32 | "scripts": {
33 | "dev": "unbuild --stub",
34 | "build": "unbuild && pnpm run patch-cjs && tsx scripts/copyRefreshRuntime.ts",
35 | "patch-cjs": "tsx ../../scripts/patchCJS.ts",
36 | "prepublishOnly": "npm run build"
37 | },
38 | "engines": {
39 | "node": "^14.18.0 || >=16.0.0"
40 | },
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/vitejs/vite-plugin-react.git",
44 | "directory": "packages/plugin-react"
45 | },
46 | "bugs": {
47 | "url": "https://github.com/vitejs/vite-plugin-react/issues"
48 | },
49 | "homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme",
50 | "dependencies": {
51 | "@babel/core": "^7.27.4",
52 | "@babel/plugin-transform-react-jsx-self": "^7.27.1",
53 | "@babel/plugin-transform-react-jsx-source": "^7.27.1",
54 | "@rolldown/pluginutils": "1.0.0-beta.11",
55 | "@types/babel__core": "^7.20.5",
56 | "react-refresh": "^0.17.0"
57 | },
58 | "peerDependencies": {
59 | "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
60 | },
61 | "devDependencies": {
62 | "@vitejs/react-common": "workspace:*",
63 | "unbuild": "^3.5.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/plugin-react/scripts/copyRefreshRuntime.ts:
--------------------------------------------------------------------------------
1 | import { copyFileSync } from 'node:fs'
2 |
3 | copyFileSync(
4 | 'node_modules/@vitejs/react-common/refresh-runtime.js',
5 | 'dist/refresh-runtime.js',
6 | )
7 |
--------------------------------------------------------------------------------
/packages/plugin-react/src/babel.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@babel/plugin-transform-react-jsx-self'
2 | declare module '@babel/plugin-transform-react-jsx-source'
3 | declare module 'react-refresh/babel.js'
4 |
--------------------------------------------------------------------------------
/packages/plugin-react/src/build.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | /** replaced by unbuild only in build */
3 | // eslint-disable-next-line no-var --- top level var has to be var
4 | var __IS_BUILD__: boolean | void
5 | }
6 |
7 | export {}
8 |
--------------------------------------------------------------------------------
/packages/plugin-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "scripts"],
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "target": "ES2020",
6 | "module": "ES2020",
7 | "moduleResolution": "bundler",
8 | "strict": true,
9 | "declaration": true,
10 | "sourceMap": true,
11 | "noEmit": true,
12 | "noUnusedLocals": true,
13 | "esModuleInterop": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/playground/class-components/__tests__/class-components.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import {
3 | editFile,
4 | isServe,
5 | page,
6 | untilBrowserLogAfter,
7 | untilUpdated,
8 | } from '~utils'
9 |
10 | test('should render', async () => {
11 | expect(await page.textContent('span')).toMatch('Hello World')
12 | })
13 |
14 | if (isServe) {
15 | test('Class component HMR', async () => {
16 | editFile('src/App.tsx', (code) => code.replace('World', 'class components'))
17 | await untilBrowserLogAfter(
18 | () => page.textContent('span'),
19 | '[vite] hot updated: /src/App.tsx',
20 | )
21 | await untilUpdated(() => page.textContent('span'), 'Hello class components')
22 |
23 | editFile('src/utils.tsx', (code) => code.replace('Hello', 'Hi'))
24 | await untilBrowserLogAfter(
25 | () => page.textContent('span'),
26 | '[vite] hot updated: /src/App.tsx',
27 | )
28 | await untilUpdated(() => page.textContent('span'), 'Hi class components')
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/playground/class-components/__tests__/oxc/class-components.spec.ts:
--------------------------------------------------------------------------------
1 | import '../class-components.spec'
2 |
--------------------------------------------------------------------------------
/playground/class-components/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + class components
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/class-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-class-components",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playground/class-components/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/class-components/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | import { getGetting } from './utils'
3 |
4 | export class App extends Component {
5 | render() {
6 | return {getGetting()} World
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/playground/class-components/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/playground/class-components/src/utils.tsx:
--------------------------------------------------------------------------------
1 | export const getGetting = () => Hello
2 |
--------------------------------------------------------------------------------
/playground/class-components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/class-components/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | server: { port: 8908 /* Should be unique */ },
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/__tests__/compiler-react-18.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { editFile, isServe, page, untilUpdated } from '~utils'
3 |
4 | test('should render', async () => {
5 | expect(await page.textContent('button')).toMatch('count is 0')
6 | expect(await page.click('button'))
7 | expect(await page.textContent('button')).toMatch('count is 1')
8 | })
9 |
10 | test.runIf(isServe)('should hmr', async () => {
11 | editFile('src/App.tsx', (code) =>
12 | code.replace('count is {count}', 'count is {count}!'),
13 | )
14 | await untilUpdated(() => page.textContent('button'), 'count is 1!')
15 | })
16 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-compiler-react-18",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^18.3.1",
12 | "react-compiler-runtime": "19.1.0-rc.2",
13 | "react-dom": "^18.3.1"
14 | },
15 | "devDependencies": {
16 | "@babel/plugin-transform-react-jsx-development": "^7.27.1",
17 | "@types/react": "^18.3.20",
18 | "@types/react-dom": "^18.3.6",
19 | "@vitejs/plugin-react": "workspace:*",
20 | "babel-plugin-react-compiler": "19.1.0-rc.2",
21 | "typescript": "^5.8.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/public/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import './App.css'
3 |
4 | export function App() {
5 | const [count, setCount] = useState(0)
6 |
7 | return (
8 | <>
9 | Vite + React 18 + compiler
10 |
11 |
14 |
15 | Edit src/App.tsx
and save to test HMR
16 |
17 |
18 |
19 | Click on the Vite and React logos to learn more
20 |
21 | >
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | display: flex;
29 | place-items: center;
30 | min-width: 320px;
31 | min-height: 100vh;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | :root {
60 | color: #213547;
61 | background-color: #ffffff;
62 | }
63 | a:hover {
64 | color: #747bff;
65 | }
66 | button {
67 | background-color: #f9f9f9;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { App } from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "types": ["vite/client"],
8 | "module": "ESNext",
9 | "skipLibCheck": true,
10 |
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 |
19 | /* Linting */
20 | "strict": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "noFallthroughCasesInSwitch": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/playground/compiler-react-18/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig(({ command }) => {
6 | return {
7 | server: { port: 8901 /* Should be unique */ },
8 | plugins: [
9 | react({
10 | babel: {
11 | plugins: [['babel-plugin-react-compiler', { target: '18' }]],
12 | },
13 | }),
14 | ],
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/playground/compiler/__tests__/compiler.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { editFile, isServe, page, untilUpdated } from '~utils'
3 |
4 | test('should render', async () => {
5 | expect(await page.textContent('button')).toMatch('count is 0')
6 | expect(await page.click('button'))
7 | expect(await page.textContent('button')).toMatch('count is 1')
8 | })
9 |
10 | test.runIf(isServe)('should hmr', async () => {
11 | editFile('src/App.tsx', (code) =>
12 | code.replace('count is {count}', 'count is {count}!'),
13 | )
14 | await untilUpdated(() => page.textContent('button'), 'count is 1!')
15 | })
16 |
--------------------------------------------------------------------------------
/playground/compiler/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/compiler/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-compiler",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@babel/plugin-transform-react-jsx-development": "^7.27.1",
16 | "@types/react": "^19.1.6",
17 | "@types/react-dom": "^19.1.6",
18 | "@vitejs/plugin-react": "workspace:*",
19 | "babel-plugin-react-compiler": "19.1.0-rc.2",
20 | "typescript": "^5.8.3"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/playground/compiler/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/compiler/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/playground/compiler/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import './App.css'
3 |
4 | export function App() {
5 | const [count, setCount] = useState(0)
6 |
7 | return (
8 | <>
9 | Vite + React Compiler
10 |
11 |
14 |
15 | Edit src/App.tsx
and save to test HMR
16 |
17 |
18 |
19 | Click on the Vite and React logos to learn more
20 |
21 | >
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/playground/compiler/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | display: flex;
29 | place-items: center;
30 | min-width: 320px;
31 | min-height: 100vh;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | :root {
60 | color: #213547;
61 | background-color: #ffffff;
62 | }
63 | a:hover {
64 | color: #747bff;
65 | }
66 | button {
67 | background-color: #f9f9f9;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/playground/compiler/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { App } from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/playground/compiler/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "types": ["vite/client"],
8 | "module": "ESNext",
9 | "skipLibCheck": true,
10 |
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 |
19 | /* Linting */
20 | "strict": true,
21 | "noUnusedLocals": true,
22 | "noUnusedParameters": true,
23 | "noFallthroughCasesInSwitch": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/playground/compiler/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig(({ command }) => {
6 | const babelPlugins = [['babel-plugin-react-compiler', {}]]
7 | if (command === 'serve') {
8 | babelPlugins.push(['@babel/plugin-transform-react-jsx-development', {}])
9 | }
10 |
11 | return {
12 | server: { port: 8900 /* Should be unique */ },
13 | plugins: [react({ babel: { plugins: babelPlugins } })],
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/__tests__/hook-with-jsx.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import {
3 | editFile,
4 | isServe,
5 | page,
6 | untilBrowserLogAfter,
7 | untilUpdated,
8 | } from '~utils'
9 |
10 | test('should render', async () => {
11 | expect(await page.textContent('button')).toMatch('count is 0')
12 | expect(await page.click('button'))
13 | expect(await page.textContent('button')).toMatch('count is 1')
14 | })
15 |
16 | if (isServe) {
17 | test('Hook with JSX HMR', async () => {
18 | editFile('src/useButtonHook.tsx', (code) =>
19 | code.replace('count is {count}', 'count is {count}!'),
20 | )
21 | await untilBrowserLogAfter(
22 | () => page.textContent('button'),
23 | '[vite] hot updated: /src/App.tsx',
24 | )
25 | await untilUpdated(() => page.textContent('button'), 'count is 1!')
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/__tests__/oxc/hook-with-jsx.spec.ts:
--------------------------------------------------------------------------------
1 | import '../hook-with-jsx.spec'
2 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React hook with JSX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-hook-with-jsx",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^19.1.6",
16 | "@types/react-dom": "^19.1.6",
17 | "@vitejs/plugin-react": "workspace:*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useButtonHook } from './useButtonHook.tsx'
2 |
3 | export function App() {
4 | const button = useButtonHook()
5 | return {button}
6 | }
7 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { App } from './App.tsx'
4 |
5 | createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/src/useButtonHook.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | export function useButtonHook() {
4 | const [count, setCount] = useState(0)
5 | return (
6 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src"],
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "lib": ["ESNext", "DOM", "DOM.Iterable"],
6 | "target": "ESNext",
7 | "jsx": "react-jsx",
8 | "types": ["vite/client"],
9 | "noEmit": true,
10 | "isolatedModules": true,
11 | "skipLibCheck": true,
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "resolveJsonModule": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "useUnknownInCatchVariables": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/hook-with-jsx/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | export default defineConfig({
5 | server: { port: 8909 /* Should be unique */ },
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/playground/mdx/__tests__/mdx.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import {
3 | editFile,
4 | isServe,
5 | page,
6 | untilBrowserLogAfter,
7 | untilUpdated,
8 | } from '~utils'
9 |
10 | test('should render', async () => {
11 | expect(await page.textContent('h1')).toMatch('Vite + MDX')
12 | })
13 |
14 | test('.md extension should work', async () => {
15 | expect(await page.getByText('.md extension works.').textContent()).toEqual(
16 | '.md extension works. This is bold text.',
17 | )
18 | })
19 |
20 | if (isServe) {
21 | test('should hmr', async () => {
22 | editFile('src/demo.mdx', (code) => code.replace('Vite + MDX', 'Updated'))
23 | await untilBrowserLogAfter(
24 | () => page.textContent('h1'),
25 | '[vite] hot updated: /src/demo.mdx',
26 | )
27 | await untilUpdated(() => page.textContent('h1'), 'Updated')
28 | })
29 |
30 | test('should hmr with .md extension', async () => {
31 | await untilBrowserLogAfter(
32 | () =>
33 | editFile('src/demo2.md', (code) =>
34 | code.replace('`.md` extension works.', '`.md` extension hmr works.'),
35 | ),
36 | '[vite] hot updated: /src/demo2.md',
37 | )
38 | await untilUpdated(
39 | () => page.getByText('.md extension hmr works.').textContent(),
40 | '.md extension hmr works. This is bold text.',
41 | )
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/playground/mdx/__tests__/oxc/mdx.spec.ts:
--------------------------------------------------------------------------------
1 | import '../mdx.spec'
2 |
--------------------------------------------------------------------------------
/playground/mdx/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + MDX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/playground/mdx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-mdx",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "tsc && vite build",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@mdx-js/rollup": "^3.1.0",
16 | "@types/react": "^19.1.6",
17 | "@types/react-dom": "^19.1.6",
18 | "@vitejs/plugin-react": "workspace:*"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/playground/mdx/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/playground/mdx/src/demo.mdx:
--------------------------------------------------------------------------------
1 | # Vite + MDX
2 |
3 | Sint sit cillum pariatur eiusmod nulla pariatur ipsum.
4 |
5 | ### Unordered List
6 |
7 | - Olive
8 | - Orange
9 | - Blood orange
10 | - Clementine
11 | - Papaya
12 |
--------------------------------------------------------------------------------
/playground/mdx/src/demo2.md:
--------------------------------------------------------------------------------
1 | ## test md extension
2 |
3 | `.md` extension works. This is **bold text**.
4 |
--------------------------------------------------------------------------------
/playground/mdx/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import Demo from './demo.mdx'
4 | import Demo2 from './demo2.md'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')!).render(
7 |
8 |
9 |
10 | ,
11 | )
12 |
--------------------------------------------------------------------------------
/playground/mdx/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.mdx' {
4 | import { JSX } from 'react'
5 | export default () => JSX.Element
6 | }
7 |
8 | declare module '*.md' {
9 | import { JSX } from 'react'
10 | export default () => JSX.Element
11 | }
12 |
--------------------------------------------------------------------------------
/playground/mdx/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"],
20 | "references": [{ "path": "./tsconfig.node.json" }]
21 | }
22 |
--------------------------------------------------------------------------------
/playground/mdx/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/playground/mdx/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import mdx from '@mdx-js/rollup'
4 |
5 | // https://vite.dev/config/
6 | export default defineConfig({
7 | server: { port: 8901 /* Should be unique */ },
8 | plugins: [
9 | { enforce: 'pre', ...mdx() },
10 | react({ include: /\.(mdx|md|ts|tsx)$/ }),
11 | ],
12 | })
13 |
--------------------------------------------------------------------------------
/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/vite-plugin-react-playground",
3 | "private": true,
4 | "type": "module",
5 | "devDependencies": {
6 | "kill-port": "^1.6.1",
7 | "node-fetch": "^3.3.2"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/playground/react-classic/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 |
3 | function App() {
4 | const [count, setCount] = useState(0)
5 | return (
6 |
7 |
8 | Hello Vite + React
9 |
10 |
13 |
14 |
15 | Edit App.jsx
and save to test HMR updates.
16 |
17 |
23 | Learn React
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default App
31 |
--------------------------------------------------------------------------------
/playground/react-classic/__tests__/react.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { editFile, isServe, page, untilUpdated, viteTestUrl } from '~utils'
3 |
4 | test('should render', async () => {
5 | expect(await page.textContent('h1')).toMatch('Hello Vite + React')
6 | })
7 |
8 | test('should update', async () => {
9 | expect(await page.textContent('button')).toMatch('count is: 0')
10 | await page.click('button')
11 | expect(await page.textContent('button')).toMatch('count is: 1')
12 | })
13 |
14 | test.runIf(isServe)('should hmr', async () => {
15 | editFile('App.jsx', (code) => code.replace('Vite + React', 'Updated'))
16 | await untilUpdated(() => page.textContent('h1'), 'Hello Updated')
17 | // preserve state
18 | expect(await page.textContent('button')).toMatch('count is: 1')
19 | })
20 |
21 | test.runIf(isServe)(
22 | 'should have annotated jsx with file location metadata',
23 | async () => {
24 | const res = await page.request.get(viteTestUrl + '/App.jsx')
25 | const code = await res.text()
26 | expect(code).toMatch(/lineNumber:\s*\d+/)
27 | expect(code).toMatch(/columnNumber:\s*\d+/)
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/playground/react-classic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/playground/react-classic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-react-classic",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "debug": "node --inspect-brk vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^19.1.0",
13 | "react-dom": "^19.1.0"
14 | },
15 | "devDependencies": {
16 | "@vitejs/plugin-react": "workspace:*"
17 | },
18 | "babel": {
19 | "presets": [
20 | "@babel/preset-env"
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/playground/react-classic/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import type { UserConfig } from 'vite'
3 |
4 | const config: UserConfig = {
5 | server: { port: 8903 /* Should be unique */ },
6 | plugins: [
7 | react({
8 | jsxRuntime: 'classic',
9 | }),
10 | ],
11 | build: {
12 | // to make tests faster
13 | minify: false,
14 | },
15 | }
16 |
17 | export default config
18 |
--------------------------------------------------------------------------------
/playground/react-emotion/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import _Switch from 'react-switch'
3 | import { Counter, StyledCode } from './Counter'
4 | const Switch = _Switch.default || _Switch
5 |
6 | function FragmentTest() {
7 | const [checked, setChecked] = useState(false)
8 | return (
9 | <>
10 |
11 |
12 |
13 |
14 | >
15 | )
16 | }
17 |
18 | function App() {
19 | return (
20 |
37 | )
38 | }
39 |
40 | export default App
41 |
--------------------------------------------------------------------------------
/playground/react-emotion/Counter.jsx:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled'
2 | import { css } from '@emotion/react'
3 | import { useState } from 'react'
4 |
5 | // Ensure HMR of styled component alongside other components
6 | export const StyledCode = styled.code`
7 | color: #646cff;
8 | `
9 |
10 | export function Counter() {
11 | const [count, setCount] = useState(0)
12 |
13 | return (
14 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/playground/react-emotion/__tests__/react.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { editFile, getColor, isServe, page, untilUpdated } from '~utils'
3 |
4 | test('should render', async () => {
5 | expect(await page.textContent('h1')).toMatch(
6 | 'Hello Vite + React + @emotion/react',
7 | )
8 | })
9 |
10 | test('should update', async () => {
11 | expect(await page.textContent('button')).toMatch('count is: 0')
12 | await page.click('button')
13 | expect(await page.textContent('button')).toMatch('count is: 1')
14 | })
15 |
16 | test.runIf(isServe)('should hmr', async () => {
17 | editFile('App.jsx', (code) =>
18 | code.replace('Vite + React + @emotion/react', 'Updated'),
19 | )
20 | await untilUpdated(() => page.textContent('h1'), 'Hello Updated')
21 |
22 | editFile('Counter.jsx', (code) =>
23 | code.replace('color: #646cff;', 'color: #d26ac2;'),
24 | )
25 |
26 | await untilUpdated(() => getColor('code'), '#d26ac2')
27 |
28 | // preserve state
29 | expect(await page.textContent('button')).toMatch('count is: 1')
30 | })
31 |
32 | test('should update button style', async () => {
33 | function getButtonBorderStyle() {
34 | return page.evaluate(() => {
35 | return window.getComputedStyle(document.querySelector('button')).border
36 | })
37 | }
38 |
39 | await page.evaluate(() => {
40 | return document.querySelector('button').style
41 | })
42 |
43 | expect(await getButtonBorderStyle()).toMatch('2px solid rgb(0, 0, 0)')
44 |
45 | if (isServe) {
46 | editFile('Counter.jsx', (code) =>
47 | code.replace('border: 2px solid #000', 'border: 4px solid red'),
48 | )
49 |
50 | await untilUpdated(getButtonBorderStyle, '4px solid rgb(255, 0, 0)')
51 |
52 | // preserve state
53 | expect(await page.textContent('button')).toMatch('count is: 1')
54 | }
55 | })
56 |
--------------------------------------------------------------------------------
/playground/react-emotion/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/playground/react-emotion/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-react-emotion",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "debug": "node --inspect-brk vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@emotion/react": "^11.14.0",
13 | "@emotion/styled": "^11.14.0",
14 | "react": "^19.1.0",
15 | "react-dom": "^19.1.0",
16 | "react-switch": "^7.1.0"
17 | },
18 | "devDependencies": {
19 | "@babel/plugin-proposal-pipeline-operator": "^7.27.1",
20 | "@emotion/babel-plugin": "^11.13.5",
21 | "@vitejs/plugin-react": "workspace:*"
22 | },
23 | "babel": {
24 | "presets": [
25 | "@babel/preset-env"
26 | ]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/playground/react-emotion/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import { defineConfig } from 'vite'
3 |
4 | export default defineConfig({
5 | server: { port: 8904 /* Should be unique */ },
6 | plugins: [
7 | react({
8 | jsxImportSource: '@emotion/react',
9 | babel: {
10 | plugins: ['@emotion/babel-plugin'],
11 | },
12 | }),
13 | ],
14 | clearScreen: false,
15 | build: {
16 | // to make tests faster
17 | minify: false,
18 | },
19 | })
20 |
--------------------------------------------------------------------------------
/playground/react-env/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 |
3 | function App() {
4 | const [count, setCount] = useState(0)
5 | return (
6 |
7 |
8 | Hello Vite + React
9 |
10 |
11 | )
12 | }
13 |
14 | export default App
15 |
--------------------------------------------------------------------------------
/playground/react-env/__tests__/oxc/react.spec.ts:
--------------------------------------------------------------------------------
1 | import '../react.spec'
2 |
--------------------------------------------------------------------------------
/playground/react-env/__tests__/react.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { page } from '~utils'
3 |
4 | test('should work', async () => {
5 | expect(await page.textContent('h1')).toMatch('Hello Vite + React')
6 | })
7 |
--------------------------------------------------------------------------------
/playground/react-env/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/playground/react-env/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-react-env",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "debug": "node --inspect-brk vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "react": "^19.1.0",
13 | "react-dom": "^19.1.0"
14 | },
15 | "devDependencies": {
16 | "@vitejs/plugin-react": "workspace:*"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/playground/react-env/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import type { UserConfig } from 'vite'
3 |
4 | // Overriding the NODE_ENV set by vitest
5 | process.env.NODE_ENV = ''
6 |
7 | const config: UserConfig = {
8 | server: { port: 8905 /* Should be unique */ },
9 | plugins: [react()],
10 | mode: 'staging',
11 | build: {
12 | // to make tests faster
13 | minify: false,
14 | },
15 | }
16 |
17 | export default config
18 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/App.jsx:
--------------------------------------------------------------------------------
1 | console.log('App.jsx 1') // for sourcemap
2 | function App() {
3 | return foo
4 | }
5 |
6 | console.log('App.jsx 2') // for sourcemap
7 |
8 | export default App
9 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/__tests__/oxc/react-sourcemap.spec.ts:
--------------------------------------------------------------------------------
1 | import '../react-sourcemap.spec'
2 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/__tests__/react-sourcemap.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { isBuild, serverLogs } from '~utils'
3 |
4 | test.runIf(isBuild)('should not output sourcemap warning', () => {
5 | serverLogs.forEach((log) => {
6 | expect(log).not.toMatch('Sourcemap is likely to be incorrect')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 |
5 | ReactDOM.createRoot(document.getElementById('app')).render(
6 | React.createElement(App),
7 | )
8 |
9 | console.log('main.jsx') // for sourcemap
10 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-react-sourcemap",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "dev:classic": "USE_CLASSIC=1 vite",
8 | "build": "vite build",
9 | "build:classic": "USE_CLASSIC=1 vite build",
10 | "debug": "node --inspect-brk vite",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "react": "^19.1.0",
15 | "react-dom": "^19.1.0"
16 | },
17 | "devDependencies": {
18 | "@vitejs/plugin-react": "workspace:*"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/playground/react-sourcemap/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import type { UserConfig } from 'vite'
3 |
4 | const config: UserConfig = {
5 | server: { port: 8906 /* Should be unique */ },
6 | plugins: [
7 | react({
8 | jsxRuntime: process.env.USE_CLASSIC === '1' ? 'classic' : 'automatic',
9 | }),
10 | ],
11 | build: {
12 | sourcemap: true,
13 | },
14 | }
15 |
16 | export default config
17 |
--------------------------------------------------------------------------------
/playground/react/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react'
2 | import Button from 'jsx-entry'
3 | import Dummy from './components/Dummy?qs-should-not-break-plugin-react'
4 | import Parent from './hmr/parent'
5 | import { JsxImportRuntime } from './hmr/jsx-import-runtime'
6 | import { CountProvider } from './context/CountProvider'
7 | import { ContextButton } from './context/ContextButton'
8 | import { TestImportAttributes } from './import-attributes/test'
9 |
10 | function App() {
11 | const [count, setCount] = useState(0)
12 | return (
13 |
14 |
15 | Hello Vite + React
16 |
17 |
23 |
24 |
25 |
26 |
27 |
28 | Edit App.jsx
and save to test HMR updates.
29 |
30 |
36 | Learn React
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
49 | function AppWithProviders() {
50 | return (
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export default AppWithProviders
58 |
--------------------------------------------------------------------------------
/playground/react/__tests__/oxc/react.spec.ts:
--------------------------------------------------------------------------------
1 | import '../react.spec'
2 |
--------------------------------------------------------------------------------
/playground/react/components/Dummy.jsx:
--------------------------------------------------------------------------------
1 | export default function Dummy() {
2 | return <>>
3 | }
4 |
--------------------------------------------------------------------------------
/playground/react/context/ContextButton.jsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react'
2 | import { CountContext } from './CountProvider'
3 |
4 | export function ContextButton() {
5 | const { count, setCount } = useContext(CountContext)
6 | return (
7 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/playground/react/context/CountProvider.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useState } from 'react'
2 | export const CountContext = createContext()
3 |
4 | export const CountProvider = ({ children }) => {
5 | const [count, setCount] = useState(0)
6 | return (
7 |
8 | {children}
9 | context provider
10 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/playground/react/hmr/jsx-import-runtime.js:
--------------------------------------------------------------------------------
1 | import * as JsxRuntime from 'react/jsx-runtime'
2 |
3 | export function JsxImportRuntime() {
4 | return JsxRuntime.jsx('p', {
5 | id: 'jsx-import-runtime',
6 | children: 'JSX import runtime works',
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/playground/react/hmr/no-exported-comp.jsx:
--------------------------------------------------------------------------------
1 | // This un-exported react component should not cause this file to be treated
2 | // as an HMR boundary
3 | const Unused = () => An unused react component
4 |
5 | export const Foo = {
6 | is: 'An Object',
7 | }
8 |
--------------------------------------------------------------------------------
/playground/react/hmr/parent.jsx:
--------------------------------------------------------------------------------
1 | import { Foo } from './no-exported-comp'
2 |
3 | export default function Parent() {
4 | console.log('Parent rendered')
5 |
6 | return {Foo.is}
7 | }
8 |
--------------------------------------------------------------------------------
/playground/react/import-attributes/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "ok"
3 | }
4 |
--------------------------------------------------------------------------------
/playground/react/import-attributes/test.jsx:
--------------------------------------------------------------------------------
1 | import data from './data.json' with { type: 'json' }
2 |
3 | export function TestImportAttributes() {
4 | return (
5 |
6 | import-attirbutes: {data.message}
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/playground/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/playground/react/jsx-entry/Button.jsx:
--------------------------------------------------------------------------------
1 | const Button = ({ children }) => {
2 | return
3 | }
4 |
5 | export default Button
6 |
--------------------------------------------------------------------------------
/playground/react/jsx-entry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsx-entry",
3 | "private": true,
4 | "type": "module",
5 | "main": "Button.jsx"
6 | }
7 |
--------------------------------------------------------------------------------
/playground/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-react",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "debug": "node --inspect-brk vite",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "jsx-entry": "file:./jsx-entry",
13 | "react": "^19.1.0",
14 | "react-dom": "^19.1.0"
15 | },
16 | "devDependencies": {
17 | "@vitejs/plugin-react": "workspace:*"
18 | },
19 | "babel": {
20 | "presets": [
21 | "@babel/preset-env"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/playground/react/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react'
2 | import type { UserConfig } from 'vite'
3 |
4 | const config: UserConfig = {
5 | server: { port: 8902 /* Should be unique */ },
6 | mode: 'development',
7 | plugins: [react()],
8 | build: {
9 | // to make tests faster
10 | minify: false,
11 | },
12 | }
13 |
14 | export default config
15 |
--------------------------------------------------------------------------------
/playground/shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'kill-port' {
2 | const kill: (port: number) => Promise
3 | export default kill
4 | }
5 |
--------------------------------------------------------------------------------
/playground/ssr-react/__tests__/oxc/ssr-react.spec.ts:
--------------------------------------------------------------------------------
1 | import '../ssr-react.spec'
2 |
--------------------------------------------------------------------------------
/playground/ssr-react/__tests__/ssr-react.spec.ts:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch'
2 | import { expect, test } from 'vitest'
3 | import {
4 | browserLogs,
5 | editFile,
6 | isBuild,
7 | page,
8 | untilBrowserLogAfter,
9 | untilUpdated,
10 | viteTestUrl as url,
11 | } from '~utils'
12 |
13 | test('/env', async () => {
14 | await untilBrowserLogAfter(() => page.goto(url + '/env'), 'hydrated')
15 |
16 | expect(await page.textContent('h1')).toMatch('default message here')
17 |
18 | // raw http request
19 | const envHtml = await (await fetch(url + '/env')).text()
20 | expect(envHtml).toMatch('API_KEY_qwertyuiop')
21 | })
22 |
23 | test('/about', async () => {
24 | await untilBrowserLogAfter(() => page.goto(url + '/about'), 'hydrated')
25 |
26 | expect(await page.textContent('h1')).toMatch('About')
27 | // should not have hydration mismatch
28 | browserLogs.forEach((msg) => {
29 | expect(msg).not.toMatch('Expected server HTML')
30 | })
31 |
32 | // raw http request
33 | const aboutHtml = await (await fetch(url + '/about')).text()
34 | expect(aboutHtml).toMatch('About')
35 | })
36 |
37 | test('/', async () => {
38 | await untilBrowserLogAfter(() => page.goto(url), 'hydrated')
39 |
40 | expect(await page.textContent('h1')).toMatch('Home')
41 | // should not have hydration mismatch
42 | browserLogs.forEach((msg) => {
43 | expect(msg).not.toMatch('Expected server HTML')
44 | })
45 |
46 | // raw http request
47 | const html = await (await fetch(url)).text()
48 | expect(html).toMatch('Home')
49 | })
50 |
51 | test.skipIf(isBuild)('hmr', async () => {
52 | await untilBrowserLogAfter(() => page.goto(url), 'hydrated')
53 |
54 | await untilUpdated(() => page.textContent('h1'), 'Home')
55 | editFile('src/pages/Home.jsx', (code) =>
56 | code.replace('Home', 'changed'),
57 | )
58 | await untilUpdated(() => page.textContent('h1'), 'changed')
59 |
60 | // verify the change also affects next SSR
61 | const res = await page.reload()
62 | expect(await res?.text()).toContain('changed')
63 | })
64 |
65 | test('client navigation', async () => {
66 | await untilBrowserLogAfter(() => page.goto(url), 'hydrated')
67 |
68 | await untilUpdated(() => page.textContent('a[href="/about"]'), 'About')
69 | await page.click('a[href="/about"]')
70 | await untilUpdated(() => page.textContent('h1'), 'About')
71 |
72 | if (!isBuild) {
73 | editFile('src/pages/About.jsx', (code) =>
74 | code.replace('About', 'changed'),
75 | )
76 | await untilUpdated(() => page.textContent('h1'), 'changed')
77 | }
78 | })
79 |
--------------------------------------------------------------------------------
/playground/ssr-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vite App
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/playground/ssr-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vitejs/test-ssr-react",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build --app",
8 | "preview": "vite preview"
9 | },
10 | "dependencies": {
11 | "react": "^19.1.0",
12 | "react-dom": "^19.1.0"
13 | },
14 | "devDependencies": {
15 | "@vitejs/plugin-react": "workspace:*"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/playground/ssr-react/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vitejs/vite-plugin-react/6db7e7c95826009c7db20b990cdc874569967489/playground/ssr-react/public/favicon.ico
--------------------------------------------------------------------------------
/playground/ssr-react/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // Auto generates routes from files under ./pages
4 | // https://vite.dev/guide/features.html#glob-import
5 | const pages = import.meta.glob('./pages/*.jsx', { eager: true })
6 |
7 | const routes = Object.keys(pages).map((path) => {
8 | const name = path.match(/\.\/pages\/(.*)\.jsx$/)[1]
9 | return {
10 | name,
11 | path: name === 'Home' ? '/' : `/${name.toLowerCase()}`,
12 | component: pages[path].default,
13 | }
14 | })
15 |
16 | function NotFound() {
17 | return Not found
18 | }
19 |
20 | /**
21 | * @param {{ url: URL }} props
22 | */
23 | export function App(props) {
24 | const [url, setUrl] = React.useState(props.url)
25 |
26 | React.useEffect(() => {
27 | return listenNavigation(() => {
28 | setUrl(new URL(window.location.href))
29 | })
30 | }, [setUrl])
31 |
32 | const route = routes.find((route) => route.path === url.pathname)
33 | const Component = route?.component ?? NotFound
34 | return (
35 | <>
36 |
47 |
48 | >
49 | )
50 | }
51 |
52 | /**
53 | * @param {() => void} onNavigation
54 | */
55 | function listenNavigation(onNavigation) {
56 | /**
57 | * @param {MouseEvent} e
58 | */
59 | function onClick(e) {
60 | const link = e.target.closest('a')
61 | if (
62 | link &&
63 | link instanceof HTMLAnchorElement &&
64 | link.href &&
65 | (!link.target || link.target === '_self') &&
66 | link.origin === location.origin &&
67 | !link.hasAttribute('download') &&
68 | e.button === 0 &&
69 | !(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
70 | ) {
71 | e.preventDefault()
72 | history.pushState(null, '', link.href)
73 | onNavigation()
74 | }
75 | }
76 | document.addEventListener('click', onClick)
77 | return () => {
78 | document.removeEventListener('click', onClick)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/add.js:
--------------------------------------------------------------------------------
1 | import { multiply } from './multiply'
2 |
3 | export function add(a, b) {
4 | return a + b
5 | }
6 |
7 | export function addAndMultiply(a, b, c) {
8 | return multiply(add(a, b), c)
9 | }
10 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/entry-client.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom/client'
2 | import { App } from './App'
3 |
4 | ReactDOM.hydrateRoot(
5 | document.getElementById('app'),
6 | ,
7 | )
8 | console.log('hydrated')
9 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/entry-server.jsx:
--------------------------------------------------------------------------------
1 | import ReactDOMServer from 'react-dom/server'
2 | import { App } from './App'
3 |
4 | export function render(url) {
5 | return ReactDOMServer.renderToString(
6 | ,
7 | )
8 | }
9 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/multiply.js:
--------------------------------------------------------------------------------
1 | import { add } from './add'
2 |
3 | export function multiply(a, b) {
4 | return a * b
5 | }
6 |
7 | export function multiplyAndAdd(a, b, c) {
8 | return add(multiply(a, b), c)
9 | }
10 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/pages/About.jsx:
--------------------------------------------------------------------------------
1 | import { addAndMultiply } from '../add'
2 | import { multiplyAndAdd } from '../multiply'
3 |
4 | export default function About() {
5 | return (
6 | <>
7 | About
8 | {addAndMultiply(1, 2, 3)}
9 | {multiplyAndAdd(1, 2, 3)}
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/pages/Env.jsx:
--------------------------------------------------------------------------------
1 | export default function Env() {
2 | let msg = 'default message here'
3 | try {
4 | msg = process.env.MY_CUSTOM_SECRET || msg
5 | } catch {}
6 | return {msg}
7 | }
8 |
--------------------------------------------------------------------------------
/playground/ssr-react/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import { addAndMultiply } from '../add'
2 | import { multiplyAndAdd } from '../multiply'
3 |
4 | export default function Home() {
5 | return (
6 | <>
7 | Home
8 | {addAndMultiply(1, 2, 3)}
9 | {multiplyAndAdd(1, 2, 3)}
10 | >
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/playground/ssr-react/vite.config.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs'
2 | import path from 'node:path'
3 | import url from 'node:url'
4 | import { defineConfig } from 'vite'
5 | import react from '@vitejs/plugin-react'
6 |
7 | const _dirname = path.dirname(url.fileURLToPath(import.meta.url))
8 |
9 | process.env.MY_CUSTOM_SECRET = 'API_KEY_qwertyuiop'
10 |
11 | export default defineConfig({
12 | appType: 'custom',
13 | build: {
14 | minify: false,
15 | },
16 | environments: {
17 | client: {
18 | build: {
19 | outDir: 'dist/client',
20 | },
21 | },
22 | ssr: {
23 | build: {
24 | outDir: 'dist/server',
25 | rollupOptions: {
26 | input: path.resolve(_dirname, 'src/entry-server.jsx'),
27 | },
28 | },
29 | },
30 | },
31 | plugins: [
32 | react(),
33 | {
34 | name: 'ssr-middleware',
35 | configureServer(server) {
36 | return () => {
37 | server.middlewares.use(async (req, res, next) => {
38 | const url = req.originalUrl ?? '/'
39 | try {
40 | const { render } = await server.ssrLoadModule(
41 | '/src/entry-server.jsx',
42 | )
43 | const appHtml = render(url)
44 | const template = await server.transformIndexHtml(
45 | url,
46 | fs.readFileSync(path.resolve(_dirname, 'index.html'), 'utf-8'),
47 | )
48 | const html = template.replace(``, appHtml)
49 | res.setHeader('content-type', 'text/html').end(html)
50 | } catch (e) {
51 | next(e)
52 | }
53 | })
54 | }
55 | },
56 | async configurePreviewServer(server) {
57 | const template = fs.readFileSync(
58 | path.resolve(_dirname, 'dist/client/index.html'),
59 | 'utf-8',
60 | )
61 | const { render } = await import(
62 | url.pathToFileURL(
63 | path.resolve(_dirname, './dist/server/entry-server.js'),
64 | )
65 | )
66 | return () => {
67 | server.middlewares.use(async (req, res, next) => {
68 | const url = req.originalUrl ?? '/'
69 | try {
70 | const appHtml = render(url)
71 | const html = template.replace(``, appHtml)
72 | res.setHeader('content-type', 'text/html').end(html)
73 | } catch (e) {
74 | next(e)
75 | }
76 | })
77 | }
78 | },
79 | },
80 | ],
81 | // tell vitestSetup.ts to use buildApp API
82 | builder: {},
83 | })
84 |
--------------------------------------------------------------------------------
/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["*.ts", "**/__tests__/*.ts", "**/vite.config.ts"],
3 | "exclude": ["**/dist/**"],
4 | "compilerOptions": {
5 | "target": "ES2020",
6 | "module": "ESNext",
7 | "outDir": "dist",
8 | "baseUrl": ".",
9 | "allowJs": true,
10 | "esModuleInterop": true,
11 | "resolveJsonModule": true,
12 | "moduleResolution": "Node",
13 | "skipLibCheck": true,
14 | "noEmit": true,
15 | "noUnusedLocals": true,
16 | "jsx": "preserve",
17 | "types": ["vite/client", "node"],
18 | "paths": {
19 | "~utils": ["./test-utils.ts"]
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/playground/vitest.config.e2e.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { defineConfig } from 'vitest/config'
3 |
4 | const timeout = process.env.PWDEBUG ? Infinity : process.env.CI ? 20_000 : 5_000
5 |
6 | export default defineConfig({
7 | resolve: {
8 | alias: {
9 | '~utils': resolve(__dirname, './test-utils'),
10 | },
11 | },
12 | test: {
13 | pool: 'forks',
14 | include: ['./playground/**/*.spec.[tj]s'],
15 | setupFiles: ['./playground/vitestSetup.ts'],
16 | globalSetup: ['./playground/vitestGlobalSetup.ts'],
17 | testTimeout: timeout,
18 | hookTimeout: timeout,
19 | reporters: 'dot',
20 | },
21 | esbuild: {
22 | target: 'node14',
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 | - 'playground/**'
4 | - 'packages/plugin-react-swc/playground/**'
5 |
6 | catalogs:
7 | rolldown-vite:
8 | vite: npm:rolldown-vite@^6.3.18
9 |
--------------------------------------------------------------------------------
/scripts/patchCJS.ts:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | It converts
4 |
5 | ```ts
6 | exports.default = vuePlugin;
7 | exports.parseVueRequest = parseVueRequest;
8 | ```
9 |
10 | to
11 |
12 | ```ts
13 | module.exports = vuePlugin;
14 | module.exports.default = vuePlugin;
15 | module.exports.parseVueRequest = parseVueRequest;
16 | ```
17 | */
18 |
19 | import { readFileSync, writeFileSync } from 'node:fs'
20 | import colors from 'picocolors'
21 |
22 | const indexPath = 'dist/index.cjs'
23 | let code = readFileSync(indexPath, 'utf-8')
24 |
25 | const matchMixed = code.match(/\nexports.default = (\w+);/)
26 | if (matchMixed) {
27 | const name = matchMixed[1]
28 |
29 | const lines = code.trimEnd().split('\n')
30 |
31 | // search from the end to prepend `modules.` to `export[xxx]`
32 | for (let i = lines.length - 1; i > 0; i--) {
33 | if (lines[i].startsWith('exports')) lines[i] = 'module.' + lines[i]
34 | else {
35 | // at the beginning of exports, export the default function
36 | lines[i] += `\nmodule.exports = ${name};`
37 | break
38 | }
39 | }
40 |
41 | writeFileSync(indexPath, lines.join('\n'))
42 |
43 | console.log(colors.bold(`${indexPath} CJS patched`))
44 | process.exit(0)
45 | }
46 |
47 | const matchDefault = code.match(/\nmodule.exports = (\w+);/)
48 |
49 | if (matchDefault) {
50 | code += `module.exports.default = ${matchDefault[1]};\n`
51 | writeFileSync(indexPath, code)
52 | console.log(colors.bold(`${indexPath} CJS patched`))
53 | process.exit(0)
54 | }
55 |
56 | console.error(colors.red(`${indexPath} CJS patch failed`))
57 | process.exit(1)
58 |
--------------------------------------------------------------------------------
/scripts/publishCI.ts:
--------------------------------------------------------------------------------
1 | import { publish } from '@vitejs/release-scripts'
2 |
3 | publish({
4 | provenance: true,
5 | getPkgDir(pkg) {
6 | if (pkg === 'plugin-react-swc') {
7 | return `packages/${pkg}/dist`
8 | }
9 | return `packages/${pkg}`
10 | },
11 | })
12 |
--------------------------------------------------------------------------------
/scripts/release.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from 'node:fs'
2 | import { release } from '@vitejs/release-scripts'
3 | import colors from 'picocolors'
4 |
5 | const nextH2RE = /^## /gm
6 |
7 | release({
8 | repo: 'vite-plugin-react',
9 | packages: ['plugin-react', 'plugin-react-swc', 'plugin-react-oxc'],
10 | getPkgDir(pkg) {
11 | if (pkg === 'plugin-react-swc') {
12 | return `packages/${pkg}/dist`
13 | }
14 | return `packages/${pkg}`
15 | },
16 | toTag: (pkg, version) => `${pkg}@${version}`,
17 | logChangelog: async (pkgName) => {
18 | const changelog = readFileSync(`packages/${pkgName}/CHANGELOG.md`, 'utf-8')
19 | if (!changelog.includes('## Unreleased')) {
20 | throw new Error("Can't find '## Unreleased' section in CHANGELOG.md")
21 | }
22 | const index = changelog.indexOf('## Unreleased') + 13
23 | nextH2RE.lastIndex = index
24 | const nextH2Pos = nextH2RE.exec(changelog)?.index
25 | console.log(colors.dim(changelog.slice(index, nextH2Pos).trim()))
26 | },
27 | generateChangelog: async (pkgName, version) => {
28 | if (pkgName === 'plugin-react-swc') {
29 | console.log(colors.cyan('\nUpdating package.json version...'))
30 | const pkgJsonPath = `packages/${pkgName}/package.json`
31 | const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))
32 | pkg.version = version
33 | writeFileSync(pkgJsonPath, `${JSON.stringify(pkg, null, 2)}\n`)
34 | }
35 |
36 | const changelog = readFileSync(`packages/${pkgName}/CHANGELOG.md`, 'utf-8')
37 | console.log(colors.cyan('\nUpdating CHANGELOG.md...'))
38 | const date = new Date().toISOString().slice(0, 10)
39 | writeFileSync(
40 | `packages/${pkgName}/CHANGELOG.md`,
41 | changelog.replace(
42 | '## Unreleased',
43 | `## Unreleased\n\n## ${version} (${date})`,
44 | ),
45 | )
46 | },
47 | })
48 |
--------------------------------------------------------------------------------
/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "include": ["."],
4 | "compilerOptions": {
5 | "module": "CommonJS",
6 | "target": "ES2020",
7 | "moduleResolution": "Node",
8 | "noEmit": true,
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "noUnusedLocals": true,
13 | "forceConsistentCasingInFileNames": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------