Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: allow to run Babel on non js/ts extensions (fixes #38) #53

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 46 additions & 54 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ import {
export interface Options {
include?: string | RegExp | Array<string | RegExp>
exclude?: string | RegExp | Array<string | RegExp>
/**
* Enable `react-refresh` integration. Vite disables this in prod env or build mode.
* @default true
*/
fastRefresh?: boolean
/**
* Set this to `"automatic"` to use [vite-react-jsx](https://github.com/alloc/vite-react-jsx).
* @default "automatic"
Expand Down Expand Up @@ -89,14 +84,26 @@ declare module 'vite' {

const prependReactImportCode = "import React from 'react'; "

const defaultIncludeRE = /\.(?:mjs|[tj]sx?)$/

export default function viteReact(opts: Options = {}): PluginOption[] {
// Provide default values for Rollup compat.
let devBase = '/'
let filter = createFilter(opts.include, opts.exclude)
const filter = createFilter(
[
defaultIncludeRE,
...(Array.isArray(opts.include)
? opts.include
: opts.include
? [opts.include]
: []),
],
opts.exclude,
)
let needHiresSourcemap = false
let isProduction = true
let projectRoot = process.cwd()
let skipFastRefresh = opts.fastRefresh === false
let skipFastRefresh = false
let skipReactImport = false
let runPluginOverrides = (
options: ReactBabelOptions,
Expand Down Expand Up @@ -158,13 +165,10 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
configResolved(config) {
devBase = config.base
projectRoot = config.root
filter = createFilter(opts.include, opts.exclude, {
resolve: projectRoot,
})
needHiresSourcemap =
config.command === 'build' && !!config.build.sourcemap
isProduction = config.isProduction
skipFastRefresh ||= isProduction || config.command === 'build'
skipFastRefresh = isProduction || config.command === 'build'

const jsxInject = config.esbuild && config.esbuild.jsxInject
if (jsxInject && importReactRE.test(jsxInject)) {
Expand Down Expand Up @@ -203,6 +207,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
},
async transform(code, id, options) {
if (id.includes('/node_modules/')) return
const ssr = options?.ssr === true
// File extension could be mocked/overridden in querystring.
const [filepath, querystring = ''] = id.split('?')
Expand All @@ -211,11 +216,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
filepath.match(fileExtensionRE) ||
[]

if (/\.(?:mjs|[tj]sx?)$/.test(extension)) {
if (filter(id)) {
Comment on lines -214 to +219
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this changes behavior when id is foo.txt?a=.jsx. (it was true but now it's false)

const isJSX = extension.endsWith('x')
const isNodeModules = id.includes('/node_modules/')
const isProjectFile =
!isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))

let babelOptions = staticBabelOptions
if (typeof opts.babel === 'function') {
Expand All @@ -229,40 +231,35 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
}

const plugins = isProjectFile ? [...babelOptions.plugins] : []

let useFastRefresh = false
if (!skipFastRefresh && !ssr && !isNodeModules) {
// Modules with .js or .ts extension must import React.
const isReactModule = isJSX || importReactRE.test(code)
if (isReactModule && filter(id)) {
useFastRefresh = true
plugins.push([
await loadPlugin('react-refresh/babel'),
{ skipEnvCheck: true },
])
}
const plugins = [...babelOptions.plugins]

const useFastRefresh =
// Non-jsx extensions must import React.
!skipFastRefresh && !ssr && (isJSX || importReactRE.test(code))

if (useFastRefresh) {
plugins.push([
await loadPlugin('react-refresh/babel'),
{ skipEnvCheck: true },
])
}

let prependReactImport = false
if (!isProjectFile || isJSX) {
if (!useAutomaticRuntime && isProjectFile) {
// These plugins are only needed for the classic runtime.
if (!isProduction) {
plugins.push(
await loadPlugin('@babel/plugin-transform-react-jsx-self'),
await loadPlugin('@babel/plugin-transform-react-jsx-source'),
)
}

// Even if the automatic JSX runtime is not used, we can still
// inject the React import for .jsx and .tsx modules.
if (!skipReactImport && !importReactRE.test(code)) {
prependReactImport = true
}
}
if (isJSX && !useAutomaticRuntime && !isProduction) {
// These development plugins are only needed for the classic runtime.
plugins.push(
await loadPlugin('@babel/plugin-transform-react-jsx-self'),
await loadPlugin('@babel/plugin-transform-react-jsx-source'),
)
}

// Even if the automatic JSX runtime is not used, we can still
// inject the React import for .jsx and .tsx modules.
const prependReactImport =
isJSX &&
!useAutomaticRuntime &&
!skipReactImport &&
!importReactRE.test(code)

let inputMap: SourceMap | undefined
if (prependReactImport) {
if (needHiresSourcemap) {
Expand All @@ -275,17 +272,12 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
}

// Plugins defined through this Vite plugin are only applied
// to modules within the project root, but "babel.config.js"
// files can define plugins that need to be applied to every
// module, including node_modules and linked packages.
const shouldSkip =
// Avoid parsing if no special transformation is needed
if (
!plugins.length &&
!babelOptions.configFile &&
!(isProjectFile && babelOptions.babelrc)

// Avoid parsing if no plugins exist.
if (shouldSkip) {
!babelOptions.babelrc
) {
return {
code,
map: inputMap ?? null,
Expand Down
23 changes: 23 additions & 0 deletions playground/mdx/__tests__/mdx.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect, test } from 'vitest'
import {
editFile,
isServe,
page,
untilBrowserLogAfter,
untilUpdated,
} from '~utils'

test('should render', async () => {
expect(await page.textContent('h1')).toMatch('Vite + MDX')
})

if (isServe) {
test('should hmr', async () => {
editFile('src/demo.mdx', (code) => code.replace('Vite + MDX', 'Updated'))
await untilBrowserLogAfter(
() => page.textContent('h1'),
'[vite] hot updated: /src/demo.mdx',
)
await untilUpdated(() => page.textContent('h1'), 'Updated')
})
}
13 changes: 13 additions & 0 deletions playground/mdx/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + MDX</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
20 changes: 20 additions & 0 deletions playground/mdx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@vitejs/test-mdx",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@mdx-js/rollup": "^2.1.5",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "workspace:*"
}
}
1 change: 1 addition & 0 deletions playground/mdx/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions playground/mdx/src/demo.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Vite + MDX

Sint sit cillum pariatur eiusmod nulla pariatur ipsum.

### Unordered List

- Olive
- Orange
- Blood orange
- Clementine
- Papaya
9 changes: 9 additions & 0 deletions playground/mdx/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Demo from './demo.mdx'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Demo />
</React.StrictMode>,
)
6 changes: 6 additions & 0 deletions playground/mdx/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />

declare module '*.mdx' {
import { JSX } from 'react'
export default () => JSX.Element
}
21 changes: 21 additions & 0 deletions playground/mdx/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
9 changes: 9 additions & 0 deletions playground/mdx/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
8 changes: 8 additions & 0 deletions playground/mdx/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mdx from '@mdx-js/rollup'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [{ enforce: 'pre', ...mdx() }, react({ include: /.mdx$/ })],
})
Loading