1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

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`).
This commit is contained in:
David Sherret 2024-10-14 20:48:39 -04:00 committed by GitHub
parent ee7d450143
commit 1a0cb5b531
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 520 additions and 155 deletions

View file

@ -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)
} else if self.idx == 2 {
.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)
.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
}
UnstableArgsConfig::ResolutionAndRuntime => Some("true"),
}
} else {
None
})
} else {
return None;
};
self.idx += 1;
Some(arg.display_order(self.idx + 1000))
}
}
.display_order(next_display_order())
);
fn unstable_args(cfg: UnstableArgsConfig) -> impl IntoIterator<Item = Arg> {
UnstableArgsIter { idx: 0, cfg }
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"),
}
} else {
None
})
.display_order(next_display_order()),
);
}
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");

View file

@ -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

57
cli/cache/mod.rs vendored
View file

@ -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

View file

@ -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())
}
}

View file

@ -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(),
})
}
}

View file

@ -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(),
},
)
}

View file

@ -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())
}

View file

@ -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 {

View file

@ -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) {

View file

@ -528,6 +528,7 @@
"bare-node-builtins",
"byonm",
"cron",
"detect-cjs",
"ffi",
"fs",
"http",

View file

@ -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(),
},

View file

@ -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,
},
);

View file

@ -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,

View file

@ -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;

View file

@ -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 =

View file

@ -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
}]
}

View file

@ -0,0 +1,3 @@
module.exports.add = function (a, b) {
return a + b;
};

View file

@ -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]

View file

@ -0,0 +1,5 @@
{
"unstable": [
"detect-cjs"
]
}

View file

@ -0,0 +1,3 @@
import { add } from "./add.js";
console.log(add(1, 2));

View file

@ -0,0 +1 @@
error: Module not found: file:///[WILDLINE]/add.js

View file

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View file

@ -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"
}
}
}

View file

@ -0,0 +1,3 @@
module.exports.add = function (a, b) {
return a + b;
};

View file

@ -0,0 +1,5 @@
{
"unstable": [
"detect-cjs"
]
}

View file

@ -0,0 +1,3 @@
import { value } from "./import_meta.js";
console.log(value);

View file

@ -0,0 +1 @@
export const value = import.meta.url;

View file

@ -0,0 +1,2 @@
const { add } = require("./add");
console.log(add(1, 2));

View file

@ -0,0 +1,3 @@
import { add } from "./add.js";
console.log(add(1, 2));

View file

@ -0,0 +1,2 @@
console.log(import.meta.url);
console.log(require("./add"));

View file

@ -0,0 +1,2 @@
[WILDLINE]main_esm_import_meta.js
error: Uncaught (in promise) ReferenceError: require is not defined[WILDCARD]

View file

@ -0,0 +1,6 @@
import { add } from "./add.js";
console.log(add(1, 2));
// will error
console.log(require("./add").add(1, 2));

View file

@ -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]

View file

@ -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));

View file

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View file

@ -0,0 +1,2 @@
await new Promise((r) => r());
console.log("loaded");

View file

@ -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"
}
}
}

View file

@ -0,0 +1,3 @@
module.exports.add = function (a, b) {
return a + b;
};

View file

@ -0,0 +1,3 @@
module.exports.add = function (a, b) {
return a + b;
};

View file

@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View file

@ -0,0 +1,5 @@
{
"unstable": [
"detect-cjs"
]
}

View file

@ -0,0 +1,2 @@
const { add } = require("./add");
console.log(add(1, 2));

View file

@ -0,0 +1,4 @@
error: Uncaught (in promise) ReferenceError: require is not defined
const { add } = require("./add");
^
at file:///[WILDLINE]

View file

@ -0,0 +1,3 @@
import { add } from "./add.js";
console.log(add(1, 2));

View file

@ -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])

View file

@ -0,0 +1,2 @@
{
}

View file

@ -0,0 +1,3 @@
import { add } from "./commonjs/add.js";
console.log(add(1, 2));