@vitejs/plugin-react 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts DELETED
@@ -1,473 +0,0 @@
1
- import path from 'node:path'
2
- import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
3
- import * as babel from '@babel/core'
4
- import { createFilter, normalizePath } from 'vite'
5
- import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
6
- import MagicString from 'magic-string'
7
- import type { SourceMap } from 'magic-string'
8
- import {
9
- addRefreshWrapper,
10
- isRefreshBoundary,
11
- preambleCode,
12
- runtimeCode,
13
- runtimePublicPath
14
- } from './fast-refresh'
15
- import { babelImportToRequire } from './jsx-runtime/babel-import-to-require'
16
- import { restoreJSX } from './jsx-runtime/restore-jsx'
17
-
18
- export interface Options {
19
- include?: string | RegExp | Array<string | RegExp>
20
- exclude?: string | RegExp | Array<string | RegExp>
21
- /**
22
- * Enable `react-refresh` integration. Vite disables this in prod env or build mode.
23
- * @default true
24
- */
25
- fastRefresh?: boolean
26
- /**
27
- * Set this to `"automatic"` to use [vite-react-jsx](https://github.com/alloc/vite-react-jsx).
28
- * @default "automatic"
29
- */
30
- jsxRuntime?: 'classic' | 'automatic'
31
- /**
32
- * Control where the JSX factory is imported from.
33
- * This option is ignored when `jsxRuntime` is not `"automatic"`.
34
- * @default "react"
35
- */
36
- jsxImportSource?: string
37
- /**
38
- * Set this to `true` to annotate the JSX factory with `\/* @__PURE__ *\/`.
39
- * This option is ignored when `jsxRuntime` is not `"automatic"`.
40
- * @default true
41
- */
42
- jsxPure?: boolean
43
- /**
44
- * Babel configuration applied in both dev and prod.
45
- */
46
- babel?:
47
- | BabelOptions
48
- | ((id: string, options: { ssr?: boolean }) => BabelOptions)
49
- }
50
-
51
- export type BabelOptions = Omit<
52
- TransformOptions,
53
- | 'ast'
54
- | 'filename'
55
- | 'root'
56
- | 'sourceFileName'
57
- | 'sourceMaps'
58
- | 'inputSourceMap'
59
- >
60
-
61
- /**
62
- * The object type used by the `options` passed to plugins with
63
- * an `api.reactBabel` method.
64
- */
65
- export interface ReactBabelOptions extends BabelOptions {
66
- plugins: Extract<BabelOptions['plugins'], any[]>
67
- presets: Extract<BabelOptions['presets'], any[]>
68
- overrides: Extract<BabelOptions['overrides'], any[]>
69
- parserOpts: ParserOptions & {
70
- plugins: Extract<ParserOptions['plugins'], any[]>
71
- }
72
- }
73
-
74
- type ReactBabelHook = (
75
- babelConfig: ReactBabelOptions,
76
- context: ReactBabelHookContext,
77
- config: ResolvedConfig
78
- ) => void
79
-
80
- type ReactBabelHookContext = { ssr: boolean; id: string }
81
-
82
- declare module 'vite' {
83
- export interface Plugin {
84
- api?: {
85
- /**
86
- * Manipulate the Babel options of `@vitejs/plugin-react`
87
- */
88
- reactBabel?: ReactBabelHook
89
- }
90
- }
91
- }
92
-
93
- const prependReactImportCode = "import React from 'react'; "
94
-
95
- export default function viteReact(opts: Options = {}): PluginOption[] {
96
- // Provide default values for Rollup compat.
97
- let devBase = '/'
98
- let resolvedCacheDir: string
99
- let filter = createFilter(opts.include, opts.exclude)
100
- let needHiresSourcemap = false
101
- let isProduction = true
102
- let projectRoot = process.cwd()
103
- let skipFastRefresh = opts.fastRefresh === false
104
- let skipReactImport = false
105
- let runPluginOverrides = (
106
- options: ReactBabelOptions,
107
- context: ReactBabelHookContext
108
- ) => false
109
- let staticBabelOptions: ReactBabelOptions | undefined
110
-
111
- const useAutomaticRuntime = opts.jsxRuntime !== 'classic'
112
-
113
- // Support patterns like:
114
- // - import * as React from 'react';
115
- // - import React from 'react';
116
- // - import React, {useEffect} from 'react';
117
- const importReactRE = /(^|\n)import\s+(\*\s+as\s+)?React(,|\s+)/
118
-
119
- // Any extension, including compound ones like '.bs.js'
120
- const fileExtensionRE = /\.[^\/\s\?]+$/
121
-
122
- const viteBabel: Plugin = {
123
- name: 'vite:react-babel',
124
- enforce: 'pre',
125
- config() {
126
- if (opts.jsxRuntime === 'classic') {
127
- return {
128
- esbuild: {
129
- logOverride: {
130
- 'this-is-undefined-in-esm': 'silent'
131
- }
132
- }
133
- }
134
- }
135
- },
136
- configResolved(config) {
137
- devBase = config.base
138
- projectRoot = config.root
139
- resolvedCacheDir = normalizePath(path.resolve(config.cacheDir))
140
- filter = createFilter(opts.include, opts.exclude, {
141
- resolve: projectRoot
142
- })
143
- needHiresSourcemap =
144
- config.command === 'build' && !!config.build.sourcemap
145
- isProduction = config.isProduction
146
- skipFastRefresh ||= isProduction || config.command === 'build'
147
-
148
- const jsxInject = config.esbuild && config.esbuild.jsxInject
149
- if (jsxInject && importReactRE.test(jsxInject)) {
150
- skipReactImport = true
151
- config.logger.warn(
152
- '[@vitejs/plugin-react] This plugin imports React for you automatically,' +
153
- ' so you can stop using `esbuild.jsxInject` for that purpose.'
154
- )
155
- }
156
-
157
- config.plugins.forEach((plugin) => {
158
- const hasConflict =
159
- plugin.name === 'react-refresh' ||
160
- (plugin !== viteReactJsx && plugin.name === 'vite:react-jsx')
161
-
162
- if (hasConflict)
163
- return config.logger.warn(
164
- `[@vitejs/plugin-react] You should stop using "${plugin.name}" ` +
165
- `since this plugin conflicts with it.`
166
- )
167
- })
168
-
169
- runPluginOverrides = (babelOptions, context) => {
170
- const hooks = config.plugins
171
- .map((plugin) => plugin.api?.reactBabel)
172
- .filter(Boolean) as ReactBabelHook[]
173
-
174
- if (hooks.length > 0) {
175
- return (runPluginOverrides = (babelOptions, context) => {
176
- hooks.forEach((hook) => hook(babelOptions, context, config))
177
- return true
178
- })(babelOptions, context)
179
- }
180
- runPluginOverrides = () => false
181
- return false
182
- }
183
- },
184
- async transform(code, id, options) {
185
- const ssr = options?.ssr === true
186
- // File extension could be mocked/overridden in querystring.
187
- const [filepath, querystring = ''] = id.split('?')
188
- const [extension = ''] =
189
- querystring.match(fileExtensionRE) ||
190
- filepath.match(fileExtensionRE) ||
191
- []
192
-
193
- if (/\.(mjs|[tj]sx?)$/.test(extension)) {
194
- const isJSX = extension.endsWith('x')
195
- const isNodeModules = id.includes('/node_modules/')
196
- const isProjectFile =
197
- !isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))
198
-
199
- let babelOptions = staticBabelOptions
200
- if (typeof opts.babel === 'function') {
201
- const rawOptions = opts.babel(id, { ssr })
202
- babelOptions = createBabelOptions(rawOptions)
203
- runPluginOverrides(babelOptions, { ssr, id: id })
204
- } else if (!babelOptions) {
205
- babelOptions = createBabelOptions(opts.babel)
206
- if (!runPluginOverrides(babelOptions, { ssr, id: id })) {
207
- staticBabelOptions = babelOptions
208
- }
209
- }
210
-
211
- const plugins = isProjectFile ? [...babelOptions.plugins] : []
212
-
213
- let useFastRefresh = false
214
- if (!skipFastRefresh && !ssr && !isNodeModules) {
215
- // Modules with .js or .ts extension must import React.
216
- const isReactModule = isJSX || importReactRE.test(code)
217
- if (isReactModule && filter(id)) {
218
- useFastRefresh = true
219
- plugins.push([
220
- await loadPlugin('react-refresh/babel'),
221
- { skipEnvCheck: true }
222
- ])
223
- }
224
- }
225
-
226
- let ast: t.File | null | undefined
227
- let prependReactImport = false
228
- if (!isProjectFile || isJSX) {
229
- if (useAutomaticRuntime) {
230
- // By reverse-compiling "React.createElement" calls into JSX,
231
- // React elements provided by dependencies will also use the
232
- // automatic runtime!
233
- // Avoid parsing the optimized react-dom since it will never
234
- // contain compiled JSX and it's a pretty big file (800kb).
235
- const isOptimizedReactDom =
236
- id.startsWith(resolvedCacheDir) && id.includes('/react-dom.js')
237
- const [restoredAst, isCommonJS] =
238
- !isProjectFile && !isJSX && !isOptimizedReactDom
239
- ? await restoreJSX(babel, code, id)
240
- : [null, false]
241
-
242
- if (isJSX || (ast = restoredAst)) {
243
- plugins.push([
244
- await loadPlugin(
245
- '@babel/plugin-transform-react-jsx' +
246
- (isProduction ? '' : '-development')
247
- ),
248
- {
249
- runtime: 'automatic',
250
- importSource: opts.jsxImportSource,
251
- pure: opts.jsxPure !== false
252
- }
253
- ])
254
-
255
- // Avoid inserting `import` statements into CJS modules.
256
- if (isCommonJS) {
257
- plugins.push(babelImportToRequire)
258
- }
259
- }
260
- } else if (isProjectFile) {
261
- // These plugins are only needed for the classic runtime.
262
- if (!isProduction) {
263
- plugins.push(
264
- await loadPlugin('@babel/plugin-transform-react-jsx-self'),
265
- await loadPlugin('@babel/plugin-transform-react-jsx-source')
266
- )
267
- }
268
-
269
- // Even if the automatic JSX runtime is not used, we can still
270
- // inject the React import for .jsx and .tsx modules.
271
- if (!skipReactImport && !importReactRE.test(code)) {
272
- prependReactImport = true
273
- }
274
- }
275
- }
276
-
277
- let inputMap: SourceMap | undefined
278
- if (prependReactImport) {
279
- if (needHiresSourcemap) {
280
- const s = new MagicString(code)
281
- s.prepend(prependReactImportCode)
282
- code = s.toString()
283
- inputMap = s.generateMap({ hires: true, source: id })
284
- } else {
285
- code = prependReactImportCode + code
286
- }
287
- }
288
-
289
- // Plugins defined through this Vite plugin are only applied
290
- // to modules within the project root, but "babel.config.js"
291
- // files can define plugins that need to be applied to every
292
- // module, including node_modules and linked packages.
293
- const shouldSkip =
294
- !plugins.length &&
295
- !babelOptions.configFile &&
296
- !(isProjectFile && babelOptions.babelrc)
297
-
298
- // Avoid parsing if no plugins exist.
299
- if (shouldSkip) {
300
- return {
301
- code,
302
- map: inputMap ?? null
303
- }
304
- }
305
-
306
- const parserPlugins: typeof babelOptions.parserOpts.plugins = [
307
- ...babelOptions.parserOpts.plugins,
308
- 'importMeta',
309
- // This plugin is applied before esbuild transforms the code,
310
- // so we need to enable some stage 3 syntax that is supported in
311
- // TypeScript and some environments already.
312
- 'topLevelAwait',
313
- 'classProperties',
314
- 'classPrivateProperties',
315
- 'classPrivateMethods'
316
- ]
317
-
318
- if (!extension.endsWith('.ts')) {
319
- parserPlugins.push('jsx')
320
- }
321
-
322
- if (/\.tsx?$/.test(extension)) {
323
- parserPlugins.push('typescript')
324
- }
325
-
326
- const transformAsync = ast
327
- ? babel.transformFromAstAsync.bind(babel, ast, code)
328
- : babel.transformAsync.bind(babel, code)
329
-
330
- const isReasonReact = extension.endsWith('.bs.js')
331
- const result = await transformAsync({
332
- ...babelOptions,
333
- ast: !isReasonReact,
334
- root: projectRoot,
335
- filename: id,
336
- sourceFileName: filepath,
337
- parserOpts: {
338
- ...babelOptions.parserOpts,
339
- sourceType: 'module',
340
- allowAwaitOutsideFunction: true,
341
- plugins: parserPlugins
342
- },
343
- generatorOpts: {
344
- ...babelOptions.generatorOpts,
345
- decoratorsBeforeExport: true
346
- },
347
- plugins,
348
- sourceMaps: true,
349
- // Vite handles sourcemap flattening
350
- inputSourceMap: inputMap ?? (false as any)
351
- })
352
-
353
- if (result) {
354
- let code = result.code!
355
- if (useFastRefresh && /\$RefreshReg\$\(/.test(code)) {
356
- const accept = isReasonReact || isRefreshBoundary(result.ast!)
357
- code = addRefreshWrapper(code, id, accept)
358
- }
359
- return {
360
- code,
361
- map: result.map
362
- }
363
- }
364
- }
365
- }
366
- }
367
-
368
- const viteReactRefresh: Plugin = {
369
- name: 'vite:react-refresh',
370
- enforce: 'pre',
371
- config: () => ({
372
- resolve: {
373
- dedupe: ['react', 'react-dom']
374
- }
375
- }),
376
- resolveId(id) {
377
- if (id === runtimePublicPath) {
378
- return id
379
- }
380
- },
381
- load(id) {
382
- if (id === runtimePublicPath) {
383
- return runtimeCode
384
- }
385
- },
386
- transformIndexHtml() {
387
- if (!skipFastRefresh)
388
- return [
389
- {
390
- tag: 'script',
391
- attrs: { type: 'module' },
392
- children: preambleCode.replace(`__BASE__`, devBase)
393
- }
394
- ]
395
- }
396
- }
397
-
398
- const reactJsxRuntimeId = 'react/jsx-runtime'
399
- const reactJsxDevRuntimeId = 'react/jsx-dev-runtime'
400
- const virtualReactJsxRuntimeId = '\0' + reactJsxRuntimeId
401
- const virtualReactJsxDevRuntimeId = '\0' + reactJsxDevRuntimeId
402
- // Adapted from https://github.com/alloc/vite-react-jsx
403
- const viteReactJsx: Plugin = {
404
- name: 'vite:react-jsx',
405
- enforce: 'pre',
406
- config() {
407
- return {
408
- optimizeDeps: {
409
- // We can't add `react-dom` because the dependency is `react-dom/client`
410
- // for React 18 while it's `react-dom` for React 17. We'd need to detect
411
- // what React version the user has installed.
412
- include: [reactJsxRuntimeId, reactJsxDevRuntimeId, 'react']
413
- }
414
- }
415
- },
416
- resolveId(id, importer) {
417
- // Resolve runtime to a virtual path to be interoped.
418
- // Since the interop code re-imports `id`, we need to prevent re-resolving
419
- // to the virtual id if the importer is already the virtual id.
420
- if (id === reactJsxRuntimeId && importer !== virtualReactJsxRuntimeId) {
421
- return virtualReactJsxRuntimeId
422
- }
423
- if (
424
- id === reactJsxDevRuntimeId &&
425
- importer !== virtualReactJsxDevRuntimeId
426
- ) {
427
- return virtualReactJsxDevRuntimeId
428
- }
429
- },
430
- load(id) {
431
- // Apply manual interop
432
- if (id === virtualReactJsxRuntimeId) {
433
- return [
434
- `import * as jsxRuntime from ${JSON.stringify(reactJsxRuntimeId)}`,
435
- `export const Fragment = jsxRuntime.Fragment`,
436
- `export const jsx = jsxRuntime.jsx`,
437
- `export const jsxs = jsxRuntime.jsxs`
438
- ].join('\n')
439
- }
440
- if (id === virtualReactJsxDevRuntimeId) {
441
- return [
442
- `import * as jsxRuntime from ${JSON.stringify(reactJsxDevRuntimeId)}`,
443
- `export const Fragment = jsxRuntime.Fragment`,
444
- `export const jsxDEV = jsxRuntime.jsxDEV`
445
- ].join('\n')
446
- }
447
- }
448
- }
449
-
450
- return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx]
451
- }
452
-
453
- viteReact.preambleCode = preambleCode
454
-
455
- function loadPlugin(path: string): Promise<any> {
456
- return import(path).then((module) => module.default || module)
457
- }
458
-
459
- function createBabelOptions(rawOptions?: BabelOptions) {
460
- const babelOptions = {
461
- babelrc: false,
462
- configFile: false,
463
- ...rawOptions
464
- } as ReactBabelOptions
465
-
466
- babelOptions.plugins ||= []
467
- babelOptions.presets ||= []
468
- babelOptions.overrides ||= []
469
- babelOptions.parserOpts ||= {} as any
470
- babelOptions.parserOpts.plugins ||= []
471
-
472
- return babelOptions
473
- }
@@ -1,35 +0,0 @@
1
- import type * as babelCore from '@babel/core'
2
-
3
- /**
4
- * Replace this:
5
- *
6
- * import { jsx as _jsx } from "react/jsx-runtime"
7
- *
8
- * with this:
9
- *
10
- * var _jsx = require("react/jsx-runtime").jsx
11
- */
12
- export function babelImportToRequire({ types: t }: typeof babelCore): {
13
- visitor: babelCore.Visitor
14
- } {
15
- return {
16
- visitor: {
17
- ImportDeclaration(path) {
18
- const decl = path.node
19
- const spec = decl.specifiers[0] as babelCore.types.ImportSpecifier
20
-
21
- path.replaceWith(
22
- t.variableDeclaration('var', [
23
- t.variableDeclarator(
24
- spec.local,
25
- t.memberExpression(
26
- t.callExpression(t.identifier('require'), [decl.source]),
27
- spec.imported
28
- )
29
- )
30
- ])
31
- )
32
- }
33
- }
34
- }
35
- }
@@ -1,132 +0,0 @@
1
- import * as babel from '@babel/core'
2
- import { describe, expect, it } from 'vitest'
3
- import babelRestoreJSX from './babel-restore-jsx'
4
-
5
- function jsx(code: string) {
6
- return babel.transform(code, {
7
- parserOpts: { plugins: ['jsx'] },
8
- plugins: [babelRestoreJSX]
9
- })?.code
10
- }
11
-
12
- // Tests adapted from: https://github.com/flying-sheep/babel-plugin-transform-react-createelement-to-jsx/blob/63137b6/test/index.js
13
- describe('babel-restore-jsx', () => {
14
- it('should convert 1-argument calls', () => {
15
- expect(jsx('React.createElement("h1")')).toMatchInlineSnapshot(`"<h1 />;"`)
16
- expect(jsx('React.createElement(Foo)')).toMatchInlineSnapshot(`"<Foo />;"`)
17
- expect(jsx('React.createElement(Foo.Bar)')).toMatchInlineSnapshot(
18
- `"<Foo.Bar />;"`
19
- )
20
- expect(jsx('React.createElement(Foo.Bar.Baz)')).toMatchInlineSnapshot(
21
- `"<Foo.Bar.Baz />;"`
22
- )
23
- })
24
-
25
- it('should convert effective 1-argument calls (with null or undefined)', () => {
26
- expect(jsx('React.createElement("h1", null)')).toMatchInlineSnapshot(
27
- `"<h1 />;"`
28
- )
29
- expect(jsx('React.createElement("h2", null, null)')).toMatchInlineSnapshot(
30
- `"<h2 />;"`
31
- )
32
- expect(jsx('React.createElement("h3", undefined)')).toMatchInlineSnapshot(
33
- `"<h3 />;"`
34
- )
35
- })
36
-
37
- it('should handle props without children', () => {
38
- expect(jsx('React.createElement("h1", {hi: there})')).toMatchInlineSnapshot(
39
- `"<h1 hi={there} />;"`
40
- )
41
- expect(
42
- jsx('React.createElement("h2", {"hi": there})')
43
- ).toMatchInlineSnapshot(`"<h2 hi={there} />;"`)
44
- expect(
45
- jsx('React.createElement("h3", {hi: "there"})')
46
- ).toMatchInlineSnapshot(`"<h3 hi=\\"there\\" />;"`)
47
- })
48
-
49
- it('should handle spread props', () => {
50
- expect(jsx('React.createElement("h1", props)')).toMatchInlineSnapshot(
51
- `"<h1 {...props} />;"`
52
- )
53
- expect(jsx('React.createElement("h1", getProps())')).toMatchInlineSnapshot(
54
- `"<h1 {...getProps()} />;"`
55
- )
56
- })
57
-
58
- it('should handle mixed props', () => {
59
- expect(
60
- jsx('React.createElement("h1", _extends({ hi: "there" }, props))')
61
- ).toMatchInlineSnapshot(`"<h1 hi=\\"there\\" {...props} />;"`)
62
- expect(
63
- jsx('React.createElement("h1", _extends({}, props, { hi: "there" }))')
64
- ).toMatchInlineSnapshot(`"<h1 {...props} hi=\\"there\\" />;"`)
65
- expect(
66
- jsx('React.createElement("h1", { ...props, hi: "there" })')
67
- ).toMatchInlineSnapshot(`"<h1 {...props} hi=\\"there\\" />;"`)
68
- })
69
-
70
- it('should handle props and ignore “null”/“undefined” children', () => {
71
- expect(
72
- jsx('React.createElement("h1", {hi: there}, null, undefined)')
73
- ).toMatchInlineSnapshot(`"<h1 hi={there} />;"`)
74
- })
75
-
76
- it('should ignore “null”/“undefined” props and handle children', () => {
77
- expect(
78
- jsx('React.createElement("h1", null, "Header")')
79
- ).toMatchInlineSnapshot(`"<h1>Header</h1>;"`)
80
- //this can be created from e.g. '<h2>Header{"harhar"}</h2>', but i think there’s no downside to merging it
81
- expect(
82
- jsx('React.createElement("h2", null, "Header", "harhar")')
83
- ).toMatchInlineSnapshot(`"<h2>Headerharhar</h2>;"`)
84
- expect(
85
- jsx('React.createElement("h3", null, React.createElement("i"))')
86
- ).toMatchInlineSnapshot(`"<h3><i /></h3>;"`)
87
- expect(
88
- jsx('React.createElement("h4", null, "a", React.createElement("b"), "c")')
89
- ).toMatchInlineSnapshot(`"<h4>a<b />c</h4>;"`)
90
- })
91
-
92
- it('should handle props and children', () => {
93
- //we extensively tested props and children separately, so only sth. basic
94
- expect(
95
- jsx('React.createElement("h1", {hi: there}, "Header")')
96
- ).toMatchInlineSnapshot(`"<h1 hi={there}>Header</h1>;"`)
97
- })
98
-
99
- it('should ignore intermingled “null”/“undefined” children', () => {
100
- expect(
101
- jsx('React.createElement("h1", null, null, "Header", undefined)')
102
- ).toMatchInlineSnapshot(`"<h1>Header</h1>;"`)
103
- })
104
-
105
- it('should handle children in nested expressions', () => {
106
- expect(
107
- jsx(
108
- 'React.createElement("h1", null, foo ? React.createElement("p") : null)'
109
- )
110
- ).toMatchInlineSnapshot(`"<h1>{foo ? <p /> : null}</h1>;"`)
111
- })
112
-
113
- it('should handle lowercase component names', () => {
114
- expect(jsx('React.createElement(aaa)')).toMatchInlineSnapshot(
115
- `"React.createElement(aaa);"`
116
- )
117
- })
118
-
119
- it('should not handle contains __self prop', () => {
120
- expect(
121
- jsx('React.createElement(Provider, { __self: this })')
122
- ).toMatchInlineSnapshot('"<Provider />;"')
123
- })
124
-
125
- it('should not handle contains __source prop', () => {
126
- expect(
127
- jsx(
128
- 'React.createElement(Provider, { __source: { fileName: _jsxFileName, lineNumber: 133 }})'
129
- )
130
- ).toMatchInlineSnapshot('"<Provider />;"')
131
- })
132
- })