Skip to content

Commit

Permalink
Transpile all code on app browser layer (#59569)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored Dec 20, 2023
1 parent 490d238 commit 9f432cb
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 77 deletions.
41 changes: 30 additions & 11 deletions packages/next/src/build/swc/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WEBPACK_LAYERS, type WebpackLayerName } from '../../lib/constants'
import type {
NextConfig,
ExperimentalConfig,
Expand All @@ -9,6 +10,8 @@ import type { ResolvedBaseUrl } from '../load-jsconfig'
const nextDistPath =
/(next[\\/]dist[\\/]shared[\\/]lib)|(next[\\/]dist[\\/]client)|(next[\\/]dist[\\/]pages)/

const nodeModulesPath = /[\\/]node_modules[\\/]/

const regeneratorRuntimePath = require.resolve(
'next/dist/compiled/regenerator-runtime'
)
Expand Down Expand Up @@ -44,7 +47,7 @@ function getBaseSWCOptions({
jsConfig,
swcCacheDir,
serverComponents,
isReactServerLayer,
bundleLayer,
}: {
filename: string
jest?: boolean
Expand All @@ -59,8 +62,10 @@ function getBaseSWCOptions({
jsConfig: any
swcCacheDir?: string
serverComponents?: boolean
isReactServerLayer?: boolean
bundleLayer?: WebpackLayerName
}) {
const isReactServerLayer =
bundleLayer === WEBPACK_LAYERS.reactServerComponents
const parserConfig = getParserOptions({ filename, jsConfig })
const paths = jsConfig?.compilerOptions?.paths
const enableDecorators = Boolean(
Expand Down Expand Up @@ -177,7 +182,7 @@ function getBaseSWCOptions({
serverComponents:
serverComponents && !jest
? {
isReactServerLayer: !!isReactServerLayer,
isReactServerLayer,
}
: undefined,
serverActions:
Expand All @@ -186,7 +191,7 @@ function getBaseSWCOptions({
// always enable server actions
// TODO: remove this option
enabled: true,
isReactServerLayer: !!isReactServerLayer,
isReactServerLayer,
}
: undefined,
// For app router we prefer to bundle ESM,
Expand Down Expand Up @@ -295,8 +300,8 @@ export function getJestSWCOptions({
resolvedBaseUrl,
esm,
// Don't apply server layer transformations for Jest
isReactServerLayer: false,
// Disable server / client graph assertions for Jest
bundleLayer: undefined,
serverComponents: false,
})

Expand Down Expand Up @@ -339,7 +344,7 @@ export function getLoaderSWCOptions({
swcCacheDir,
relativeFilePathFromRoot,
serverComponents,
isReactServerLayer,
bundleLayer,
esm,
}: {
filename: string
Expand All @@ -362,7 +367,7 @@ export function getLoaderSWCOptions({
relativeFilePathFromRoot: string
esm?: boolean
serverComponents?: boolean
isReactServerLayer?: boolean
bundleLayer?: WebpackLayerName
}) {
let baseOptions: any = getBaseSWCOptions({
filename,
Expand All @@ -375,7 +380,7 @@ export function getLoaderSWCOptions({
jsConfig,
// resolvedBaseUrl,
swcCacheDir,
isReactServerLayer,
bundleLayer,
serverComponents,
esm: !!esm,
})
Expand Down Expand Up @@ -418,9 +423,12 @@ export function getLoaderSWCOptions({
}

const isNextDist = nextDistPath.test(filename)
const isNodeModules = nodeModulesPath.test(filename)
const isAppBrowserLayer = bundleLayer === WEBPACK_LAYERS.appPagesBrowser

let options: any
if (isServer) {
return {
options = {
...baseOptions,
// Disables getStaticProps/getServerSideProps tree shaking on the server compilation for pages
disableNextSsg: true,
Expand All @@ -440,7 +448,7 @@ export function getLoaderSWCOptions({
...getModuleOptions(esm),
}
} else {
const options = {
options = {
...baseOptions,
// Ensure Next.js internals are output as commonjs modules
...(isNextDist
Expand Down Expand Up @@ -468,6 +476,17 @@ export function getLoaderSWCOptions({
// Matches default @babel/preset-env behavior
options.jsc.target = 'es5'
}
return options
}

// For node_modules in app browser layer, we don't need to do any server side transformation.
// Only keep server actions transform to discover server actions from client components.
if (isAppBrowserLayer && isNodeModules) {
options.disableNextSsg = true
options.disablePageConfig = true
options.isPageFile = false
options.optimizeServerReact = undefined
options.cjsRequireOptimizer = undefined
}

return options
}
111 changes: 65 additions & 46 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ const devtoolRevertWarning = execOnce(

let loggedSwcDisabled = false
let loggedIgnoredCompilerOptions = false
const reactRefreshLoaderName =
'next/dist/compiled/@next/react-refresh-utils/dist/loader'

export function attachReactRefresh(
webpackConfig: webpack.Configuration,
targetLoader: webpack.RuleSetUseItem
) {
let injections = 0
const reactRefreshLoaderName =
'next/dist/compiled/@next/react-refresh-utils/dist/loader'
const reactRefreshLoader = require.resolve(reactRefreshLoaderName)
webpackConfig.module?.rules?.forEach((rule) => {
if (rule && typeof rule === 'object' && 'use' in rule) {
Expand Down Expand Up @@ -445,18 +445,22 @@ export default async function getBaseWebpackConfig(
// RSC loaders, prefer ESM, set `esm` to true
const swcServerLayerLoader = getSwcLoader({
serverComponents: true,
isReactServerLayer: true,
bundleLayer: WEBPACK_LAYERS.reactServerComponents,
esm: true,
})
const swcClientLayerLoader = getSwcLoader({
const swcSSRLayerLoader = getSwcLoader({
serverComponents: true,
isReactServerLayer: false,
bundleLayer: WEBPACK_LAYERS.serverSideRendering,
esm: true,
})
const swcBrowserLayerLoader = getSwcLoader({
serverComponents: true,
bundleLayer: WEBPACK_LAYERS.appPagesBrowser,
esm: true,
})
// Default swc loaders for pages doesn't prefer ESM.
const swcDefaultLoader = getSwcLoader({
serverComponents: true,
isReactServerLayer: false,
esm: false,
})

Expand All @@ -475,31 +479,30 @@ export default async function getBaseWebpackConfig(
].filter(Boolean)
: []

const swcLoaderForMiddlewareLayer = useSWCLoader
? getSwcLoader({
serverComponents: false,
isReactServerLayer: false,
})
: // When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
[
getSwcLoader({
serverComponents: false,
isReactServerLayer: false,
}),
]
const swcLoaderForMiddlewareLayer = [
// When using Babel, we will have to use SWC to do the optimization
// for middleware to tree shake the unused default optimized imports like "next/server".
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
getSwcLoader({
serverComponents: false,
bundleLayer: WEBPACK_LAYERS.middleware,
}),
babelLoader,
].filter(Boolean)

// client components layers: SSR + browser
const swcLoaderForClientLayer = [
...(dev && isClient
? [
require.resolve(
'next/dist/compiled/@next/react-refresh-utils/dist/loader'
),
]
: []),
const reactRefreshLoaders =
dev && isClient ? [require.resolve(reactRefreshLoaderName)] : []

// client components layers: SSR or browser
const createSwcLoaderForClientLayer = ({
isBrowserLayer,
reactRefresh,
}: {
isBrowserLayer: boolean
reactRefresh: boolean
}) => [
...(reactRefresh ? reactRefreshLoaders : []),
{
// This loader handles actions and client entries
// in the client layer.
Expand All @@ -511,20 +514,30 @@ export default async function getBaseWebpackConfig(
// as an additional pass to handle RSC correctly.
// This will cause some performance overhead but
// acceptable as Babel will not be recommended.
swcClientLayerLoader,
isBrowserLayer ? swcBrowserLayerLoader : swcSSRLayerLoader,
babelLoader,
].filter(Boolean)
: []),
]

const swcLoaderForBrowserLayer = createSwcLoaderForClientLayer({
isBrowserLayer: true,
// reactRefresh for browser layer is applied conditionally to user-land source
reactRefresh: false,
})
const swcLoaderForSSRLayer = createSwcLoaderForClientLayer({
isBrowserLayer: false,
reactRefresh: true,
})

// Loader for API routes needs to be differently configured as it shouldn't
// have RSC transpiler enabled, so syntax checks such as invalid imports won't
// be performed.
const loaderForAPIRoutes =
hasAppDir && useSWCLoader
? getSwcLoader({
serverComponents: false,
isReactServerLayer: false,
bundleLayer: WEBPACK_LAYERS.api,
})
: defaultLoaders.babel

Expand Down Expand Up @@ -1379,6 +1392,20 @@ export default async function getBaseWebpackConfig(
},
]
: []),
// Do not apply react-refresh-loader to node_modules for app router browser layer
...(hasAppDir && dev && isClient
? [
{
test: codeCondition.test,
exclude: codeCondition.exclude,
issuerLayer: WEBPACK_LAYERS.appPagesBrowser,
use: reactRefreshLoaders,
resolve: {
mainFields: getMainField(compilerType, true),
},
},
]
: []),
{
oneOf: [
{
Expand Down Expand Up @@ -1412,17 +1439,16 @@ export default async function getBaseWebpackConfig(
},
{
test: codeCondition.test,
exclude: codeCondition.exclude,
issuerLayer: [WEBPACK_LAYERS.appPagesBrowser],
use: swcLoaderForClientLayer,
issuerLayer: WEBPACK_LAYERS.appPagesBrowser,
use: swcLoaderForBrowserLayer,
resolve: {
mainFields: getMainField(compilerType, true),
},
},
{
test: codeCondition.test,
issuerLayer: [WEBPACK_LAYERS.serverSideRendering],
use: swcLoaderForClientLayer,
issuerLayer: WEBPACK_LAYERS.serverSideRendering,
use: swcLoaderForSSRLayer,
resolve: {
mainFields: getMainField(compilerType, true),
},
Expand All @@ -1431,18 +1457,11 @@ export default async function getBaseWebpackConfig(
: []),
{
...codeCondition,
use:
dev && isClient
? [
require.resolve(
'next/dist/compiled/@next/react-refresh-utils/dist/loader'
),
defaultLoaders.babel,
]
: defaultLoaders.babel,
use: [...reactRefreshLoaders, defaultLoaders.babel],
},
],
},

...(!config.images.disableStaticImages
? [
{
Expand Down
7 changes: 4 additions & 3 deletions packages/next/src/build/webpack/loaders/next-swc-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ DEALINGS IN THE SOFTWARE.
*/

import type { NextConfig } from '../../../../types'
import type { WebpackLayerName } from '../../../lib/constants'
import { isWasm, transform } from '../../swc'
import { getLoaderSWCOptions } from '../../swc/options'
import path, { isAbsolute } from 'path'
Expand All @@ -43,7 +44,7 @@ export interface SWCLoaderOptions {
supportedBrowsers: string[] | undefined
swcCacheDir: string
serverComponents?: boolean
isReactServerLayer?: boolean
bundleLayer?: WebpackLayerName
esm?: boolean
}

Expand All @@ -69,7 +70,7 @@ async function loaderTransform(
supportedBrowsers,
swcCacheDir,
serverComponents,
isReactServerLayer,
bundleLayer,
esm,
} = loaderOptions
const isPageFile = filename.startsWith(pagesDir)
Expand All @@ -93,7 +94,7 @@ async function loaderTransform(
swcCacheDir,
relativeFilePathFromRoot,
serverComponents,
isReactServerLayer,
bundleLayer,
esm,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox app %s', () => {
)
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"./node_modules/my-package/index.js:1:0
"./node_modules/my-package/index.js:1:12
Module not found: Can't resolve 'dns'
https://nextjs.org/docs/messages/module-not-found
Expand Down
Loading

0 comments on commit 9f432cb

Please sign in to comment.