11//! Utilities for compiling/bundling JavaScript into
22//! a single source.
33
4+ use crate :: { artifact:: Artifact , Error , ImportMap } ;
45use anyhow:: anyhow;
56use deno_emit:: {
67 bundle, BundleOptions , BundleType , EmitOptions , LoadFuture , LoadOptions , Loader ,
@@ -10,8 +11,6 @@ use deno_graph::source::LoadResponse;
1011use reqwest:: Client ;
1112use url:: Url ;
1213
13- use crate :: { artifact:: Artifact , Error } ;
14-
1514// Root module must have `.tsx` in order to be
1615// interprete as Typescript/JSX.
1716const ROOT_MODULE_URL : & str = "bundler:root.tsx" ;
@@ -20,13 +19,15 @@ const ROOT_MODULE_SCHEME: &str = "bundler";
2019struct JavaScriptLoader {
2120 root : Option < Vec < u8 > > ,
2221 client : Client ,
22+ import_map : Option < ImportMap > ,
2323}
2424
2525impl JavaScriptLoader {
26- pub fn new ( root : Option < Vec < u8 > > ) -> Self {
26+ pub fn new ( root : Option < Vec < u8 > > , import_map : Option < ImportMap > ) -> Self {
2727 Self {
2828 root,
2929 client : Client :: new ( ) ,
30+ import_map,
3031 }
3132 }
3233}
@@ -35,8 +36,8 @@ impl Loader for JavaScriptLoader {
3536 fn load ( & self , specifier : & ModuleSpecifier , _options : LoadOptions ) -> LoadFuture {
3637 let root = self . root . clone ( ) ;
3738 let client = self . client . clone ( ) ;
39+ let import_map = self . import_map . clone ( ) ;
3840 let specifier = specifier. clone ( ) ;
39-
4041 debug ! ( "Attempting to load '{}'" , specifier) ;
4142
4243 Box :: pin ( async move {
@@ -62,31 +63,55 @@ impl Loader for JavaScriptLoader {
6263 let bytes = response. bytes ( ) . await ?;
6364 let content = bytes. to_vec ( ) . into ( ) ;
6465
66+ let maybe_headers = Some (
67+ headers
68+ . into_iter ( )
69+ . filter_map ( |( h, v) | {
70+ h. map ( |header| {
71+ (
72+ header. to_string ( ) ,
73+ v. to_str ( ) . unwrap_or_default ( ) . to_string ( ) ,
74+ )
75+ } )
76+ } )
77+ . collect ( ) ,
78+ ) ;
79+ trace ! ( "maybe_headers {:#?}" , maybe_headers) ;
6580 trace ! ( "Loaded remote module: {}" , String :: from_utf8_lossy( & bytes) ) ;
6681 Ok ( Some ( LoadResponse :: Module {
6782 content,
6883 specifier,
69- maybe_headers : Some (
70- headers
71- . into_iter ( )
72- . filter_map ( |( h, v) | {
73- h. map ( |header| {
74- (
75- header. to_string ( ) ,
76- v. to_str ( ) . unwrap_or_default ( ) . to_string ( ) ,
77- )
78- } )
79- } )
80- . collect ( ) ,
81- ) ,
84+ maybe_headers,
8285 } ) )
8386 }
8487 "node" | "npm" => Err ( anyhow ! (
8588 "Could not import '{specifier}'. Node.js and NPM modules are not supported."
8689 ) ) ,
87- _ => Err ( anyhow ! (
88- "Could not import '{specifier}'. Unrecognize specifier format.'"
89- ) ) ,
90+ _ => {
91+ let specifier_str = specifier. to_string ( ) ;
92+ debug ! ( "Attempting to load {} from import map" , specifier_str) ;
93+ if let Some ( import_map) = import_map {
94+ if let Some ( module_path) = import_map. get ( & specifier_str) {
95+ let module_str = tokio:: fs:: read_to_string ( module_path) . await ?;
96+
97+ // `LoadResponse::Module` appears to require at least
98+ // *some* headers.
99+ let headers = Some ( std:: collections:: HashMap :: from ( [ (
100+ "content-type" . into ( ) ,
101+ "text/javascript" . into ( ) ,
102+ ) ] ) ) ;
103+
104+ return Ok ( Some ( LoadResponse :: Module {
105+ content : module_str. into_bytes ( ) . into ( ) ,
106+ specifier,
107+ maybe_headers : headers,
108+ } ) ) ;
109+ }
110+ }
111+ Err ( anyhow ! (
112+ "Could not import '{specifier}'. Unrecognize specifier format.'"
113+ ) )
114+ }
90115 }
91116 } )
92117 }
@@ -114,15 +139,21 @@ impl JavaScriptBundler {
114139 }
115140
116141 /// Bundle a JavaScript module via URL.
117- pub async fn bundle_from_url ( url : Url ) -> Result < Artifact , Error > {
118- let mut loader = JavaScriptLoader :: new ( None ) ;
142+ pub async fn bundle_from_url (
143+ url : Url ,
144+ import_map : Option < ImportMap > ,
145+ ) -> Result < Artifact , Error > {
146+ let mut loader = JavaScriptLoader :: new ( None , import_map) ;
119147 let emit = bundle ( url, & mut loader, None , Self :: bundle_options ( ) ) . await ?;
120148 Ok ( emit. into ( ) )
121149 }
122150
123151 /// Bundle a JavaScript module from bytes.
124- pub async fn bundle_from_bytes ( module : Vec < u8 > ) -> Result < Artifact , Error > {
125- let mut loader = JavaScriptLoader :: new ( Some ( module) ) ;
152+ pub async fn bundle_from_bytes (
153+ module : Vec < u8 > ,
154+ import_map : Option < ImportMap > ,
155+ ) -> Result < Artifact , Error > {
156+ let mut loader = JavaScriptLoader :: new ( Some ( module) , import_map) ;
126157 let emit = bundle (
127158 Url :: parse ( ROOT_MODULE_URL ) . map_err ( |error| Error :: Internal ( format ! ( "{error}" ) ) ) ?,
128159 & mut loader,
@@ -136,18 +167,25 @@ impl JavaScriptBundler {
136167 /// Spawns a blocking bundle operation on a thread dedicated to blocking
137168 /// operations. This is needed in cases where bundling is taking place e.g.,
138169 /// within a web server.
139- pub async fn bundle_from_bytes_sync ( source_code : Vec < u8 > ) -> Result < Artifact , Error > {
170+ pub async fn bundle_from_bytes_sync (
171+ source_code : Vec < u8 > ,
172+ import_map : Option < ImportMap > ,
173+ ) -> Result < Artifact , Error > {
140174 tokio:: task:: spawn_blocking ( move || {
141- tokio:: runtime:: Handle :: current ( )
142- . block_on ( JavaScriptBundler :: bundle_from_bytes ( source_code) )
175+ tokio:: runtime:: Handle :: current ( ) . block_on ( JavaScriptBundler :: bundle_from_bytes (
176+ source_code,
177+ import_map,
178+ ) )
143179 } )
144180 . await ?
145181 }
146182}
147183
148184#[ cfg( test) ]
149185pub mod tests {
150- use crate :: JavaScriptBundler ;
186+ use std:: path:: PathBuf ;
187+
188+ use crate :: { ImportMap , JavaScriptBundler } ;
151189 use anyhow:: Result ;
152190 use ct_test_fixtures:: EsmTestServer ;
153191 use ct_tracing:: ct_tracing;
@@ -165,7 +203,7 @@ pub mod tests {
165203 let mut server = EsmTestServer :: default ( ) ;
166204 let addr = server. start ( ) . await ?;
167205 let candidate = Url :: parse ( & format ! ( "http://{}/math/index.js" , addr) ) ?;
168- let bundle = JavaScriptBundler :: bundle_from_url ( candidate) . await ?;
206+ let bundle = JavaScriptBundler :: bundle_from_url ( candidate, None ) . await ?;
169207
170208 assert_math_bundle ( & bundle. component ) ;
171209
@@ -178,7 +216,7 @@ pub mod tests {
178216 let mut server = EsmTestServer :: default ( ) ;
179217 let addr = server. start ( ) . await ?;
180218 let candidate = Url :: parse ( & format ! ( "http://{}/math/index.ts" , addr) ) ?;
181- let bundle = JavaScriptBundler :: bundle_from_url ( candidate) . await ?;
219+ let bundle = JavaScriptBundler :: bundle_from_url ( candidate, None ) . await ?;
182220
183221 assert_math_bundle ( & bundle. component ) ;
184222
@@ -195,7 +233,7 @@ pub mod tests {
195233"# ,
196234 addr
197235 ) ;
198- let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) ) . await ?;
236+ let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) , None ) . await ?;
199237
200238 assert_math_bundle ( & bundle. component ) ;
201239
@@ -212,7 +250,7 @@ export const add = function add(x: number, y: number): number {
212250"#
213251 . to_string ( ) ;
214252
215- let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) ) . await ?;
253+ let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) , None ) . await ?;
216254 assert ! ( bundle. component. contains( "function add" ) ) ;
217255
218256 Ok ( ( ) )
@@ -230,12 +268,30 @@ console.log(read, write);
230268"#
231269 . to_string ( ) ;
232270
233- let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) ) . await ?;
271+ let bundle = JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) , None ) . await ?;
234272
235273 assert ! ( bundle
236274 . component
237275 . contains( "import { read, write } from \" common:io/state@0.0.1\" " ) ) ;
238276
239277 Ok ( ( ) )
240278 }
279+
280+ #[ tokio:: test]
281+ #[ ct_tracing]
282+ async fn it_bundles_modules_from_import_map ( ) -> Result < ( ) > {
283+ let fixtures_dir = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "tests/fixtures" ) ;
284+ let import_map_path = fixtures_dir. join ( "imports.json" ) ;
285+ let import_map = ImportMap :: from_path ( import_map_path) . await ?;
286+ let candidate = r#"
287+ import { add } from "test:math";
288+ console.log(add(3, 5));
289+ "#
290+ . to_string ( ) ;
291+
292+ let bundle =
293+ JavaScriptBundler :: bundle_from_bytes ( candidate. into ( ) , Some ( import_map) ) . await ?;
294+ assert ! ( bundle. component. contains( "a + b" ) ) ;
295+ Ok ( ( ) )
296+ }
241297}
0 commit comments