Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

next-swc: add next-font-loaders to crates/core #40221

Merged
merged 7 commits into from Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/next-swc/crates/core/src/lib.rs
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -105,6 +107,9 @@ pub struct TransformOptions {

#[serde(default)]
pub modularize_imports: Option<modularize_imports::Config>,

#[serde(default)]
pub font_loaders: Option<Vec<JsWord>>,
}

pub fn custom_before_pass<'a, C: Comments + 'a>(
Expand Down Expand Up @@ -195,6 +200,11 @@ pub fn custom_before_pass<'a, C: Comments + 'a>(
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()),
}
)
}
Expand Down
@@ -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()
});
}
}
}
@@ -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()
});
}
}
}
}
}
}
@@ -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<ImportDecl> {
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<Vec<Value>, ()> = 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<String> =
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<Value, ()> {
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<Vec<Value>, ()> = 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(())
}),
}
}