diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2024-10-14 20:48:39 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-14 20:48:39 -0400 |
commit | 1a0cb5b5312941521ab021cfe9eaed498f35900b (patch) | |
tree | 2e5c58e25e8506b993ac678e83ba0c2feac37d75 | |
parent | ee7d4501435f0ebd655c8b50bd6e41ca19e71abc (diff) |
feat(unstable): `--unstable-detect-cjs` for respecting explicit `"type": "commonjs"` (#26149)
When using the `--unstable-detect-cjs` flag or adding `"unstable":
["detect-cjs"]` to a deno.json, it will make a JS file CJS if the
closest package.json contains `"type": "commonjs"` and the file is not
an ESM module (no TLA, no `import.meta`, no `import`/`export`).
47 files changed, 519 insertions, 154 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs index d59e5ac1a..acaf74a67 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -575,7 +575,8 @@ fn parse_packages_allowed_scripts(s: &str) -> Result<String, AnyError> { pub struct UnstableConfig { // TODO(bartlomieju): remove in Deno 2.5 pub legacy_flag_enabled: bool, // --unstable - pub bare_node_builtins: bool, // --unstable-bare-node-builts + pub bare_node_builtins: bool, + pub detect_cjs: bool, pub sloppy_imports: bool, pub features: Vec<String>, // --unstabe-kv --unstable-cron } @@ -1528,7 +1529,7 @@ pub fn clap_root() -> Command { ); run_args(Command::new("deno"), true) - .args(unstable_args(UnstableArgsConfig::ResolutionAndRuntime)) + .with_unstable_args(UnstableArgsConfig::ResolutionAndRuntime) .next_line_help(false) .bin_name("deno") .styles( @@ -1630,7 +1631,7 @@ fn command( ) -> Command { Command::new(name) .about(about) - .args(unstable_args(unstable_args_config)) + .with_unstable_args(unstable_args_config) } fn help_subcommand(app: &Command) -> Command { @@ -4142,23 +4143,29 @@ enum UnstableArgsConfig { ResolutionAndRuntime, } -struct UnstableArgsIter { - idx: usize, - cfg: UnstableArgsConfig, +trait CommandExt { + fn with_unstable_args(self, cfg: UnstableArgsConfig) -> Self; } -impl Iterator for UnstableArgsIter { - type Item = Arg; +impl CommandExt for Command { + fn with_unstable_args(self, cfg: UnstableArgsConfig) -> Self { + let mut next_display_order = { + let mut value = 1000; + move || { + value += 1; + value + } + }; - fn next(&mut self) -> Option<Self::Item> { - let arg = if self.idx == 0 { + let mut cmd = self.arg( Arg::new("unstable") - .long("unstable") - .help(cstr!("Enable all unstable features and APIs. Instead of using this flag, consider enabling individual unstable features + .long("unstable") + .help(cstr!("Enable all unstable features and APIs. Instead of using this flag, consider enabling individual unstable features <p(245)>To view the list of individual unstable feature flags, run this command again with --help=unstable</>")) - .action(ArgAction::SetTrue) - .hide(matches!(self.cfg, UnstableArgsConfig::None)) - } else if self.idx == 1 { + .action(ArgAction::SetTrue) + .hide(matches!(cfg, UnstableArgsConfig::None)) + .display_order(next_display_order()) + ).arg( Arg::new("unstable-bare-node-builtins") .long("unstable-bare-node-builtins") .help("Enable unstable bare node builtins feature") @@ -4166,20 +4173,36 @@ impl Iterator for UnstableArgsIter { .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .hide(true) - .long_help(match self.cfg { + .long_help(match cfg { + UnstableArgsConfig::None => None, + UnstableArgsConfig::ResolutionOnly + | UnstableArgsConfig::ResolutionAndRuntime => Some("true"), + }) + .help_heading(UNSTABLE_HEADING) + .display_order(next_display_order()), + ).arg( + Arg::new("unstable-detect-cjs") + .long("unstable-detect-cjs") + .help("Reads the package.json type field in a project to treat .js files as .cjs") + .value_parser(FalseyValueParser::new()) + .action(ArgAction::SetTrue) + .hide(true) + .long_help(match cfg { UnstableArgsConfig::None => None, UnstableArgsConfig::ResolutionOnly | UnstableArgsConfig::ResolutionAndRuntime => Some("true"), }) .help_heading(UNSTABLE_HEADING) - } else if self.idx == 2 { + .display_order(next_display_order()) + ).arg( Arg::new("unstable-byonm") .long("unstable-byonm") .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .hide(true) .help_heading(UNSTABLE_HEADING) - } else if self.idx == 3 { + .display_order(next_display_order()), + ).arg( Arg::new("unstable-sloppy-imports") .long("unstable-sloppy-imports") .help("Enable unstable resolving of specifiers by extension probing, .js to .ts, and directory probing") @@ -4187,40 +4210,39 @@ impl Iterator for UnstableArgsIter { .value_parser(FalseyValueParser::new()) .action(ArgAction::SetTrue) .hide(true) - .long_help(match self.cfg { + .long_help(match cfg { UnstableArgsConfig::None => None, UnstableArgsConfig::ResolutionOnly | UnstableArgsConfig::ResolutionAndRuntime => Some("true") }) .help_heading(UNSTABLE_HEADING) - } else if self.idx > 3 { - let granular_flag = crate::UNSTABLE_GRANULAR_FLAGS.get(self.idx - 4)?; - Arg::new(format!("unstable-{}", granular_flag.name)) - .long(format!("unstable-{}", granular_flag.name)) - .help(granular_flag.help_text) - .action(ArgAction::SetTrue) - .hide(true) - .help_heading(UNSTABLE_HEADING) - // we don't render long help, so using it here as a sort of metadata - .long_help(if granular_flag.show_in_help { - match self.cfg { - UnstableArgsConfig::None | UnstableArgsConfig::ResolutionOnly => { - None + .display_order(next_display_order()) + ); + + for granular_flag in crate::UNSTABLE_GRANULAR_FLAGS.iter() { + cmd = cmd.arg( + Arg::new(format!("unstable-{}", granular_flag.name)) + .long(format!("unstable-{}", granular_flag.name)) + .help(granular_flag.help_text) + .action(ArgAction::SetTrue) + .hide(true) + .help_heading(UNSTABLE_HEADING) + // we don't render long help, so using it here as a sort of metadata + .long_help(if granular_flag.show_in_help { + match cfg { + UnstableArgsConfig::None | UnstableArgsConfig::ResolutionOnly => { + None + } + UnstableArgsConfig::ResolutionAndRuntime => Some("true"), } - UnstableArgsConfig::ResolutionAndRuntime => Some("true"), - } - } else { - None - }) - } else { - return None; - }; - self.idx += 1; - Some(arg.display_order(self.idx + 1000)) - } -} + } else { + None + }) + .display_order(next_display_order()), + ); + } -fn unstable_args(cfg: UnstableArgsConfig) -> impl IntoIterator<Item = Arg> { - UnstableArgsIter { idx: 0, cfg } + cmd + } } fn allow_scripts_arg_parse( @@ -5678,6 +5700,7 @@ fn unstable_args_parse( flags.unstable_config.bare_node_builtins = matches.get_flag("unstable-bare-node-builtins"); + flags.unstable_config.detect_cjs = matches.get_flag("unstable-detect-cjs"); flags.unstable_config.sloppy_imports = matches.get_flag("unstable-sloppy-imports"); diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 07906a86a..f905e186b 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1576,6 +1576,11 @@ impl CliOptions { || self.workspace().has_unstable("bare-node-builtins") } + pub fn unstable_detect_cjs(&self) -> bool { + self.flags.unstable_config.detect_cjs + || self.workspace().has_unstable("detect-cjs") + } + fn byonm_enabled(&self) -> bool { // check if enabled via unstable self.node_modules_dir().ok().flatten() == Some(NodeModulesDirMode::Manual) @@ -1620,21 +1625,17 @@ impl CliOptions { }); if !from_config_file.is_empty() { - // collect unstable granular flags - let mut all_valid_unstable_flags: Vec<&str> = - crate::UNSTABLE_GRANULAR_FLAGS - .iter() - .map(|granular_flag| granular_flag.name) - .collect(); - - let mut another_unstable_flags = Vec::from([ - "sloppy-imports", - "byonm", - "bare-node-builtins", - "fmt-component", - ]); - // add more unstable flags to the same vector holding granular flags - all_valid_unstable_flags.append(&mut another_unstable_flags); + let all_valid_unstable_flags: Vec<&str> = crate::UNSTABLE_GRANULAR_FLAGS + .iter() + .map(|granular_flag| granular_flag.name) + .chain([ + "sloppy-imports", + "byonm", + "bare-node-builtins", + "fmt-component", + "detect-cjs", + ]) + .collect(); // check and warn if the unstable flag of config file isn't supported, by // iterating through the vector holding the unstable flags diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index 628502c50..ded163b4e 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -9,10 +9,13 @@ use crate::file_fetcher::FetchPermissionsOptionRef; use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileOrRedirect; use crate::npm::CliNpmResolver; +use crate::resolver::CliNodeResolver; use crate::util::fs::atomic_write_file_with_retries; use crate::util::fs::atomic_write_file_with_retries_and_fs; use crate::util::fs::AtomicWriteFileFsAdapter; use crate::util::path::specifier_has_extension; +use crate::util::text_encoding::arc_str_to_bytes; +use crate::util::text_encoding::from_utf8_lossy_owned; use deno_ast::MediaType; use deno_core::futures; @@ -57,6 +60,7 @@ pub use fast_check::FastCheckCache; pub use incremental::IncrementalCache; pub use module_info::ModuleInfoCache; pub use node::NodeAnalysisCache; +pub use parsed_source::EsmOrCjsChecker; pub use parsed_source::LazyGraphSourceParser; pub use parsed_source::ParsedSourceCache; @@ -177,37 +181,46 @@ pub struct FetchCacherOptions { pub permissions: PermissionsContainer, /// If we're publishing for `deno publish`. pub is_deno_publish: bool, + pub unstable_detect_cjs: bool, } /// A "wrapper" for the FileFetcher and DiskCache for the Deno CLI that provides /// a concise interface to the DENO_DIR when building module graphs. pub struct FetchCacher { - file_fetcher: Arc<FileFetcher>, pub file_header_overrides: HashMap<ModuleSpecifier, HashMap<String, String>>, + esm_or_cjs_checker: Arc<EsmOrCjsChecker>, + file_fetcher: Arc<FileFetcher>, global_http_cache: Arc<GlobalHttpCache>, + node_resolver: Arc<CliNodeResolver>, npm_resolver: Arc<dyn CliNpmResolver>, module_info_cache: Arc<ModuleInfoCache>, permissions: PermissionsContainer, - cache_info_enabled: bool, is_deno_publish: bool, + unstable_detect_cjs: bool, + cache_info_enabled: bool, } impl FetchCacher { pub fn new( + esm_or_cjs_checker: Arc<EsmOrCjsChecker>, file_fetcher: Arc<FileFetcher>, global_http_cache: Arc<GlobalHttpCache>, + node_resolver: Arc<CliNodeResolver>, npm_resolver: Arc<dyn CliNpmResolver>, module_info_cache: Arc<ModuleInfoCache>, options: FetchCacherOptions, ) -> Self { Self { file_fetcher, + esm_or_cjs_checker, global_http_cache, + node_resolver, npm_resolver, module_info_cache, file_header_overrides: options.file_header_overrides, permissions: options.permissions, is_deno_publish: options.is_deno_publish, + unstable_detect_cjs: options.unstable_detect_cjs, cache_info_enabled: false, } } @@ -282,6 +295,46 @@ impl Loader for FetchCacher { }, )))); } + + if self.unstable_detect_cjs && specifier_has_extension(specifier, "js") { + if let Ok(Some(pkg_json)) = + self.node_resolver.get_closest_package_json(specifier) + { + if pkg_json.typ == "commonjs" { + if let Ok(path) = specifier.to_file_path() { + if let Ok(bytes) = std::fs::read(&path) { + let text: Arc<str> = from_utf8_lossy_owned(bytes).into(); + let is_es_module = match self.esm_or_cjs_checker.is_esm( + specifier, + text.clone(), + MediaType::JavaScript, + ) { + Ok(value) => value, + Err(err) => { + return Box::pin(futures::future::ready(Err(err.into()))); + } + }; + if !is_es_module { + self.node_resolver.mark_cjs_resolution(specifier.clone()); + return Box::pin(futures::future::ready(Ok(Some( + LoadResponse::External { + specifier: specifier.clone(), + }, + )))); + } else { + return Box::pin(futures::future::ready(Ok(Some( + LoadResponse::Module { + specifier: specifier.clone(), + content: arc_str_to_bytes(text), + maybe_headers: None, + }, + )))); + } + } + } + } + } + } } if self.is_deno_publish diff --git a/cli/cache/parsed_source.rs b/cli/cache/parsed_source.rs index e956361f4..df6e45c35 100644 --- a/cli/cache/parsed_source.rs +++ b/cli/cache/parsed_source.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use deno_ast::MediaType; use deno_ast::ModuleSpecifier; +use deno_ast::ParseDiagnostic; use deno_ast::ParsedSource; use deno_core::parking_lot::Mutex; use deno_graph::CapturingModuleParser; @@ -149,3 +150,42 @@ impl deno_graph::ParsedSourceStore for ParsedSourceCache { } } } + +pub struct EsmOrCjsChecker { + parsed_source_cache: Arc<ParsedSourceCache>, +} + +impl EsmOrCjsChecker { + pub fn new(parsed_source_cache: Arc<ParsedSourceCache>) -> Self { + Self { + parsed_source_cache, + } + } + + pub fn is_esm( + &self, + specifier: &ModuleSpecifier, + source: Arc<str>, + media_type: MediaType, + ) -> Result<bool, ParseDiagnostic> { + // todo(dsherret): add a file cache here to avoid parsing with swc on each run + let source = match self.parsed_source_cache.get_parsed_source(specifier) { + Some(source) => source.clone(), + None => { + let source = deno_ast::parse_program(deno_ast::ParseParams { + specifier: specifier.clone(), + text: source, + media_type, + capture_tokens: true, // capture because it's used for cjs export analysis + scope_analysis: false, + maybe_syntax: None, + })?; + self + .parsed_source_cache + .set_parsed_source(specifier.clone(), source.clone()); + source + } + }; + Ok(source.is_module()) + } +} diff --git a/cli/factory.rs b/cli/factory.rs index b96a133e9..25f355110 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -14,6 +14,7 @@ use crate::cache::CodeCache; use crate::cache::DenoDir; use crate::cache::DenoDirProvider; use crate::cache::EmitCache; +use crate::cache::EsmOrCjsChecker; use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; use crate::cache::LocalHttpCache; @@ -171,6 +172,7 @@ struct CliFactoryServices { http_client_provider: Deferred<Arc<HttpClientProvider>>, emit_cache: Deferred<Arc<EmitCache>>, emitter: Deferred<Arc<Emitter>>, + esm_or_cjs_checker: Deferred<Arc<EsmOrCjsChecker>>, fs: Deferred<Arc<dyn deno_fs::FileSystem>>, main_graph_container: Deferred<Arc<MainModuleGraphContainer>>, maybe_inspector_server: Deferred<Option<Arc<InspectorServer>>>, @@ -298,6 +300,12 @@ impl CliFactory { .get_or_init(|| ProgressBar::new(ProgressBarStyle::TextOnly)) } + pub fn esm_or_cjs_checker(&self) -> &Arc<EsmOrCjsChecker> { + self.services.esm_or_cjs_checker.get_or_init(|| { + Arc::new(EsmOrCjsChecker::new(self.parsed_source_cache().clone())) + }) + } + pub fn global_http_cache(&self) -> Result<&Arc<GlobalHttpCache>, AnyError> { self.services.global_http_cache.get_or_try_init(|| { Ok(Arc::new(GlobalHttpCache::new( @@ -579,6 +587,7 @@ impl CliFactory { node_analysis_cache, self.fs().clone(), node_resolver, + Some(self.parsed_source_cache().clone()), ); Ok(Arc::new(NodeCodeTranslator::new( @@ -619,8 +628,10 @@ impl CliFactory { Ok(Arc::new(ModuleGraphBuilder::new( cli_options.clone(), self.caches()?.clone(), + self.esm_or_cjs_checker().clone(), self.fs().clone(), self.resolver().await?.clone(), + self.cli_node_resolver().await?.clone(), self.npm_resolver().await?.clone(), self.module_info_cache()?.clone(), self.parsed_source_cache().clone(), @@ -792,6 +803,7 @@ impl CliFactory { Ok(CliMainWorkerFactory::new( self.blob_store().clone(), + self.cjs_resolutions().clone(), if cli_options.code_cache_enabled() { Some(self.code_cache()?.clone()) } else { @@ -896,6 +908,7 @@ impl CliFactory { node_ipc: cli_options.node_ipc_fd(), serve_port: cli_options.serve_port(), serve_host: cli_options.serve_host(), + unstable_detect_cjs: cli_options.unstable_detect_cjs(), }) } } diff --git a/cli/graph_util.rs b/cli/graph_util.rs index e2f6246e7..e67ae7821 100644 --- a/cli/graph_util.rs +++ b/cli/graph_util.rs @@ -6,6 +6,7 @@ use crate::args::CliLockfile; use crate::args::CliOptions; use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::cache; +use crate::cache::EsmOrCjsChecker; use crate::cache::GlobalHttpCache; use crate::cache::ModuleInfoCache; use crate::cache::ParsedSourceCache; @@ -14,6 +15,7 @@ use crate::errors::get_error_class_name; use crate::file_fetcher::FileFetcher; use crate::npm::CliNpmResolver; use crate::resolver::CliGraphResolver; +use crate::resolver::CliNodeResolver; use crate::resolver::CliSloppyImportsResolver; use crate::resolver::SloppyImportsCachedFs; use crate::tools::check; @@ -379,8 +381,10 @@ pub struct BuildFastCheckGraphOptions<'a> { pub struct ModuleGraphBuilder { options: Arc<CliOptions>, caches: Arc<cache::Caches>, + esm_or_cjs_checker: Arc<EsmOrCjsChecker>, fs: Arc<dyn FileSystem>, resolver: Arc<CliGraphResolver>, + node_resolver: Arc<CliNodeResolver>, npm_resolver: Arc<dyn CliNpmResolver>, module_info_cache: Arc<ModuleInfoCache>, parsed_source_cache: Arc<ParsedSourceCache>, @@ -396,8 +400,10 @@ impl ModuleGraphBuilder { pub fn new( options: Arc<CliOptions>, caches: Arc<cache::Caches>, + esm_or_cjs_checker: Arc<EsmOrCjsChecker>, fs: Arc<dyn FileSystem>, resolver: Arc<CliGraphResolver>, + node_resolver: Arc<CliNodeResolver>, npm_resolver: Arc<dyn CliNpmResolver>, module_info_cache: Arc<ModuleInfoCache>, parsed_source_cache: Arc<ParsedSourceCache>, @@ -410,8 +416,10 @@ impl ModuleGraphBuilder { Self { options, caches, + esm_or_cjs_checker, fs, resolver, + node_resolver, npm_resolver, module_info_cache, parsed_source_cache, @@ -691,8 +699,10 @@ impl ModuleGraphBuilder { permissions: PermissionsContainer, ) -> cache::FetchCacher { cache::FetchCacher::new( + self.esm_or_cjs_checker.clone(), self.file_fetcher.clone(), self.global_http_cache.clone(), + self.node_resolver.clone(), self.npm_resolver.clone(), self.module_info_cache.clone(), cache::FetchCacherOptions { @@ -702,6 +712,7 @@ impl ModuleGraphBuilder { self.options.sub_command(), crate::args::DenoSubcommand::Publish { .. } ), + unstable_detect_cjs: self.options.unstable_detect_cjs(), }, ) } diff --git a/cli/module_loader.rs b/cli/module_loader.rs index 293f41dda..37d42f78e 100644 --- a/cli/module_loader.rs +++ b/cli/module_loader.rs @@ -331,15 +331,23 @@ impl<TGraphContainer: ModuleGraphContainer> maybe_referrer: Option<&ModuleSpecifier>, requested_module_type: RequestedModuleType, ) -> Result<ModuleSource, AnyError> { - let code_source = if let Some(result) = self - .shared - .npm_module_loader - .load_if_in_npm_package(specifier, maybe_referrer) - .await - { - result? - } else { - self.load_prepared_module(specifier, maybe_referrer).await? + let code_source = match self.load_prepared_module(specifier).await? { + Some(code_source) => code_source, + None => { + if self.shared.npm_module_loader.if_in_npm_package(specifier) { + self + .shared + .npm_module_loader + .load(specifier, maybe_referrer) + .await? + } else { + let mut msg = format!("Loading unprepared module: {specifier}"); + if let Some(referrer) = maybe_referrer { + msg = format!("{}, imported from: {}", msg, referrer.as_str()); + } + return Err(anyhow!(msg)); + } + } }; let code = if self.shared.is_inspecting { // we need the code with the source map in order for @@ -514,17 +522,12 @@ impl<TGraphContainer: ModuleGraphContainer> async fn load_prepared_module( &self, specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Result<ModuleCodeStringSource, AnyError> { + ) -> Result<Option<ModuleCodeStringSource>, AnyError> { // Note: keep this in sync with the sync version below let graph = self.graph_container.graph(); - match self.load_prepared_module_or_defer_emit( - &graph, - specifier, - maybe_referrer, - ) { - Ok(CodeOrDeferredEmit::Code(code_source)) => Ok(code_source), - Ok(CodeOrDeferredEmit::DeferredEmit { + match self.load_prepared_module_or_defer_emit(&graph, specifier)? { + Some(CodeOrDeferredEmit::Code(code_source)) => Ok(Some(code_source)), + Some(CodeOrDeferredEmit::DeferredEmit { specifier, media_type, source, @@ -537,30 +540,25 @@ impl<TGraphContainer: ModuleGraphContainer> // at this point, we no longer need the parsed source in memory, so free it self.parsed_source_cache.free(specifier); - Ok(ModuleCodeStringSource { + Ok(Some(ModuleCodeStringSource { code: ModuleSourceCode::Bytes(transpile_result), found_url: specifier.clone(), media_type, - }) + })) } - Err(err) => Err(err), + None => Ok(None), } } fn load_prepared_module_sync( &self, specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Result<ModuleCodeStringSource, AnyError> { + ) -> Result<Option<ModuleCodeStringSource>, AnyError> { // Note: keep this in sync with the async version above let graph = self.graph_container.graph(); - match self.load_prepared_module_or_defer_emit( - &graph, - specifier, - maybe_referrer, - ) { - Ok(CodeOrDeferredEmit::Code(code_source)) => Ok(code_source), - Ok(CodeOrDeferredEmit::DeferredEmit { + match self.load_prepared_module_or_defer_emit(&graph, specifier)? { + Some(CodeOrDeferredEmit::Code(code_source)) => Ok(Some(code_source)), + Some(CodeOrDeferredEmit::DeferredEmit { specifier, media_type, source, @@ -572,13 +570,13 @@ impl<TGraphContainer: ModuleGraphContainer> // at this point, we no longer need the parsed source in memory, so free it self.parsed_source_cache.free(specifier); - Ok(ModuleCodeStringSource { + Ok(Some(ModuleCodeStringSource { code: ModuleSourceCode::Bytes(transpile_result), found_url: specifier.clone(), media_type, - }) + })) } - Err(err) => Err(err), + None => Ok(None), } } @@ -586,8 +584,7 @@ impl<TGraphContainer: ModuleGraphContainer> &self, graph: &'graph ModuleGraph, specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Result<CodeOrDeferredEmit<'graph>, AnyError> { + ) -> Result<Option<CodeOrDeferredEmit<'graph>>, AnyError> { if specifier.scheme() == "node" { // Node built-in modules should be handled internally. unreachable!("Deno bug. {} was misconfigured internally.", specifier); @@ -599,11 +596,11 @@ impl<TGraphContainer: ModuleGraphContainer> media_type, specifier, .. - })) => Ok(CodeOrDeferredEmit::Code(ModuleCodeStringSource { + })) => Ok(Some(CodeOrDeferredEmit::Code(ModuleCodeStringSource { code: ModuleSourceCode::String(source.clone().into()), found_url: specifier.clone(), media_type: *media_type, - })), + }))), Some(deno_graph::Module::Js(JsModule { source, media_type, @@ -624,11 +621,11 @@ impl<TGraphContainer: ModuleGraphContainer> | MediaType::Cts | MediaType::Jsx | MediaType::Tsx => { - return Ok(CodeOrDeferredEmit::DeferredEmit { + return Ok(Some(CodeOrDeferredEmit::DeferredEmit { specifier, media_type: *media_type, source, - }); + })); } MediaType::TsBuildInfo | MediaType::Wasm | MediaType::SourceMap => { panic!("Unexpected media type {media_type} for {specifier}") @@ -638,24 +635,18 @@ impl<TGraphContainer: ModuleGraphContainer> // at this point, we no longer need the parsed source in memory, so free it self.parsed_source_cache.free(specifier); - Ok(CodeOrDeferredEmit::Code(ModuleCodeStringSource { + Ok(Some(CodeOrDeferredEmit::Code(ModuleCodeStringSource { code: ModuleSourceCode::String(code), found_url: specifier.clone(), media_type: *media_type, - })) + }))) } Some( deno_graph::Module::External(_) | deno_graph::Module::Node(_) | deno_graph::Module::Npm(_), ) - | None => { - let mut msg = format!("Loading unprepared module: {specifier}"); - if let Some(referrer) = maybe_referrer { - msg = format!("{}, imported from: {}", msg, referrer.as_str()); - } - Err(anyhow!(msg)) - } + | None => Ok(None), } } } @@ -828,7 +819,7 @@ impl<TGraphContainer: ModuleGraphContainer> ModuleLoader "wasm" | "file" | "http" | "https" | "data" | "blob" => (), _ => return None, } - let source = self.0.load_prepared_module_sync(&specifier, None).ok()?; + let source = self.0.load_prepared_module_sync(&specifier).ok()??; source_map_from_code(source.code.as_bytes()) } diff --git a/cli/node.rs b/cli/node.rs index a3cee8dde..733d5f871 100644 --- a/cli/node.rs +++ b/cli/node.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use deno_ast::MediaType; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; +use deno_graph::ParsedSourceStore; use deno_runtime::deno_fs; use deno_runtime::deno_node::DenoFsNodeResolverEnv; use node_resolver::analyze::CjsAnalysis as ExtNodeCjsAnalysis; @@ -16,6 +17,7 @@ use serde::Serialize; use crate::cache::CacheDBHash; use crate::cache::NodeAnalysisCache; +use crate::cache::ParsedSourceCache; use crate::resolver::CliNodeResolver; use crate::util::fs::canonicalize_path_maybe_not_exists; @@ -56,6 +58,7 @@ pub struct CliCjsCodeAnalyzer { cache: NodeAnalysisCache, fs: deno_fs::FileSystemRc, node_resolver: Arc<CliNodeResolver>, + parsed_source_cache: Option<Arc<ParsedSourceCache>>, } impl CliCjsCodeAnalyzer { @@ -63,11 +66,13 @@ impl CliCjsCodeAnalyzer { cache: NodeAnalysisCache, fs: deno_fs::FileSystemRc, node_resolver: Arc<CliNodeResolver>, + parsed_source_cache: Option<Arc<ParsedSourceCache>>, ) -> Self { Self { cache, fs, node_resolver, + parsed_source_cache, } } @@ -107,18 +112,26 @@ impl CliCjsCodeAnalyzer { } } + let maybe_parsed_source = self + .parsed_source_cache + .as_ref() + .and_then(|c| c.remove_parsed_source(specifier)); + let analysis = deno_core::unsync::spawn_blocking({ let specifier = specifier.clone(); let source: Arc<str> = source.into(); move || -> Result<_, deno_ast::ParseDiagnostic> { - let parsed_source = deno_ast::parse_program(deno_ast::ParseParams { - specifier, - text: source, - media_type, - capture_tokens: true, - scope_analysis: false, - maybe_syntax: None, - })?; + let parsed_source = + maybe_parsed_source.map(Ok).unwrap_or_else(|| { + deno_ast::parse_program(deno_ast::ParseParams { + specifier, + text: source, + media_type, + capture_tokens: true, + scope_analysis: false, + maybe_syntax: None, + }) + })?; if parsed_source.is_script() { let analysis = parsed_source.analyze_cjs(); Ok(CliCjsAnalysis::Cjs { diff --git a/cli/resolver.rs b/cli/resolver.rs index 7804261b8..84c671268 100644 --- a/cli/resolver.rs +++ b/cli/resolver.rs @@ -43,7 +43,6 @@ use node_resolver::NodeModuleKind; use node_resolver::NodeResolution; use node_resolver::NodeResolutionMode; use node_resolver::PackageJson; -use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -53,7 +52,9 @@ use crate::args::DENO_DISABLE_PEDANTIC_NODE_WARNINGS; use crate::node::CliNodeCodeTranslator; use crate::npm::CliNpmResolver; use crate::npm::InnerCliNpmResolverRef; +use crate::util::path::specifier_has_extension; use crate::util::sync::AtomicFlag; +use crate::util::text_encoding::from_utf8_lossy_owned; pub struct ModuleCodeStringSource { pub code: ModuleSourceCode, @@ -215,7 +216,7 @@ impl CliNodeResolver { referrer: &ModuleSpecifier, mode: NodeResolutionMode, ) -> Result<NodeResolution, NodeResolveError> { - let referrer_kind = if self.cjs_resolutions.contains(referrer) { + let referrer_kind = if self.cjs_resolutions.is_known_cjs(referrer) { NodeModuleKind::Cjs } else { NodeModuleKind::Esm @@ -310,9 +311,7 @@ impl CliNodeResolver { if self.in_npm_package(&specifier) { let resolution = self.node_resolver.url_to_node_resolution(specifier)?; - if let NodeResolution::CommonJs(specifier) = &resolution { - self.cjs_resolutions.insert(specifier.clone()); - } + let resolution = self.handle_node_resolution(resolution); return Ok(Some(resolution.into_url())); } } @@ -333,12 +332,17 @@ impl CliNodeResolver { ) -> NodeResolution { if let NodeResolution::CommonJs(specifier) = &resolution { // remember that this was a common js resolution - self.cjs_resolutions.insert(specifier.clone()); + self.mark_cjs_resolution(specifier.clone()); } resolution } + + pub fn mark_cjs_resolution(&self, specifier: ModuleSpecifier) { + self.cjs_resolutions.insert(specifier); + } } +// todo(dsherret): move to module_loader.rs #[derive(Clone)] pub struct NpmModuleLoader { cjs_resolutions: Arc<CjsResolutionStore>, @@ -362,18 +366,9 @@ impl NpmModuleLoader { } } - pub async fn load_if_in_npm_package( - &self, - specifier: &ModuleSpecifier, - maybe_referrer: Option<&ModuleSpecifier>, - ) -> Option<Result<ModuleCodeStringSource, AnyError>> { - if self.node_resolver.in_npm_package(specifier) - || (specifier.scheme() == "file" && specifier.path().ends_with(".cjs")) - { - Some(self.load(specifier, maybe_referrer).await) - } else { - None - } + pub fn if_in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { + self.node_resolver.in_npm_package(specifier) + || self.cjs_resolutions.is_known_cjs(specifier) } pub async fn load( @@ -418,16 +413,9 @@ impl NpmModuleLoader { } })?; - let code = if self.cjs_resolutions.contains(specifier) - || (specifier.scheme() == "file" && specifier.path().ends_with(".cjs")) - { + let code = if self.cjs_resolutions.is_known_cjs(specifier) { // translate cjs to esm if it's cjs and inject node globals - let code = match String::from_utf8_lossy(&code) { - Cow::Owned(code) => code, - // SAFETY: `String::from_utf8_lossy` guarantees that the result is valid - // UTF-8 if `Cow::Borrowed` is returned. - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(code) }, - }; + let code = from_utf8_lossy_owned(code); ModuleSourceCode::String( self .node_code_translator @@ -452,8 +440,12 @@ impl NpmModuleLoader { pub struct CjsResolutionStore(DashSet<ModuleSpecifier>); impl CjsResolutionStore { - pub fn contains(&self, specifier: &ModuleSpecifier) -> bool { - self.0.contains(specifier) + pub fn is_known_cjs(&self, specifier: &ModuleSpecifier) -> bool { + if specifier.scheme() != "file" { + return false; + } + + specifier_has_extension(specifier, "cjs") || self.0.contains(specifier) } pub fn insert(&self, specifier: ModuleSpecifier) { diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index af18e6f21..4a6239f0d 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -528,6 +528,7 @@ "bare-node-builtins", "byonm", "cron", + "detect-cjs", "ffi", "fs", "http", diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 1290a238f..6e747bed4 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -622,6 +622,7 @@ impl<'a> DenoCompileBinaryWriter<'a> { unstable_config: UnstableConfig { legacy_flag_enabled: false, bare_node_builtins: cli_options.unstable_bare_node_builtins(), + detect_cjs: cli_options.unstable_detect_cjs(), sloppy_imports: cli_options.unstable_sloppy_imports(), features: cli_options.unstable_features(), }, diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 258de0dad..60018228b 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -586,6 +586,7 @@ pub async fn run( node_analysis_cache, fs.clone(), cli_node_resolver.clone(), + None, ); let node_code_translator = Arc::new(NodeCodeTranslator::new( cjs_esm_code_analyzer, @@ -651,7 +652,7 @@ pub async fn run( workspace_resolver, node_resolver: cli_node_resolver.clone(), npm_module_loader: Arc::new(NpmModuleLoader::new( - cjs_resolutions, + cjs_resolutions.clone(), node_code_translator, fs.clone(), cli_node_resolver, @@ -696,6 +697,7 @@ pub async fn run( }); let worker_factory = CliMainWorkerFactory::new( Arc::new(BlobStore::default()), + cjs_resolutions, // Code cache is not supported for standalone binary yet. None, feature_checker, @@ -738,6 +740,7 @@ pub async fn run( node_ipc: None, serve_port: None, serve_host: None, + unstable_detect_cjs: metadata.unstable_config.detect_cjs, }, ); diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index c1f98bc08..3cc4414fc 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -54,6 +54,16 @@ pub async fn compile( ); } + if cli_options.unstable_detect_cjs() { + log::warn!( + concat!( + "{} --unstable-detect-cjs is not properly supported in deno compile. ", + "The compiled executable may encounter runtime errors.", + ), + crate::colors::yellow("Warning"), + ); + } + let output_path = resolve_compile_executable_output_path( http_client, &compile_flags, diff --git a/cli/util/text_encoding.rs b/cli/util/text_encoding.rs index d2e0832c9..0b7601cb9 100644 --- a/cli/util/text_encoding.rs +++ b/cli/util/text_encoding.rs @@ -1,6 +1,8 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::ops::Range; +use std::sync::Arc; use base64::prelude::BASE64_STANDARD; use base64::Engine; @@ -9,6 +11,15 @@ use deno_core::ModuleSourceCode; static SOURCE_MAP_PREFIX: &[u8] = b"//# sourceMappingURL=data:application/json;base64,"; +pub fn from_utf8_lossy_owned(bytes: Vec<u8>) -> String { + match String::from_utf8_lossy(&bytes) { + Cow::Owned(code) => code, + // SAFETY: `String::from_utf8_lossy` guarantees that the result is valid + // UTF-8 if `Cow::Borrowed` is returned. + Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(bytes) }, + } +} + pub fn source_map_from_code(code: &[u8]) -> Option<Vec<u8>> { let range = find_source_map_range(code)?; let source_map_range = &code[range]; @@ -85,6 +96,13 @@ fn find_source_map_range(code: &[u8]) -> Option<Range<usize>> { } } +/// Converts an `Arc<str>` to an `Arc<[u8]>`. +pub fn arc_str_to_bytes(arc_str: Arc<str>) -> Arc<[u8]> { + let raw = Arc::into_raw(arc_str); + // SAFETY: This is safe because they have the same memory layout. + unsafe { Arc::from_raw(raw as *const [u8]) } +} + #[cfg(test)] mod tests { use std::sync::Arc; diff --git a/cli/worker.rs b/cli/worker.rs index cc18c0d15..489b2dd93 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -51,9 +51,11 @@ use crate::args::DenoSubcommand; use crate::args::StorageKeyResolver; use crate::errors; use crate::npm::CliNpmResolver; +use crate::resolver::CjsResolutionStore; use crate::util::checksum; use crate::util::file_watcher::WatcherCommunicator; use crate::util::file_watcher::WatcherRestartMode; +use crate::util::path::specifier_has_extension; use crate::version; pub struct ModuleLoaderAndSourceMapGetter { @@ -120,11 +122,13 @@ pub struct CliMainWorkerOptions { pub node_ipc: Option<i64>, pub serve_port: Option<u16>, pub serve_host: Option<String>, + pub unstable_detect_cjs: bool, } struct SharedWorkerState { blob_store: Arc<BlobStore>, broadcast_channel: InMemoryBroadcastChannel, + cjs_resolution_store: Arc<CjsResolutionStore>, code_cache: Option<Arc<dyn code_cache::CodeCache>>, compiled_wasm_module_store: CompiledWasmModuleStore, feature_checker: Arc<FeatureChecker>, @@ -422,6 +426,7 @@ impl CliMainWorkerFactory { #[allow(clippy::too_many_arguments)] pub fn new( blob_store: Arc<BlobStore>, + cjs_resolution_store: Arc<CjsResolutionStore>, code_cache: Option<Arc<dyn code_cache::CodeCache>>, feature_checker: Arc<FeatureChecker>, fs: Arc<dyn deno_fs::FileSystem>, @@ -441,6 +446,7 @@ impl CliMainWorkerFactory { shared: Arc::new(SharedWorkerState { blob_store, broadcast_channel: Default::default(), + cjs_resolution_store, code_cache, compiled_wasm_module_store: Default::default(), feature_checker, @@ -486,6 +492,9 @@ impl CliMainWorkerFactory { stdio: deno_runtime::deno_io::Stdio, ) -> Result<CliMainWorker, AnyError> { let shared = &self.shared; + let ModuleLoaderAndSourceMapGetter { module_loader } = shared + .module_loader_factory + .create_for_main(permissions.clone()); let (main_module, is_main_cjs) = if let Ok(package_ref) = NpmPackageReqReference::from_specifier(&main_module) { @@ -526,13 +535,28 @@ impl CliMainWorkerFactory { let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_)); (node_resolution.into_url(), is_main_cjs) } else { - let is_cjs = main_module.path().ends_with(".cjs"); + let is_maybe_cjs_js_ext = self.shared.options.unstable_detect_cjs + && specifier_has_extension(&main_module, "js") + && self + .shared + .node_resolver + .get_closest_package_json(&main_module) + .ok() + .flatten() + .map(|pkg_json| pkg_json.typ == "commonjs") + .unwrap_or(false); + let is_cjs = if is_maybe_cjs_js_ext { + // fill the cjs resolution store by preparing the module load + module_loader + .prepare_load(&main_module, None, false) + .await?; + self.shared.cjs_resolution_store.is_known_cjs(&main_module) + } else { + specifier_has_extension(&main_module, "cjs") + }; (main_module, is_cjs) }; - let ModuleLoaderAndSourceMapGetter { module_loader } = shared - .module_loader_factory - .create_for_main(permissions.clone()); let maybe_inspector_server = shared.maybe_inspector_server.clone(); let create_web_worker_cb = diff --git a/tests/specs/compile/detect_cjs/__test__.jsonc b/tests/specs/compile/detect_cjs/__test__.jsonc new file mode 100644 index 000000000..32bebb7a5 --- /dev/null +++ b/tests/specs/compile/detect_cjs/__test__.jsonc @@ -0,0 +1,24 @@ +{ + "tempDir": true, + "steps": [{ + "if": "unix", + "args": "compile --allow-read --output main main.js", + "output": "compile.out" + }, { + "if": "unix", + "commandName": "./main", + "args": [], + "output": "output.out", + "exitCode": 1 + }, { + "if": "windows", + "args": "compile --allow-read --output main.exe main.js", + "output": "compile.out" + }, { + "if": "windows", + "commandName": "./main.exe", + "args": [], + "output": "output.out", + "exitCode": 1 + }] +} diff --git a/tests/specs/compile/detect_cjs/add.js b/tests/specs/compile/detect_cjs/add.js new file mode 100644 index 000000000..2a886fbc1 --- /dev/null +++ b/tests/specs/compile/detect_cjs/add.js @@ -0,0 +1,3 @@ +module.exports.add = function (a, b) { + return a + b; +}; diff --git a/tests/specs/compile/detect_cjs/compile.out b/tests/specs/compile/detect_cjs/compile.out new file mode 100644 index 000000000..6509b7f29 --- /dev/null +++ b/tests/specs/compile/detect_cjs/compile.out @@ -0,0 +1,3 @@ +Warning --unstable-detect-cjs is not properly supported in deno compile. The compiled executable may encounter runtime errors. +Check file:///[WILDLINE]/main.js +Compile file:///[WILDLINE] diff --git a/tests/specs/compile/detect_cjs/deno.json b/tests/specs/compile/detect_cjs/deno.json new file mode 100644 index 000000000..35f64c86f --- /dev/null +++ b/tests/specs/compile/detect_cjs/deno.json @@ -0,0 +1,5 @@ +{ + "unstable": [ + "detect-cjs" + ] +} diff --git a/tests/specs/compile/detect_cjs/main.js b/tests/specs/compile/detect_cjs/main.js new file mode 100644 index 000000000..8c55f673b --- /dev/null +++ b/tests/specs/compile/detect_cjs/main.js @@ -0,0 +1,3 @@ +import { add } from "./add.js"; + +console.log(add(1, 2)); diff --git a/tests/specs/compile/detect_cjs/output.out b/tests/specs/compile/detect_cjs/output.out new file mode 100644 index 000000000..b53c44369 --- /dev/null +++ b/tests/specs/compile/detect_cjs/output.out @@ -0,0 +1 @@ +error: Module not found: file:///[WILDLINE]/add.js diff --git a/tests/specs/compile/detect_cjs/package.json b/tests/specs/compile/detect_cjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/specs/compile/detect_cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/specs/run/package_json_type/commonjs/__test__.jsonc b/tests/specs/run/package_json_type/commonjs/__test__.jsonc new file mode 100644 index 000000000..85b7219fa --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/__test__.jsonc @@ -0,0 +1,34 @@ +{ + "tests": { + "main_cjs": { + "args": "run --allow-read=. main_cjs.js", + "output": "3\n" + }, + "main_esm": { + "args": "run --allow-read=. main_esm.js", + "output": "3\n" + }, + "main_mix": { + "args": "run --allow-read=. main_mix.js", + "output": "main_mix.out", + "exitCode": 1 + }, + "import_import_meta": { + "args": "run import_import_meta.js", + "output": "[WILDLINE]/import_meta.js\n" + }, + "main_import_meta": { + "args": "run main_esm_import_meta.js", + "output": "main_esm_import_meta.out", + "exitCode": 1 + }, + "not_import_meta": { + "args": "run --allow-read=. not_import_meta.js", + "output": "3\n" + }, + "tla": { + "args": "run --allow-read=. tla.js", + "output": "loaded\n" + } + } +} diff --git a/tests/specs/run/package_json_type/commonjs/add.js b/tests/specs/run/package_json_type/commonjs/add.js new file mode 100644 index 000000000..2a886fbc1 --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/add.js @@ -0,0 +1,3 @@ +module.exports.add = function (a, b) { + return a + b; +}; diff --git a/tests/specs/run/package_json_type/commonjs/deno.jsonc b/tests/specs/run/package_json_type/commonjs/deno.jsonc new file mode 100644 index 000000000..35f64c86f --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/deno.jsonc @@ -0,0 +1,5 @@ +{ + "unstable": [ + "detect-cjs" + ] +} diff --git a/tests/specs/run/package_json_type/commonjs/import_import_meta.js b/tests/specs/run/package_json_type/commonjs/import_import_meta.js new file mode 100644 index 000000000..f07518985 --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/import_import_meta.js @@ -0,0 +1,3 @@ +import { value } from "./import_meta.js"; + +console.log(value); diff --git a/tests/specs/run/package_json_type/commonjs/import_meta.js b/tests/specs/run/package_json_type/commonjs/import_meta.js new file mode 100644 index 000000000..2bdbc30fe --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/import_meta.js @@ -0,0 +1 @@ +export const value = import.meta.url; diff --git a/tests/specs/run/package_json_type/commonjs/main_cjs.js b/tests/specs/run/package_json_type/commonjs/main_cjs.js new file mode 100644 index 000000000..365e8e06f --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_cjs.js @@ -0,0 +1,2 @@ +const { add } = require("./add"); +console.log(add(1, 2)); diff --git a/tests/specs/run/package_json_type/commonjs/main_esm.js b/tests/specs/run/package_json_type/commonjs/main_esm.js new file mode 100644 index 000000000..8c55f673b --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_esm.js @@ -0,0 +1,3 @@ +import { add } from "./add.js"; + +console.log(add(1, 2)); diff --git a/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.js b/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.js new file mode 100644 index 000000000..f1dff20b5 --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.js @@ -0,0 +1,2 @@ +console.log(import.meta.url); +console.log(require("./add")); diff --git a/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.out b/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.out new file mode 100644 index 000000000..e177defff --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_esm_import_meta.out @@ -0,0 +1,2 @@ +[WILDLINE]main_esm_import_meta.js +error: Uncaught (in promise) ReferenceError: require is not defined[WILDCARD] diff --git a/tests/specs/run/package_json_type/commonjs/main_mix.js b/tests/specs/run/package_json_type/commonjs/main_mix.js new file mode 100644 index 000000000..2a2c2c62a --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_mix.js @@ -0,0 +1,6 @@ +import { add } from "./add.js"; + +console.log(add(1, 2)); + +// will error +console.log(require("./add").add(1, 2)); diff --git a/tests/specs/run/package_json_type/commonjs/main_mix.out b/tests/specs/run/package_json_type/commonjs/main_mix.out new file mode 100644 index 000000000..d6123d48b --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/main_mix.out @@ -0,0 +1,5 @@ +3 +error: Uncaught (in promise) ReferenceError: require is not defined +console.log(require("./add").add(1, 2)); + ^ + at file:///[WILDLINE]main_mix.js:[WILDLINE] diff --git a/tests/specs/run/package_json_type/commonjs/not_import_meta.js b/tests/specs/run/package_json_type/commonjs/not_import_meta.js new file mode 100644 index 000000000..216b900df --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/not_import_meta.js @@ -0,0 +1,8 @@ +try { + console.log(test.import.meta.url); +} catch { + // ignore +} + +// should work because this is not an ESM file +console.log(require("./add").add(1, 2)); diff --git a/tests/specs/run/package_json_type/commonjs/package.json b/tests/specs/run/package_json_type/commonjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/specs/run/package_json_type/commonjs/tla.js b/tests/specs/run/package_json_type/commonjs/tla.js new file mode 100644 index 000000000..978578de4 --- /dev/null +++ b/tests/specs/run/package_json_type/commonjs/tla.js @@ -0,0 +1,2 @@ +await new Promise((r) => r()); +console.log("loaded"); diff --git a/tests/specs/run/package_json_type/none/__test__.jsonc b/tests/specs/run/package_json_type/none/__test__.jsonc new file mode 100644 index 000000000..fa1d22ca1 --- /dev/null +++ b/tests/specs/run/package_json_type/none/__test__.jsonc @@ -0,0 +1,18 @@ +{ + "tests": { + "main_cjs": { + "args": "run --allow-read=. main_cjs.js", + "output": "main_cjs.out", + "exitCode": 1 + }, + "main_esm": { + "args": "run --allow-read=. main_esm.js", + "output": "main_esm.out", + "exitCode": 1 + }, + "sub_folder_cjs": { + "args": "run --allow-read=. sub_folder_cjs.js", + "output": "3\n" + } + } +} diff --git a/tests/specs/run/package_json_type/none/add.js b/tests/specs/run/package_json_type/none/add.js new file mode 100644 index 000000000..2a886fbc1 --- /dev/null +++ b/tests/specs/run/package_json_type/none/add.js @@ -0,0 +1,3 @@ +module.exports.add = function (a, b) { + return a + b; +}; diff --git a/tests/specs/run/package_json_type/none/commonjs/add.js b/tests/specs/run/package_json_type/none/commonjs/add.js new file mode 100644 index 000000000..2a886fbc1 --- /dev/null +++ b/tests/specs/run/package_json_type/none/commonjs/add.js @@ -0,0 +1,3 @@ +module.exports.add = function (a, b) { + return a + b; +}; diff --git a/tests/specs/run/package_json_type/none/commonjs/package.json b/tests/specs/run/package_json_type/none/commonjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/specs/run/package_json_type/none/commonjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/specs/run/package_json_type/none/deno.jsonc b/tests/specs/run/package_json_type/none/deno.jsonc new file mode 100644 index 000000000..35f64c86f --- /dev/null +++ b/tests/specs/run/package_json_type/none/deno.jsonc @@ -0,0 +1,5 @@ +{ + "unstable": [ + "detect-cjs" + ] +} diff --git a/tests/specs/run/package_json_type/none/main_cjs.js b/tests/specs/run/package_json_type/none/main_cjs.js new file mode 100644 index 000000000..365e8e06f --- /dev/null +++ b/tests/specs/run/package_json_type/none/main_cjs.js @@ -0,0 +1,2 @@ +const { add } = require("./add"); +console.log(add(1, 2)); diff --git a/tests/specs/run/package_json_type/none/main_cjs.out b/tests/specs/run/package_json_type/none/main_cjs.out new file mode 100644 index 000000000..fe9acf009 --- /dev/null +++ b/tests/specs/run/package_json_type/none/main_cjs.out @@ -0,0 +1,4 @@ +error: Uncaught (in promise) ReferenceError: require is not defined +const { add } = require("./add"); + ^ + at file:///[WILDLINE] diff --git a/tests/specs/run/package_json_type/none/main_esm.js b/tests/specs/run/package_json_type/none/main_esm.js new file mode 100644 index 000000000..8c55f673b --- /dev/null +++ b/tests/specs/run/package_json_type/none/main_esm.js @@ -0,0 +1,3 @@ +import { add } from "./add.js"; + +console.log(add(1, 2)); diff --git a/tests/specs/run/package_json_type/none/main_esm.out b/tests/specs/run/package_json_type/none/main_esm.out new file mode 100644 index 000000000..5f16c5f35 --- /dev/null +++ b/tests/specs/run/package_json_type/none/main_esm.out @@ -0,0 +1,4 @@ +error: Uncaught SyntaxError: The requested module './add.js' does not provide an export named 'add' +import { add } from "./add.js"; + ^ + at <anonymous> (file:///[WILDLINE]) diff --git a/tests/specs/run/package_json_type/none/package.json b/tests/specs/run/package_json_type/none/package.json new file mode 100644 index 000000000..2c63c0851 --- /dev/null +++ b/tests/specs/run/package_json_type/none/package.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/specs/run/package_json_type/none/sub_folder_cjs.js b/tests/specs/run/package_json_type/none/sub_folder_cjs.js new file mode 100644 index 000000000..ad04e6730 --- /dev/null +++ b/tests/specs/run/package_json_type/none/sub_folder_cjs.js @@ -0,0 +1,3 @@ +import { add } from "./commonjs/add.js"; + +console.log(add(1, 2)); |