Skip to content

Commit

Permalink
feat(turbopack-ecmascript): use import attributes for annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
ForsakenHarmony committed Apr 24, 2024
1 parent db0eaf2 commit da02f16
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,30 @@ use swc_core::{
common::DUMMY_SP,

Check warning on line 2 in crates/turbopack-ecmascript-plugins/src/transform/directives/server_to_client_proxy.rs

View workflow job for this annotation

GitHub Actions / Rust lints

Diff in /root/actions-runner/_work/turbo/turbo/crates/turbopack-ecmascript-plugins/src/transform/directives/server_to_client_proxy.rs
ecma::{
ast::{
Expr, ExprStmt, Ident, ImportDecl, ImportSpecifier, ImportStarAsSpecifier,
KeyValueProp, Lit, Module, ModuleDecl, ModuleItem, ObjectLit, Program, Prop, PropName,
PropOrSpread, Stmt, Str,
ImportDecl, ImportSpecifier, ImportStarAsSpecifier,
Module, ModuleDecl, ModuleItem, Program,
},
utils::private_ident,
},
quote,
};
use turbopack_ecmascript::TURBOPACK_HELPER;
use turbopack_ecmascript::{annotations::with_clause, TURBOPACK_HELPER};

pub fn create_proxy_module(transition_name: &str, target_import: &str) -> Program {
let ident = private_ident!("clientProxy");
Program::Module(Module {
body: vec![
ModuleItem::Stmt(Stmt::Expr(ExprStmt {
expr: Box::new(Expr::Lit(Lit::Str(Str {
value: format!("TURBOPACK {{ transition: {transition_name} }}").into(),
raw: None,
span: DUMMY_SP,
}))),
span: DUMMY_SP,
})),
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
specifiers: vec![ImportSpecifier::Namespace(ImportStarAsSpecifier {
local: ident.clone(),
span: DUMMY_SP,
})],
src: Box::new(target_import.into()),
type_only: false,
with: Some(Box::new(ObjectLit {
span: DUMMY_SP,
props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new(TURBOPACK_HELPER.into(), DUMMY_SP)),
value: Box::new(Expr::Lit(true.into())),
})))],
})),
with: Some(with_clause(&[
(TURBOPACK_HELPER.as_str(), "true"),
("transition", transition_name),
])),
span: DUMMY_SP,
phase: Default::default(),
})),
Expand Down
194 changes: 87 additions & 107 deletions crates/turbopack-ecmascript/src/analyzer/imports.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::BTreeMap, fmt::Display, mem::take};
use std::{collections::BTreeMap, fmt::Display};

use indexmap::{IndexMap, IndexSet};
use once_cell::sync::Lazy;
Expand All @@ -14,14 +14,13 @@ use turbo_tasks::Vc;
use turbopack_core::{issue::IssueSource, source::Source};

use super::{JsValue, ModuleValue};
use crate::utils::unparen;

#[turbo_tasks::value(serialization = "auto_for_input")]
#[derive(Default, Debug, Clone, Hash, PartialOrd, Ord)]
pub struct ImportAnnotations {
// TODO store this in more structured way
#[turbo_tasks(trace_ignore)]
map: BTreeMap<JsWord, Option<JsWord>>,
map: BTreeMap<JsWord, JsWord>,
}

/// Enables a specified transtion for the annotated import
Expand All @@ -31,47 +30,63 @@ static ANNOTATION_TRANSITION: Lazy<JsWord> = Lazy::new(|| "transition".into());
static ANNOTATION_CHUNKING_TYPE: Lazy<JsWord> = Lazy::new(|| "chunking-type".into());

