1use std::borrow::Cow;
4use std::collections::BTreeSet;
5use std::collections::HashSet;
6use std::path::Path;
7use std::path::PathBuf;
8
9use deno_error::JsErrorBox;
10use deno_path_util::url_to_file_path;
11use futures::FutureExt;
12use futures::StreamExt;
13use futures::future::LocalBoxFuture;
14use futures::stream::FuturesUnordered;
15use once_cell::sync::Lazy;
16use serde::Deserialize;
17use serde::Serialize;
18use url::Url;
19
20use crate::InNpmPackageChecker;
21use crate::IsBuiltInNodeModuleChecker;
22use crate::NodeResolutionKind;
23use crate::NodeResolverSys;
24use crate::NpmPackageFolderResolver;
25use crate::PackageJsonResolverRc;
26use crate::PathClean;
27use crate::ResolutionMode;
28use crate::UrlOrPath;
29use crate::UrlOrPathRef;
30use crate::errors::ModuleNotFoundError;
31use crate::resolution::NodeResolverRc;
32use crate::resolution::parse_npm_pkg_name;
33
34#[derive(Debug, Clone)]
35pub enum CjsAnalysis<'a> {
36 Esm(Cow<'a, str>, Option<CjsAnalysisExports>),
39 Cjs(CjsAnalysisExports),
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct CjsAnalysisExports {
44 pub exports: Vec<String>,
45 pub reexports: Vec<String>,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum EsmAnalysisMode {
51 SourceOnly,
52 SourceImportsAndExports,
53}
54
55#[async_trait::async_trait(?Send)]
57pub trait CjsCodeAnalyzer {
58 async fn analyze_cjs<'a>(
66 &self,
67 specifier: &Url,
68 maybe_source: Option<Cow<'a, str>>,
69 esm_analysis_mode: EsmAnalysisMode,
70 ) -> Result<CjsAnalysis<'a>, JsErrorBox>;
71}
72
73pub enum ResolvedCjsAnalysis<'a> {
74 Esm(Cow<'a, str>),
75 Cjs(BTreeSet<String>),
76}
77
78#[sys_traits::auto_impl]
79pub trait CjsModuleExportAnalyzerSys: NodeResolverSys {}
80
81#[allow(clippy::disallowed_types)]
82pub type CjsModuleExportAnalyzerRc<
83 TCjsCodeAnalyzer,
84 TInNpmPackageChecker,
85 TIsBuiltInNodeModuleChecker,
86 TNpmPackageFolderResolver,
87 TSys,
88> = deno_maybe_sync::MaybeArc<
89 CjsModuleExportAnalyzer<
90 TCjsCodeAnalyzer,
91 TInNpmPackageChecker,
92 TIsBuiltInNodeModuleChecker,
93 TNpmPackageFolderResolver,
94 TSys,
95 >,
96>;
97
98pub struct CjsModuleExportAnalyzer<
99 TCjsCodeAnalyzer: CjsCodeAnalyzer,
100 TInNpmPackageChecker: InNpmPackageChecker,
101 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
102 TNpmPackageFolderResolver: NpmPackageFolderResolver,
103 TSys: CjsModuleExportAnalyzerSys,
104> {
105 cjs_code_analyzer: TCjsCodeAnalyzer,
106 in_npm_pkg_checker: TInNpmPackageChecker,
107 node_resolver: NodeResolverRc<
108 TInNpmPackageChecker,
109 TIsBuiltInNodeModuleChecker,
110 TNpmPackageFolderResolver,
111 TSys,
112 >,
113 npm_resolver: TNpmPackageFolderResolver,
114 pkg_json_resolver: PackageJsonResolverRc<TSys>,
115 sys: TSys,
116}
117
118impl<
119 TCjsCodeAnalyzer: CjsCodeAnalyzer,
120 TInNpmPackageChecker: InNpmPackageChecker,
121 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
122 TNpmPackageFolderResolver: NpmPackageFolderResolver,
123 TSys: CjsModuleExportAnalyzerSys,
124>
125 CjsModuleExportAnalyzer<
126 TCjsCodeAnalyzer,
127 TInNpmPackageChecker,
128 TIsBuiltInNodeModuleChecker,
129 TNpmPackageFolderResolver,
130 TSys,
131 >
132{
133 pub fn new(
134 cjs_code_analyzer: TCjsCodeAnalyzer,
135 in_npm_pkg_checker: TInNpmPackageChecker,
136 node_resolver: NodeResolverRc<
137 TInNpmPackageChecker,
138 TIsBuiltInNodeModuleChecker,
139 TNpmPackageFolderResolver,
140 TSys,
141 >,
142 npm_resolver: TNpmPackageFolderResolver,
143 pkg_json_resolver: PackageJsonResolverRc<TSys>,
144 sys: TSys,
145 ) -> Self {
146 Self {
147 cjs_code_analyzer,
148 in_npm_pkg_checker,
149 node_resolver,
150 npm_resolver,
151 pkg_json_resolver,
152 sys,
153 }
154 }
155
156 pub async fn analyze_all_exports<'a>(
157 &self,
158 entry_specifier: &Url,
159 source: Option<Cow<'a, str>>,
160 ) -> Result<ResolvedCjsAnalysis<'a>, TranslateCjsToEsmError> {
161 let analysis = self
162 .cjs_code_analyzer
163 .analyze_cjs(entry_specifier, source, EsmAnalysisMode::SourceOnly)
164 .await
165 .map_err(TranslateCjsToEsmError::CjsCodeAnalysis)?;
166
167 let analysis = match analysis {
168 CjsAnalysis::Esm(source, _) => {
169 return Ok(ResolvedCjsAnalysis::Esm(source));
170 }
171 CjsAnalysis::Cjs(analysis) => analysis,
172 };
173
174 let mut all_exports = analysis.exports.into_iter().collect::<BTreeSet<_>>();
176
177 if !analysis.reexports.is_empty() {
178 let mut errors = Vec::new();
179 self
180 .analyze_reexports(
181 entry_specifier,
182 analysis.reexports,
183 &mut all_exports,
184 &mut errors,
185 )
186 .await;
187
188 if !errors.is_empty() {
190 errors.sort_by_cached_key(|e| e.to_string());
191 return Err(TranslateCjsToEsmError::ExportAnalysis(errors.remove(0)));
192 }
193 }
194
195 Ok(ResolvedCjsAnalysis::Cjs(all_exports))
196 }
197
198 #[allow(clippy::needless_lifetimes)]
199 async fn analyze_reexports<'a>(
200 &'a self,
201 entry_specifier: &url::Url,
202 reexports: Vec<String>,
203 all_exports: &mut BTreeSet<String>,
204 errors: &mut Vec<JsErrorBox>,
207 ) {
208 struct Analysis {
209 reexport_specifier: url::Url,
210 analysis: CjsAnalysis<'static>,
211 }
212
213 type AnalysisFuture<'a> = LocalBoxFuture<'a, Result<Analysis, JsErrorBox>>;
214
215 let mut handled_reexports: HashSet<Url> = HashSet::default();
216 handled_reexports.insert(entry_specifier.clone());
217 let mut analyze_futures: FuturesUnordered<AnalysisFuture<'a>> =
218 FuturesUnordered::new();
219 let cjs_code_analyzer = &self.cjs_code_analyzer;
220 let mut handle_reexports =
221 |referrer: url::Url,
222 reexports: Vec<String>,
223 analyze_futures: &mut FuturesUnordered<AnalysisFuture<'a>>,
224 errors: &mut Vec<JsErrorBox>| {
225 for reexport in reexports {
227 let result = self
228 .resolve(
229 &reexport,
230 &referrer,
231 &[
234 Cow::Borrowed("deno"),
235 Cow::Borrowed("node"),
236 Cow::Borrowed("require"),
237 Cow::Borrowed("default"),
238 ],
239 NodeResolutionKind::Execution,
240 )
241 .and_then(|value| {
242 value
243 .map(|url_or_path| url_or_path.into_url())
244 .transpose()
245 .map_err(JsErrorBox::from_err)
246 });
247 let reexport_specifier = match result {
248 Ok(Some(specifier)) => specifier,
249 Ok(None) => continue,
250 Err(err) => {
251 errors.push(err);
252 continue;
253 }
254 };
255
256 if !handled_reexports.insert(reexport_specifier.clone()) {
257 continue;
258 }
259
260 let referrer = referrer.clone();
261 let future = async move {
262 let analysis = cjs_code_analyzer
263 .analyze_cjs(
264 &reexport_specifier,
265 None,
266 EsmAnalysisMode::SourceImportsAndExports,
267 )
268 .await
269 .map_err(|source| {
270 JsErrorBox::from_err(CjsAnalysisCouldNotLoadError {
271 reexport,
272 reexport_specifier: reexport_specifier.clone(),
273 referrer: referrer.clone(),
274 source,
275 })
276 })?;
277
278 Ok(Analysis {
279 reexport_specifier,
280 analysis,
281 })
282 }
283 .boxed_local();
284 analyze_futures.push(future);
285 }
286 };
287
288 handle_reexports(
289 entry_specifier.clone(),
290 reexports,
291 &mut analyze_futures,
292 errors,
293 );
294
295 while let Some(analysis_result) = analyze_futures.next().await {
296 let Analysis {
298 reexport_specifier,
299 analysis,
300 } = match analysis_result {
301 Ok(analysis) => analysis,
302 Err(err) => {
303 errors.push(err);
304 continue;
305 }
306 };
307 match analysis {
308 CjsAnalysis::Cjs(analysis) | CjsAnalysis::Esm(_, Some(analysis)) => {
309 if !analysis.reexports.is_empty() {
310 handle_reexports(
311 reexport_specifier.clone(),
312 analysis.reexports,
313 &mut analyze_futures,
314 errors,
315 );
316 }
317
318 all_exports.extend(
319 analysis
320 .exports
321 .into_iter()
322 .filter(|e| e.as_str() != "default"),
323 );
324 }
325 CjsAnalysis::Esm(_, None) => {
326 debug_assert!(false);
328 }
329 }
330 }
331 }
332
333 fn resolve(
335 &self,
336 specifier: &str,
337 referrer: &Url,
338 conditions: &[Cow<'static, str>],
339 resolution_kind: NodeResolutionKind,
340 ) -> Result<Option<UrlOrPath>, JsErrorBox> {
341 if specifier.starts_with('/') {
342 todo!();
343 }
344
345 let referrer = UrlOrPathRef::from_url(referrer);
346 let referrer_path = referrer.path().unwrap();
347 if specifier.starts_with("./") || specifier.starts_with("../") {
348 if let Some(parent) = referrer_path.parent() {
349 return self
350 .file_extension_probe(parent.join(specifier), referrer_path)
351 .map(|p| Some(UrlOrPath::Path(p)));
352 } else {
353 todo!();
354 }
355 }
356
357 let (package_specifier, package_subpath, _is_scoped) =
359 parse_npm_pkg_name(specifier, &referrer).map_err(JsErrorBox::from_err)?;
360
361 let module_dir = match self
362 .npm_resolver
363 .resolve_package_folder_from_package(package_specifier, &referrer)
364 {
365 Err(err)
366 if matches!(
367 err.as_kind(),
368 crate::errors::PackageFolderResolveErrorKind::PackageNotFound(..)
369 ) =>
370 {
371 return Ok(None);
372 }
373 other => other.map_err(JsErrorBox::from_err)?,
374 };
375
376 let package_json_path = module_dir.join("package.json");
377 let maybe_package_json = self
378 .pkg_json_resolver
379 .load_package_json(&package_json_path)
380 .map_err(JsErrorBox::from_err)?;
381 if let Some(package_json) = maybe_package_json {
382 if let Some(exports) = &package_json.exports {
383 return Some(
384 self
385 .node_resolver
386 .package_exports_resolve(
387 &package_json_path,
388 &package_subpath,
389 exports,
390 Some(&referrer),
391 ResolutionMode::Require,
392 conditions,
393 resolution_kind,
394 )
395 .map_err(JsErrorBox::from_err),
396 )
397 .transpose();
398 }
399
400 if package_subpath != "." {
402 let d = module_dir.join(package_subpath.as_ref());
403 if self.sys.fs_is_dir_no_err(&d) {
404 let package_json_path = d.join("package.json");
406 let maybe_package_json = self
407 .pkg_json_resolver
408 .load_package_json(&package_json_path)
409 .map_err(JsErrorBox::from_err)?;
410 if let Some(package_json) = maybe_package_json
411 && let Some(main) =
412 self.node_resolver.legacy_fallback_resolve(&package_json)
413 {
414 return Ok(Some(UrlOrPath::Path(d.join(main).clean())));
415 }
416
417 return Ok(Some(UrlOrPath::Path(d.join("index.js").clean())));
418 }
419 return self
420 .file_extension_probe(d, referrer_path)
421 .map(|p| Some(UrlOrPath::Path(p)));
422 } else if let Some(main) =
423 self.node_resolver.legacy_fallback_resolve(&package_json)
424 {
425 return Ok(Some(UrlOrPath::Path(module_dir.join(main).clean())));
426 } else {
427 return Ok(Some(UrlOrPath::Path(module_dir.join("index.js").clean())));
428 }
429 }
430
431 let mut last = referrer_path;
433 while let Some(parent) = last.parent() {
434 if !self.in_npm_pkg_checker.in_npm_package_at_dir_path(parent) {
435 break;
436 }
437 let path = if parent.ends_with("node_modules") {
438 parent.join(specifier)
439 } else {
440 parent.join("node_modules").join(specifier)
441 };
442 if let Ok(path) = self.file_extension_probe(path, referrer_path) {
443 return Ok(Some(UrlOrPath::Path(path)));
444 }
445 last = parent;
446 }
447
448 Err(JsErrorBox::from_err(ModuleNotFoundError {
449 specifier: UrlOrPath::Path(PathBuf::from(specifier)),
450 maybe_referrer: Some(UrlOrPath::Path(referrer_path.to_path_buf())),
451 suggested_ext: None,
452 }))
453 }
454
455 fn file_extension_probe(
456 &self,
457 p: PathBuf,
458 referrer: &Path,
459 ) -> Result<PathBuf, JsErrorBox> {
460 let p = p.clean();
461 if self.sys.fs_exists_no_err(&p) {
462 let file_name = p.file_name().unwrap();
463 let p_js =
464 p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
465 if self.sys.fs_is_file_no_err(&p_js) {
466 return Ok(p_js);
467 } else if self.sys.fs_is_dir_no_err(&p) {
468 return Ok(p.join("index.js"));
469 } else {
470 return Ok(p);
471 }
472 } else if let Some(file_name) = p.file_name() {
473 {
474 let p_js =
475 p.with_file_name(format!("{}.js", file_name.to_str().unwrap()));
476 if self.sys.fs_is_file_no_err(&p_js) {
477 return Ok(p_js);
478 }
479 }
480 {
481 let p_json =
482 p.with_file_name(format!("{}.json", file_name.to_str().unwrap()));
483 if self.sys.fs_is_file_no_err(&p_json) {
484 return Ok(p_json);
485 }
486 }
487 }
488 Err(JsErrorBox::from_err(ModuleNotFoundError {
489 specifier: UrlOrPath::Path(p),
490 maybe_referrer: Some(UrlOrPath::Path(referrer.to_path_buf())),
491 suggested_ext: None,
492 }))
493 }
494}
495
496#[derive(Debug, thiserror::Error, deno_error::JsError)]
497pub enum TranslateCjsToEsmError {
498 #[class(inherit)]
499 #[error(transparent)]
500 CjsCodeAnalysis(JsErrorBox),
501 #[class(inherit)]
502 #[error(transparent)]
503 ExportAnalysis(JsErrorBox),
504}
505
506#[derive(Debug, thiserror::Error, deno_error::JsError)]
507#[class(generic)]
508#[error(
509 "Could not load '{reexport}' ({reexport_specifier}) referenced from {referrer}"
510)]
511pub struct CjsAnalysisCouldNotLoadError {
512 reexport: String,
513 reexport_specifier: Url,
514 referrer: Url,
515 #[source]
516 source: JsErrorBox,
517}
518
519#[sys_traits::auto_impl]
520pub trait NodeCodeTranslatorSys: CjsModuleExportAnalyzerSys {}
521
522#[allow(clippy::disallowed_types)]
523pub type NodeCodeTranslatorRc<
524 TCjsCodeAnalyzer,
525 TInNpmPackageChecker,
526 TIsBuiltInNodeModuleChecker,
527 TNpmPackageFolderResolver,
528 TSys,
529> = deno_maybe_sync::MaybeArc<
530 NodeCodeTranslator<
531 TCjsCodeAnalyzer,
532 TInNpmPackageChecker,
533 TIsBuiltInNodeModuleChecker,
534 TNpmPackageFolderResolver,
535 TSys,
536 >,
537>;
538
539pub struct NodeCodeTranslator<
540 TCjsCodeAnalyzer: CjsCodeAnalyzer,
541 TInNpmPackageChecker: InNpmPackageChecker,
542 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
543 TNpmPackageFolderResolver: NpmPackageFolderResolver,
544 TSys: NodeCodeTranslatorSys,
545> {
546 module_export_analyzer: CjsModuleExportAnalyzerRc<
547 TCjsCodeAnalyzer,
548 TInNpmPackageChecker,
549 TIsBuiltInNodeModuleChecker,
550 TNpmPackageFolderResolver,
551 TSys,
552 >,
553 mode: NodeCodeTranslatorMode,
554}
555
556#[derive(Debug, Default, Clone, Copy)]
557pub enum NodeCodeTranslatorMode {
558 Disabled,
559 #[default]
560 ModuleLoader,
561}
562
563impl<
564 TCjsCodeAnalyzer: CjsCodeAnalyzer,
565 TInNpmPackageChecker: InNpmPackageChecker,
566 TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
567 TNpmPackageFolderResolver: NpmPackageFolderResolver,
568 TSys: NodeCodeTranslatorSys,
569>
570 NodeCodeTranslator<
571 TCjsCodeAnalyzer,
572 TInNpmPackageChecker,
573 TIsBuiltInNodeModuleChecker,
574 TNpmPackageFolderResolver,
575 TSys,
576 >
577{
578 pub fn new(
579 module_export_analyzer: CjsModuleExportAnalyzerRc<
580 TCjsCodeAnalyzer,
581 TInNpmPackageChecker,
582 TIsBuiltInNodeModuleChecker,
583 TNpmPackageFolderResolver,
584 TSys,
585 >,
586 mode: NodeCodeTranslatorMode,
587 ) -> Self {
588 Self {
589 module_export_analyzer,
590 mode,
591 }
592 }
593
594 pub async fn translate_cjs_to_esm<'a>(
601 &self,
602 entry_specifier: &Url,
603 source: Option<Cow<'a, str>>,
604 ) -> Result<Cow<'a, str>, TranslateCjsToEsmError> {
605 let all_exports = if matches!(self.mode, NodeCodeTranslatorMode::Disabled) {
606 return Ok(source.unwrap());
607 } else {
608 let analysis = self
609 .module_export_analyzer
610 .analyze_all_exports(entry_specifier, source)
611 .await?;
612
613 match analysis {
614 ResolvedCjsAnalysis::Esm(source) => return Ok(source),
615 ResolvedCjsAnalysis::Cjs(all_exports) => all_exports,
616 }
617 };
618 Ok(Cow::Owned(exports_to_wrapper_module(
619 entry_specifier,
620 &all_exports,
621 )))
622 }
623}
624
625static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
626 HashSet::from([
627 "abstract",
628 "arguments",
629 "async",
630 "await",
631 "boolean",
632 "break",
633 "byte",
634 "case",
635 "catch",
636 "char",
637 "class",
638 "const",
639 "continue",
640 "debugger",
641 "default",
642 "delete",
643 "do",
644 "double",
645 "else",
646 "enum",
647 "eval",
648 "export",
649 "extends",
650 "false",
651 "final",
652 "finally",
653 "float",
654 "for",
655 "function",
656 "get",
657 "goto",
658 "if",
659 "implements",
660 "import",
661 "in",
662 "instanceof",
663 "int",
664 "interface",
665 "let",
666 "long",
667 "mod",
668 "native",
669 "new",
670 "null",
671 "package",
672 "private",
673 "protected",
674 "public",
675 "return",
676 "set",
677 "short",
678 "static",
679 "super",
680 "switch",
681 "synchronized",
682 "this",
683 "throw",
684 "throws",
685 "transient",
686 "true",
687 "try",
688 "typeof",
689 "var",
690 "void",
691 "volatile",
692 "while",
693 "with",
694 "yield",
695 ])
696});
697
698fn exports_to_wrapper_module(
699 entry_specifier: &Url,
700 all_exports: &BTreeSet<String>,
701) -> String {
702 let quoted_entry_specifier_text = to_double_quote_string(
703 url_to_file_path(entry_specifier).unwrap().to_str().unwrap(),
704 );
705 let export_names_with_quoted = all_exports
706 .iter()
707 .map(|export| (export.as_str(), to_double_quote_string(export)))
708 .collect::<Vec<_>>();
709 capacity_builder::StringBuilder::<String>::build(|builder| {
710 let mut temp_var_count = 0;
711 builder.append(
712 r#"import { createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
713const require = __internalCreateRequire(import.meta.url);
714let mod;
715if (import.meta.main) {
716 mod = __internalModule._load("#,
717 );
718 builder.append("ed_entry_specifier_text);
719 builder.append(
720 r#", null, true)
721} else {
722 mod = require("#,
723 );
724 builder.append("ed_entry_specifier_text);
725 builder.append(r#");
726}
727"#);
728
729 for (export_name, quoted_name) in &export_names_with_quoted {
730 if !matches!(*export_name, "default" | "module.exports") {
731 add_export(
732 builder,
733 export_name,
734 quoted_name,
735 |builder| {
736 builder.append("mod[");
737 builder.append(quoted_name);
738 builder.append("]");
739 },
740 &mut temp_var_count,
741 );
742 }
743 }
744
745 builder.append("export default mod;\n");
746 add_export(
747 builder,
748 "module.exports",
749 "\"module.exports\"",
750 |builder| builder.append("mod"),
751 &mut temp_var_count,
752 );
753 }).unwrap()
754}
755
756fn add_export<'a>(
757 builder: &mut capacity_builder::StringBuilder<'a, String>,
758 name: &'a str,
759 quoted_name: &'a str,
760 build_initializer: impl FnOnce(&mut capacity_builder::StringBuilder<'a, String>),
761 temp_var_count: &mut usize,
762) {
763 fn is_valid_var_decl(name: &str) -> bool {
764 if name.is_empty() {
766 return false;
767 }
768
769 if let Some(first) = name.chars().next()
770 && !first.is_ascii_alphabetic()
771 && first != '_'
772 && first != '$'
773 {
774 return false;
775 }
776
777 name
778 .chars()
779 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
780 }
781
782 if RESERVED_WORDS.contains(name) || !is_valid_var_decl(name) {
785 *temp_var_count += 1;
786 builder.append("const __deno_export_");
790 builder.append(*temp_var_count);
791 builder.append("__ = ");
792 build_initializer(builder);
793 builder.append(";\nexport { __deno_export_");
794 builder.append(*temp_var_count);
795 builder.append("__ as ");
796 builder.append(quoted_name);
797 builder.append(" };\n");
798 } else {
799 builder.append("export const ");
800 builder.append(name);
801 builder.append(" = ");
802 build_initializer(builder);
803 builder.append(";\n");
804 }
805}
806
807fn to_double_quote_string(text: &str) -> String {
808 serde_json::to_string(text).unwrap()
810}
811
812#[cfg(test)]
813mod tests {
814 use pretty_assertions::assert_eq;
815
816 use super::*;
817
818 #[test]
819 fn test_exports_to_wrapper_module() {
820 let url = Url::parse("file:///test/test.ts").unwrap();
821 let exports = BTreeSet::from(
822 ["static", "server", "app", "dashed-export", "3d"].map(|s| s.to_string()),
823 );
824 let text = exports_to_wrapper_module(&url, &exports);
825 assert_eq!(
826 text,
827 r#"import { createRequire as __internalCreateRequire, Module as __internalModule } from "node:module";
828const require = __internalCreateRequire(import.meta.url);
829let mod;
830if (import.meta.main) {
831 mod = __internalModule._load("/test/test.ts", null, true)
832} else {
833 mod = require("/test/test.ts");
834}
835const __deno_export_1__ = mod["3d"];
836export { __deno_export_1__ as "3d" };
837export const app = mod["app"];
838const __deno_export_2__ = mod["dashed-export"];
839export { __deno_export_2__ as "dashed-export" };
840export const server = mod["server"];
841const __deno_export_3__ = mod["static"];
842export { __deno_export_3__ as "static" };
843export default mod;
844const __deno_export_4__ = mod;
845export { __deno_export_4__ as "module.exports" };
846"#
847 );
848 }
849
850 #[test]
851 fn test_to_double_quote_string() {
852 assert_eq!(to_double_quote_string("test"), "\"test\"");
853 assert_eq!(
854 to_double_quote_string("\r\n\t\"test"),
855 "\"\\r\\n\\t\\\"test\""
856 );
857 }
858}