Turbopack: Implement next/font/local with BeforeResolvePlugin and show custom error message (#65870)

Depends on vercel/turbo#8165

This:
- Creates and uses a `BeforeResolvePlugin` to handle requests to
`next/font/local/target.css` instead of `ImportMapping` replacers
- Returns a `ResolveResultItem::Error` which includes a custom
`StyledString` describing the missing font file

Test Plan: `TURBOPACK=1 pnpm test-dev
test/e2e/app-dir/next-font/next-font.test.ts`
This commit is contained in:
Will Binns-Smith 2024-05-20 15:46:19 -07:00 committed by GitHub
parent 325a490616
commit 4ff11d0631
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 329 additions and 349 deletions

99
Cargo.lock generated
View file

@ -321,7 +321,7 @@ dependencies = [
[[package]]
name = "auto-hash-map"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"serde",
"smallvec",
@ -1144,7 +1144,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.10.1",
"phf 0.11.2",
"serde",
"smallvec",
]
@ -3097,7 +3097,7 @@ dependencies = [
[[package]]
name = "node-file-trace"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"serde",
@ -3567,9 +3567,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_macros 0.10.0",
"phf_shared 0.10.0",
"proc-macro-hack",
]
[[package]]
@ -3578,7 +3576,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros 0.11.2",
"phf_macros",
"phf_shared 0.11.2",
]
@ -3612,20 +3610,6 @@ dependencies = [
"rand",
]
[[package]]
name = "phf_macros"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
@ -3820,12 +3804,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.79"
@ -6965,7 +6943,7 @@ dependencies = [
[[package]]
name = "turbo-tasks"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-trait",
@ -6997,7 +6975,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-build"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"cargo-lock",
@ -7009,7 +6987,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-bytes"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"bytes",
@ -7023,7 +7001,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"dotenvs",
@ -7037,7 +7015,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fetch"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"lazy_static",
@ -7053,7 +7031,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-fs"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7085,7 +7063,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-hash"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"base16",
"hex",
@ -7097,7 +7075,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"proc-macro-error",
@ -7111,7 +7089,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-macros-shared"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"proc-macro2",
"quote",
@ -7121,7 +7099,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-malloc"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"mimalloc",
]
@ -7129,7 +7107,7 @@ dependencies = [
[[package]]
name = "turbo-tasks-memory"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"auto-hash-map",
@ -7155,7 +7133,7 @@ dependencies = [
[[package]]
name = "turbopack"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-recursion",
@ -7185,7 +7163,7 @@ dependencies = [
[[package]]
name = "turbopack-binding"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"auto-hash-map",
"mdxjs",
@ -7226,7 +7204,7 @@ dependencies = [
[[package]]
name = "turbopack-browser"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@ -7249,7 +7227,7 @@ dependencies = [
[[package]]
name = "turbopack-cli-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"clap",
@ -7266,7 +7244,7 @@ dependencies = [
[[package]]
name = "turbopack-core"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-recursion",
@ -7295,7 +7273,7 @@ dependencies = [
[[package]]
name = "turbopack-css"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@ -7322,7 +7300,7 @@ dependencies = [
[[package]]
name = "turbopack-dev-server"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-compression",
@ -7358,7 +7336,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-trait",
@ -7393,7 +7371,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-hmr-protocol"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"serde",
"serde_json",
@ -7404,7 +7382,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-plugins"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-trait",
@ -7428,7 +7406,7 @@ dependencies = [
[[package]]
name = "turbopack-ecmascript-runtime"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indoc",
@ -7444,7 +7422,7 @@ dependencies = [
[[package]]
name = "turbopack-env"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@ -7460,7 +7438,7 @@ dependencies = [
[[package]]
name = "turbopack-image"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"base64 0.21.4",
@ -7479,7 +7457,7 @@ dependencies = [
[[package]]
name = "turbopack-json"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"serde",
@ -7494,7 +7472,7 @@ dependencies = [
[[package]]
name = "turbopack-mdx"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"mdxjs",
@ -7509,7 +7487,7 @@ dependencies = [
[[package]]
name = "turbopack-node"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"async-stream",
@ -7543,7 +7521,7 @@ dependencies = [
[[package]]
name = "turbopack-nodejs"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@ -7563,7 +7541,7 @@ dependencies = [
[[package]]
name = "turbopack-resolve"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",
@ -7581,7 +7559,7 @@ dependencies = [
[[package]]
name = "turbopack-static"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"serde",
@ -7597,7 +7575,7 @@ dependencies = [
[[package]]
name = "turbopack-swc-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"swc_core",
"turbo-tasks",
@ -7608,7 +7586,7 @@ dependencies = [
[[package]]
name = "turbopack-trace-server"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"either",
@ -7616,6 +7594,7 @@ dependencies = [
"indexmap 1.9.3",
"itertools 0.10.5",
"postcard",
"rayon",
"rustc-demangle",
"serde",
"serde_json",
@ -7627,7 +7606,7 @@ dependencies = [
[[package]]
name = "turbopack-trace-utils"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"crossbeam-channel",
@ -7643,7 +7622,7 @@ dependencies = [
[[package]]
name = "turbopack-wasm"
version = "0.1.0"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240517.2#a56214480242960a36ad6940ce6c5e84664e8705"
source = "git+https://github.com/vercel/turbo.git?tag=turbopack-240520.2#6b184a3afc57a0038346cb875f8e2a377a505b43"
dependencies = [
"anyhow",
"indexmap 1.9.3",

View file

@ -37,11 +37,11 @@ swc_core = { version = "0.92.5", features = [
testing = { version = "0.35.24" }
# Turbo crates
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240517.2" }
turbopack-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240520.2" }
# [TODO]: need to refactor embed_directory! macro usages, as well as resolving turbo_tasks::function, macros..
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240517.2" }
turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240520.2" }
# [TODO]: need to refactor embed_directory! macro usage in next-core
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240517.2" }
turbo-tasks-fs = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-240520.2" }
# General Deps

View file

@ -41,6 +41,7 @@ use crate::{
next_build::get_postcss_package_mapping,
next_client::runtime_entry::{RuntimeEntries, RuntimeEntry},
next_config::NextConfig,
next_font::local::NextFontLocalResolvePlugin,
next_import_map::{
get_next_client_fallback_import_map, get_next_client_import_map,
get_next_client_resolved_map,
@ -157,7 +158,8 @@ pub async fn get_client_resolve_options_context(
resolved_map: Some(next_client_resolved_map),
browser: true,
module: true,
plugins: vec![
before_resolve_plugins: vec![Vc::upcast(NextFontLocalResolvePlugin::new(project_path))],
after_resolve_plugins: vec![
Vc::upcast(ModuleFeatureReportResolvePlugin::new(project_path)),
Vc::upcast(UnsupportedModulesResolvePlugin::new(project_path)),
Vc::upcast(NextSharedRuntimeResolvePlugin::new(project_path)),

View file

@ -22,6 +22,7 @@ use turbopack_binding::{
use crate::{
mode::NextMode,
next_config::NextConfig,
next_font::local::NextFontLocalResolvePlugin,
next_import_map::get_next_edge_import_map,
next_server::context::ServerContextType,
next_shared::resolve::{
@ -100,7 +101,20 @@ pub async fn get_edge_resolve_options_context(
let ty = ty.into_value();
let mut plugins = match ty {
let before_resolve_plugins = match ty {
ServerContextType::Pages { .. }
| ServerContextType::AppSSR { .. }
| ServerContextType::AppRSC { .. } => {
vec![Vc::upcast(NextFontLocalResolvePlugin::new(project_path))]
}
ServerContextType::PagesData { .. }
| ServerContextType::PagesApi { .. }
| ServerContextType::AppRoute { .. }
| ServerContextType::Middleware { .. }
| ServerContextType::Instrumentation => vec![],
};
let mut after_resolve_plugins = match ty {
ServerContextType::Pages { .. }
| ServerContextType::PagesApi { .. }
| ServerContextType::AppSSR { .. } => {
@ -124,7 +138,7 @@ pub async fn get_edge_resolve_options_context(
Vc::upcast(NextSharedRuntimeResolvePlugin::new(project_path)),
];
plugins.extend_from_slice(&base_plugins);
after_resolve_plugins.extend_from_slice(&base_plugins);
// https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102
let mut custom_conditions = vec![mode.await?.condition().to_string()];
@ -146,7 +160,8 @@ pub async fn get_edge_resolve_options_context(
import_map: Some(next_edge_import_map),
module: true,
browser: true,
plugins,
after_resolve_plugins,
before_resolve_plugins,
..Default::default()
};

View file

@ -0,0 +1,7 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FontError {
#[error("could not find font file")]
FontFileNotFound(String),
}

View file

@ -15,6 +15,7 @@ use crate::next_font::{
AutomaticFontFallback, DefaultFallbackFont, FontAdjustment, FontFallback, FontFallbacks,
DEFAULT_SANS_SERIF_FONT, DEFAULT_SERIF_FONT,
},
local::errors::FontError,
util::{get_scoped_font_family, FontFamilyType},
};
@ -74,7 +75,7 @@ async fn get_font_adjustment(
let main_descriptor = pick_font_for_fallback_generation(&options.fonts)?;
let font_file = &*context.join(main_descriptor.path.clone()).read().await?;
let font_file_rope = match font_file {
FileContent::NotFound => bail!("Expected font file content"),
FileContent::NotFound => bail!(FontError::FontFileNotFound(main_descriptor.path.clone())),
FileContent::Content(file) => file.content(),
};

View file

@ -2,6 +2,7 @@ use anyhow::{bail, Context, Result};
use indoc::formatdoc;
use serde::{Deserialize, Serialize};
use turbo_tasks::Vc;
use turbo_tasks_fs::glob::Glob;
use turbopack_binding::{
turbo::{
tasks::Value,
@ -9,10 +10,12 @@ use turbopack_binding::{
},
turbopack::core::{
asset::AssetContent,
issue::{Issue, IssueExt, IssueSeverity, IssueStage, StyledString},
reference_type::ReferenceType,
resolve::{
options::{ImportMapResult, ImportMapping, ImportMappingReplacement},
parse::Request,
ResolveResult,
plugin::{BeforeResolvePlugin, BeforeResolvePluginCondition},
RequestKey, ResolveResult, ResolveResultItem, ResolveResultOption,
},
virtual_source::VirtualSource,
},
@ -31,197 +34,18 @@ use super::{
use crate::{
next_app::metadata::split_extension,
next_font::{
local::options::FontWeight,
local::{errors::FontError, options::FontWeight},
util::{get_request_hash, get_request_id},
},
};
mod errors;
pub mod font_fallback;
pub mod options;
pub mod request;
pub mod stylesheet;
pub mod util;
#[turbo_tasks::value(shared)]
pub(crate) struct NextFontLocalReplacer {
project_path: Vc<FileSystemPath>,
}
#[turbo_tasks::value_impl]
impl NextFontLocalReplacer {
#[turbo_tasks::function]
pub fn new(project_path: Vc<FileSystemPath>) -> Vc<Self> {
Self::cell(NextFontLocalReplacer { project_path })
}
#[turbo_tasks::function]
async fn import_map_result(
&self,
context: Vc<FileSystemPath>,
query: String,
) -> Result<Vc<ImportMapResult>> {
let request_hash = get_request_hash(&query).await?;
let qstr = qstring::QString::from(query.as_str());
let query_vc = Vc::cell(query);
let options_vc = font_options_from_query_map(query_vc);
let font_fallbacks = get_font_fallbacks(context, options_vc);
let properties = &*get_font_css_properties(options_vc, font_fallbacks).await?;
let file_content = formatdoc!(
r#"
import cssModule from "@vercel/turbopack-next/internal/font/local/cssmodule.module.css?{}";
const fontData = {{
className: cssModule.className,
style: {{
fontFamily: "{}",
{}{}
}},
}};
if (cssModule.variable != null) {{
fontData.variable = cssModule.variable;
}}
export default fontData;
"#,
// Pass along whichever options we received to the css handler
qstr,
properties.font_family.await?,
properties
.weight
.await?
.as_ref()
.map(|w| format!("fontWeight: {},\n", w))
.unwrap_or_else(|| "".to_owned()),
properties
.style
.await?
.as_ref()
.map(|s| format!("fontStyle: \"{}\",\n", s))
.unwrap_or_else(|| "".to_owned()),
);
let js_asset = VirtualSource::new(
context.join(format!(
"{}.js",
get_request_id(options_vc.font_family(), request_hash).await?
)),
AssetContent::file(FileContent::Content(file_content.into()).into()),
);
Ok(ImportMapResult::Result(ResolveResult::source(Vc::upcast(js_asset)).into()).into())
}
}
#[turbo_tasks::value_impl]
impl ImportMappingReplacement for NextFontLocalReplacer {
#[turbo_tasks::function]
fn replace(&self, _capture: String) -> Vc<ImportMapping> {
ImportMapping::Ignore.into()
}
/// Intercepts requests for `next/font/local/target.css` and returns a
/// JavaScript object with a generated className from a referenced css
/// module.
#[turbo_tasks::function]
async fn result(
self: Vc<Self>,
context: Vc<FileSystemPath>,
request: Vc<Request>,
) -> Result<Vc<ImportMapResult>> {
let Request::Module {
module: _,
path: _,
query: query_vc,
fragment: _,
} = &*request.await?
else {
return Ok(ImportMapResult::NoEntry.into());
};
let this = &*self.await?;
if can_use_next_font(this.project_path, *query_vc).await? {
Ok(self.import_map_result(context, query_vc.await?.to_string()))
} else {
Ok(ImportMapResult::NoEntry.into())
}
}
}
#[turbo_tasks::value(shared)]
pub struct NextFontLocalCssModuleReplacer {}
#[turbo_tasks::value_impl]
impl NextFontLocalCssModuleReplacer {
#[turbo_tasks::function]
pub fn new() -> Vc<Self> {
Self::cell(NextFontLocalCssModuleReplacer {})
}
#[turbo_tasks::function]
async fn import_map_result(
context: Vc<FileSystemPath>,
query: String,
) -> Result<Vc<ImportMapResult>> {
let request_hash = get_request_hash(&query).await?;
let query_vc = Vc::cell(query);
let options = font_options_from_query_map(query_vc);
let css_virtual_path = context.join(format!(
"/{}.module.css",
get_request_id(options.font_family(), request_hash).await?
));
let fallback = get_font_fallbacks(context, options);
let stylesheet = build_stylesheet(
font_options_from_query_map(query_vc),
fallback,
get_font_css_properties(options, fallback),
)
.await?;
let css_asset = VirtualSource::new(
css_virtual_path,
AssetContent::file(FileContent::Content(stylesheet.into()).into()),
);
Ok(ImportMapResult::Result(ResolveResult::source(Vc::upcast(css_asset)).into()).into())
}
}
#[turbo_tasks::value_impl]
impl ImportMappingReplacement for NextFontLocalCssModuleReplacer {
#[turbo_tasks::function]
fn replace(&self, _capture: String) -> Vc<ImportMapping> {
ImportMapping::Ignore.into()
}
/// Intercepts requests for the css module made by the virtual JavaScript
/// asset generated by the above replacer. Returns a VirtualSource of a CSS
/// Module containing font face definitions and exporting class names for
/// the font and an optional css variable.
#[turbo_tasks::function]
async fn result(
self: Vc<Self>,
context: Vc<FileSystemPath>,
request: Vc<Request>,
) -> Result<Vc<ImportMapResult>> {
let request = &*request.await?;
let Request::Module {
module: _,
path: _,
query: query_vc,
fragment: _,
} = request
else {
return Ok(ImportMapResult::NoEntry.into());
};
Ok(Self::import_map_result(
context,
query_vc.await?.to_string(),
))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct NextFontLocalFontFileOptions {
pub path: String,
@ -229,36 +53,42 @@ struct NextFontLocalFontFileOptions {
pub has_size_adjust: bool,
}
#[turbo_tasks::value(shared)]
pub struct NextFontLocalFontFileReplacer {
project_path: Vc<FileSystemPath>,
#[turbo_tasks::value]
pub(crate) struct NextFontLocalResolvePlugin {
root: Vc<FileSystemPath>,
}
#[turbo_tasks::value_impl]
impl NextFontLocalFontFileReplacer {
impl NextFontLocalResolvePlugin {
#[turbo_tasks::function]
pub fn new(project_path: Vc<FileSystemPath>) -> Vc<Self> {
Self::cell(NextFontLocalFontFileReplacer { project_path })
pub fn new(root: Vc<FileSystemPath>) -> Vc<Self> {
NextFontLocalResolvePlugin { root }.cell()
}
}
#[turbo_tasks::value_impl]
impl ImportMappingReplacement for NextFontLocalFontFileReplacer {
impl BeforeResolvePlugin for NextFontLocalResolvePlugin {
#[turbo_tasks::function]
fn replace(&self, _capture: String) -> Vc<ImportMapping> {
ImportMapping::Ignore.into()
async fn before_resolve_condition(&self) -> Vc<BeforeResolvePluginCondition> {
BeforeResolvePluginCondition::new(Glob::new(
"{next,@vercel/turbopack-next/internal}/font/local/*".to_string(),
))
}
/// Intercepts requests for the font made by the CSS
/// asset generated by the above replacer.
/// Mainly here to rename the asset for the manifest.
#[turbo_tasks::function]
async fn result(
&self,
context: Vc<FileSystemPath>,
request: Vc<Request>,
) -> Result<Vc<ImportMapResult>> {
let request = &*request.await?;
async fn before_resolve(
self: Vc<Self>,
lookup_path: Vc<FileSystemPath>,
_reference_type: Value<ReferenceType>,
request_vc: Vc<Request>,
) -> Result<Vc<ResolveResultOption>> {
let this = &*self.await?;
let request = &*request_vc.await?;
let Some(request_key) = request.request() else {
return Ok(ResolveResultOption::none());
};
let Request::Module {
module: _,
path: _,
@ -266,34 +96,152 @@ impl ImportMappingReplacement for NextFontLocalFontFileReplacer {
fragment: _,
} = request
else {
return Ok(ImportMapResult::NoEntry.into());
return Ok(ResolveResultOption::none());
};
let NextFontLocalFontFileOptions {
path,
preload,
has_size_adjust: size_adjust,
} = font_file_options_from_query_map(*query_vc).await?;
match request_key.as_str() {
"next/font/local/target.css" => {
if !can_use_next_font(this.root, *query_vc).await? {
return Ok(ResolveResultOption::none());
}
let (filename, ext) = split_extension(&path);
let ext = ext.with_context(|| format!("font {} needs an extension", &path))?;
let query = query_vc.await?.to_string();
let request_hash = get_request_hash(&query).await?;
let qstr = qstring::QString::from(query.as_str());
let options_vc = font_options_from_query_map(*query_vc);
let font_fallbacks = get_font_fallbacks(lookup_path, options_vc);
let properties = get_font_css_properties(options_vc, font_fallbacks).await;
// remove dashes and dots as they might be used for the markers below.
let mut name = filename.replace(['-', '.'], "_");
if size_adjust {
name.push_str("-s")
if let Err(e) = &properties {
for source_error in e.chain() {
if let Some(FontError::FontFileNotFound(font_path)) =
source_error.downcast_ref::<FontError>()
{
FontResolvingIssue {
origin_path: lookup_path,
font_path: Vc::cell(font_path.to_string()),
}
.cell()
.emit();
return Ok(ResolveResultOption::some(
ResolveResult::primary_with_key(
RequestKey::new(font_path.to_string()),
ResolveResultItem::Error(Vc::cell(format!(
"Font file not found: Can't resolve {}'",
font_path
))),
)
.into(),
));
}
}
}
let properties = properties?;
let file_content = formatdoc!(
r#"
import cssModule from "@vercel/turbopack-next/internal/font/local/cssmodule.module.css?{}";
const fontData = {{
className: cssModule.className,
style: {{
fontFamily: "{}",
{}{}
}},
}};
if (cssModule.variable != null) {{
fontData.variable = cssModule.variable;
}}
export default fontData;
"#,
// Pass along whichever options we received to the css handler
qstr,
properties.font_family.await?,
properties
.weight
.await?
.as_ref()
.map(|w| format!("fontWeight: {},\n", w))
.unwrap_or_else(|| "".to_owned()),
properties
.style
.await?
.as_ref()
.map(|s| format!("fontStyle: \"{}\",\n", s))
.unwrap_or_else(|| "".to_owned()),
);
let js_asset = VirtualSource::new(
lookup_path.join(format!(
"{}.js",
get_request_id(options_vc.font_family(), request_hash).await?
)),
AssetContent::file(FileContent::Content(file_content.into()).into()),
);
Ok(ResolveResultOption::some(
ResolveResult::source(Vc::upcast(js_asset)).into(),
))
}
"@vercel/turbopack-next/internal/font/local/cssmodule.module.css" => {
let query = query_vc.await?.to_string();
let request_hash = get_request_hash(&query).await?;
let options = font_options_from_query_map(*query_vc);
let css_virtual_path = lookup_path.join(format!(
"/{}.module.css",
get_request_id(options.font_family(), request_hash).await?
));
let fallback = get_font_fallbacks(lookup_path, options);
let stylesheet = build_stylesheet(
font_options_from_query_map(*query_vc),
fallback,
get_font_css_properties(options, fallback),
)
.await?;
let css_asset = VirtualSource::new(
css_virtual_path,
AssetContent::file(FileContent::Content(stylesheet.into()).into()),
);
Ok(ResolveResultOption::some(
ResolveResult::source(Vc::upcast(css_asset)).into(),
))
}
"@vercel/turbopack-next/internal/font/local/font" => {
let NextFontLocalFontFileOptions {
path,
preload,
has_size_adjust: size_adjust,
} = font_file_options_from_query_map(*query_vc).await?;
let (filename, ext) = split_extension(&path);
let ext = ext.with_context(|| format!("font {} needs an extension", &path))?;
// remove dashes and dots as they might be used for the markers below.
let mut name = filename.replace(['-', '.'], "_");
if size_adjust {
name.push_str("-s")
}
if preload {
name.push_str(".p")
}
let font_virtual_path = lookup_path.join(format!("/{}.{}", name, ext));
let font_file = lookup_path.join(path.clone()).read();
let font_source =
VirtualSource::new(font_virtual_path, AssetContent::file(font_file));
Ok(ResolveResultOption::some(
ResolveResult::source(Vc::upcast(font_source)).into(),
))
}
_ => Ok(ResolveResultOption::none()),
}
if preload {
name.push_str(".p")
}
let font_virtual_path = context.join(format!("/{}.{}", name, ext));
let font_file = context.join(path.clone()).read();
let font_source = VirtualSource::new(font_virtual_path, AssetContent::file(font_file));
Ok(ImportMapResult::Result(ResolveResult::source(Vc::upcast(font_source)).into()).into())
}
}
@ -359,3 +307,38 @@ async fn font_file_options_from_query_map(
parse_json_with_source_context(&json)
}
#[turbo_tasks::value(shared)]
struct FontResolvingIssue {
font_path: Vc<String>,
origin_path: Vc<FileSystemPath>,
}
#[turbo_tasks::value_impl]
impl Issue for FontResolvingIssue {
#[turbo_tasks::function]
fn severity(&self) -> Vc<IssueSeverity> {
IssueSeverity::Error.cell()
}
#[turbo_tasks::function]
async fn file_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
Ok(self.await?.origin_path)
}
#[turbo_tasks::function]
fn stage(self: Vc<Self>) -> Vc<IssueStage> {
IssueStage::Resolve.cell()
}
#[turbo_tasks::function]
async fn title(self: Vc<Self>) -> Result<Vc<StyledString>> {
let this = self.await?;
Ok(StyledString::Line(vec![
StyledString::Text("Font file not found: Can't resolve '".to_string()),
StyledString::Code(this.font_path.await?.to_string()),
StyledString::Text("'".to_string()),
])
.cell())
}
}

View file

@ -27,13 +27,8 @@ use crate::{
next_client::context::ClientContextType,
next_config::NextConfig,
next_edge::unsupported::NextEdgeUnsupportedModuleReplacer,
next_font::{
google::{
NextFontGoogleCssModuleReplacer, NextFontGoogleFontFileReplacer, NextFontGoogleReplacer,
},
local::{
NextFontLocalCssModuleReplacer, NextFontLocalFontFileReplacer, NextFontLocalReplacer,
},
next_font::google::{
NextFontGoogleCssModuleReplacer, NextFontGoogleFontFileReplacer, NextFontGoogleReplacer,
},
next_server::context::ServerContextType,
util::NextRuntime,
@ -797,6 +792,11 @@ async fn insert_next_shared_aliases(
package_root,
);
// NOTE: `@next/font/local` has moved to a BeforeResolve Plugin, so it does not
// have ImportMapping replacers here.
//
// TODO: Add BeforeResolve plugins for `@next/font/google`
import_map.insert_alias(
// Request path from js via next-font swc transform
AliasPattern::exact("next/font/google/target.css"),
@ -826,28 +826,6 @@ async fn insert_next_shared_aliases(
.into(),
);
import_map.insert_alias(
// Request path from js via next-font swc transform
AliasPattern::exact("next/font/local/target.css"),
ImportMapping::Dynamic(Vc::upcast(NextFontLocalReplacer::new(project_path))).into(),
);
import_map.insert_alias(
// Request path from js via next-font swc transform
AliasPattern::exact("@next/font/local/target.css"),
ImportMapping::Dynamic(Vc::upcast(NextFontLocalReplacer::new(project_path))).into(),
);
import_map.insert_alias(
AliasPattern::exact("@vercel/turbopack-next/internal/font/local/cssmodule.module.css"),
ImportMapping::Dynamic(Vc::upcast(NextFontLocalCssModuleReplacer::new())).into(),
);
import_map.insert_alias(
AliasPattern::exact("@vercel/turbopack-next/internal/font/local/font"),
ImportMapping::Dynamic(Vc::upcast(NextFontLocalFontFileReplacer::new(project_path))).into(),
);
import_map.insert_singleton_alias("@swc/helpers", get_next_package(project_path));
import_map.insert_singleton_alias("styled-jsx", get_next_package(project_path));
import_map.insert_singleton_alias("next", project_path);

View file

@ -45,6 +45,7 @@ use crate::{
next_build::get_postcss_package_mapping,
next_client::RuntimeEntries,
next_config::NextConfig,
next_font::local::NextFontLocalResolvePlugin,
next_import_map::get_next_server_import_map,
next_server::resolve::ExternalPredicate,
next_shared::{
@ -199,7 +200,20 @@ pub async fn get_server_resolve_options_context(
let next_node_shared_runtime_plugin =
NextNodeSharedRuntimeResolvePlugin::new(project_path, Value::new(ty));
let mut plugins = match ty {
let before_resolve_plugins = match ty {
ServerContextType::Pages { .. }
| ServerContextType::AppSSR { .. }
| ServerContextType::AppRSC { .. } => {
vec![Vc::upcast(NextFontLocalResolvePlugin::new(project_path))]
}
ServerContextType::PagesData { .. }
| ServerContextType::PagesApi { .. }
| ServerContextType::AppRoute { .. }
| ServerContextType::Middleware { .. }
| ServerContextType::Instrumentation => vec![],
};
let mut after_resolve_plugins = match ty {
ServerContextType::Pages { .. }
| ServerContextType::PagesApi { .. }
| ServerContextType::PagesData { .. } => {
@ -255,8 +269,8 @@ pub async fn get_server_resolve_options_context(
| ServerContextType::AppRoute { .. }
| ServerContextType::Middleware { .. }
| ServerContextType::Instrumentation => {
plugins.push(Vc::upcast(invalid_client_only_resolve_plugin));
plugins.push(Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin));
after_resolve_plugins.push(Vc::upcast(invalid_client_only_resolve_plugin));
after_resolve_plugins.push(Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin));
}
ServerContextType::AppSSR { .. } => {
//[TODO] Build error in this context makes rsc-build-error.ts fail which expects runtime error code
@ -271,7 +285,8 @@ pub async fn get_server_resolve_options_context(
module: true,
custom_conditions,
import_map: Some(next_server_import_map),
plugins,
before_resolve_plugins,
after_resolve_plugins,
..Default::default()
};

View file

@ -11,7 +11,7 @@ use turbopack_binding::{
node::{node_cjs_resolve_options, node_esm_resolve_options},
package_json,
parse::Request,
plugin::{ResolvePlugin, ResolvePluginCondition},
plugin::{AfterResolvePlugin, AfterResolvePluginCondition},
resolve, ExternalType, FindContextFileResult, ResolveResult, ResolveResultItem,
ResolveResultOption,
},
@ -61,14 +61,14 @@ impl ExternalCjsModulesResolvePlugin {
}
#[turbo_tasks::function]
fn condition(root: Vc<FileSystemPath>) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(root, Glob::new("**/node_modules/**".to_string()))
fn condition(root: Vc<FileSystemPath>) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(root, Glob::new("**/node_modules/**".to_string()))
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for ExternalCjsModulesResolvePlugin {
impl AfterResolvePlugin for ExternalCjsModulesResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
condition(self.root)
}

View file

@ -17,7 +17,7 @@ use turbopack_binding::{
resolve::{
parse::Request,
pattern::Pattern,
plugin::{ResolvePlugin, ResolvePluginCondition},
plugin::{AfterResolvePlugin, AfterResolvePluginCondition},
ExternalType, ResolveResult, ResolveResultItem, ResolveResultOption,
},
},
@ -60,10 +60,10 @@ impl UnsupportedModulesResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for UnsupportedModulesResolvePlugin {
impl AfterResolvePlugin for UnsupportedModulesResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
}
#[turbo_tasks::function]
@ -189,10 +189,10 @@ impl InvalidImportResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for InvalidImportResolvePlugin {
impl AfterResolvePlugin for InvalidImportResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
}
#[turbo_tasks::function]
@ -287,10 +287,10 @@ impl NextExternalResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for NextExternalResolvePlugin {
impl AfterResolvePlugin for NextExternalResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(
self.root.root(),
Glob::new("**/next/dist/**/*.{external,runtime.dev,runtime.prod}.js".to_string()),
)
@ -337,10 +337,10 @@ impl NextNodeSharedRuntimeResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for NextNodeSharedRuntimeResolvePlugin {
impl AfterResolvePlugin for NextNodeSharedRuntimeResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(
self.root.root(),
Glob::new("**/next/dist/**/*.shared-runtime.js".to_string()),
)
@ -402,10 +402,10 @@ impl ModuleFeatureReportResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for ModuleFeatureReportResolvePlugin {
impl AfterResolvePlugin for ModuleFeatureReportResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(self.root.root(), Glob::new("**".to_string()))
}
#[turbo_tasks::function]
@ -455,10 +455,10 @@ impl NextSharedRuntimeResolvePlugin {
}
#[turbo_tasks::value_impl]
impl ResolvePlugin for NextSharedRuntimeResolvePlugin {
impl AfterResolvePlugin for NextSharedRuntimeResolvePlugin {
#[turbo_tasks::function]
fn after_resolve_condition(&self) -> Vc<ResolvePluginCondition> {
ResolvePluginCondition::new(
fn after_resolve_condition(&self) -> Vc<AfterResolvePluginCondition> {
AfterResolvePluginCondition::new(
self.root.root(),
Glob::new("**/next/dist/esm/**/*.shared-runtime.js".to_string()),
)