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(plugin-react): change how babel.include/exclude options behave #6202

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
76 changes: 49 additions & 27 deletions packages/plugin-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
import * as babel from '@babel/core'
import { createFilter } from '@rollup/pluginutils'
import resolve from 'resolve'
import type { Plugin, PluginOption } from 'vite'
import {
Expand All @@ -12,15 +11,22 @@ import {
} from './fast-refresh'
import { babelImportToRequire } from './jsx-runtime/babel-import-to-require'
import { restoreJSX } from './jsx-runtime/restore-jsx'
import {
createFileFilter,
FileFilter,
FilterOptions,
loadPlugin
} from './utils'

export type BabelOptions = Omit<TransformOptions, keyof FilterOptions> &
FilterOptions

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
fastRefresh?: boolean | FilterOptions
/**
* Set this to `"automatic"` to use [vite-react-jsx](https://github.com/alloc/vite-react-jsx).
* @default "automatic"
Expand All @@ -36,7 +42,7 @@ export interface Options {
/**
* Babel configuration applied in both dev and prod.
*/
babel?: TransformOptions
babel?: BabelOptions
/**
* @deprecated Use `babel.parserOpts.plugins` instead
*/
Expand All @@ -46,19 +52,39 @@ export interface Options {
export default function viteReact(opts: Options = {}): PluginOption[] {
// Provide default values for Rollup compat.
let base = '/'
let filter = createFilter(opts.include, opts.exclude)
let isProduction = true
let projectRoot = process.cwd()
let skipFastRefresh = opts.fastRefresh === false
let skipReactImport = false
let shouldProjectInclude: FileFilter
let shouldProjectExclude: FileFilter
let shouldUseFastRefresh: FileFilter
initializeFilters()

function initializeFilters(command?: string) {
shouldProjectInclude = createFileFilter(
{ include: opts.babel?.include },
false,
projectRoot
)
shouldProjectExclude = createFileFilter(
{ include: opts.babel?.exclude },
Copy link

Choose a reason for hiding this comment

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

Should this be "exclude" instead of "include"?

Copy link
Member Author

Choose a reason for hiding this comment

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

No but it does need a comment to clarify that :)

It's creating a function that returns true if a babel.exclude pattern is matched.

Copy link

Choose a reason for hiding this comment

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

Ahh, got it, thanks!

false,
projectRoot
)
shouldUseFastRefresh = createFileFilter(
!(isProduction || command === 'build') && opts.fastRefresh,
true,
projectRoot
)
}

const useAutomaticRuntime = opts.jsxRuntime !== 'classic'

const userPlugins = opts.babel?.plugins || []
const userParserPlugins =
opts.parserPlugins || opts.babel?.parserOpts?.plugins || []

// Support pattens like:
// Support patterns like:
// - import * as React from 'react';
// - import React from 'react';
// - import React, {useEffect} from 'react';
Expand All @@ -73,11 +99,8 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
configResolved(config) {
base = config.base
projectRoot = config.root
filter = createFilter(opts.include, opts.exclude, {
resolve: projectRoot
})
isProduction = config.isProduction
skipFastRefresh ||= isProduction || config.command === 'build'
initializeFilters(config.command)

const jsxInject = config.esbuild && config.esbuild.jsxInject
if (jsxInject && importReactRE.test(jsxInject)) {
Expand Down Expand Up @@ -112,20 +135,23 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
const isNodeModules = id.includes('/node_modules/')
const isProjectFile =
!isNodeModules && (id[0] === '\0' || id.startsWith(projectRoot + '/'))
? !shouldProjectExclude(id)
: shouldProjectInclude(id)
Copy link

Choose a reason for hiding this comment

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

Would any "exclude" settings be ignored in this case?

Copy link
Member Author

@aleclarson aleclarson Oct 18, 2022

Choose a reason for hiding this comment

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

Good observation! With how this is currently written, one cannot exclude a subset of paths matched by include, which is certainly a valid use case.

Should be a simple fix, just add exclude: opts.babel?.exclude to the shouldProjectInclude definition.


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

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

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

let ast: t.File | null | undefined
Expand Down Expand Up @@ -238,7 +264,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {

if (result) {
let code = result.code!
if (useFastRefresh && /\$RefreshReg\$\(/.test(code)) {
if (usingFastRefresh && /\$RefreshReg\$\(/.test(code)) {
const accept = isReasonReact || isRefreshBoundary(result.ast!)
code = addRefreshWrapper(code, id, accept)
}
Expand Down Expand Up @@ -318,10 +344,6 @@ export default function viteReact(opts: Options = {}): PluginOption[] {

viteReact.preambleCode = preambleCode

function loadPlugin(path: string): Promise<any> {
return import(path).then((module) => module.default || module)
}

// overwrite for cjs require('...')() usage
module.exports = viteReact
viteReact['default'] = viteReact
31 changes: 31 additions & 0 deletions packages/plugin-react/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createFilter } from '@rollup/pluginutils'

export function loadPlugin(path: string): Promise<any> {
return import(path).then((module) => module.default || module)
}

export interface FilterOptions {
include?: string | RegExp | Array<string | RegExp>
exclude?: string | RegExp | Array<string | RegExp>
}

const returnTrue = () => true
const returnFalse = () => false

export type FileFilter = (id: string) => boolean

export function createFileFilter(
arg: boolean | FilterOptions | undefined,
defaultArg: boolean,
resolve?: string
): FileFilter {
return arg === true
? returnTrue
: arg === false
? returnFalse
: arg && (arg.include || arg.exclude)
? createFilter(arg.include, arg.exclude, { resolve })
: defaultArg
? returnTrue
: returnFalse
}