impl ImportAnnotations {
fn insert(&mut self, key: JsWord, value: Option<JsWord>) {
self.map.insert(key, value);
}
pub fn parse(with: Option<&Box<ObjectLit>>) -> ImportAnnotations {
let Some(with) = with else {
return ImportAnnotations::default();
};

let mut map = BTreeMap::new();

// The `with` clause is way more restrictive than `ObjectLit`, it only allows
// string -> value and value can only be a string.
// We just ignore everything else here till the SWC ast is more restrictive.
for (key, value) in with.props.iter().filter_map(|prop| {
let kv = prop.as_prop()?.as_key_value()?;

let Lit::Str(str) = kv.value.as_lit()? else {
return None;
};

Some((&kv.key, str))
}) {
let key = match key {
PropName::Ident(ident) => ident.sym.as_str(),
PropName::Str(str) => str.value.as_str(),
// the rest are invalid, ignore for now till SWC ast is correct
_ => continue,
};

map.insert(key.into(), value.value.as_str().into());
}

fn clear(&mut self) {
self.map.clear();
ImportAnnotations { map }
}

/// Returns the content on the transition annotation
pub fn transition(&self) -> Option<&str> {
self.map
.get(&ANNOTATION_TRANSITION)
.and_then(|w| w.as_ref().map(|w| &**w))
self.get(&ANNOTATION_TRANSITION)
}

/// Returns the content on the chunking-type annotation
pub fn chunking_type(&self) -> Option<&str> {
self.map
.get(&ANNOTATION_CHUNKING_TYPE)
.and_then(|w| w.as_ref().map(|w| &**w))
self.get(&ANNOTATION_CHUNKING_TYPE)
}

pub fn get(&self, key: &JsWord) -> Option<&str> {
self.map.get(key).map(|w| w.as_str())
}
}

impl Display for ImportAnnotations {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut it = self.map.iter();
if let Some((k, v)) = it.next() {
if let Some(v) = v {
write!(f, "{{ {k}: {v}")?
} else {
write!(f, "{{ {k}")?
}
write!(f, "{{ {k}: {v}")?
} else {
return f.write_str("{}");
};
for (k, v) in it {
if let Some(v) = v {
write!(f, "; {k}: {v}")?
} else {
write!(f, "; {k}")?
}
write!(f, ", {k}: {v}")?
}
f.write_str(" }")
}
Expand Down Expand Up @@ -172,7 +187,6 @@ impl ImportMap {

m.visit_with(&mut Analyzer {
data: &mut data,
current_annotations: ImportAnnotations::default(),
source,
});

Expand All @@ -182,7 +196,6 @@ impl ImportMap {

struct Analyzer<'a> {
data: &'a mut ImportMap,
current_annotations: ImportAnnotations,
source: Option<Vc<Box<dyn Source>>>,
}

Expand Down Expand Up @@ -222,42 +235,9 @@ fn to_word(name: &ModuleExportName) -> JsWord {
}

impl Visit for Analyzer<'_> {
fn visit_module_item(&mut self, n: &ModuleItem) {
if let ModuleItem::Stmt(Stmt::Expr(ExprStmt { expr, .. })) = n {
if let Expr::Lit(Lit::Str(s)) = unparen(expr) {
if s.value.starts_with("TURBOPACK") {
let value = &*s.value;
let value = value["TURBOPACK".len()..].trim();
if !value.starts_with('{') || !value.ends_with('}') {
// TODO report issue
} else {
value[1..value.len() - 1]
.trim()
.split(';')
.map(|p| p.trim())
.filter(|p| !p.is_empty())
.for_each(|part| {
if let Some(colon) = part.find(':') {
self.current_annotations.insert(
part[..colon].trim_end().into(),
Some(part[colon + 1..].trim_start().into()),
);
} else {
self.current_annotations.insert(part.into(), None);
}
})
}
n.visit_children_with(self);
return;
}
}
}
n.visit_children_with(self);
self.current_annotations.clear();
}

fn visit_import_decl(&mut self, import: &ImportDecl) {
let annotations = take(&mut self.current_annotations);
let annotations = ImportAnnotations::parse(import.with.as_ref());

self.ensure_reference(
import.span,
import.src.value.clone(),
Expand Down Expand Up @@ -295,7 +275,8 @@ impl Visit for Analyzer<'_> {
fn visit_export_all(&mut self, export: &ExportAll) {
self.data.has_exports = true;

let annotations = take(&mut self.current_annotations);
let annotations = ImportAnnotations::parse(export.with.as_ref());

self.ensure_reference(
export.span,
export.src.value.clone(),
Expand All @@ -313,53 +294,52 @@ impl Visit for Analyzer<'_> {

fn visit_named_export(&mut self, export: &NamedExport) {
self.data.has_exports = true;
if let Some(ref src) = export.src {
let annotations = take(&mut self.current_annotations);

self.ensure_reference(
export.span,
src.value.clone(),
ImportedSymbol::ModuleEvaluation,
annotations.clone(),
);
let Some(ref src) = export.src else {
return;
};

let annotations = ImportAnnotations::parse(export.with.as_ref());

for spec in export.specifiers.iter() {
let symbol = get_import_symbol_from_export(spec);

let i = self.ensure_reference(
export.span,
src.value.clone(),
symbol,
annotations.clone(),
);

match spec {
ExportSpecifier::Namespace(n) => {
self.data.reexports.push((
i,
Reexport::Namespace {
exported: to_word(&n.name),
},
));
}
ExportSpecifier::Default(d) => {
self.data.reexports.push((
i,
Reexport::Named {
imported: js_word!("default"),
exported: d.exported.sym.clone(),
},
));
}
ExportSpecifier::Named(n) => {
self.data.reexports.push((
i,
Reexport::Named {
imported: to_word(&n.orig),
exported: to_word(n.exported.as_ref().unwrap_or(&n.orig)),
},
));
}
self.ensure_reference(
export.span,
src.value.clone(),
ImportedSymbol::ModuleEvaluation,
annotations.clone(),
);

for spec in export.specifiers.iter() {
let symbol = get_import_symbol_from_export(spec);

let i =
self.ensure_reference(export.span, src.value.clone(), symbol, annotations.clone());

match spec {
ExportSpecifier::Namespace(n) => {
self.data.reexports.push((
i,
Reexport::Namespace {
exported: to_word(&n.name),
},
));
}
ExportSpecifier::Default(d) => {
self.data.reexports.push((
i,
Reexport::Named {
imported: js_word!("default"),
exported: d.exported.sym.clone(),
},
));
}
ExportSpecifier::Named(n) => {
self.data.reexports.push((
i,
Reexport::Named {
imported: to_word(&n.orig),
exported: to_word(n.exported.as_ref().unwrap_or(&n.orig)),
},
));
}
}
}
Expand Down
28 changes: 28 additions & 0 deletions crates/turbopack-ecmascript/src/annotations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use swc_core::{
common::DUMMY_SP,
ecma::ast::{Expr, KeyValueProp, ObjectLit, Prop, PropName, PropOrSpread},
};

pub fn with_chunking_type(chunking_type: &str) -> Box<ObjectLit> {
with_clause(&[("chunking-type", chunking_type)])
}

pub fn with_transition(transition_name: &str) -> Box<ObjectLit> {
with_clause(&[("transition", transition_name)])
}

pub fn with_clause<'a>(
entries: impl IntoIterator<Item = &'a (&'a str, &'a str)>,
) -> Box<ObjectLit> {
Box::new(ObjectLit {
span: DUMMY_SP,
props: entries.into_iter().map(|(k, v)| with_prop(k, v)).collect(),
})
}

fn with_prop(key: &str, value: &str) -> PropOrSpread {
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Str(key.into()),
value: Box::new(Expr::Lit(value.into())),
})))
}
1 change: 1 addition & 0 deletions crates/turbopack-ecmascript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod tree_shake;
pub mod typescript;

Check warning on line 29 in crates/turbopack-ecmascript/src/lib.rs

View workflow job for this annotation

GitHub Actions / Rust lints

Diff in /root/actions-runner/_work/turbo/turbo/crates/turbopack-ecmascript/src/lib.rs
pub mod utils;
pub mod webpack;
pub mod annotations;

use std::fmt::{Display, Formatter};

Expand Down
2 changes: 1 addition & 1 deletion crates/turbopack-ecmascript/src/references/esm/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl ValueToString for EsmAssetReference {
#[turbo_tasks::function]
async fn to_string(&self) -> Result<Vc<String>> {
Ok(Vc::cell(format!(
"import {} {}",
"import {} with {}",
self.request.to_string().await?,
self.annotations
)))
Expand Down
Loading

0 comments on commit da02f16

Please sign in to comment.