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

Add transpilePackages option #41583

Merged
merged 2 commits into from Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 28 additions & 2 deletions packages/next/build/webpack-config.ts
Expand Up @@ -119,7 +119,9 @@ function errorIfEnvConflicted(config: NextConfigComplete, key: string) {

function isResourceInPackages(resource: string, packageNames?: string[]) {
return packageNames?.some((p: string) =>
new RegExp('[/\\\\]node_modules[/\\\\]' + p + '[/\\\\]').test(resource)
resource.includes(
path.sep + pathJoin('node_modules', p.replace(/\//g, path.sep)) + path.sep
Copy link
Member

Choose a reason for hiding this comment

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

Will this work for the case where pnpm symlinks the package to a different name that includes the version e.g. .pnpm/react@18.2.0...

Copy link
Contributor

@SukkaW SukkaW Oct 20, 2022

Choose a reason for hiding this comment

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

IIRC Webpack has an option resolve.symlink and is enabled by default.

I do, however, worry about Yarn PnP support. The community-maintained plugin next-transpile-module is using enhanced-resolve for Yarn PnP.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I need to verify pnpm and Yarn PnP. isResourceInPackages itself accepts the resource location that is already resolved by webpack (which uses enhanced-resolve under the hood). So here we just need to ensure that it can correctly detect the package name / subpath.

Copy link
Member Author

Choose a reason for hiding this comment

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

@ijjk Just confirmed that pnpm works because it always has the "/node_modules/<package>" substring in the resolved path: /Users/shu/Documents/git/next.js/node_modules/.pnpm/@swc+helpers@0.4.11/node_modules/@swc/helpers/lib/_extends.js

Copy link
Member Author

Choose a reason for hiding this comment

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

Also confirmed that although the Yarn PnP packages are re-mapped, but the resolved package is still under a node_modules folder. I think that's for compatibility.

)
)
}

Expand Down Expand Up @@ -1168,21 +1170,38 @@ export default async function getBaseWebpackConfig(
return
}

// If a package should be transpiled by Next.js, we skip making it external.
// It doesn't matter what the extension is, as we'll transpile it anyway.
const shouldBeBundled = isResourceInPackages(
res,
config.experimental.transpilePackages
)

if (/node_modules[/\\].*\.[mc]?js$/.test(res)) {
if (layer === WEBPACK_LAYERS.server) {
// All packages should be bundled for the server layer if they're not opted out.
if (isResourceInPackages(res, optoutBundlingPackages)) {
// This option takes priority over the transpilePackages option.
if (
isResourceInPackages(
res,
config.experimental.serverComponentsExternalPackages
)
) {
return `${externalType} ${request}`
}

return
}

if (shouldBeBundled) return

// Anything else that is standard JavaScript within `node_modules`
// can be externalized.
return `${externalType} ${request}`
}

if (shouldBeBundled) return

// Default behavior: bundle the code!
}

Expand All @@ -1196,6 +1215,13 @@ export default async function getBaseWebpackConfig(
if (babelIncludeRegexes.some((r) => r.test(excludePath))) {
return false
}

const shouldBeBundled = isResourceInPackages(
excludePath,
config.experimental.transpilePackages
)
if (shouldBeBundled) return false

return excludePath.includes('node_modules')
},
}
Expand Down
6 changes: 6 additions & 0 deletions packages/next/server/config-schema.ts
Expand Up @@ -348,6 +348,12 @@ const configSchema = {
},
type: 'array',
},
transpilePackages: {
items: {
type: 'string',
},
type: 'array',
},
scrollRestoration: {
type: 'boolean',
},
Expand Down
3 changes: 3 additions & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -159,6 +159,9 @@ export interface ExperimentalConfig {
// A list of packages that should be treated as external in the RSC server build
serverComponentsExternalPackages?: string[]

// A list of packages that should always be transpiled and bundled in the server
transpilePackages?: string[]

fontLoaders?: [{ loader: string; options?: any }]

webVitalsAttribution?: Array<typeof WEB_VITALS[number]>
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/app-dir/rsc-external.test.ts
Expand Up @@ -84,6 +84,11 @@ describe('app dir - rsc external dependency', () => {
)
})

it('should transpile specific external packages with the `transpilePackages` option', async () => {
const clientHtml = await renderViaHTTP(next.url, '/external-imports/client')
expect(clientHtml).toContain('transpilePackages:5')
})

it('should resolve the subset react in server components based on the react-server condition', async () => {
await fetchViaHTTP(next.url, '/react-server').then(async (response) => {
const result = await resolveStreamResponse(response)
Expand Down
Expand Up @@ -2,6 +2,8 @@

import getType, { named, value, array, obj } from 'non-isomorphic-text'

import add from 'untranspiled-module'

export default function Page() {
return (
<div>
Expand All @@ -10,6 +12,7 @@ export default function Page() {
<div>{`export value:${value}`}</div>
<div>{`export array:${array.join(',')}`}</div>
<div>{`export object:{x:${obj.x}}`}</div>
<div>{`transpilePackages:${add(2, 3)}`}</div>
</div>
)
}
1 change: 1 addition & 0 deletions test/e2e/app-dir/rsc-external/next.config.js
Expand Up @@ -3,5 +3,6 @@ module.exports = {
experimental: {
appDir: true,
serverComponentsExternalPackages: ['conditional-exports-optout'],
transpilePackages: ['untranspiled-module'],
},
}
@@ -0,0 +1,3 @@
export default function add(a: number, b: number) {
return a + b
}
@@ -0,0 +1,4 @@
{
"name": "untranspiled-module",
"main": "index.ts"
}