summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2024-10-14 20:48:39 -0400
committerGitHub <noreply@github.com>2024-10-14 20:48:39 -0400
commit1a0cb5b5312941521ab021cfe9eaed498f35900b (patch)
tree2e5c58e25e8506b993ac678e83ba0c2feac37d75
parentee7d4501435f0ebd655c8b50bd6e41ca19e71abc (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`).
-rw-r--r--cli/args/flags.rs115
-rw-r--r--cli/args/mod.rs31
-rw-r--r--cli/cache/mod.rs57
-rw-r--r--cli/cache/parsed_source.rs40
-rw-r--r--cli/factory.rs13
-rw-r--r--cli/graph_util.rs11
-rw-r--r--cli/module_loader.rs89
-rw-r--r--cli/node.rs29
-rw-r--r--cli/resolver.rs50
-rw-r--r--cli/schemas/config-file.v1.json1
-rw-r--r--cli/standalone/binary.rs1
-rw-r--r--cli/standalone/mod.rs5
-rw-r--r--cli/tools/compile.rs10
-rw-r--r--cli/util/text_encoding.rs18
-rw-r--r--cli/worker.rs32
-rw-r--r--tests/specs/compile/detect_cjs/__test__.jsonc24
-rw-r--r--tests/specs/compile/detect_cjs/add.js3
-rw-r--r--tests/specs/compile/detect_cjs/compile.out3
-rw-r--r--tests/specs/compile/detect_cjs/deno.json5
-rw-r--r--tests/specs/compile/detect_cjs/main.js3
-rw-r--r--tests/specs/compile/detect_cjs/output.out1
-rw-r--r--tests/specs/compile/detect_cjs/package.json3
-rw-r--r--tests/specs/run/package_json_type/commonjs/__test__.jsonc34
-rw-r--r--tests/specs/run/package_json_type/commonjs/add.js3
-rw-r--r--tests/specs/run/package_json_type/commonjs/deno.jsonc5
-rw-r--r--tests/specs/run/package_json_type/commonjs/import_import_meta.js3
-rw-r--r--tests/specs/run/package_json_type/commonjs/import_meta.js1
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_cjs.js2
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_esm.js3
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_esm_import_meta.js2
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_esm_import_meta.out2
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_mix.js6
-rw-r--r--tests/specs/run/package_json_type/commonjs/main_mix.out5
-rw-r--r--tests/specs/run/package_json_type/commonjs/not_import_meta.js8
-rw-r--r--tests/specs/run/package_json_type/commonjs/package.json3
-rw-r--r--tests/specs/run/package_json_type/commonjs/tla.js2
-rw-r--r--tests/specs/run/package_json_type/none/__test__.jsonc18
-rw-r--r--tests/specs/run/package_json_type/none/add.js3
-rw-r--r--tests/specs/run/package_json_type/none/commonjs/add.js3
-rw-r--r--tests/specs/run/package_json_type/none/commonjs/package.json3
-rw-r--r--tests/specs/run/package_json_type/none/deno.jsonc5
-rw-r--r--tests/specs/run/package_json_type/none/main_cjs.js2
-rw-r--r--tests/specs/run/package_json_type/none/main_cjs.out4
-rw-r--r--tests/specs/run/package_json_type/none/main_esm.js3
-rw-r--r--tests/specs/run/package_json_type/none/main_esm.out4
-rw-r--r--tests/specs/run/package_json_type/none/package.json2
-rw-r--r--tests/specs/run/package_json_type/none/sub_folder_cjs.js3
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));