Skip to content

Commit ce16f86

Browse files
committed
feat: redo how we transpile JSR packages for NPM
This fixes various bugs, and introduces some features. Specifically: - Source code is now included in the bundle to enable better "Go to source definition" - More usecases using `/// <reference types="..." />` work correctly now, especially when d.ts file is not a sibling of the JS file. - We strip `/// <reference types="..." />` out of JS files correctly now. - We transpile `npm:` and `jsr:` specifiers in user provided `.d.ts` files now.
1 parent 783cc8b commit ce16f86

14 files changed

+1075
-399
lines changed

api/src/analysis.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async fn analyze_package_inner(
242242

243243
let npm_tarball = create_npm_tarball(NpmTarballOptions {
244244
graph: &graph,
245-
sources: &module_analyzer.analyzer,
245+
analyzer: &module_analyzer.analyzer,
246246
registry_url: registry.registry_url(),
247247
scope: &scope,
248248
package: &name,
@@ -661,7 +661,7 @@ async fn rebuild_npm_tarball_inner(
661661

662662
let npm_tarball = create_npm_tarball(NpmTarballOptions {
663663
graph: &graph,
664-
sources: &module_analyzer.analyzer,
664+
analyzer: &module_analyzer.analyzer,
665665
registry_url: registry.registry_url(),
666666
scope: &scope,
667667
package: &name,

api/src/npm/emit.rs

Lines changed: 66 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -2,215 +2,101 @@
22

33
use deno_ast::emit;
44
use deno_ast::fold_program;
5-
use deno_ast::swc::ast::CallExpr;
6-
use deno_ast::swc::ast::Callee;
7-
use deno_ast::swc::ast::ExportAll;
8-
use deno_ast::swc::ast::Expr;
9-
use deno_ast::swc::ast::ExprOrSpread;
10-
use deno_ast::swc::ast::ImportDecl;
11-
use deno_ast::swc::ast::Lit;
12-
use deno_ast::swc::ast::Module;
13-
use deno_ast::swc::ast::NamedExport;
14-
use deno_ast::swc::ast::Str;
155
use deno_ast::swc::common::Globals;
166
use deno_ast::swc::common::Mark;
17-
use deno_ast::swc::visit::as_folder;
18-
use deno_ast::swc::visit::noop_visit_mut_type;
19-
use deno_ast::swc::visit::FoldWith;
20-
use deno_ast::swc::visit::VisitMut;
217
use deno_ast::swc::visit::VisitMutWith;
8+
use deno_ast::EmittedSource;
229
use deno_ast::ParsedSource;
2310
use deno_ast::SourceMap;
2411
use deno_ast::SourceMapOption;
25-
use url::Url;
12+
use deno_ast::TranspileOptions;
13+
use deno_graph::FastCheckTypeModule;
2614

27-
use super::specifiers::rewrite_specifier;
15+
use crate::npm::import_transform::ImportRewriteTransformer;
2816

29-
// todo: a lot of code is duplicated with `ParsedSource::transpile` because we
30-
// can't fold a `ParsedSource` directly.
31-
pub fn transpile_to_js(
32-
source: ParsedSource,
33-
source_url: Url,
17+
use super::specifiers::RewriteKind;
18+
use super::specifiers::SpecifierRewriter;
19+
20+
pub fn transpile_to_js<'a>(
21+
source: &ParsedSource,
22+
specifier_rewriter: SpecifierRewriter<'a>,
3423
) -> Result<String, anyhow::Error> {
35-
let transpile_options = deno_ast::TranspileOptions {
36-
// FIXME: JSX?
37-
..Default::default()
38-
};
3924
let emit_options = deno_ast::EmitOptions {
4025
source_map: SourceMapOption::Inline,
41-
inline_sources: true,
26+
inline_sources: false,
4227
keep_comments: true,
4328
};
4429

30+
let file_name = source.specifier().path().split('/').last().unwrap();
4531
let source_map =
46-
SourceMap::single(source_url, source.text_info().text_str().to_string());
32+
SourceMap::single(file_name, source.text_info().text_str().to_owned());
4733

48-
let mut folder = as_folder(NpmImportTransform);
49-
let program = source.program_ref().clone().fold_with(&mut folder);
34+
let mut program = source.program_ref().clone();
35+
36+
let mut import_rewrite_transformer = ImportRewriteTransformer {
37+
specifier_rewriter,
38+
kind: RewriteKind::Source,
39+
};
40+
program.visit_mut_with(&mut import_rewrite_transformer);
5041

51-
// needs to align with what's done internally in source map
52-
assert_eq!(1, source.text_info().range().start.as_byte_pos().0);
53-
// we need the comments to be mutable, so make it single threaded
5442
let comments = source.comments().as_single_threaded();
43+
44+
let transpile_options = TranspileOptions {
45+
use_decorators_proposal: true,
46+
use_ts_decorators: false,
47+
48+
// TODO: JSX
49+
..Default::default()
50+
};
51+
5552
let globals = Globals::new();
56-
deno_ast::swc::common::GLOBALS.set(&globals, || {
53+
let program = deno_ast::swc::common::GLOBALS.set(&globals, || {
5754
let top_level_mark = Mark::fresh(Mark::root());
58-
let program = fold_program(
55+
56+
fold_program(
5957
program,
6058
&transpile_options,
6159
&source_map,
6260
&comments,
6361
top_level_mark,
6462
source.diagnostics(),
65-
)?;
63+
)
64+
})?;
6665

67-
let emitted = emit(&program, &comments, &source_map, &emit_options)?;
66+
let emitted = emit(&program, &comments, &source_map, &emit_options)?;
6867

69-
Ok(emitted.text)
70-
})
68+
Ok(emitted.text)
7169
}
7270

73-
pub struct NpmImportTransform;
74-
75-
impl VisitMut for NpmImportTransform {
76-
noop_visit_mut_type!();
77-
78-
fn visit_mut_module(&mut self, module: &mut Module) {
79-
module.visit_mut_children_with(self);
80-
}
81-
82-
fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) {
83-
node.visit_mut_children_with(self);
84-
85-
if let Some(remapped) = rewrite_specifier(&node.src.value) {
86-
node.src = Box::new(remapped.into());
87-
}
88-
}
89-
90-
fn visit_mut_named_export(&mut self, node: &mut NamedExport) {
91-
node.visit_mut_children_with(self);
92-
93-
if let Some(src) = &node.src {
94-
if let Some(remapped) = rewrite_specifier(&src.value) {
95-
node.src = Some(Box::new(remapped.into()));
96-
}
97-
}
98-
}
99-
100-
fn visit_mut_export_all(&mut self, node: &mut ExportAll) {
101-
node.visit_mut_children_with(self);
102-
103-
if let Some(remapped) = rewrite_specifier(&node.src.value) {
104-
node.src = Box::new(remapped.into());
105-
}
106-
}
107-
108-
fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
109-
node.visit_mut_children_with(self);
110-
111-
if let Callee::Import(_) = node.callee {
112-
if let Some(arg) = node.args.first() {
113-
if let Expr::Lit(Lit::Str(lit_str)) = *arg.expr.clone() {
114-
let maybe_rewritten = rewrite_specifier(&lit_str.value);
115-
if let Some(rewritten) = maybe_rewritten {
116-
let replacer = Expr::Lit(Lit::Str(Str {
117-
span: lit_str.span,
118-
value: rewritten.into(),
119-
raw: None,
120-
}));
121-
node.args[0] = ExprOrSpread {
122-
spread: None,
123-
expr: Box::new(replacer),
124-
};
125-
}
126-
}
127-
}
128-
}
129-
}
130-
}
71+
pub fn transpile_to_dts<'a>(
72+
source: &ParsedSource,
73+
fast_check_module: &FastCheckTypeModule,
74+
specifier_rewriter: SpecifierRewriter<'a>,
75+
) -> Result<String, anyhow::Error> {
76+
let dts = fast_check_module.dts.as_ref().unwrap();
77+
78+
let emit_options = deno_ast::EmitOptions {
79+
source_map: SourceMapOption::Inline,
80+
inline_sources: false,
81+
keep_comments: true,
82+
};
83+
84+
let file_name = source.specifier().path().split('/').last().unwrap();
85+
let source_map =
86+
SourceMap::single(file_name, source.text_info().text_str().to_owned());
87+
88+
let mut program = dts.program.clone();
89+
90+
let mut import_rewrite_transformer = ImportRewriteTransformer {
91+
specifier_rewriter,
92+
kind: RewriteKind::Declaration,
93+
};
94+
program.visit_mut_with(&mut import_rewrite_transformer);
95+
96+
let comments = dts.comments.as_single_threaded();
97+
98+
let EmittedSource { text, .. } =
99+
emit(&program, &comments, &source_map, &emit_options)?;
131100

132-
#[cfg(test)]
133-
mod tests {
134-
use deno_ast::ModuleSpecifier;
135-
use deno_ast::ParseParams;
136-
use deno_ast::SourceTextInfo;
137-
138-
use super::transpile_to_js;
139-
140-
fn test_transform(source: &str, expect: &str) {
141-
let specifier = "file:///main.ts";
142-
143-
let parsed_source = deno_ast::parse_module(ParseParams {
144-
specifier: ModuleSpecifier::parse(specifier).unwrap(),
145-
media_type: deno_ast::MediaType::TypeScript,
146-
text_info: SourceTextInfo::new(source.into()),
147-
capture_tokens: false,
148-
scope_analysis: false,
149-
maybe_syntax: None,
150-
})
151-
.unwrap();
152-
153-
let result =
154-
transpile_to_js(parsed_source, specifier.parse().unwrap()).unwrap();
155-
let (result, _) = result
156-
.rsplit_once("\n//# sourceMappingURL=data:application/json;base64,")
157-
.unwrap();
158-
159-
assert_eq!(result.trim_end(), expect);
160-
}
161-
162-
#[test]
163-
fn test_transform_specifiers() {
164-
test_transform(r#"import "./foo/bar.ts";"#, r#"import "./foo/bar.js";"#);
165-
test_transform(r#"import "../foo.tsx";"#, r#"import "../foo.js";"#);
166-
test_transform(r#"import "jsr:@std/path";"#, r#"import "@jsr/std__path";"#);
167-
test_transform(r#"import "npm:@std/path";"#, r#"import "@std/path";"#);
168-
169-
test_transform(
170-
"import * as foo from \"./bar.ts\";\nexport { foo };",
171-
"import * as foo from \"./bar.js\";\nexport { foo };",
172-
);
173-
test_transform(
174-
"import { asd } from \"./bar.ts\";\nexport { asd };",
175-
"import { asd } from \"./bar.js\";\nexport { asd };",
176-
);
177-
test_transform(
178-
"import { asd as foo } from \"./bar.ts\";\nexport { foo };",
179-
"import { asd as foo } from \"./bar.js\";\nexport { foo };",
180-
);
181-
test_transform(
182-
"import { asd, foo } from \"./bar.ts\";\nexport { asd, foo };",
183-
"import { asd, foo } from \"./bar.js\";\nexport { asd, foo };",
184-
);
185-
test_transform(
186-
"import asd from \"./bar.ts\";\nexport { asd };",
187-
"import asd from \"./bar.js\";\nexport { asd };",
188-
);
189-
test_transform(
190-
"import asd, { foo } from \"./bar.ts\";\nexport { asd, foo };",
191-
"import asd, { foo } from \"./bar.js\";\nexport { asd, foo };",
192-
);
193-
194-
test_transform(
195-
"export * from \"./foo/bar.ts\";",
196-
"export * from \"./foo/bar.js\";",
197-
);
198-
test_transform(
199-
"export * as foo from \"./foo/bar.ts\";",
200-
"export * as foo from \"./foo/bar.js\";",
201-
);
202-
test_transform(
203-
"export { asd } from \"./foo/bar.ts\";",
204-
"export { asd } from \"./foo/bar.js\";",
205-
);
206-
test_transform(
207-
"export { asd as foo } from \"./foo/bar.ts\";",
208-
"export { asd as foo } from \"./foo/bar.js\";",
209-
);
210-
211-
test_transform(
212-
"await import(\"./foo/bar.ts\");",
213-
"await import(\"./foo/bar.js\");",
214-
);
215-
}
101+
Ok(text)
216102
}

api/src/npm/import_transform.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use deno_ast::swc::ast::CallExpr;
2+
use deno_ast::swc::ast::Callee;
3+
use deno_ast::swc::ast::ExportAll;
4+
use deno_ast::swc::ast::Expr;
5+
use deno_ast::swc::ast::ExprOrSpread;
6+
use deno_ast::swc::ast::ImportDecl;
7+
use deno_ast::swc::ast::Lit;
8+
use deno_ast::swc::ast::Module;
9+
use deno_ast::swc::ast::NamedExport;
10+
use deno_ast::swc::ast::Str;
11+
use deno_ast::swc::ast::TsImportType;
12+
use deno_ast::swc::visit::VisitMut;
13+
use deno_ast::swc::visit::VisitMutWith;
14+
15+
use super::specifiers::RewriteKind;
16+
use super::specifiers::SpecifierRewriter;
17+
18+
pub struct ImportRewriteTransformer<'a> {
19+
pub specifier_rewriter: SpecifierRewriter<'a>,
20+
pub kind: RewriteKind,
21+
}
22+
23+
impl<'a> VisitMut for ImportRewriteTransformer<'a> {
24+
fn visit_mut_module(&mut self, module: &mut Module) {
25+
module.visit_mut_children_with(self);
26+
}
27+
28+
fn visit_mut_import_decl(&mut self, node: &mut ImportDecl) {
29+
node.visit_mut_children_with(self);
30+
31+
if let Some(remapped) = self
32+
.specifier_rewriter
33+
.rewrite(&node.src.value.as_str(), self.kind)
34+
{
35+
node.src = Box::new(remapped.into());
36+
}
37+
}
38+
39+
fn visit_mut_named_export(&mut self, node: &mut NamedExport) {
40+
node.visit_mut_children_with(self);
41+
42+
if let Some(src) = &node.src {
43+
if let Some(remapped) = self
44+
.specifier_rewriter
45+
.rewrite(&src.value.as_str(), self.kind)
46+
{
47+
node.src = Some(Box::new(remapped.into()));
48+
}
49+
}
50+
}
51+
52+
fn visit_mut_export_all(&mut self, node: &mut ExportAll) {
53+
node.visit_mut_children_with(self);
54+
55+
if let Some(remapped) = self
56+
.specifier_rewriter
57+
.rewrite(&node.src.value.as_str(), self.kind)
58+
{
59+
node.src = Box::new(remapped.into());
60+
}
61+
}
62+
63+
fn visit_mut_ts_import_type(&mut self, n: &mut TsImportType) {
64+
n.visit_mut_children_with(self);
65+
66+
if let Some(remapped) = self
67+
.specifier_rewriter
68+
.rewrite(&n.arg.value.as_str(), RewriteKind::Declaration)
69+
{
70+
n.arg = remapped.into();
71+
}
72+
}
73+
74+
fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
75+
node.visit_mut_children_with(self);
76+
77+
if let Callee::Import(_) = node.callee {
78+
if let Some(arg) = node.args.first() {
79+
if let Expr::Lit(Lit::Str(lit_str)) = *arg.expr.clone() {
80+
let maybe_rewritten =
81+
self.specifier_rewriter.rewrite(&lit_str.value, self.kind);
82+
if let Some(rewritten) = maybe_rewritten {
83+
let replacer = Expr::Lit(Lit::Str(Str {
84+
span: lit_str.span,
85+
value: rewritten.into(),
86+
raw: None,
87+
}));
88+
node.args[0] = ExprOrSpread {
89+
spread: None,
90+
expr: Box::new(replacer),
91+
};
92+
}
93+
}
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)