Skip to content

Commit

Permalink
feat(next): apply babel react compiler plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
kwonoj committed May 15, 2024
1 parent 930a8d4 commit 2f47a89
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 80 deletions.
62 changes: 58 additions & 4 deletions packages/next-swc/crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,30 +419,30 @@ pub struct ExperimentalTurboConfig {
pub use_swc_css: Option<bool>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase")]
pub struct RuleConfigItemOptions {
pub loaders: Vec<LoaderItem>,
#[serde(default, alias = "as")]
pub rename_as: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RuleConfigItemOrShortcut {
Loaders(Vec<LoaderItem>),
Advanced(RuleConfigItem),
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase", untagged)]
pub enum RuleConfigItem {
Options(RuleConfigItemOptions),
Conditional(IndexMap<String, RuleConfigItem>),
Boolean(bool),
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
#[serde(untagged)]
pub enum LoaderItem {
LoaderName(String),
Expand All @@ -456,6 +456,36 @@ pub enum MdxRsOptions {
Option(MdxTransformOptions),
}

#[turbo_tasks::value(shared)]
#[derive(Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub enum ReactCompilerMode {
Infer,
Annotation,
All,
}

/// Subset of react compiler options
#[turbo_tasks::value(shared)]
#[derive(Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ReactCompilerOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub compilation_mode: Option<ReactCompilerMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub panic_threshold: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(untagged)]
pub enum ReactCompilerOptionsOrBoolean {
Boolean(bool),
Option(ReactCompilerOptions),
}

#[turbo_tasks::value(transparent)]
pub struct OptionalReactCompilerOptions(Option<Vc<ReactCompilerOptions>>);

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[serde(rename_all = "camelCase")]
pub struct ExperimentalConfig {
Expand Down Expand Up @@ -489,6 +519,7 @@ pub struct ExperimentalConfig {
pub web_vitals_attribution: Option<Vec<String>>,
pub server_actions: Option<ServerActionsOrLegacyBool>,
pub sri: Option<SubResourceIntegrity>,
react_compiler: Option<ReactCompilerOptionsOrBoolean>,

// ---
// UNSUPPORTED
Expand Down Expand Up @@ -961,6 +992,29 @@ impl NextConfig {
Ok(options.cell())
}

#[turbo_tasks::function]
pub async fn react_compiler(self: Vc<Self>) -> Result<Vc<OptionalReactCompilerOptions>> {
let options = &self.await?.experimental.react_compiler;

let options = match options {
Some(ReactCompilerOptionsOrBoolean::Boolean(true)) => {
OptionalReactCompilerOptions(Some(
ReactCompilerOptions {
compilation_mode: None,
panic_threshold: None,
}
.cell(),
))
}
Some(ReactCompilerOptionsOrBoolean::Option(options)) => OptionalReactCompilerOptions(
Some(ReactCompilerOptions { ..options.clone() }.cell()),
),
_ => OptionalReactCompilerOptions(None),
};

Ok(options.cell())
}

#[turbo_tasks::function]
pub async fn sass_config(self: Vc<Self>) -> Result<Vc<JsonValue>> {
Ok(Vc::cell(
Expand Down
123 changes: 76 additions & 47 deletions packages/next/src/build/babel/loader/get-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ function getPlugins(
const { isServer, isPageFile, isNextDist, hasModuleExports } =
cacheCharacteristics

const { hasReactRefresh, development } = loaderOptions
const { development } = loaderOptions
const hasReactRefresh =
loaderOptions.transformMode !== 'standalone'
? loaderOptions.hasReactRefresh
: false

const applyCommonJsItem = hasModuleExports
? createConfigItem(require('../plugins/commonjs'), { type: 'plugin' })
Expand Down Expand Up @@ -260,44 +264,83 @@ function getFreshConfig(
filename: string,
inputSourceMap?: object | null
) {
let { isServer, pagesDir, development, hasJsxRuntime, configFile, srcDir } =
loaderOptions

let customConfig: any = configFile
? getCustomBabelConfig(configFile)
: undefined

checkCustomBabelConfigDeprecation(customConfig)
let { isServer, pagesDir, srcDir, development } = loaderOptions

let options = {
babelrc: false,
cloneInputAst: false,
filename,
inputSourceMap: inputSourceMap || undefined,

// Set the default sourcemap behavior based on Webpack's mapping flag,
// but allow users to override if they want.
sourceMaps:
loaderOptions.sourceMaps === undefined
? this.sourceMap
: loaderOptions.sourceMaps,

// Ensure that Webpack will get a full absolute path in the sourcemap
// so that it can properly map the module back to its internal cached
// modules.
sourceFileName: filename,
sourceMaps: this.sourceMap,
} as any

const baseCaller = {
name: 'next-babel-turbo-loader',
supportsStaticESM: true,
supportsDynamicImport: true,

// Provide plugins with insight into webpack target.
// https://github.com/babel/babel-loader/issues/787
target: target,

// Webpack 5 supports TLA behind a flag. We enable it by default
// for Babel, and then webpack will throw an error if the experimental
// flag isn't enabled.
supportsTopLevelAwait: true,

isServer,
srcDir,
pagesDir,
isDev: development,

...loaderOptions.caller,
}

if (loaderOptions.transformMode === 'standalone') {
options.plugins = [
'@babel/plugin-syntax-jsx',
...(loaderOptions.plugins ?? []),
]
options.presets = [
[
require('next/dist/compiled/babel/preset-typescript'),
{ allowNamespaces: true },
],
]
options.caller = baseCaller
} else {
let { configFile, plugins, hasJsxRuntime } = loaderOptions
let customConfig: any = configFile
? getCustomBabelConfig(configFile)
: undefined

checkCustomBabelConfigDeprecation(customConfig)

plugins: [
// Set the default sourcemap behavior based on Webpack's mapping flag,
// but allow users to override if they want.
options.sourceMaps =
loaderOptions.sourceMaps === undefined
? this.sourceMap
: loaderOptions.sourceMaps

options.plugins = [
...getPlugins(loaderOptions, cacheCharacteristics),
...(plugins || []),
...(customConfig?.plugins || []),
],
]

// target can be provided in babelrc
target: isServer ? undefined : customConfig?.target,
options.target = isServer ? undefined : customConfig?.target

// env can be provided in babelrc
env: customConfig?.env,
options.env = customConfig?.env

presets: (() => {
options.presets = (() => {
// If presets is defined the user will have next/babel in their babelrc
if (customConfig?.presets) {
return customConfig.presets
Expand All @@ -310,33 +353,15 @@ function getFreshConfig(

// If no custom config is provided the default is to use next/babel
return ['next/babel']
})(),

overrides: loaderOptions.overrides,
})()

caller: {
name: 'next-babel-turbo-loader',
supportsStaticESM: true,
supportsDynamicImport: true,
options.overrides = loaderOptions.overrides

// Provide plugins with insight into webpack target.
// https://github.com/babel/babel-loader/issues/787
target: target,

// Webpack 5 supports TLA behind a flag. We enable it by default
// for Babel, and then webpack will throw an error if the experimental
// flag isn't enabled.
supportsTopLevelAwait: true,

isServer,
srcDir,
pagesDir,
isDev: development,
options.caller = {
...baseCaller,
hasJsxRuntime,

...loaderOptions.caller,
},
} as any
}
}

// Babel does strict checks on the config so undefined is not allowed
if (typeof options.target === 'undefined') {
Expand Down Expand Up @@ -405,7 +430,7 @@ export default function getConfig(
filename
)

if (loaderOptions.configFile) {
if (loaderOptions.transformMode === 'default' && loaderOptions.configFile) {
// Ensures webpack invalidates the cache for this loader when the config file changes
this.addDependency(loaderOptions.configFile)
}
Expand All @@ -426,7 +451,11 @@ export default function getConfig(
}
}

if (loaderOptions.configFile && !configFiles.has(loaderOptions.configFile)) {
if (
loaderOptions.transformMode === 'default' &&
loaderOptions.configFile &&
!configFiles.has(loaderOptions.configFile)
) {
configFiles.add(loaderOptions.configFile)
Log.info(
`Using external babel configuration from ${loaderOptions.configFile}`
Expand Down
43 changes: 36 additions & 7 deletions packages/next/src/build/babel/loader/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,45 @@ export interface NextJsLoaderContext extends webpack.LoaderContext<{}> {
target: string
}

export interface NextBabelLoaderOptions {
hasJsxRuntime: boolean
hasReactRefresh: boolean
export interface NextBabelLoaderBaseOptions {
isServer: boolean
development: boolean
distDir: string
pagesDir: string
cwd: string
srcDir: string
caller: any
development: boolean

// Custom plugins to be added to the generated babel options.
plugins?: Array<any>
}

/**
* Options to create babel loader for the default transformations.
*
* This is primary usecase of babel-loader configuration for running
* all of the necessary transforms for the ecmascript instead of swc loader.
*/
export type NextBabelLoaderOptionDefaultPresets = NextBabelLoaderBaseOptions & {
transformMode: 'default'
hasJsxRuntime: boolean
hasReactRefresh: boolean
sourceMaps?: any[]
overrides: any
caller: any
configFile: string | undefined
cwd: string
srcDir: string
}

/**
* Options to create babel loader for 'standalone' transformations.
*
* This'll create a babel loader does not enable any of the default presets or plugins,
* only the ones specified in the options where swc loader is enabled but need to inject
* a babel specific plugins like react compiler.
*/
export type NextBabelLoaderOptionStandalone = NextBabelLoaderBaseOptions & {
transformMode: 'standalone'
}

export type NextBabelLoaderOptions =
| NextBabelLoaderOptionDefaultPresets
| NextBabelLoaderOptionStandalone
Loading

0 comments on commit 2f47a89

Please sign in to comment.