next/font/local support for Turbopack (#47369)
Fixes WEB-249 Depends on vercel/turbo#4288 This implements support for `next/font/local` for Turbopack. It: * Removes the compile-time feature restricting access to `next/font/local` * Implements `NextFontLocalReplacerVc` and `NextFontLocalCssModuleReplacerVc`, similar to their `next/font/google` equivalents, and adds them as ImportMappings to handle requests for `next/font/local/target.css` and `@vercel/turbopack-next/internal/font/local/cssmodule.module.css` (these requests are created by the JavaScript returned by the requests for the `target.css` module) * Implements fallback support for Times New Roman and Arial via `adjust_font_fallback` as the webpack implementation does. Font metric override adjustment will be added in a future PR. Test Plan: New passes 14 next-font integration tests in the Next.js integration test suite, up from 8.
This commit is contained in:
parent
4c2ad81ea7
commit
e3875d7421
16 changed files with 1030 additions and 84 deletions
|
@ -7,7 +7,6 @@ edition = "2021"
|
|||
autobenches = false
|
||||
|
||||
[features]
|
||||
next-font-local = ["next-core/next-font-local"]
|
||||
native-tls = ["next-core/native-tls"]
|
||||
rustls-tls = ["next-core/rustls-tls"]
|
||||
custom_allocator = ["turbo-malloc/custom_allocator"]
|
||||
|
@ -24,4 +23,4 @@ turbo-tasks-build = { workspace = true }
|
|||
vergen = { version = "7.3.2", default-features = false, features = [
|
||||
"cargo",
|
||||
"build",
|
||||
] }
|
||||
] }
|
||||
|
|
|
@ -43,7 +43,6 @@ swc_core = { workspace = true, features = ["ecma_ast", "common"] }
|
|||
turbo-tasks-build = { workspace = true }
|
||||
|
||||
[features]
|
||||
next-font-local = []
|
||||
native-tls = ["turbo-tasks-fetch/native-tls"]
|
||||
rustls-tls = ["turbo-tasks-fetch/rustls-tls"]
|
||||
# Internal only. Enabled when building for the Next.js integration test suite.
|
||||
|
|
|
@ -34,6 +34,7 @@ pub(crate) struct AutomaticFontFallback {
|
|||
pub adjustment: Option<FontAdjustment>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[turbo_tasks::value(shared)]
|
||||
pub(crate) enum FontFallback {
|
||||
Automatic(AutomaticFontFallbackVc),
|
||||
|
@ -45,7 +46,7 @@ pub(crate) enum FontFallback {
|
|||
}
|
||||
|
||||
#[turbo_tasks::value(transparent)]
|
||||
pub(crate) struct FontFallbacks(Vec<FontFallback>);
|
||||
pub(crate) struct FontFallbacks(Vec<FontFallbackVc>);
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
|
||||
pub(crate) struct FontAdjustment {
|
||||
|
|
|
@ -382,7 +382,7 @@ async fn get_mock_stylesheet(
|
|||
) -> Result<Option<String>> {
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use turbo_tasks::{CompletionVc, Value};
|
||||
use turbo_tasks::CompletionVc;
|
||||
use turbo_tasks_env::{CommandLineProcessEnvVc, ProcessEnv};
|
||||
use turbo_tasks_fs::{
|
||||
json::parse_json_with_source_context, DiskFileSystemVc, File, FileSystem,
|
||||
|
@ -418,7 +418,7 @@ async fn get_mock_stylesheet(
|
|||
let ExecutionContext {
|
||||
env,
|
||||
project_path,
|
||||
intermediate_output_path,
|
||||
chunking_context,
|
||||
} = *execution_context.await?;
|
||||
let context = node_evaluate_asset_context(project_path, None, None);
|
||||
let loader_path = mock_fs.root().join("loader.js");
|
||||
|
@ -449,7 +449,7 @@ async fn get_mock_stylesheet(
|
|||
env,
|
||||
AssetIdentVc::from_path(loader_path),
|
||||
context,
|
||||
intermediate_output_path,
|
||||
chunking_context,
|
||||
None,
|
||||
vec![],
|
||||
CompletionVc::immutable(),
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use indoc::formatdoc;
|
||||
use turbo_tasks::primitives::{OptionStringVc, StringVc};
|
||||
|
||||
use super::FontCssPropertiesVc;
|
||||
use crate::next_font::{font_fallback::FontFallbackVc, stylesheet::build_fallback_definition};
|
||||
use crate::next_font::{
|
||||
font_fallback::{FontFallbackVc, FontFallbacksVc},
|
||||
stylesheet::{build_fallback_definition, build_font_class_rules},
|
||||
};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn build_stylesheet(
|
||||
|
@ -15,51 +17,9 @@ pub(super) async fn build_stylesheet(
|
|||
let mut stylesheet = base_stylesheet
|
||||
.as_ref()
|
||||
.map_or_else(|| "".to_owned(), |s| s.to_owned());
|
||||
if let Some(definition) = &*build_fallback_definition(font_fallback).await? {
|
||||
stylesheet.push_str(definition);
|
||||
}
|
||||
|
||||
stylesheet
|
||||
.push_str(&build_fallback_definition(FontFallbacksVc::cell(vec![font_fallback])).await?);
|
||||
stylesheet.push_str(&build_font_class_rules(font_css_properties).await?);
|
||||
Ok(StringVc::cell(stylesheet))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn build_font_class_rules(properties: FontCssPropertiesVc) -> Result<StringVc> {
|
||||
let properties = &*properties.await?;
|
||||
let font_family = &*properties.font_family.await?;
|
||||
|
||||
let mut result = formatdoc!(
|
||||
r#"
|
||||
.className {{
|
||||
font-family: {};
|
||||
{}{}
|
||||
}}
|
||||
"#,
|
||||
font_family,
|
||||
properties
|
||||
.weight
|
||||
.await?
|
||||
.as_ref()
|
||||
.map(|w| format!("font-weight: {};\n", w))
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
properties
|
||||
.style
|
||||
.await?
|
||||
.as_ref()
|
||||
.map(|s| format!("font-style: {};\n", s))
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
);
|
||||
|
||||
if let Some(variable) = &*properties.variable.await? {
|
||||
result.push_str(&formatdoc!(
|
||||
r#"
|
||||
.variable {{
|
||||
{}: {};
|
||||
}}
|
||||
"#,
|
||||
variable,
|
||||
font_family,
|
||||
))
|
||||
}
|
||||
|
||||
Ok(StringVc::cell(result))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
use anyhow::Result;
|
||||
use turbo_tasks::primitives::{StringVc, StringsVc, U32Vc};
|
||||
|
||||
use super::{options::NextFontLocalOptionsVc, request::AdjustFontFallback};
|
||||
use crate::next_font::{
|
||||
font_fallback::{AutomaticFontFallback, FontFallback, FontFallbacksVc},
|
||||
util::{get_scoped_font_family, FontFamilyType},
|
||||
};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn get_font_fallbacks(
|
||||
options_vc: NextFontLocalOptionsVc,
|
||||
request_hash: U32Vc,
|
||||
) -> Result<FontFallbacksVc> {
|
||||
let options = &*options_vc.await?;
|
||||
let mut font_fallbacks = vec![];
|
||||
let scoped_font_family = get_scoped_font_family(
|
||||
FontFamilyType::Fallback.cell(),
|
||||
options_vc.font_family(),
|
||||
request_hash,
|
||||
);
|
||||
|
||||
match options.adjust_font_fallback {
|
||||
AdjustFontFallback::Arial => font_fallbacks.push(
|
||||
FontFallback::Automatic(
|
||||
AutomaticFontFallback {
|
||||
scoped_font_family,
|
||||
local_font_family: StringVc::cell("Arial".to_owned()),
|
||||
adjustment: None,
|
||||
}
|
||||
.cell(),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
AdjustFontFallback::TimesNewRoman => font_fallbacks.push(
|
||||
FontFallback::Automatic(
|
||||
AutomaticFontFallback {
|
||||
scoped_font_family,
|
||||
local_font_family: StringVc::cell("Times New Roman".to_owned()),
|
||||
adjustment: None,
|
||||
}
|
||||
.cell(),
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
AdjustFontFallback::None => (),
|
||||
};
|
||||
|
||||
if let Some(fallback) = &options.fallback {
|
||||
font_fallbacks.push(FontFallback::Manual(StringsVc::cell(fallback.clone())).into());
|
||||
}
|
||||
|
||||
Ok(FontFallbacksVc::cell(font_fallbacks))
|
||||
}
|
233
packages/next-swc/crates/next-core/src/next_font/local/mod.rs
Normal file
233
packages/next-swc/crates/next-core/src/next_font/local/mod.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use indoc::formatdoc;
|
||||
use turbo_tasks::{
|
||||
primitives::{OptionStringVc, U32Vc},
|
||||
Value,
|
||||
};
|
||||
use turbo_tasks_fs::{json::parse_json_with_source_context, FileContent, FileSystemPathVc};
|
||||
use turbopack_core::{
|
||||
resolve::{
|
||||
options::{
|
||||
ImportMapResult, ImportMapResultVc, ImportMapping, ImportMappingReplacement,
|
||||
ImportMappingReplacementVc, ImportMappingVc,
|
||||
},
|
||||
parse::{Request, RequestVc},
|
||||
pattern::QueryMapVc,
|
||||
ResolveResult,
|
||||
},
|
||||
virtual_asset::VirtualAssetVc,
|
||||
};
|
||||
|
||||
use self::{
|
||||
font_fallback::get_font_fallbacks,
|
||||
options::{options_from_request, FontDescriptors, NextFontLocalOptionsVc},
|
||||
stylesheet::build_stylesheet,
|
||||
util::build_font_family_string,
|
||||
};
|
||||
use super::{
|
||||
font_fallback::FontFallbacksVc,
|
||||
util::{FontCssProperties, FontCssPropertiesVc},
|
||||
};
|
||||
use crate::next_font::{
|
||||
local::options::FontWeight,
|
||||
util::{get_request_hash, get_request_id},
|
||||
};
|
||||
|
||||
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: FileSystemPathVc,
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl NextFontLocalReplacerVc {
|
||||
#[turbo_tasks::function]
|
||||
pub fn new(project_path: FileSystemPathVc) -> Self {
|
||||
Self::cell(NextFontLocalReplacer { project_path })
|
||||
}
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl ImportMappingReplacement for NextFontLocalReplacer {
|
||||
#[turbo_tasks::function]
|
||||
fn replace(&self, _capture: &str) -> ImportMappingVc {
|
||||
ImportMapping::Ignore.into()
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn result(
|
||||
&self,
|
||||
context: FileSystemPathVc,
|
||||
request: RequestVc,
|
||||
) -> Result<ImportMapResultVc> {
|
||||
let Request::Module {
|
||||
module: _,
|
||||
path: _,
|
||||
query: query_vc
|
||||
} = &*request.await? else {
|
||||
return Ok(ImportMapResult::NoEntry.into());
|
||||
};
|
||||
|
||||
let request_hash = get_request_hash(*query_vc);
|
||||
let options_vc = font_options_from_query_map(*query_vc);
|
||||
let font_fallbacks = get_font_fallbacks(options_vc, request_hash);
|
||||
let properties =
|
||||
&*get_font_css_properties(options_vc, font_fallbacks, request_hash).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
|
||||
qstring::QString::new(query_vc.await?.as_ref().unwrap().iter().collect()),
|
||||
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 = VirtualAssetVc::new(
|
||||
context.join(&format!(
|
||||
"{}.js",
|
||||
get_request_id(options_vc.font_family(), request_hash).await?
|
||||
)),
|
||||
FileContent::Content(file_content.into()).into(),
|
||||
);
|
||||
|
||||
Ok(ImportMapResult::Result(ResolveResult::asset(js_asset.into()).into()).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[turbo_tasks::value(shared)]
|
||||
pub struct NextFontLocalCssModuleReplacer {
|
||||
project_path: FileSystemPathVc,
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl NextFontLocalCssModuleReplacerVc {
|
||||
#[turbo_tasks::function]
|
||||
pub fn new(project_path: FileSystemPathVc) -> Self {
|
||||
Self::cell(NextFontLocalCssModuleReplacer { project_path })
|
||||
}
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl ImportMappingReplacement for NextFontLocalCssModuleReplacer {
|
||||
#[turbo_tasks::function]
|
||||
fn replace(&self, _capture: &str) -> ImportMappingVc {
|
||||
ImportMapping::Ignore.into()
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn result(
|
||||
&self,
|
||||
context: FileSystemPathVc,
|
||||
request: RequestVc,
|
||||
) -> Result<ImportMapResultVc> {
|
||||
let request = &*request.await?;
|
||||
let Request::Module {
|
||||
module: _,
|
||||
path: _,
|
||||
query: query_vc,
|
||||
} = request else {
|
||||
return Ok(ImportMapResult::NoEntry.into());
|
||||
};
|
||||
|
||||
let options = font_options_from_query_map(*query_vc);
|
||||
let request_hash = get_request_hash(*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(options, request_hash);
|
||||
|
||||
let stylesheet = build_stylesheet(
|
||||
font_options_from_query_map(*query_vc),
|
||||
fallback,
|
||||
get_font_css_properties(options, fallback, request_hash),
|
||||
get_request_hash(*query_vc),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let css_asset = VirtualAssetVc::new(
|
||||
css_virtual_path,
|
||||
FileContent::Content(stylesheet.into()).into(),
|
||||
);
|
||||
|
||||
Ok(ImportMapResult::Result(ResolveResult::asset(css_asset.into()).into()).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn get_font_css_properties(
|
||||
options_vc: NextFontLocalOptionsVc,
|
||||
font_fallbacks: FontFallbacksVc,
|
||||
request_hash: U32Vc,
|
||||
) -> Result<FontCssPropertiesVc> {
|
||||
let options = &*options_vc.await?;
|
||||
|
||||
Ok(FontCssPropertiesVc::cell(FontCssProperties {
|
||||
font_family: build_font_family_string(options_vc, font_fallbacks, request_hash),
|
||||
weight: OptionStringVc::cell(match &options.fonts {
|
||||
FontDescriptors::Many(_) => None,
|
||||
FontDescriptors::One(descriptor) => descriptor
|
||||
.weight
|
||||
.as_ref()
|
||||
// Don't include values for variable fonts. These are included in font-face
|
||||
// definitions only.
|
||||
.filter(|w| !matches!(w, FontWeight::Variable(_, _)))
|
||||
.map(|w| w.to_string()),
|
||||
}),
|
||||
style: OptionStringVc::cell(match &options.fonts {
|
||||
FontDescriptors::Many(_) => None,
|
||||
FontDescriptors::One(descriptor) => descriptor.style.clone(),
|
||||
}),
|
||||
variable: OptionStringVc::cell(options.variable.clone()),
|
||||
}))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn font_options_from_query_map(query: QueryMapVc) -> Result<NextFontLocalOptionsVc> {
|
||||
let query_map = &*query.await?;
|
||||
// These are invariants from the next/font swc transform. Regular errors instead
|
||||
// of Issues should be okay.
|
||||
let query_map = query_map
|
||||
.as_ref()
|
||||
.context("next/font/local queries must exist")?;
|
||||
|
||||
if query_map.len() != 1 {
|
||||
bail!("next/font/local queries must only have one entry");
|
||||
}
|
||||
|
||||
let Some((json, _)) = query_map.iter().next() else {
|
||||
bail!("Expected one entry");
|
||||
};
|
||||
|
||||
options_from_request(&parse_json_with_source_context(json)?)
|
||||
.map(|o| NextFontLocalOptionsVc::new(Value::new(o)))
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use turbo_tasks::{primitives::StringVc, trace::TraceRawVcs, Value};
|
||||
|
||||
use super::request::{
|
||||
AdjustFontFallback, NextFontLocalRequest, NextFontLocalRequestArguments, SrcDescriptor,
|
||||
SrcRequest,
|
||||
};
|
||||
|
||||
#[turbo_tasks::value(serialization = "auto_for_input")]
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct NextFontLocalOptions {
|
||||
pub fonts: FontDescriptors,
|
||||
pub default_weight: Option<FontWeight>,
|
||||
pub default_style: Option<String>,
|
||||
pub display: String,
|
||||
pub preload: bool,
|
||||
pub fallback: Option<Vec<String>>,
|
||||
pub adjust_font_fallback: AdjustFontFallback,
|
||||
/// An optional name for a css custom property (css variable) that applies
|
||||
/// the font family when used.
|
||||
pub variable: Option<String>,
|
||||
/// The name of the variable assigned to the results of calling the
|
||||
/// `localFont` function. This is used as the font family's base name.
|
||||
pub variable_name: String,
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl NextFontLocalOptionsVc {
|
||||
#[turbo_tasks::function]
|
||||
pub fn new(options: Value<NextFontLocalOptions>) -> NextFontLocalOptionsVc {
|
||||
Self::cell(options.into_value())
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub async fn font_family(self) -> Result<StringVc> {
|
||||
Ok(StringVc::cell((*self.await?.variable_name).to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TraceRawVcs,
|
||||
)]
|
||||
pub(super) struct FontDescriptor {
|
||||
pub weight: Option<FontWeight>,
|
||||
pub style: Option<String>,
|
||||
pub path: String,
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
impl FontDescriptor {
|
||||
fn from_src_request(src_descriptor: &SrcDescriptor) -> Result<Self> {
|
||||
let ext = src_descriptor
|
||||
.path
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.context("Extension required")?
|
||||
.to_owned();
|
||||
|
||||
Ok(Self {
|
||||
path: src_descriptor.path.to_owned(),
|
||||
weight: src_descriptor
|
||||
.weight
|
||||
.as_ref()
|
||||
.and_then(|w| FontWeight::from_str(w).ok()),
|
||||
style: src_descriptor.style.clone(),
|
||||
ext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, TraceRawVcs,
|
||||
)]
|
||||
pub(super) enum FontDescriptors {
|
||||
One(FontDescriptor),
|
||||
Many(Vec<FontDescriptor>),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Hash, TraceRawVcs,
|
||||
)]
|
||||
pub(super) enum FontWeight {
|
||||
Variable(String, String),
|
||||
Fixed(String),
|
||||
}
|
||||
|
||||
pub struct ParseFontWeightErr;
|
||||
impl FromStr for FontWeight {
|
||||
type Err = ParseFontWeightErr;
|
||||
|
||||
fn from_str(weight_str: &str) -> std::result::Result<Self, Self::Err> {
|
||||
if let Some((start, end)) = weight_str.split_once(' ') {
|
||||
Ok(FontWeight::Variable(start.to_owned(), end.to_owned()))
|
||||
} else {
|
||||
Ok(FontWeight::Fixed(weight_str.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FontWeight {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Variable(start, end) => format!("{} {}", start, end),
|
||||
Self::Fixed(val) => val.to_owned(),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Transforms the request fields to a validated struct.
|
||||
// Similar to next/font/local's validateData:
|
||||
// https://github.com/vercel/next.js/blob/28454c6ddbc310419467e5415aee26e48d079b46/packages/font/src/local/utils.ts#L31
|
||||
pub(super) fn options_from_request(request: &NextFontLocalRequest) -> Result<NextFontLocalOptions> {
|
||||
// Invariant enforced above: either None or Some(the only item in the vec)
|
||||
let NextFontLocalRequestArguments {
|
||||
display,
|
||||
weight,
|
||||
style,
|
||||
preload,
|
||||
fallback,
|
||||
src,
|
||||
adjust_font_fallback,
|
||||
variable,
|
||||
} = &request.arguments.0;
|
||||
|
||||
let fonts = match src {
|
||||
SrcRequest::Many(descriptors) => FontDescriptors::Many(
|
||||
descriptors
|
||||
.iter()
|
||||
.map(FontDescriptor::from_src_request)
|
||||
.collect::<Result<Vec<FontDescriptor>>>()?,
|
||||
),
|
||||
SrcRequest::One(path) => {
|
||||
FontDescriptors::One(FontDescriptor::from_src_request(&SrcDescriptor {
|
||||
path: path.to_owned(),
|
||||
weight: weight.to_owned(),
|
||||
style: style.to_owned(),
|
||||
})?)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(NextFontLocalOptions {
|
||||
fonts,
|
||||
display: display.to_owned(),
|
||||
preload: preload.to_owned(),
|
||||
fallback: fallback.to_owned(),
|
||||
adjust_font_fallback: adjust_font_fallback.to_owned(),
|
||||
variable: variable.to_owned(),
|
||||
variable_name: request.variable_name.to_owned(),
|
||||
default_weight: weight.as_ref().and_then(|s| s.parse().ok()),
|
||||
default_style: style.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use turbo_tasks_fs::json::parse_json_with_source_context;
|
||||
|
||||
use super::{options_from_request, NextFontLocalOptions};
|
||||
use crate::next_font::local::{
|
||||
options::{FontDescriptor, FontDescriptors, FontWeight},
|
||||
request::{AdjustFontFallback, NextFontLocalRequest},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_uses_defaults() -> Result<()> {
|
||||
let request: NextFontLocalRequest = parse_json_with_source_context(
|
||||
r#"
|
||||
{
|
||||
"import": "",
|
||||
"path": "index.js",
|
||||
"variableName": "myFont",
|
||||
"arguments": [{
|
||||
"src": "./Roboto-Regular.ttf"
|
||||
}]
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
options_from_request(&request)?,
|
||||
NextFontLocalOptions {
|
||||
fonts: FontDescriptors::One(FontDescriptor {
|
||||
path: "./Roboto-Regular.ttf".to_owned(),
|
||||
weight: None,
|
||||
style: None,
|
||||
ext: "ttf".to_owned(),
|
||||
}),
|
||||
default_style: None,
|
||||
default_weight: None,
|
||||
display: "swap".to_owned(),
|
||||
preload: true,
|
||||
fallback: None,
|
||||
adjust_font_fallback: AdjustFontFallback::Arial,
|
||||
variable: None,
|
||||
variable_name: "myFont".to_owned()
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_src() -> Result<()> {
|
||||
let request: NextFontLocalRequest = parse_json_with_source_context(
|
||||
r#"
|
||||
{
|
||||
"import": "",
|
||||
"path": "index.js",
|
||||
"variableName": "myFont",
|
||||
"arguments": [{
|
||||
"src": [{
|
||||
"path": "./Roboto-Regular.ttf",
|
||||
"weight": "400",
|
||||
"style": "normal"
|
||||
}, {
|
||||
"path": "./Roboto-Italic.ttf",
|
||||
"weight": "400"
|
||||
}],
|
||||
"weight": "300",
|
||||
"style": "italic"
|
||||
}]
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
options_from_request(&request)?,
|
||||
NextFontLocalOptions {
|
||||
fonts: FontDescriptors::Many(vec![
|
||||
FontDescriptor {
|
||||
path: "./Roboto-Regular.ttf".to_owned(),
|
||||
weight: Some(FontWeight::Fixed("400".to_owned())),
|
||||
style: Some("normal".to_owned()),
|
||||
ext: "ttf".to_owned(),
|
||||
},
|
||||
FontDescriptor {
|
||||
path: "./Roboto-Italic.ttf".to_owned(),
|
||||
weight: Some(FontWeight::Fixed("400".to_owned())),
|
||||
style: None,
|
||||
ext: "ttf".to_owned(),
|
||||
}
|
||||
]),
|
||||
default_weight: Some(FontWeight::Fixed("300".to_owned())),
|
||||
default_style: Some("italic".to_owned()),
|
||||
display: "swap".to_owned(),
|
||||
preload: true,
|
||||
fallback: None,
|
||||
adjust_font_fallback: AdjustFontFallback::Arial,
|
||||
variable: None,
|
||||
variable_name: "myFont".to_owned()
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_true_adjust_fallback_fails() -> Result<()> {
|
||||
let request: Result<NextFontLocalRequest> = parse_json_with_source_context(
|
||||
r#"
|
||||
{
|
||||
"import": "",
|
||||
"path": "index.js",
|
||||
"variableName": "myFont",
|
||||
"arguments": [{
|
||||
"src": "./Roboto-Regular.ttf",
|
||||
"adjustFontFallback": true
|
||||
}]
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
match request {
|
||||
Ok(r) => panic!("Expected failure, received {:?}", r),
|
||||
Err(err) => {
|
||||
assert!(err
|
||||
.to_string()
|
||||
.contains("expected Expected string or `false`. Received `true`"),)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specified_options() -> Result<()> {
|
||||
let request: NextFontLocalRequest = parse_json_with_source_context(
|
||||
r#"
|
||||
{
|
||||
"import": "",
|
||||
"path": "index.js",
|
||||
"variableName": "myFont",
|
||||
"arguments": [{
|
||||
"src": "./Roboto-Regular.woff",
|
||||
"preload": false,
|
||||
"weight": "500",
|
||||
"style": "italic",
|
||||
"fallback": ["Fallback"],
|
||||
"adjustFontFallback": "Times New Roman",
|
||||
"display": "optional",
|
||||
"variable": "myvar"
|
||||
}]
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
options_from_request(&request)?,
|
||||
NextFontLocalOptions {
|
||||
fonts: FontDescriptors::One(FontDescriptor {
|
||||
path: "./Roboto-Regular.woff".to_owned(),
|
||||
weight: Some(FontWeight::Fixed("500".to_owned())),
|
||||
style: Some("italic".to_owned()),
|
||||
ext: "woff".to_owned(),
|
||||
}),
|
||||
default_style: Some("italic".to_owned()),
|
||||
default_weight: Some(FontWeight::Fixed("500".to_owned())),
|
||||
display: "optional".to_owned(),
|
||||
preload: false,
|
||||
fallback: Some(vec!["Fallback".to_owned()]),
|
||||
adjust_font_fallback: AdjustFontFallback::TimesNewRoman,
|
||||
variable: Some("myvar".to_owned()),
|
||||
variable_name: "myFont".to_owned()
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use turbo_tasks::trace::TraceRawVcs;
|
||||
|
||||
/// The top-most structure encoded into the query param in requests to
|
||||
/// `next/font/local` generated by the next/font swc transform. e.g.
|
||||
/// `next/font/local/target.css?{"path": "index.js", "arguments": {"src":...
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct NextFontLocalRequest {
|
||||
pub arguments: (NextFontLocalRequestArguments,),
|
||||
pub variable_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct NextFontLocalRequestArguments {
|
||||
pub src: SrcRequest,
|
||||
pub weight: Option<String>,
|
||||
pub style: Option<String>,
|
||||
#[serde(default = "default_display")]
|
||||
pub display: String,
|
||||
#[serde(default = "default_preload")]
|
||||
pub preload: bool,
|
||||
pub fallback: Option<Vec<String>>,
|
||||
#[serde(
|
||||
default = "default_adjust_font_fallback",
|
||||
deserialize_with = "deserialize_adjust_font_fallback"
|
||||
)]
|
||||
pub adjust_font_fallback: AdjustFontFallback,
|
||||
pub variable: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub(super) enum SrcRequest {
|
||||
One(String),
|
||||
Many(Vec<SrcDescriptor>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub(super) struct SrcDescriptor {
|
||||
pub path: String,
|
||||
pub weight: Option<String>,
|
||||
pub style: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, Deserialize, Hash, Ord, PartialOrd, PartialEq, Eq, Serialize, TraceRawVcs,
|
||||
)]
|
||||
pub(super) enum AdjustFontFallback {
|
||||
Arial,
|
||||
TimesNewRoman,
|
||||
None,
|
||||
}
|
||||
|
||||
fn default_adjust_font_fallback() -> AdjustFontFallback {
|
||||
AdjustFontFallback::Arial
|
||||
}
|
||||
|
||||
fn deserialize_adjust_font_fallback<'de, D>(
|
||||
de: D,
|
||||
) -> std::result::Result<AdjustFontFallback, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum AdjustFontFallbackInner {
|
||||
Named(String),
|
||||
None(bool),
|
||||
}
|
||||
|
||||
match AdjustFontFallbackInner::deserialize(de)? {
|
||||
AdjustFontFallbackInner::Named(name) => match name.as_str() {
|
||||
"Arial" => Ok(AdjustFontFallback::Arial),
|
||||
"Times New Roman" => Ok(AdjustFontFallback::TimesNewRoman),
|
||||
_ => Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other("adjust_font_fallback"),
|
||||
&"Expected either \"Arial\" or \"Times New Roman\"",
|
||||
)),
|
||||
},
|
||||
AdjustFontFallbackInner::None(val) => {
|
||||
if val {
|
||||
Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other("adjust_font_fallback"),
|
||||
&"Expected string or `false`. Received `true`",
|
||||
))
|
||||
} else {
|
||||
Ok(AdjustFontFallback::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_preload() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_display() -> String {
|
||||
"swap".to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{default_adjust_font_fallback, deserialize_adjust_font_fallback};
|
||||
use anyhow::Result;
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::AdjustFontFallback;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct TestFallback {
|
||||
#[serde(
|
||||
default = "default_adjust_font_fallback",
|
||||
deserialize_with = "deserialize_adjust_font_fallback"
|
||||
)]
|
||||
pub adjust_font_fallback: AdjustFontFallback,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_adjust_font_fallback_fails_on_true() {
|
||||
match serde_json::from_str::<TestFallback>(r#"{"adjustFontFallback": true}"#) {
|
||||
Ok(_) => panic!("Should fail"),
|
||||
Err(error) => assert!(error.to_string().contains(
|
||||
"invalid value: adjust_font_fallback, expected Expected string or `false`. \
|
||||
Received `true`"
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_adjust_font_fallback_fails_on_unknown_string() {
|
||||
match serde_json::from_str::<TestFallback>(r#"{"adjustFontFallback": "Roboto"}"#) {
|
||||
Ok(_) => panic!("Should fail"),
|
||||
Err(error) => assert!(
|
||||
error.to_string().contains(
|
||||
r#"invalid value: adjust_font_fallback, expected Expected either "Arial" or "Times New Roman""#
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserializes_false_as_none() -> Result<()> {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<TestFallback>(r#"{"adjustFontFallback": false}"#)?,
|
||||
TestFallback {
|
||||
adjust_font_fallback: AdjustFontFallback::None
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserializes_arial() -> Result<()> {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<TestFallback>(r#"{"adjustFontFallback": "Arial"}"#)?,
|
||||
TestFallback {
|
||||
adjust_font_fallback: AdjustFontFallback::Arial
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
use anyhow::{bail, Result};
|
||||
use indoc::formatdoc;
|
||||
use turbo_tasks::primitives::{StringVc, U32Vc};
|
||||
|
||||
use super::options::{FontDescriptors, NextFontLocalOptionsVc};
|
||||
use crate::next_font::{
|
||||
font_fallback::FontFallbacksVc,
|
||||
stylesheet::{build_fallback_definition, build_font_class_rules},
|
||||
util::{get_scoped_font_family, FontCssPropertiesVc, FontFamilyType},
|
||||
};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn build_stylesheet(
|
||||
options: NextFontLocalOptionsVc,
|
||||
fallbacks: FontFallbacksVc,
|
||||
css_properties: FontCssPropertiesVc,
|
||||
request_hash: U32Vc,
|
||||
) -> Result<StringVc> {
|
||||
let scoped_font_family = get_scoped_font_family(
|
||||
FontFamilyType::WebFont.cell(),
|
||||
options.font_family(),
|
||||
request_hash,
|
||||
);
|
||||
|
||||
Ok(StringVc::cell(formatdoc!(
|
||||
r#"
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
"#,
|
||||
*build_font_face_definitions(scoped_font_family, options).await?,
|
||||
(*build_fallback_definition(fallbacks).await?),
|
||||
*build_font_class_rules(css_properties).await?
|
||||
)))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn build_font_face_definitions(
|
||||
scoped_font_family: StringVc,
|
||||
options: NextFontLocalOptionsVc,
|
||||
) -> Result<StringVc> {
|
||||
let options = &*options.await?;
|
||||
|
||||
let mut definitions = String::new();
|
||||
let fonts = match &options.fonts {
|
||||
FontDescriptors::One(d) => vec![d.clone()],
|
||||
FontDescriptors::Many(d) => d.clone(),
|
||||
};
|
||||
|
||||
for font in fonts {
|
||||
definitions.push_str(&formatdoc!(
|
||||
r#"
|
||||
@font-face {{
|
||||
font-family: '{}';
|
||||
src: url('{}') format('{}');
|
||||
font-display: {};
|
||||
{}{}
|
||||
}}
|
||||
"#,
|
||||
*scoped_font_family.await?,
|
||||
&font.path,
|
||||
ext_to_format(&font.ext)?,
|
||||
options.display,
|
||||
&font
|
||||
.weight
|
||||
.as_ref()
|
||||
.or(options.default_weight.as_ref())
|
||||
.map_or_else(|| "".to_owned(), |w| format!("font-weight: {};", w)),
|
||||
&font
|
||||
.style
|
||||
.as_ref()
|
||||
.or(options.default_style.as_ref())
|
||||
.map_or_else(|| "".to_owned(), |s| format!("font-style: {};", s)),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(StringVc::cell(definitions))
|
||||
}
|
||||
|
||||
fn ext_to_format(ext: &str) -> Result<String> {
|
||||
Ok(match ext {
|
||||
"woff" => "woff",
|
||||
"woff2" => "woff2",
|
||||
"ttf" => "truetype",
|
||||
"otf" => "opentype",
|
||||
"eot" => "embedded-opentype",
|
||||
_ => bail!("Unknown font file extension"),
|
||||
}
|
||||
.to_owned())
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use turbo_tasks::primitives::{StringVc, U32Vc};
|
||||
|
||||
use super::options::NextFontLocalOptionsVc;
|
||||
use crate::next_font::{
|
||||
font_fallback::{FontFallback, FontFallbacksVc},
|
||||
util::{get_scoped_font_family, FontFamilyType},
|
||||
};
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn build_font_family_string(
|
||||
options: NextFontLocalOptionsVc,
|
||||
font_fallbacks: FontFallbacksVc,
|
||||
request_hash: U32Vc,
|
||||
) -> Result<StringVc> {
|
||||
let mut font_families = vec![format!(
|
||||
"'{}'",
|
||||
*get_scoped_font_family(
|
||||
FontFamilyType::WebFont.cell(),
|
||||
options.font_family(),
|
||||
request_hash,
|
||||
)
|
||||
.await?
|
||||
)];
|
||||
|
||||
for font_fallback in &*font_fallbacks.await? {
|
||||
match *font_fallback.await? {
|
||||
FontFallback::Automatic(fallback) => {
|
||||
font_families.push(format!("'{}'", *fallback.await?.scoped_font_family.await?));
|
||||
}
|
||||
FontFallback::Manual(fallbacks) => {
|
||||
font_families.extend_from_slice(&fallbacks.await?);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StringVc::cell(font_families.join(", ")))
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
pub(crate) mod font_fallback;
|
||||
pub(crate) mod google;
|
||||
pub(crate) mod issue;
|
||||
pub(crate) mod local;
|
||||
pub(crate) mod stylesheet;
|
||||
pub(crate) mod util;
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
use anyhow::Result;
|
||||
use indoc::formatdoc;
|
||||
use turbo_tasks::primitives::OptionStringVc;
|
||||
use turbo_tasks::primitives::StringVc;
|
||||
|
||||
use super::font_fallback::{FontFallback, FontFallbackVc};
|
||||
use super::{
|
||||
font_fallback::{FontFallback, FontFallbacksVc},
|
||||
util::FontCssPropertiesVc,
|
||||
};
|
||||
|
||||
/// Builds `@font-face` stylesheet definition for a given FontFallback
|
||||
#[turbo_tasks::function]
|
||||
pub(crate) async fn build_fallback_definition(fallback: FontFallbackVc) -> Result<OptionStringVc> {
|
||||
Ok(OptionStringVc::cell(match *fallback.await? {
|
||||
FontFallback::Error => None,
|
||||
FontFallback::Manual(_) => None,
|
||||
FontFallback::Automatic(fallback) => {
|
||||
pub(crate) async fn build_fallback_definition(fallbacks: FontFallbacksVc) -> Result<StringVc> {
|
||||
let mut res = "".to_owned();
|
||||
for fallback_vc in &*fallbacks.await? {
|
||||
if let FontFallback::Automatic(fallback) = &*fallback_vc.await? {
|
||||
let fallback = fallback.await?;
|
||||
|
||||
let override_properties = match &fallback.adjustment {
|
||||
None => "".to_owned(),
|
||||
Some(adjustment) => formatdoc!(
|
||||
r#"
|
||||
ascent-override: {}%;
|
||||
descent-override: {}%;
|
||||
line-gap-override: {}%;
|
||||
size-adjust: {}%;
|
||||
"#,
|
||||
ascent-override: {}%;
|
||||
descent-override: {}%;
|
||||
line-gap-override: {}%;
|
||||
size-adjust: {}%;
|
||||
"#,
|
||||
format_fixed_percentage(adjustment.ascent),
|
||||
format_fixed_percentage(adjustment.descent.abs()),
|
||||
format_fixed_percentage(adjustment.line_gap),
|
||||
|
@ -29,20 +31,66 @@ pub(crate) async fn build_fallback_definition(fallback: FontFallbackVc) -> Resul
|
|||
),
|
||||
};
|
||||
|
||||
Some(formatdoc!(
|
||||
res.push_str(&formatdoc!(
|
||||
r#"
|
||||
@font-face {{
|
||||
font-family: '{}';
|
||||
src: local("{}");
|
||||
{}
|
||||
}}
|
||||
"#,
|
||||
@font-face {{
|
||||
font-family: '{}';
|
||||
src: local("{}");
|
||||
{}
|
||||
}}
|
||||
"#,
|
||||
fallback.scoped_font_family.await?,
|
||||
fallback.local_font_family.await?,
|
||||
override_properties
|
||||
))
|
||||
));
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
Ok(StringVc::cell(res))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub(super) async fn build_font_class_rules(
|
||||
css_properties: FontCssPropertiesVc,
|
||||
) -> Result<StringVc> {
|
||||
let css_properties = &*css_properties.await?;
|
||||
let font_family_string = &*css_properties.font_family.await?;
|
||||
|
||||
let mut rules = formatdoc!(
|
||||
r#"
|
||||
.className {{
|
||||
font-family: {};
|
||||
{}{}
|
||||
}}
|
||||
"#,
|
||||
font_family_string,
|
||||
css_properties
|
||||
.weight
|
||||
.await?
|
||||
.as_ref()
|
||||
.map(|w| format!("font-weight: {};\n", w))
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
css_properties
|
||||
.style
|
||||
.await?
|
||||
.as_ref()
|
||||
.map(|s| format!("font-style: {};\n", s))
|
||||
.unwrap_or_else(|| "".to_owned()),
|
||||
);
|
||||
|
||||
if let Some(variable) = &*css_properties.variable.await? {
|
||||
rules.push_str(&formatdoc!(
|
||||
r#"
|
||||
.variable {{
|
||||
{}: {};
|
||||
}}
|
||||
"#,
|
||||
variable,
|
||||
font_family_string
|
||||
))
|
||||
}
|
||||
|
||||
Ok(StringVc::cell(rules))
|
||||
}
|
||||
|
||||
fn format_fixed_percentage(value: f64) -> String {
|
||||
|
|
|
@ -22,7 +22,10 @@ use crate::{
|
|||
embed_js::{next_js_fs, VIRTUAL_PACKAGE_NAME},
|
||||
next_client::context::ClientContextType,
|
||||
next_config::NextConfigVc,
|
||||
next_font::google::{NextFontGoogleCssModuleReplacerVc, NextFontGoogleReplacerVc},
|
||||
next_font::{
|
||||
google::{NextFontGoogleCssModuleReplacerVc, NextFontGoogleReplacerVc},
|
||||
local::{NextFontLocalCssModuleReplacerVc, NextFontLocalReplacerVc},
|
||||
},
|
||||
next_server::context::ServerContextType,
|
||||
};
|
||||
|
||||
|
@ -395,6 +398,23 @@ pub 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(NextFontLocalReplacerVc::new(project_path).into()).into(),
|
||||
);
|
||||
|
||||
import_map.insert_alias(
|
||||
// Request path from js via next-font swc transform
|
||||
AliasPattern::exact("@next/font/local/target.css"),
|
||||
ImportMapping::Dynamic(NextFontLocalReplacerVc::new(project_path).into()).into(),
|
||||
);
|
||||
|
||||
import_map.insert_alias(
|
||||
AliasPattern::exact("@vercel/turbopack-next/internal/font/local/cssmodule.module.css"),
|
||||
ImportMapping::Dynamic(NextFontLocalCssModuleReplacerVc::new(project_path).into()).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);
|
||||
|
|
|
@ -121,13 +121,12 @@ impl CustomTransformer for NextJsDynamic {
|
|||
|
||||
/// Returns a rule which applies the Next.js font transform.
|
||||
pub fn get_next_font_transform_rule() -> ModuleRule {
|
||||
#[allow(unused_mut)] // This is mutated when next-font-local is enabled
|
||||
let mut font_loaders = vec!["next/font/google".into(), "@next/font/google".into()];
|
||||
#[cfg(feature = "next-font-local")]
|
||||
{
|
||||
font_loaders.push("next/font/local".into());
|
||||
font_loaders.push("@next/font/local".into());
|
||||
}
|
||||
let font_loaders = vec![
|
||||
"next/font/google".into(),
|
||||
"@next/font/google".into(),
|
||||
"next/font/local".into(),
|
||||
"@next/font/local".into(),
|
||||
];
|
||||
|
||||
let transformer =
|
||||
EcmascriptInputTransform::Custom(CustomTransformVc::cell(box NextJsFont { font_loaders }));
|
||||
|
|
|
@ -34,7 +34,6 @@ tokio_console = [
|
|||
]
|
||||
profile = []
|
||||
custom_allocator = ["turbo-malloc/custom_allocator"]
|
||||
next-font-local = ["next-core/next-font-local"]
|
||||
native-tls = ["next-core/native-tls"]
|
||||
rustls-tls = ["next-core/rustls-tls"]
|
||||
# Internal only. Enabled when building for the Next.js integration test suite.
|
||||
|
|
Loading…
Reference in a new issue