Skip to content

Commit

Permalink
feat: remove font family hashing in next/font css (#53608)
Browse files Browse the repository at this point in the history
<!-- Thanks for opening a PR! Your contribution is much appreciated.
To make sure your PR is handled as smoothly as possible we request that
you follow the checklist sections below.
Choose the right checklist for the change(s) that you're making:

## For Contributors

### Improving Documentation

- Run `pnpm prettier-fix` to fix formatting issues before opening the
PR.
- Read the Docs Contribution Guide to ensure your contribution follows
the docs guidelines:
https://nextjs.org/docs/community/contribution-guide

### Adding or Updating Examples

- The "examples guidelines" are followed from our contributing doc
https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md
- Make sure the linting passes by running `pnpm build && pnpm lint`. See
https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md

### Fixing a bug

- Related issues linked using `fixes #number`
- Tests added. See:
https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md

### Adding a feature

- Implements an existing feature request or RFC. Make sure the feature
request has been accepted for implementation before opening a PR. (A
discussion must be opened, see
https://github.com/vercel/next.js/discussions/new?category=ideas)
- Related issues/discussions are linked using `fixes #number`
- e2e tests added
(https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs
- Documentation added
- Telemetry added. In case of a feature if it's used or not.
- Errors have a helpful link attached, see
https://github.com/vercel/next.js/blob/canary/contributing.md


## For Maintainers

- Minimal description (aim for explaining to someone not on the team to
understand the PR)
- When linking to a Slack thread, you might want to share details of the
conclusion
- Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
- Add review comments if necessary to explain to the reviewer the logic
behind a change
-->

### What?

Adding support for supporting a custom fontFamily name when using
next/font

### Why?

By default, next/font hashes the font name when generating css to
achieve proper scoping.
However, that makes it impossible to use next/font with 3rd party
libraries that provide CSS with pre-defined font names.

### How?

To solve this, I've added a new argument to the next/font function call
– `usedFontFamilyName`.
It allows developers to pick the fontFamily name that is going to be
used in the CSS output instead of the default one and make it work with
vendor CSS files.

```
import { Inter } from "next/font/google";

const inter = Inter({
  subsets: ["latin"],
  fixedFontFamily: "Inter",
});
```

Fixes [#43452](#43452)

---

Edit:

I've changed the implementation to use `disabledFontFamilyHashing`
boolean flag which removes the hashing but keeps the original font
family name instead of allowing a custom name

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
  • Loading branch information
3 people authored May 15, 2024
1 parent 411a246 commit 1c81480
Show file tree
Hide file tree
Showing 13 changed files with 91 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ struct Fallback {
pub(super) async fn get_font_fallback(
context: Vc<FileSystemPath>,
options_vc: Vc<NextFontGoogleOptions>,
request_hash: u32,
) -> Result<Vc<FontFallback>> {
let options = options_vc.await?;
Ok(match &options.fallback {
Expand All @@ -70,7 +69,6 @@ pub(super) async fn get_font_fallback(
scoped_font_family: get_scoped_font_family(
FontFamilyType::Fallback.cell(),
options_vc.font_family(),
request_hash,
),
local_font_family: Vc::cell(fallback.font_family),
adjustment: fallback.adjustment,
Expand Down
24 changes: 8 additions & 16 deletions packages/next-swc/crates/next-core/src/next_font/google/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ impl NextFontGoogleReplacer {
let font_data = load_font_data(self.project_path);
let options = font_options_from_query_map(query_vc, font_data);

let fallback = get_font_fallback(self.project_path, options, request_hash);
let properties = get_font_css_properties(options, fallback, request_hash).await?;
let fallback = get_font_fallback(self.project_path, options);
let properties = get_font_css_properties(options, fallback).await?;
let js_asset = VirtualSource::new(
next_js_file_path("internal/font/google".to_string())
.join(format!("{}.js", get_request_id(options.font_family(), request_hash).await?)),
Expand Down Expand Up @@ -201,11 +201,8 @@ impl NextFontGoogleCssModuleReplacer {
let font_data = load_font_data(self.project_path);
let options = font_options_from_query_map(query_vc, font_data);
let stylesheet_url = get_stylesheet_url_from_options(options, font_data);
let scoped_font_family = get_scoped_font_family(
FontFamilyType::WebFont.cell(),
options.font_family(),
request_hash,
);
let scoped_font_family =
get_scoped_font_family(FontFamilyType::WebFont.cell(), options.font_family());
let css_virtual_path = next_js_file_path("internal/font/google".to_string()).join(format!(
"/{}.module.css",
get_request_id(options.font_family(), request_hash).await?
Expand All @@ -226,7 +223,7 @@ impl NextFontGoogleCssModuleReplacer {
)
.await?;

let font_fallback = get_font_fallback(self.project_path, options, request_hash);
let font_fallback = get_font_fallback(self.project_path, options);

let stylesheet = match stylesheet_str {
Some(s) => Some(
Expand Down Expand Up @@ -254,7 +251,7 @@ impl NextFontGoogleCssModuleReplacer {
FileContent::Content(
build_stylesheet(
Vc::cell(stylesheet),
get_font_css_properties(options, font_fallback, request_hash),
get_font_css_properties(options, font_fallback),
font_fallback,
)
.await?
Expand Down Expand Up @@ -520,15 +517,10 @@ async fn get_stylesheet_url_from_options(
async fn get_font_css_properties(
options_vc: Vc<NextFontGoogleOptions>,
font_fallback: Vc<FontFallback>,
request_hash: u32,
) -> Result<Vc<FontCssProperties>> {
let options = &*options_vc.await?;
let scoped_font_family = &*get_scoped_font_family(
FontFamilyType::WebFont.cell(),
options_vc.font_family(),
request_hash,
)
.await?;
let scoped_font_family =
&*get_scoped_font_family(FontFamilyType::WebFont.cell(), options_vc.font_family()).await?;

let mut font_families = vec![format!("'{}'", scoped_font_family.clone())];
let font_fallback = &*font_fallback.await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ static BOLD_WEIGHT: f64 = 700.0;
pub(super) async fn get_font_fallbacks(
context: Vc<FileSystemPath>,
options_vc: Vc<NextFontLocalOptions>,
request_hash: u32,
) -> Result<Vc<FontFallbacks>> {
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,
);
let scoped_font_family =
get_scoped_font_family(FontFamilyType::Fallback.cell(), options_vc.font_family());

match options.adjust_font_fallback {
AdjustFontFallback::Arial => font_fallbacks.push(
Expand Down
13 changes: 5 additions & 8 deletions packages/next-swc/crates/next-core/src/next_font/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,8 @@ impl NextFontLocalReplacer {
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, request_hash);
let properties =
&*get_font_css_properties(options_vc, font_fallbacks, request_hash).await?;
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?{}";
Expand Down Expand Up @@ -170,13 +169,12 @@ impl NextFontLocalCssModuleReplacer {
"/{}.module.css",
get_request_id(options.font_family(), request_hash).await?
));
let fallback = get_font_fallbacks(context, options, request_hash);
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, request_hash),
request_hash,
get_font_css_properties(options, fallback),
)
.await?;

Expand Down Expand Up @@ -303,12 +301,11 @@ impl ImportMappingReplacement for NextFontLocalFontFileReplacer {
async fn get_font_css_properties(
options_vc: Vc<NextFontLocalOptions>,
font_fallbacks: Vc<FontFallbacks>,
request_hash: u32,
) -> Result<Vc<FontCssProperties>> {
let options = &*options_vc.await?;

Ok(FontCssProperties::cell(FontCssProperties {
font_family: build_font_family_string(options_vc, font_fallbacks, request_hash),
font_family: build_font_family_string(options_vc, font_fallbacks),
weight: Vc::cell(match &options.fonts {
FontDescriptors::Many(_) => None,
// When the user only provided a top-level font file, include the font weight in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@ pub(super) async fn build_stylesheet(
options: Vc<NextFontLocalOptions>,
fallbacks: Vc<FontFallbacks>,
css_properties: Vc<FontCssProperties>,
request_hash: u32,
) -> Result<Vc<String>> {
let scoped_font_family = get_scoped_font_family(
FontFamilyType::WebFont.cell(),
options.font_family(),
request_hash,
);
let scoped_font_family =
get_scoped_font_family(FontFamilyType::WebFont.cell(), options.font_family());

Ok(Vc::cell(formatdoc!(
r#"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,10 @@ use crate::next_font::{
pub(super) async fn build_font_family_string(
options: Vc<NextFontLocalOptions>,
font_fallbacks: Vc<FontFallbacks>,
request_hash: u32,
) -> Result<Vc<String>> {
let mut font_families = vec![format!(
"'{}'",
*get_scoped_font_family(
FontFamilyType::WebFont.cell(),
options.font_family(),
request_hash,
)
.await?
*get_scoped_font_family(FontFamilyType::WebFont.cell(), options.font_family(),).await?
)];

for font_fallback in &*font_fallbacks.await? {
Expand Down
13 changes: 3 additions & 10 deletions packages/next-swc/crates/next-core/src/next_font/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,14 @@ pub(crate) enum FontFamilyType {
pub(crate) async fn get_scoped_font_family(
ty: Vc<FontFamilyType>,
font_family_name: Vc<String>,
request_hash: u32,
) -> Result<Vc<String>> {
let hash = {
let mut hash = format!("{:x?}", request_hash);
hash.truncate(6);
hash
};

let font_family_base = font_family_name.await?.replace(' ', "_");
let font_family_base = font_family_name.await?.to_string();
let font_family_name = match &*ty.await? {
FontFamilyType::WebFont => font_family_base,
FontFamilyType::Fallback => format!("{}_Fallback", font_family_base),
FontFamilyType::Fallback => format!("{} Fallback", font_family_base),
};

Ok(Vc::cell(format!("__{}_{}", font_family_name, hash)))
Ok(Vc::cell(format!("{}", font_family_name)))
}

/// Returns a [Vc] for [String] uniquely identifying the request for the font.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default async function nextFontLoader(this: any) {
// Exports will be exported as is from css-loader instead of a CSS module export
const exports: { name: any; value: any }[] = []

// Generate a hash from the CSS content. Used to generate classnames and font families
// Generate a hash from the CSS content. Used to generate classnames
const fontFamilyHash = loaderUtils.getHashDigest(
Buffer.from(css),
'sha1',
Expand All @@ -133,7 +133,6 @@ export default async function nextFontLoader(this: any) {
postcss(
postcssNextFontPlugin({
exports,
fontFamilyHash,
fallbackFonts,
weight,
style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ import postcss from 'postcss'
*/
const postcssNextFontPlugin = ({
exports,
fontFamilyHash,
fallbackFonts = [],
adjustFontFallback,
variable,
weight,
style,
}: {
exports: { name: any; value: any }[]
fontFamilyHash: string
fallbackFonts?: string[]
adjustFontFallback?: AdjustFontFallback
variable?: string
Expand All @@ -45,8 +43,7 @@ const postcssNextFontPlugin = ({
}

const formatFamily = (family: string) => {
// Turn the font family unguessable to make it locally scoped
return `'__${family.replace(/ /g, '_')}_${fontFamilyHash}'`
return `'${family}'`
}

// Hash font-family names
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app-external/app-external.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe('app dir - external dependency', () => {
await browser.eval(
`window.getComputedStyle(document.querySelector('p')).fontFamily`
)
).toMatch(/^__myFont_.{6}, __myFont_Fallback_.{6}$/)
).toMatch(/^myFont, "myFont Fallback"$/)
})
// TODO: This test depends on `new Worker` which is not supported in Turbopack yet.
;(process.env.TURBOPACK ? it.skip : it)(
Expand Down
46 changes: 15 additions & 31 deletions test/e2e/app-dir/next-font/next-font.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_variable$/ : /^__variable_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font1_.{6}', '__font1_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font1', 'font1 Fallback'$/),
},
})
// page
Expand All @@ -67,9 +65,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_variable$/ : /^__variable_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font2_.{6}', '__font2_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font2', 'font2 Fallback'$/),
},
})
// Comp
Expand All @@ -78,9 +74,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_className$/ : /^__className_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font3_.{6}', '__font3_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font3', 'font3 Fallback'$/),
fontStyle: 'italic',
fontWeight: 900,
},
Expand All @@ -99,9 +93,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_variable$/ : /^__variable_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font1_.{6}', '__font1_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font1', 'font1 Fallback'$/),
},
})

Expand All @@ -111,9 +103,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_className$/ : /^__className_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font4_.{6}', '__font4_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font4', 'font4 Fallback'$/),
fontWeight: 100,
},
})
Expand All @@ -123,9 +113,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_className$/ : /^__className_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font5_.{6}', '__font5_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font5', 'font5 Fallback'$/),
fontStyle: 'italic',
},
})
Expand All @@ -135,9 +123,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_className$/ : /^__className_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font6_.{6}', '__font6_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font6', 'font6 Fallback'$/),
},
})
})
Expand All @@ -149,9 +135,7 @@ describe('app dir - next/font', () => {
process.env.TURBOPACK ? /.*_className$/ : /^__className_.*/
),
style: {
fontFamily: expect.stringMatching(
/^'__font1_.{6}', '__font1_Fallback_.{6}'$/
),
fontFamily: expect.stringMatching(/^'font1', 'font1 Fallback'$/),
},
variable: expect.stringMatching(
process.env.TURBOPACK ? /.*_variable$/ : /^__variable_.*/
Expand All @@ -169,7 +153,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}, __font1_Fallback_.{6}$/)
).toMatch(/^font1, "font1 Fallback"$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
Expand All @@ -186,7 +170,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontFamily'
)
).toMatch(/^__font2_.{6}, __font2_Fallback_.{6}$/)
).toMatch(/^font2, "font2 Fallback"$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-page")).fontWeight'
Expand All @@ -203,7 +187,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontFamily'
)
).toMatch(/^__font3_.{6}, __font3_Fallback_.{6}$/)
).toMatch(/^font3, "font3 Fallback"$$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-comp")).fontWeight'
Expand All @@ -224,7 +208,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontFamily'
)
).toMatch(/^__font1_.{6}, __font1_Fallback_.{6}$/)
).toMatch(/^font1, "font1 Fallback"$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#root-layout")).fontWeight'
Expand All @@ -241,7 +225,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontFamily'
)
).toMatch(/^__font4_.{6}, __font4_Fallback_.{6}$/)
).toMatch(/^font4, "font4 Fallback"$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-layout")).fontWeight'
Expand All @@ -258,7 +242,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontFamily'
)
).toMatch(/^__font5_.{6}, __font5_Fallback_.{6}$/)
).toMatch(/^font5, "font5 Fallback"$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-page")).fontWeight'
Expand All @@ -275,7 +259,7 @@ describe('app dir - next/font', () => {
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontFamily'
)
).toMatch(/^__font6_.{6}, __font6_Fallback_.{6}$/)
).toMatch(/^font6, "font6 Fallback"$$/)
expect(
await browser.eval(
'getComputedStyle(document.querySelector("#client-comp")).fontWeight'
Expand Down
Loading

0 comments on commit 1c81480

Please sign in to comment.