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

make sure server component externals only apply to files resolvable by node #49147

Merged
merged 2 commits into from
May 5, 2023
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
4 changes: 0 additions & 4 deletions packages/next-swc/crates/next-core/src/next_import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,6 @@ pub async fn get_next_server_import_map(
request_to_import_mapping(project_path, "next/dist/shared/lib/app-dynamic"),
);

for name in next_config.server_component_externals().await?.iter() {
import_map.insert_exact_alias(name, external);
import_map.insert_wildcard_alias(format!("{name}/"), external);
}
// The sandbox can't be bundled and needs to be external
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
}
Expand Down
22 changes: 18 additions & 4 deletions packages/next-swc/crates/next-core/src/next_server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::{
next_build::{get_external_next_compiled_package_mapping, get_postcss_package_mapping},
next_config::NextConfigVc,
next_import_map::get_next_server_import_map,
next_server::resolve::ExternalPredicate,
next_shared::resolve::UnsupportedModulesResolvePluginVc,
transform_options::{
get_decorators_transform_options, get_emotion_compiler_config, get_jsx_transform_options,
Expand Down Expand Up @@ -69,12 +70,16 @@ pub async fn get_server_resolve_options_context(
let foreign_code_context_condition = foreign_code_context_condition(next_config).await?;
let root_dir = project_path.root().resolve().await?;
let unsupported_modules_resolve_plugin = UnsupportedModulesResolvePluginVc::new(project_path);
let server_component_externals_plugin = ExternalCjsModulesResolvePluginVc::new(
project_path,
ExternalPredicate::Only(next_config.server_component_externals()).cell(),
);

Ok(match ty.into_value() {
ServerContextType::Pages { .. } | ServerContextType::PagesData { .. } => {
let external_cjs_modules_plugin = ExternalCjsModulesResolvePluginVc::new(
project_path,
next_config.transpile_packages(),
ExternalPredicate::AllExcept(next_config.transpile_packages()).cell(),
);

let resolve_options_context = ResolveOptionsContext {
Expand Down Expand Up @@ -108,7 +113,10 @@ pub async fn get_server_resolve_options_context(
module: true,
custom_conditions: vec!["development".to_string()],
import_map: Some(next_server_import_map),
plugins: vec![unsupported_modules_resolve_plugin.into()],
plugins: vec![
server_component_externals_plugin.into(),
unsupported_modules_resolve_plugin.into(),
],
..Default::default()
};
ResolveOptionsContext {
Expand All @@ -129,7 +137,10 @@ pub async fn get_server_resolve_options_context(
module: true,
custom_conditions: vec!["development".to_string(), "react-server".to_string()],
import_map: Some(next_server_import_map),
plugins: vec![unsupported_modules_resolve_plugin.into()],
plugins: vec![
server_component_externals_plugin.into(),
unsupported_modules_resolve_plugin.into(),
],
..Default::default()
};
ResolveOptionsContext {
Expand All @@ -148,7 +159,10 @@ pub async fn get_server_resolve_options_context(
module: true,
custom_conditions: vec!["development".to_string()],
import_map: Some(next_server_import_map),
plugins: vec![unsupported_modules_resolve_plugin.into()],
plugins: vec![
server_component_externals_plugin.into(),
unsupported_modules_resolve_plugin.into(),
],
..Default::default()
};
ResolveOptionsContext {
Expand Down
44 changes: 33 additions & 11 deletions packages/next-swc/crates/next-core/src/next_server/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,31 @@ use turbo_binding::{
};
use turbo_tasks::primitives::{BoolVc, StringsVc};

/// The predicated based on which the [ExternalCjsModulesResolvePlugin] decides
/// whether to mark a module as external.
#[turbo_tasks::value(into = "shared")]
pub enum ExternalPredicate {
/// Mark all modules as external if they're not listed in the list.
AllExcept(StringsVc),
/// Only mark modules listed as external.
Only(StringsVc),
}

/// Mark modules as external, so they're resolved at runtime instead of bundled.
///
/// Modules matching the predicate are marked as external as long as it's
/// possible to resolve them at runtime.
#[turbo_tasks::value]
pub(crate) struct ExternalCjsModulesResolvePlugin {
root: FileSystemPathVc,
transpiled_packages: StringsVc,
predicate: ExternalPredicateVc,
}

#[turbo_tasks::value_impl]
impl ExternalCjsModulesResolvePluginVc {
#[turbo_tasks::function]
pub fn new(root: FileSystemPathVc, transpiled_packages: StringsVc) -> Self {
ExternalCjsModulesResolvePlugin {
root,
transpiled_packages,
}
.cell()
pub fn new(root: FileSystemPathVc, predicate: ExternalPredicateVc) -> Self {
ExternalCjsModulesResolvePlugin { root, predicate }.cell()
}
}

Expand Down Expand Up @@ -87,10 +97,22 @@ impl ResolvePlugin for ExternalCjsModulesResolvePlugin {

let raw_fs_path = &*fs_path.await?;

// always bundle transpiled modules
let transpiled_glob = packages_glob(self.transpiled_packages).await?;
if transpiled_glob.execute(&raw_fs_path.path) {
return Ok(ResolveResultOptionVc::none());
let predicate = self.predicate.await?;
match &*predicate {
ExternalPredicate::AllExcept(exceptions) => {
let exception_glob = packages_glob(*exceptions).await?;

if exception_glob.execute(&raw_fs_path.path) {
return Ok(ResolveResultOptionVc::none());
}
}
ExternalPredicate::Only(externals) => {
let external_glob = packages_glob(*externals).await?;

if !external_glob.execute(&raw_fs_path.path) {
return Ok(ResolveResultOptionVc::none());
}
}
}

// node.js only supports these file extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let ranOnce = false
/**
* Run a callback once the test harness is loaded.
*/
export async function useTestHarness<T extends (harness: Harness) => void>(
export function useTestHarness<T extends (harness: Harness) => void>(
callback: T
) {
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
env,
fmt::Write,
future::{pending, Future},
net::SocketAddr,
net::{IpAddr, Ipv4Addr, SocketAddr},
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
path::{Path, PathBuf},
time::Duration,
Expand Down Expand Up @@ -227,14 +227,16 @@ async fn run_test(resource: PathBuf) -> JestRunResult {
.await
.unwrap();

let local_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), server.addr.port());

println!(
"{event_type} - server started at http://{address}",
event_type = "ready".green(),
address = server.addr
);

if *DEBUG_START {
webbrowser::open(&server.addr.to_string()).unwrap();
webbrowser::open(&local_addr.to_string()).unwrap();
tokio::select! {
_ = mock_server_future => {},
_ = pending() => {},
Expand All @@ -246,7 +248,7 @@ async fn run_test(resource: PathBuf) -> JestRunResult {
let result = tokio::select! {
// Poll the mock_server first to add the env var
_ = mock_server_future => panic!("Never resolves"),
r = run_browser(server.addr) => r.expect("error while running browser"),
r = run_browser(local_addr) => r.expect("error while running browser"),
_ = server.future => panic!("Never resolves"),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default async function handler(
res: NextApiResponse<Data>
) {
const innerRes = await fetch(
`http://[::]:${process.env.PORT}/api/basic?input=test`
`http://[::1]:${process.env.PORT}/api/basic?input=test`
)
const json = await innerRes.json()
res.status(200).json(json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function runTests(harness: Harness) {
expect(json).toEqual({ hello: 'world', input: 'test' })
})

it('should be able to use PORT and fetch from [::]', async () => {
it('should be able to use PORT and fetch from [::1]', async () => {
const res = await fetch('/api/ipv6')
const json = await res.json()
expect(json).toEqual({ hello: 'world', input: 'test' })
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
PlainIssue {
severity: Error,
context: "[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js",
category: "resolve",
title: "Error resolving commonjs request",
description: "unable to resolve module \"fail\"",
detail: "It was not possible to find the requested file.\nParsed request as written in source code: module \"fail\"\nPath where resolving has started: [project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js\nType of request: commonjs request\nImport map: No import map entry\n",
documentation_link: "",
source: Some(
PlainIssueSource {
asset: PlainAsset {
ident: "[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js",
},
start: SourcePos {
line: 2,
column: 24,
},
end: SourcePos {
line: 2,
column: 39,
},
},
),
sub_issues: [],
processing_path: Some(
[
PlainIssueProcessingPathItem {
context: Some(
"[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/pages/index.js",
),
description: "Next.js pages directory",
},
],
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
PlainIssue {
severity: Error,
context: "[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js",
category: "resolve",
title: "Error resolving commonjs request",
description: "unable to resolve module \"fail\"",
detail: "It was not possible to find the requested file.\nParsed request as written in source code: module \"fail\"\nPath where resolving has started: [project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js\nType of request: commonjs request\nImport map: No import map entry\n",
documentation_link: "",
source: Some(
PlainIssueSource {
asset: PlainAsset {
ident: "[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/node_modules/package/cjs/index.js",
},
start: SourcePos {
line: 2,
column: 24,
},
end: SourcePos {
line: 2,
column: 39,
},
},
),
sub_issues: [],
processing_path: Some(
[
PlainIssueProcessingPathItem {
context: Some(
"[project]/packages/next-swc/crates/next-dev-tests/tests/integration/next/externals/cjs-in-esm/input/pages/index.js",
),
description: "Next.js pages directory",
},
PlainIssueProcessingPathItem {
context: Some(
"[next]/entry/server-renderer.tsx",
),
description: "server-side rendering /",
},
],
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function RootLayout({ children }: { children: any }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return <>Loading</>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Test from './test'

export default function Page() {
return (
<div>
<Test />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client'

import value from 'package'
import value2 from 'package/cjs'
import 'package/style.css'

import { useTestHarness } from '@turbo/pack-test-harness'

export default function Test() {
useTestHarness(runTests)

return (
<div id="test">
{value} {value2}
</div>
)
}

function runTests() {
it("serverComponentsExternalPackages should be external if they're cjs modules", () => {
expect(value).toBe(42)
expect(value2).toBe(42)

const el = document.getElementById('test')
expect(el).not.toBeNull()

expect(el!.textContent).toBe('42 42')
})

it('serverComponentsExternalPackages should not apply to CSS', () => {
const el = document.getElementById('test')
expect(el).not.toBeNull()

expect(getComputedStyle(el!).display).toBe('none')
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
experimental: {
appDir: true,
serverComponentsExternalPackages: ['package'],
},
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.