diff --git a/packages/next-swc/crates/core/src/lib.rs b/packages/next-swc/crates/core/src/lib.rs index 4ebe788b24e1b..3d3bca8aca192 100644 --- a/packages/next-swc/crates/core/src/lib.rs +++ b/packages/next-swc/crates/core/src/lib.rs @@ -36,6 +36,7 @@ use serde::Deserialize; use std::cell::RefCell; use std::rc::Rc; use std::{path::PathBuf, sync::Arc}; +use swc_core::ecma::atoms::JsWord; use swc_core::{ base::config::ModuleConfig, @@ -51,6 +52,7 @@ mod auto_cjs; pub mod disallow_re_export_all_in_page; pub mod hook_optimizer; pub mod next_dynamic; +pub mod next_font_loaders; pub mod next_ssg; pub mod page_config; pub mod react_remove_properties; @@ -109,6 +111,9 @@ pub struct TransformOptions { #[serde(default)] pub modularize_imports: Option, + + #[serde(default)] + pub font_loaders: Option>, } pub fn custom_before_pass<'a, C: Comments + 'a>( @@ -211,6 +216,11 @@ where match &opts.modularize_imports { Some(config) => Either::Left(modularize_imports::modularize_imports(config.clone())), None => Either::Right(noop()), + }, + match &opts.font_loaders { + Some(font_loaders) => + Either::Left(next_font_loaders::next_font_loaders(font_loaders.clone())), + None => Either::Right(noop()), } ) } diff --git a/packages/next-swc/crates/core/src/next_font_loaders/find_functions_outside_module_scope.rs b/packages/next-swc/crates/core/src/next_font_loaders/find_functions_outside_module_scope.rs new file mode 100644 index 0000000000000..f83742d205436 --- /dev/null +++ b/packages/next-swc/crates/core/src/next_font_loaders/find_functions_outside_module_scope.rs @@ -0,0 +1,31 @@ +use swc_core::common::errors::HANDLER; +use swc_core::ecma::ast::*; +use swc_core::ecma::visit::noop_visit_type; +use swc_core::ecma::visit::Visit; + +pub struct FindFunctionsOutsideModuleScope<'a> { + pub state: &'a super::State, +} + +impl<'a> Visit for FindFunctionsOutsideModuleScope<'a> { + noop_visit_type!(); + + fn visit_ident(&mut self, ident: &Ident) { + if self.state.font_functions.get(&ident.to_id()).is_some() + && self + .state + .font_functions_in_allowed_scope + .get(&ident.span.lo) + .is_none() + { + HANDLER.with(|handler| { + handler + .struct_span_err( + ident.span, + "Font loaders must be called and assigned to a const in the module scope", + ) + .emit() + }); + } + } +} diff --git a/packages/next-swc/crates/core/src/next_font_loaders/font_functions_collector.rs b/packages/next-swc/crates/core/src/next_font_loaders/font_functions_collector.rs new file mode 100644 index 0000000000000..e8fdfce66f0e6 --- /dev/null +++ b/packages/next-swc/crates/core/src/next_font_loaders/font_functions_collector.rs @@ -0,0 +1,68 @@ +use swc_core::common::errors::HANDLER; +use swc_core::ecma::ast::*; +use swc_core::ecma::atoms::JsWord; +use swc_core::ecma::visit::noop_visit_type; +use swc_core::ecma::visit::Visit; + +pub struct FontFunctionsCollector<'a> { + pub font_loaders: &'a [JsWord], + pub state: &'a mut super::State, +} + +impl<'a> Visit for FontFunctionsCollector<'a> { + noop_visit_type!(); + + fn visit_import_decl(&mut self, import_decl: &ImportDecl) { + if self.font_loaders.contains(&import_decl.src.value) { + self.state + .removeable_module_items + .insert(import_decl.span.lo); + for specifier in &import_decl.specifiers { + match specifier { + ImportSpecifier::Named(ImportNamedSpecifier { + local, imported, .. + }) => { + self.state + .font_functions_in_allowed_scope + .insert(local.span.lo); + + let function_name = if let Some(ModuleExportName::Ident(ident)) = imported { + ident.sym.clone() + } else { + local.sym.clone() + }; + self.state.font_functions.insert( + local.to_id(), + super::FontFunction { + loader: import_decl.src.value.clone(), + function_name: Some(function_name), + }, + ); + } + ImportSpecifier::Default(ImportDefaultSpecifier { local, .. }) => { + self.state + .font_functions_in_allowed_scope + .insert(local.span.lo); + self.state.font_functions.insert( + local.to_id(), + super::FontFunction { + loader: import_decl.src.value.clone(), + function_name: None, + }, + ); + } + ImportSpecifier::Namespace(_) => { + HANDLER.with(|handler| { + handler + .struct_span_err( + import_decl.span, + "Font loaders can't have namespace imports", + ) + .emit() + }); + } + } + } + } + } +} diff --git a/packages/next-swc/crates/core/src/next_font_loaders/font_imports_generator.rs b/packages/next-swc/crates/core/src/next_font_loaders/font_imports_generator.rs new file mode 100644 index 0000000000000..6f8e3bb340712 --- /dev/null +++ b/packages/next-swc/crates/core/src/next_font_loaders/font_imports_generator.rs @@ -0,0 +1,220 @@ +use serde_json::Value; +use swc_core::common::errors::HANDLER; +use swc_core::common::{Spanned, DUMMY_SP}; +use swc_core::ecma::ast::*; +use swc_core::ecma::atoms::JsWord; +use swc_core::ecma::visit::{noop_visit_type, Visit}; + +pub struct FontImportsGenerator<'a> { + pub state: &'a mut super::State, +} + +impl<'a> FontImportsGenerator<'a> { + fn check_call_expr(&mut self, call_expr: &CallExpr) -> Option { + if let Callee::Expr(callee_expr) = &call_expr.callee { + if let Expr::Ident(ident) = &**callee_expr { + if let Some(font_function) = self.state.font_functions.get(&ident.to_id()) { + self.state + .font_functions_in_allowed_scope + .insert(ident.span.lo); + + let json: Result, ()> = call_expr + .args + .iter() + .map(|expr_or_spread| { + if let Some(span) = expr_or_spread.spread { + HANDLER.with(|handler| { + handler + .struct_span_err(span, "Font loaders don't accept spreads") + .emit() + }); + } + + expr_to_json(&*expr_or_spread.expr) + }) + .collect(); + + if let Ok(json) = json { + let mut json_values: Vec = + json.iter().map(|value| value.to_string()).collect(); + let function_name = match &font_function.function_name { + Some(function) => String::from(&**function), + None => String::new(), + }; + let mut values = vec![function_name]; + values.append(&mut json_values); + + return Some(ImportDecl { + src: Str { + value: JsWord::from(format!( + "{}?{}", + font_function.loader, + values.join(";") + )), + raw: None, + span: DUMMY_SP, + }, + specifiers: vec![], + type_only: false, + asserts: None, + span: DUMMY_SP, + }); + } + } + } + } + + None + } + + fn check_var_decl(&mut self, var_decl: &VarDecl) { + if let Some(decl) = var_decl.decls.get(0) { + let ident = match &decl.name { + Pat::Ident(ident) => Ok(ident.id.clone()), + pattern => Err(pattern), + }; + if let Some(expr) = &decl.init { + if let Expr::Call(call_expr) = &**expr { + let import_decl = self.check_call_expr(call_expr); + + if let Some(mut import_decl) = import_decl { + self.state.removeable_module_items.insert(var_decl.span.lo); + + match var_decl.kind { + VarDeclKind::Const => {} + _ => { + HANDLER.with(|handler| { + handler + .struct_span_err( + var_decl.span, + "Font loader calls must be assigned to a const", + ) + .emit() + }); + } + } + + match ident { + Ok(ident) => { + import_decl.specifiers = + vec![ImportSpecifier::Default(ImportDefaultSpecifier { + span: DUMMY_SP, + local: ident, + })]; + + self.state + .font_imports + .push(ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl))); + } + Err(pattern) => { + HANDLER.with(|handler| { + handler + .struct_span_err( + pattern.span(), + "Font loader calls must be assigned to an identifier", + ) + .emit() + }); + } + } + } + } + } + } + } +} + +impl<'a> Visit for FontImportsGenerator<'a> { + noop_visit_type!(); + + fn visit_module_item(&mut self, item: &ModuleItem) { + if let ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) = item { + self.check_var_decl(var_decl); + } + } +} + +fn object_lit_to_json(object_lit: &ObjectLit) -> Value { + let mut values = serde_json::Map::new(); + for prop in &object_lit.props { + match prop { + PropOrSpread::Prop(prop) => match &**prop { + Prop::KeyValue(key_val) => { + let key = match &key_val.key { + PropName::Ident(ident) => Ok(String::from(&*ident.sym)), + key => { + HANDLER.with(|handler| { + handler + .struct_span_err(key.span(), "Unexpected object key type") + .emit() + }); + Err(()) + } + }; + let val = expr_to_json(&*key_val.value); + if let (Ok(key), Ok(val)) = (key, val) { + values.insert(key, val); + } + } + key => HANDLER.with(|handler| { + handler.struct_span_err(key.span(), "Unexpected key").emit(); + }), + }, + PropOrSpread::Spread(spread_span) => HANDLER.with(|handler| { + handler + .struct_span_err(spread_span.dot3_token, "Unexpected spread") + .emit(); + }), + } + } + + Value::Object(values) +} + +fn expr_to_json(expr: &Expr) -> Result { + match expr { + Expr::Lit(Lit::Str(str)) => Ok(Value::String(String::from(&*str.value))), + Expr::Lit(Lit::Bool(Bool { value, .. })) => Ok(Value::Bool(*value)), + Expr::Lit(Lit::Num(Number { value, .. })) => { + Ok(Value::Number(serde_json::Number::from_f64(*value).unwrap())) + } + Expr::Object(object_lit) => Ok(object_lit_to_json(object_lit)), + Expr::Array(ArrayLit { + elems, + span: array_span, + .. + }) => { + let elements: Result, ()> = elems + .iter() + .map(|e| { + if let Some(expr) = e { + match expr.spread { + Some(spread_span) => HANDLER.with(|handler| { + handler + .struct_span_err(spread_span, "Unexpected spread") + .emit(); + Err(()) + }), + None => expr_to_json(&*expr.expr), + } + } else { + HANDLER.with(|handler| { + handler + .struct_span_err(*array_span, "Unexpected empty value in array") + .emit(); + Err(()) + }) + } + }) + .collect(); + + elements.map(Value::Array) + } + lit => HANDLER.with(|handler| { + handler + .struct_span_err(lit.span(), "Unexpected value") + .emit(); + Err(()) + }), + } +} diff --git a/packages/next-swc/crates/core/src/next_font_loaders/mod.rs b/packages/next-swc/crates/core/src/next_font_loaders/mod.rs new file mode 100644 index 0000000000000..4acf0c9ab2a44 --- /dev/null +++ b/packages/next-swc/crates/core/src/next_font_loaders/mod.rs @@ -0,0 +1,77 @@ +use fxhash::FxHashSet; +use swc_core::{ + common::{collections::AHashMap, BytePos, Spanned}, + ecma::{ + ast::Id, + visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitWith}, + }, + ecma::{ast::ModuleItem, atoms::JsWord}, +}; + +mod find_functions_outside_module_scope; +mod font_functions_collector; +mod font_imports_generator; + +pub fn next_font_loaders(font_loaders: Vec) -> impl Fold + VisitMut { + as_folder(NextFontLoaders { + font_loaders, + state: State { + ..Default::default() + }, + }) +} + +#[derive(Debug)] +pub struct FontFunction { + loader: JsWord, + function_name: Option, +} +#[derive(Debug, Default)] +pub struct State { + font_functions: AHashMap, + removeable_module_items: FxHashSet, + font_imports: Vec, + font_functions_in_allowed_scope: FxHashSet, +} + +struct NextFontLoaders { + font_loaders: Vec, + state: State, +} + +impl VisitMut for NextFontLoaders { + noop_visit_mut_type!(); + + fn visit_mut_module_items(&mut self, items: &mut Vec) { + // Find imported functions from font loaders + let mut functions_collector = font_functions_collector::FontFunctionsCollector { + font_loaders: &self.font_loaders, + state: &mut self.state, + }; + items.visit_with(&mut functions_collector); + + if !self.state.removeable_module_items.is_empty() { + // Generate imports from font function calls + let mut import_generator = font_imports_generator::FontImportsGenerator { + state: &mut self.state, + }; + items.visit_with(&mut import_generator); + + // Find font function refs in wrong scope + let mut wrong_scope = + find_functions_outside_module_scope::FindFunctionsOutsideModuleScope { + state: &self.state, + }; + items.visit_with(&mut wrong_scope); + + // Remove marked module items + items.retain(|item| !self.state.removeable_module_items.contains(&item.span_lo())); + + // Add font imports + let mut new_items = Vec::new(); + new_items.append(&mut self.state.font_imports); + new_items.append(items); + *items = new_items; + } + } +} diff --git a/packages/next-swc/crates/core/tests/errors.rs b/packages/next-swc/crates/core/tests/errors.rs index 5571406e6a22b..83df8f2851280 100644 --- a/packages/next-swc/crates/core/tests/errors.rs +++ b/packages/next-swc/crates/core/tests/errors.rs @@ -1,6 +1,7 @@ use next_swc::{ disallow_re_export_all_in_page::disallow_re_export_all_in_page, next_dynamic::next_dynamic, - next_ssg::next_ssg, react_server_components::server_components, + next_font_loaders::next_font_loaders, next_ssg::next_ssg, + react_server_components::server_components, }; use std::path::PathBuf; use swc_core::{ @@ -94,3 +95,14 @@ fn react_server_components_client_graph_errors(input: PathBuf) { &output, ); } + +#[fixture("tests/errors/next-font-loaders/**/input.js")] +fn next_font_loaders_errors(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + test_fixture_allowing_error( + syntax(), + &|_tr| next_font_loaders(vec!["@next/font/google".into(), "cool-fonts".into()]), + &input, + &output, + ); +} diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/input.js new file mode 100644 index 0000000000000..27cc0a2526ce1 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/input.js @@ -0,0 +1 @@ +import * as googleFonts from '@next/font/google' diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/output.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/output.stderr new file mode 100644 index 0000000000000..c9d0e143c7c65 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/import-all/output.stderr @@ -0,0 +1,6 @@ + + x Font loaders can't have namespace imports + ,-[input.js:1:1] + 1 | import * as googleFonts from '@next/font/google' + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/input.js new file mode 100644 index 0000000000000..11316faab0959 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/input.js @@ -0,0 +1,11 @@ +import { Inter } from '@next/font/google' + +var i = 10 +var inter1 = Inter({ + variant: '400', +}) + +var i2 = 20 +let inter2 = Inter({ + variant: '400', +}) diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.js new file mode 100644 index 0000000000000..7048782e0b9d9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.js @@ -0,0 +1,4 @@ +import inter1 from '@next/font/google?Inter;{"variant":"400"}'; +import inter2 from '@next/font/google?Inter;{"variant":"400"}'; +var i = 10; +var i2 = 20; diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.stderr new file mode 100644 index 0000000000000..f1ca698810bc9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-const/output.stderr @@ -0,0 +1,14 @@ + + x Font loader calls must be assigned to a const + ,-[input.js:4:1] + 4 | ,-> var inter1 = Inter({ + 5 | | variant: '400', + 6 | `-> }) + `---- + + x Font loader calls must be assigned to a const + ,-[input.js:9:1] + 9 | ,-> let inter2 = Inter({ + 10 | | variant: '400', + 11 | `-> }) + `---- diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/input.js new file mode 100644 index 0000000000000..835fb846f93c1 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/input.js @@ -0,0 +1,11 @@ +import { Inter } from '@next/font/google' + +const { a } = Inter({ + variant: '400', +}) + +const [b] = Inter({ + variant: '400', +}) + +const { e } = {} diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.js new file mode 100644 index 0000000000000..ac0e441683cdc --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.js @@ -0,0 +1 @@ +const { e } = {}; diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.stderr new file mode 100644 index 0000000000000..f8e4344e9c441 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/not-ident/output.stderr @@ -0,0 +1,12 @@ + + x Font loader calls must be assigned to an identifier + ,-[input.js:3:1] + 3 | const { a } = Inter({ + : ^^^^^ + `---- + + x Font loader calls must be assigned to an identifier + ,-[input.js:7:1] + 7 | const [b] = Inter({ + : ^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/input.js new file mode 100644 index 0000000000000..98c18015f2293 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/input.js @@ -0,0 +1,13 @@ +import { ABeeZee } from '@next/font/google' + +const a = fn({ 10: 'hello' }) +const a = ABeeZee({ 10: 'hello' }) + +const a = fn({ variant: [i1] }) +const a = ABeeZee({ variant: [i1] }) + +const a = fn({ variant: () => {} }) +const a = ABeeZee({ variant: () => {} }) + +const a = fn({ ...{} }) +const a = ABeeZee({ ...{} }) diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.js new file mode 100644 index 0000000000000..073b736199d28 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.js @@ -0,0 +1,18 @@ +import a from "@next/font/google?ABeeZee;{}"; +import a from "@next/font/google?ABeeZee;{}"; +import a from "@next/font/google?ABeeZee;{}"; +import a from "@next/font/google?ABeeZee;{}"; +const a = fn({ + 10: 'hello' +}); +const a = fn({ + variant: [ + i1 + ] +}); +const a = fn({ + variant: ()=>{} +}); +const a = fn({ + ...{} +}); diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.stderr new file mode 100644 index 0000000000000..ec93d2affb8c1 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/options-object/output.stderr @@ -0,0 +1,24 @@ + + x Unexpected object key type + ,-[input.js:4:1] + 4 | const a = ABeeZee({ 10: 'hello' }) + : ^^ + `---- + + x Unexpected value + ,-[input.js:7:1] + 7 | const a = ABeeZee({ variant: [i1] }) + : ^^ + `---- + + x Unexpected value + ,-[input.js:10:1] + 10 | const a = ABeeZee({ variant: () => {} }) + : ^^^^^^^^ + `---- + + x Unexpected spread + ,-[input.js:13:1] + 13 | const a = ABeeZee({ ...{} }) + : ^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/input.js new file mode 100644 index 0000000000000..2f16f3375aedc --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/input.js @@ -0,0 +1,4 @@ +import { Inter } from '@next/font/google' + +const a = fn(...{}, ...[]) +const inter = Inter(...{}, ...[]) diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.js new file mode 100644 index 0000000000000..786e60366018d --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.js @@ -0,0 +1,2 @@ +import inter from "@next/font/google?Inter;{};[]"; +const a = fn(...{}, ...[]); diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.stderr new file mode 100644 index 0000000000000..29b46cdfe6061 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/spread-arg/output.stderr @@ -0,0 +1,12 @@ + + x Font loaders don't accept spreads + ,-[input.js:4:1] + 4 | const inter = Inter(...{}, ...[]) + : ^^^ + `---- + + x Font loaders don't accept spreads + ,-[input.js:4:1] + 4 | const inter = Inter(...{}, ...[]) + : ^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/input.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/input.js new file mode 100644 index 0000000000000..ee86a223e6409 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/input.js @@ -0,0 +1,24 @@ +import { Aladin } from '@next/font/google' + +Aladin({}) + +let b +const a = (b = Aladin({ variant: '400' })) + +function Hello() { + const a = Aladin({ + variant: '400', + }) +} + +class C { + constructor() { + Aladin({ + variant: '400', + }) + } +} + +{ + Aladin({}) +} diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.js b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.js new file mode 100644 index 0000000000000..4ed290cf5baa1 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.js @@ -0,0 +1,20 @@ +Aladin({}); +let b; +const a = b = Aladin({ + variant: '400' +}); +function Hello() { + const a = Aladin({ + variant: '400' + }); +} +class C { + constructor(){ + Aladin({ + variant: '400' + }); + } +} +{ + Aladin({}); +} diff --git a/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.stderr b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.stderr new file mode 100644 index 0000000000000..120dbbad406c2 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/next-font-loaders/wrong-scope/output.stderr @@ -0,0 +1,30 @@ + + x Font loaders must be called and assigned to a const in the module scope + ,-[input.js:3:1] + 3 | Aladin({}) + : ^^^^^^ + `---- + + x Font loaders must be called and assigned to a const in the module scope + ,-[input.js:6:1] + 6 | const a = (b = Aladin({ variant: '400' })) + : ^^^^^^ + `---- + + x Font loaders must be called and assigned to a const in the module scope + ,-[input.js:9:3] + 9 | const a = Aladin({ + : ^^^^^^ + `---- + + x Font loaders must be called and assigned to a const in the module scope + ,-[input.js:16:5] + 16 | Aladin({ + : ^^^^^^ + `---- + + x Font loaders must be called and assigned to a const in the module scope + ,-[input.js:23:3] + 23 | Aladin({}) + : ^^^^^^ + `---- diff --git a/packages/next-swc/crates/core/tests/fixture.rs b/packages/next-swc/crates/core/tests/fixture.rs index ff6b8a230f13c..2df79b9a0002e 100644 --- a/packages/next-swc/crates/core/tests/fixture.rs +++ b/packages/next-swc/crates/core/tests/fixture.rs @@ -1,6 +1,7 @@ use next_swc::{ amp_attributes::amp_attributes, next_dynamic::next_dynamic, + next_font_loaders::next_font_loaders, next_ssg::next_ssg, page_config::page_config_test, react_remove_properties::remove_properties, @@ -248,3 +249,14 @@ fn react_server_components_client_graph_fixture(input: PathBuf) { &output, ); } + +#[fixture("tests/fixture/next-font-loaders/**/input.js")] +fn next_font_loaders_fixture(input: PathBuf) { + let output = input.parent().unwrap().join("output.js"); + test_fixture( + syntax(), + &|_tr| next_font_loaders(vec!["@next/font/google".into(), "cool-fonts".into()]), + &input, + &output, + ); +} diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/input.js new file mode 100644 index 0000000000000..d1673816d856c --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/input.js @@ -0,0 +1,3 @@ +import cool from 'cool-fonts' + +const font = cool({ prop: true }) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/output.js new file mode 100644 index 0000000000000..dddf305e87bc5 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/default-import/output.js @@ -0,0 +1 @@ +import font from 'cool-fonts?;{"prop":true}'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/input.js new file mode 100644 index 0000000000000..dfaf75ad16bf9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/input.js @@ -0,0 +1,8 @@ +import React from 'react' +import { Abel, Inter } from '@next/font/google' + +const firaCode = Abel() +const inter = Inter() + +export { firaCode } +export default inter diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/output.js new file mode 100644 index 0000000000000..c852f5621a5cb --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/exports/output.js @@ -0,0 +1,5 @@ +import firaCode from "@next/font/google?Abel"; +import inter from "@next/font/google?Inter"; +import React from 'react'; +export { firaCode }; +export default inter; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/input.js new file mode 100644 index 0000000000000..6506b7c4ab879 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/input.js @@ -0,0 +1,11 @@ +import React from 'react' +import { Fira_Code } from '@next/font/google' + +const firaCode = Fira_Code({ + variant: '400', + fallback: ['system-ui', { key: false }, []], + preload: true, + key: { key2: {} }, +}) + +console.log(firaCode) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/output.js new file mode 100644 index 0000000000000..3e921aeb54bf0 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/font-options/output.js @@ -0,0 +1,3 @@ +import firaCode from '@next/font/google?Fira_Code;{"fallback":["system-ui",{"key":false},[]],"key":{"key2":{}},"preload":true,"variant":"400"}'; +import React from 'react'; +console.log(firaCode); diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/input.js new file mode 100644 index 0000000000000..98a23c5752cbc --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/input.js @@ -0,0 +1,6 @@ +import React from 'react' +import { Acme as a } from 'cool-fonts' + +const acme1 = a({ + variant: '400', +}) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/output.js new file mode 100644 index 0000000000000..a1e93892abaa6 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/import-as/output.js @@ -0,0 +1,2 @@ +import acme1 from 'cool-fonts?Acme;{"variant":"400"}'; +import React from 'react'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/input.js new file mode 100644 index 0000000000000..9745fd807b4a9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/input.js @@ -0,0 +1,3 @@ +import { Geo } from '@next/font/google' + +const geo = Geo('test', [1], { a: 2 }, 3) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/output.js new file mode 100644 index 0000000000000..fb4b3804ff6eb --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/many-args/output.js @@ -0,0 +1 @@ +import geo from '@next/font/google?Geo;"test";[1.0];{"a":2.0};3.0'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/input.js new file mode 100644 index 0000000000000..91f5be7cb56d9 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/input.js @@ -0,0 +1,12 @@ +import React from 'react' +import { Inter } from '@next/font/google' + +const inter = Inter({ + variant: '900', + display: 'swap', +}) + +const inter = Inter({ + variant: '900', + display: 'swap', +}) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/output.js new file mode 100644 index 0000000000000..32af21eb3d17d --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-calls/output.js @@ -0,0 +1,3 @@ +import inter from '@next/font/google?Inter;{"display":"swap","variant":"900"}'; +import inter from '@next/font/google?Inter;{"display":"swap","variant":"900"}'; +import React from 'react'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/input.js new file mode 100644 index 0000000000000..3fc947f12667a --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/input.js @@ -0,0 +1,12 @@ +import React from 'react' +import { Inter } from '@next/font/google' +import { Fira_Code } from 'cool-fonts' + +const inter = Inter({ + variant: '900', +}) + +const fira = Fira_Code({ + variant: '400', + display: 'swap', +}) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/output.js new file mode 100644 index 0000000000000..b85bbd333f678 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-font-downloaders/output.js @@ -0,0 +1,3 @@ +import inter from '@next/font/google?Inter;{"variant":"900"}'; +import fira from 'cool-fonts?Fira_Code;{"display":"swap","variant":"400"}'; +import React from 'react'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/input.js new file mode 100644 index 0000000000000..14b5cd6c716f2 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/input.js @@ -0,0 +1,11 @@ +import React from 'react' +import { Fira_Code, Inter } from '@next/font/google' + +const firaCode = Fira_Code({ + variant: '400', + fallback: ['system-ui'], +}) +const inter = Inter({ + variant: '900', + display: 'swap', +}) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/output.js new file mode 100644 index 0000000000000..88a0f1bb6b416 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-fonts/output.js @@ -0,0 +1,3 @@ +import firaCode from '@next/font/google?Fira_Code;{"fallback":["system-ui"],"variant":"400"}'; +import inter from '@next/font/google?Inter;{"display":"swap","variant":"900"}'; +import React from 'react'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/input.js new file mode 100644 index 0000000000000..61a434bde7c5f --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/input.js @@ -0,0 +1,12 @@ +import React from 'react' +import { Inter } from '@next/font/google' +import { Fira_Code } from '@next/font/google' + +const inter = Inter({ + variant: '900', +}) + +const fira = Fira_Code({ + variant: '400', + display: 'swap', +}) diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/output.js new file mode 100644 index 0000000000000..2c68623d1119e --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/multiple-imports/output.js @@ -0,0 +1,3 @@ +import inter from '@next/font/google?Inter;{"variant":"900"}'; +import fira from '@next/font/google?Fira_Code;{"display":"swap","variant":"400"}'; +import React from 'react'; diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/input.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/input.js new file mode 100644 index 0000000000000..cc7d2326b91a3 --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/input.js @@ -0,0 +1,3 @@ +import { Fira_Code } from '@next/font/google' + +const fira = Fira_Code() diff --git a/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/output.js b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/output.js new file mode 100644 index 0000000000000..f371ef0ea53cf --- /dev/null +++ b/packages/next-swc/crates/core/tests/fixture/next-font-loaders/no-args/output.js @@ -0,0 +1 @@ +import fira from "@next/font/google?Fira_Code"; diff --git a/packages/next-swc/crates/core/tests/full.rs b/packages/next-swc/crates/core/tests/full.rs index 01d081f570177..964eac330067e 100644 --- a/packages/next-swc/crates/core/tests/full.rs +++ b/packages/next-swc/crates/core/tests/full.rs @@ -65,6 +65,7 @@ fn test(input: &Path, minify: bool) { shake_exports: None, emotion: Some(assert_json("{}")), modularize_imports: None, + font_loaders: None, }; let options = options.patch(&fm);