├── .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 | Vite logo 4 | 5 |

6 |
7 |

8 | node compatibility 9 | build status 10 | discord chat 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) | [![plugin-react version](https://img.shields.io/npm/v/@vitejs/plugin-react.svg?label=%20)](packages/plugin-react/CHANGELOG.md) | 23 | | [@vitejs/plugin-react-oxc](packages/plugin-react-oxc) | [![plugin-react-oxc version](https://img.shields.io/npm/v/@vitejs/plugin-react-oxc.svg?label=%20)](packages/plugin-react-oxc/CHANGELOG.md) | 24 | | [@vitejs/plugin-react-swc](packages/plugin-react-swc) | [![plugin-react-swc version](https://img.shields.io/npm/v/@vitejs/plugin-react-swc.svg?label=%20)](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 [![npm](https://img.shields.io/npm/v/@vitejs/plugin-react-oxc.svg)](https://npmjs.com/package/@vitejs/plugin-react-oxc) 2 | 3 | The future default Vite plugin for React projects. 4 | 5 | - enable [Fast Refresh](https://www.npmjs.com/package/react-refresh) in development 6 | - use the [automatic JSX runtime](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) 7 | - small installation size 8 | 9 | ```js 10 | // vite.config.js 11 | import { defineConfig } from 'vite' 12 | import react from '@vitejs/plugin-react-oxc' 13 | 14 | export default defineConfig({ 15 | plugins: [react()], 16 | }) 17 | ``` 18 | 19 | ## Caveats 20 | 21 | - `jsx runtime` is always `automatic` 22 | - this plugin only works with [`rolldown-vite`](https://vitejs.dev/guide/rolldown) 23 | 24 | ## Options 25 | 26 | ### include/exclude 27 | 28 | Includes `.js`, `.jsx`, `.ts` & `.tsx` by default. This option can be used to add fast refresh to `.mdx` files: 29 | 30 | ```js 31 | import { defineConfig } from 'vite' 32 | import react from '@vitejs/plugin-react' 33 | import mdx from '@mdx-js/rollup' 34 | 35 | export default defineConfig({ 36 | plugins: [ 37 | { enforce: 'pre', ...mdx() }, 38 | react({ include: /\.(mdx|js|jsx|ts|tsx)$/ }), 39 | ], 40 | }) 41 | ``` 42 | 43 | > `node_modules` are never processed by this plugin (but Oxc will) 44 | 45 | ### jsxImportSource 46 | 47 | Control where the JSX factory is imported from. Default to `'react'` 48 | 49 | ```js 50 | react({ jsxImportSource: '@emotion/react' }) 51 | ``` 52 | 53 | ## Middleware mode 54 | 55 | In [middleware mode](https://vite.dev/config/server-options.html#server-middlewaremode), you should make sure your entry `index.html` file is transformed by Vite. Here's an example for an Express server: 56 | 57 | ```js 58 | app.get('/', async (req, res, next) => { 59 | try { 60 | let html = fs.readFileSync(path.resolve(root, 'index.html'), 'utf-8') 61 | 62 | // Transform HTML using Vite plugins. 63 | html = await viteServer.transformIndexHtml(req.url, html) 64 | 65 | res.send(html) 66 | } catch (e) { 67 | return next(e) 68 | } 69 | }) 70 | ``` 71 | 72 | Otherwise, you'll probably get this error: 73 | 74 | ``` 75 | Uncaught Error: @vitejs/plugin-react-oxc can't detect preamble. Something is wrong. 76 | ``` 77 | 78 | ## Consistent components exports 79 | 80 | For React refresh to work correctly, your file should only export React components. You can find a good explanation in the [Gatsby docs](https://www.gatsbyjs.com/docs/reference/local-development/fast-refresh/#how-it-works). 81 | 82 | If an incompatible change in exports is found, the module will be invalidated and HMR will propagate. To make it easier to export simple constants alongside your component, the module is only invalidated when their value changes. 83 | 84 | You can catch mistakes and get more detailed warning with this [eslint rule](https://github.com/ArnaudBarre/eslint-plugin-react-refresh). 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 |
7 | 8 | Vite logo 9 | 10 | 11 | Emotion logo 16 | 17 |
18 |
19 |
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 |
7 | 8 | Vite logo 9 | 10 | 11 | Emotion logo 16 | 17 |
18 |
19 |
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 |
12 | 13 | Vite logo 14 | 15 | 16 | React logo 17 | 18 |
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 |
12 | 13 | Vite logo 14 | 15 | 16 | React logo 17 | 18 |
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 |
7 | 8 | Vite logo 9 | 10 | 11 | styled components logo 16 | 17 |
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 |
7 | Home 8 | About 9 |
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 |
21 |
22 |

Hello Vite + React + @emotion/react

23 | 24 |

25 | Edit App.jsx and save to test HMR updates. 26 |

27 | 33 | Learn React 34 | 35 |
36 |
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 | --------------------------------------------------------------------------------