Skip to content

Commit

Permalink
feat(turbopack): add support for bundlePagesRouterDependencies (#65520
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ForsakenHarmony committed May 10, 2024
1 parent eff272f commit c98c6d6
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 36 deletions.
20 changes: 17 additions & 3 deletions packages/next-swc/crates/next-core/src/next_config.rs
Expand Up @@ -88,6 +88,18 @@ pub struct NextConfig {
pub dev_indicators: Option<DevIndicatorsConfig>,
pub output: Option<OutputType>,

/// Enables the bundling of node_modules packages (externals) for pages
/// server-side bundles.
///
/// [API Reference](https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies)
pub bundle_pages_router_dependencies: Option<bool>,

/// A list of packages that should be treated as external on the server
/// build.
///
/// [API Reference](https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages)
pub server_external_packages: Option<Vec<String>>,

#[serde(rename = "_originalRedirects")]
pub original_redirects: Option<Vec<Redirect>>,

Expand Down Expand Up @@ -119,9 +131,6 @@ pub struct NextConfig {
typescript: TypeScriptConfig,
use_file_system_public_routes: bool,
webpack: Option<serde_json::Value>,
/// A list of packages that should be treated as external in the RSC server
/// build. @see [api reference](https://nextjs.org/docs/app/api-reference/next-config-js/server_external_packages)
pub server_external_packages: Option<Vec<String>>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
Expand Down Expand Up @@ -735,6 +744,11 @@ impl NextConfig {
Ok(config.cell())
}

#[turbo_tasks::function]
pub fn bundle_pages_router_dependencies(&self) -> Vc<bool> {
Vc::cell(self.bundle_pages_router_dependencies.unwrap_or_default())
}

#[turbo_tasks::function]
pub async fn server_external_packages(self: Vc<Self>) -> Result<Vc<Vec<String>>> {
Ok(Vc::cell(
Expand Down
49 changes: 35 additions & 14 deletions packages/next-swc/crates/next-core/src/next_server/context.rs
@@ -1,6 +1,6 @@
use std::iter::once;

use anyhow::Result;
use anyhow::{bail, Result};
use indexmap::IndexMap;
use turbo_tasks::{Value, Vc};
use turbo_tasks_fs::FileSystem;
Expand Down Expand Up @@ -128,22 +128,39 @@ pub async fn get_server_resolve_options_context(
let invalid_styled_jsx_client_only_resolve_plugin =
get_invalid_styled_jsx_resolve_plugin(project_path);

let mut transpile_packages = next_config.transpile_packages().await?.clone_value();
transpile_packages.extend(
(*next_config.optimize_package_imports().await?)
.iter()
.cloned(),
);

// Always load these predefined packages as external.
let mut external_packages: Vec<String> = load_next_js_templateon(
project_path,
"dist/lib/server-external-packages.json".to_string(),
)
.await?;

let transpile_packages = next_config.transpile_packages().await?;
external_packages.retain(|item| !transpile_packages.contains(item));
let server_external_packages = &*next_config.server_external_packages().await?;

let conflicting_packages = transpile_packages
.iter()
.filter(|package| server_external_packages.contains(package))
.collect::<Vec<_>>();

if !conflicting_packages.is_empty() {
bail!(
"The packages specified in the 'transpilePackages' conflict with the \
'serverExternalPackages': {:?}",
conflicting_packages
);
}

// Add the config's own list of external packages.
external_packages.extend(
(*next_config.server_external_packages().await?)
.iter()
.cloned(),
);
external_packages.extend(server_external_packages.iter().cloned());

external_packages.retain(|item| !transpile_packages.contains(item));

let server_external_packages_plugin = ExternalCjsModulesResolvePlugin::new(
project_path,
Expand All @@ -166,12 +183,16 @@ pub async fn get_server_resolve_options_context(
custom_conditions.push("react-server".to_string());
};

let external_cjs_modules_plugin = ExternalCjsModulesResolvePlugin::new(
project_path,
project_path.root(),
ExternalPredicate::AllExcept(next_config.transpile_packages()).cell(),
*next_config.import_externals().await?,
);
let external_cjs_modules_plugin = if *next_config.bundle_pages_router_dependencies().await? {
server_external_packages_plugin
} else {
ExternalCjsModulesResolvePlugin::new(
project_path,
project_path.root(),
ExternalPredicate::AllExcept(Vc::cell(transpile_packages)).cell(),
*next_config.import_externals().await?,
)
};

let next_external_plugin = NextExternalResolvePlugin::new(project_path);
let next_node_shared_runtime_plugin =
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/build/handle-externals.ts
Expand Up @@ -181,7 +181,7 @@ export function makeExternalHandler({
// Absolute requires (require('/foo')) are extremely uncommon, but
// also have no need for customization as they're already resolved.
if (!isLocal) {
if (/^(?:next$)/.test(request)) {
if (/^next$/.test(request)) {
return `commonjs ${request}`
}

Expand Down Expand Up @@ -380,13 +380,15 @@ function resolveBundlingOptOutPackages({
if (nodeModulesRegex.test(resolvedRes)) {
const shouldBundlePages =
!isAppLayer && config.bundlePagesRouterDependencies && !isOptOutBundling

const shouldBeBundled =
shouldBundlePages ||
isResourceInPackages(
resolvedRes,
config.transpilePackages,
resolvedExternalPackageDirs
)

if (!shouldBeBundled) {
return `${externalType} ${request}` // Externalize if not bundled or opted out
}
Expand Down
11 changes: 6 additions & 5 deletions packages/next/src/server/config-shared.ts
Expand Up @@ -808,11 +808,12 @@ export interface NextConfig extends Record<string, any> {

/**
* Enables the bundling of node_modules packages (externals) for pages server-side bundles.
* @see https://nextjs.org/docs/pages/api-reference/next-config-js/bundlePagesRouterDependencies
*/
bundlePagesRouterDependencies?: boolean

/**
* A list of packages that should be treated as external in the RSC server build.
* A list of packages that should be treated as external in the server build.
* @see https://nextjs.org/docs/app/api-reference/next-config-js/serverExternalPackages
*/
serverExternalPackages?: string[]
Expand Down Expand Up @@ -926,10 +927,10 @@ export const defaultConfig: NextConfig = {
// If we're testing, and the `__NEXT_EXPERIMENTAL_PPR` environment variable
// has been set to `true`, enable the experimental PPR feature so long as it
// wasn't explicitly disabled in the config.
process.env.__NEXT_TEST_MODE &&
process.env.__NEXT_EXPERIMENTAL_PPR === 'true'
? true
: false,
!!(
process.env.__NEXT_TEST_MODE &&
process.env.__NEXT_EXPERIMENTAL_PPR === 'true'
),
webpackBuildWorker: undefined,
missingSuspenseWithCSRBailout: true,
optimizeServerReact: true,
Expand Down
55 changes: 44 additions & 11 deletions test/integration/externals-pages-bundle/test/index.test.js
Expand Up @@ -11,22 +11,55 @@ describe('bundle pages externals with config.bundlePagesRouterDependencies', ()
'production mode',
() => {
beforeAll(async () => {
await nextBuild(appDir, [], { stdout: true })
await nextBuild(appDir, [])
})

it('should have no externals with the config set', async () => {
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).not.toContain('require("external-package")')
if (process.env.TURBOPACK) {
const ssrPath = join(appDir, '.next/server/chunks/ssr')
const pageBundleBasenames = (await fs.readdir(ssrPath)).filter((p) =>
p.match(/\.js$/)
)
expect(pageBundleBasenames).not.toBeEmpty()
let allBundles = ''
for (const basename of pageBundleBasenames) {
const output = await fs.readFile(join(ssrPath, basename), 'utf8')
allBundles += output
}

// we don't know the name of the minified `__turbopack_external_require__`, so we just check the arguments.
expect(allBundles).not.toContain('("external-package",!0)')
} else {
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).not.toContain('require("external-package")')
}
})

it('should respect the serverExternalPackages config', async () => {
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).toContain('require("opted-out-external-package")')
if (process.env.TURBOPACK) {
const ssrPath = join(appDir, '.next/server/chunks/ssr')
const pageBundleBasenames = (await fs.readdir(ssrPath)).filter((p) =>
p.match(/\.js$/)
)
expect(pageBundleBasenames).not.toBeEmpty()
let allBundles = ''
for (const basename of pageBundleBasenames) {
const output = await fs.readFile(join(ssrPath, basename), 'utf8')
allBundles += output
}

// we don't know the name of the minified `__turbopack_external_require__`, so we just check the arguments.
expect(allBundles).toContain('("opted-out-external-package",!0)')
} else {
const output = await fs.readFile(
join(appDir, '.next/server/pages/index.js'),
'utf8'
)
expect(output).toContain('require("opted-out-external-package")')
}
})
}
)
Expand Down
4 changes: 2 additions & 2 deletions test/turbopack-build-tests-manifest.json
Expand Up @@ -8484,11 +8484,11 @@
"runtimeError": false
},
"test/integration/externals-pages-bundle/test/index.test.js": {
"passed": [],
"failed": [
"passed": [
"bundle pages externals with config.bundlePagesRouterDependencies production mode should have no externals with the config set",
"bundle pages externals with config.bundlePagesRouterDependencies production mode should respect the serverExternalPackages config"
],
"failed": [],
"pending": [],
"flakey": [],
"runtimeError": false
Expand Down

0 comments on commit c98c6d6

Please sign in to comment.