1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

fix(npm): prefer importing esm from esm (#15676)

This commit is contained in:
David Sherret 2022-08-30 14:09:22 -04:00 committed by GitHub
parent 54be07d05e
commit 5f251b283b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 331 additions and 209 deletions

1
Cargo.lock generated
View file

@ -1141,6 +1141,7 @@ name = "deno_node"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"deno_core", "deno_core",
"path-clean",
"regex", "regex",
"serde", "serde",
] ]

View file

@ -17,11 +17,13 @@ use deno_core::serde_json::Value;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_graph::source::ResolveResponse; use deno_graph::source::ResolveResponse;
use deno_runtime::deno_node::get_closest_package_json;
use deno_runtime::deno_node::legacy_main_resolve; use deno_runtime::deno_node::legacy_main_resolve;
use deno_runtime::deno_node::package_exports_resolve; use deno_runtime::deno_node::package_exports_resolve;
use deno_runtime::deno_node::package_imports_resolve; use deno_runtime::deno_node::package_imports_resolve;
use deno_runtime::deno_node::package_resolve; use deno_runtime::deno_node::package_resolve;
use deno_runtime::deno_node::DenoDirNpmResolver; use deno_runtime::deno_node::DenoDirNpmResolver;
use deno_runtime::deno_node::NodeModuleKind;
use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_node::DEFAULT_CONDITIONS; use deno_runtime::deno_node::DEFAULT_CONDITIONS;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@ -342,6 +344,8 @@ pub fn node_resolve(
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<Option<ResolveResponse>, AnyError> { ) -> Result<Option<ResolveResponse>, AnyError> {
// Note: if we are here, then the referrer is an esm module
// TODO(bartlomieju): skipped "policy" part as we don't plan to support it // TODO(bartlomieju): skipped "policy" part as we don't plan to support it
// NOTE(bartlomieju): this will force `ProcState` to use Node.js polyfill for // NOTE(bartlomieju): this will force `ProcState` to use Node.js polyfill for
@ -385,7 +389,7 @@ pub fn node_resolve(
return Err(errors::err_unsupported_esm_url_scheme(&url)); return Err(errors::err_unsupported_esm_url_scheme(&url));
} }
// todo(THIS PR): I think this is handled upstream so can be removed? // todo(dsherret): this seems wrong
if referrer.scheme() == "data" { if referrer.scheme() == "data" {
let url = referrer.join(specifier).map_err(AnyError::from)?; let url = referrer.join(specifier).map_err(AnyError::from)?;
return Ok(Some(ResolveResponse::Specifier(url))); return Ok(Some(ResolveResponse::Specifier(url)));
@ -412,7 +416,7 @@ pub fn node_resolve_npm_reference(
let package_folder = npm_resolver let package_folder = npm_resolver
.resolve_package_from_deno_module(&reference.req)? .resolve_package_from_deno_module(&reference.req)?
.folder_path; .folder_path;
let maybe_url = package_config_resolve( let resolved_path = package_config_resolve(
&reference &reference
.sub_path .sub_path
.as_ref() .as_ref()
@ -420,16 +424,13 @@ pub fn node_resolve_npm_reference(
.unwrap_or_else(|| ".".to_string()), .unwrap_or_else(|| ".".to_string()),
&package_folder, &package_folder,
npm_resolver, npm_resolver,
NodeModuleKind::Esm,
) )
.map(Some)
.with_context(|| { .with_context(|| {
format!("Error resolving package config for '{}'.", reference) format!("Error resolving package config for '{}'.", reference)
})?; })?;
let url = match maybe_url {
Some(url) => url,
None => return Ok(None),
};
let url = ModuleSpecifier::from_file_path(resolved_path).unwrap();
let resolve_response = url_to_resolve_response(url, npm_resolver)?; let resolve_response = url_to_resolve_response(url, npm_resolver)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and // TODO(bartlomieju): skipped checking errors for commonJS resolution and
// "preserveSymlinksMain"/"preserveSymlinks" options. // "preserveSymlinksMain"/"preserveSymlinks" options.
@ -521,33 +522,30 @@ fn package_config_resolve(
package_subpath: &str, package_subpath: &str,
package_dir: &Path, package_dir: &Path,
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> { referrer_kind: NodeModuleKind,
) -> Result<PathBuf, AnyError> {
let package_json_path = package_dir.join("package.json"); let package_json_path = package_dir.join("package.json");
// todo(dsherret): remove base from this code let referrer =
let base =
ModuleSpecifier::from_directory_path(package_json_path.parent().unwrap()) ModuleSpecifier::from_directory_path(package_json_path.parent().unwrap())
.unwrap(); .unwrap();
let package_config = let package_config =
PackageJson::load(npm_resolver, package_json_path.clone())?; PackageJson::load(npm_resolver, package_json_path.clone())?;
let package_json_url =
ModuleSpecifier::from_file_path(&package_json_path).unwrap();
if let Some(exports) = &package_config.exports { if let Some(exports) = &package_config.exports {
return package_exports_resolve( return package_exports_resolve(
package_json_url, &package_json_path,
package_subpath.to_string(), package_subpath.to_string(),
exports, exports,
&base, &referrer,
referrer_kind,
DEFAULT_CONDITIONS, DEFAULT_CONDITIONS,
npm_resolver, npm_resolver,
); );
} }
if package_subpath == "." { if package_subpath == "." {
return legacy_main_resolve(&package_json_url, &package_config, &base); return legacy_main_resolve(&package_config, referrer_kind);
} }
package_json_url Ok(package_dir.join(package_subpath))
.join(package_subpath)
.map_err(AnyError::from)
} }
fn url_to_resolve_response( fn url_to_resolve_response(
@ -570,37 +568,6 @@ fn url_to_resolve_response(
}) })
} }
fn get_closest_package_json(
url: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<PackageJson, AnyError> {
let package_json_path = get_closest_package_json_path(url, npm_resolver)?;
PackageJson::load(npm_resolver, package_json_path)
}
fn get_closest_package_json_path(
url: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<PathBuf, AnyError> {
let file_path = url.to_file_path().unwrap();
let mut current_dir = file_path.parent().unwrap();
let package_json_path = current_dir.join("package.json");
if package_json_path.exists() {
return Ok(package_json_path);
}
let root_folder = npm_resolver
.resolve_package_folder_from_path(&url.to_file_path().unwrap())?;
while current_dir.starts_with(&root_folder) {
current_dir = current_dir.parent().unwrap();
let package_json_path = current_dir.join("./package.json");
if package_json_path.exists() {
return Ok(package_json_path);
}
}
bail!("did not find package.json in {}", root_folder.display())
}
fn finalize_resolution( fn finalize_resolution(
resolved: ModuleSpecifier, resolved: ModuleSpecifier,
base: &ModuleSpecifier, base: &ModuleSpecifier,
@ -667,25 +634,34 @@ fn module_resolve(
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<Option<ModuleSpecifier>, AnyError> { ) -> Result<Option<ModuleSpecifier>, AnyError> {
// note: if we're here, the referrer is an esm module
let url = if should_be_treated_as_relative_or_absolute_path(specifier) { let url = if should_be_treated_as_relative_or_absolute_path(specifier) {
let resolved_specifier = referrer.join(specifier)?; let resolved_specifier = referrer.join(specifier)?;
Some(resolved_specifier) Some(resolved_specifier)
} else if specifier.starts_with('#') { } else if specifier.starts_with('#') {
Some(package_imports_resolve( Some(
specifier, package_imports_resolve(
referrer, specifier,
conditions, referrer,
npm_resolver, NodeModuleKind::Esm,
)?) conditions,
npm_resolver,
)
.map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
)
} else if let Ok(resolved) = Url::parse(specifier) { } else if let Ok(resolved) = Url::parse(specifier) {
Some(resolved) Some(resolved)
} else { } else {
Some(package_resolve( Some(
specifier, package_resolve(
referrer, specifier,
conditions, referrer,
npm_resolver, NodeModuleKind::Esm,
)?) conditions,
npm_resolver,
)
.map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
)
}; };
Ok(match url { Ok(match url {
Some(url) => Some(finalize_resolution(url, referrer)?), Some(url) => Some(finalize_resolution(url, referrer)?),

View file

@ -75,6 +75,13 @@ itest!(conditional_exports {
http_server: true, http_server: true,
}); });
itest!(dual_cjs_esm {
args: "run --unstable -A --quiet npm/dual_cjs_esm/main.ts",
output: "npm/dual_cjs_esm/main.out",
envs: env_vars(),
http_server: true,
});
itest!(dynamic_import { itest!(dynamic_import {
args: "run --allow-read --allow-env --unstable npm/dynamic_import/main.ts", args: "run --allow-read --allow-env --unstable npm/dynamic_import/main.ts",
output: "npm/dynamic_import/main.out", output: "npm/dynamic_import/main.out",

View file

@ -0,0 +1 @@
esm

View file

@ -0,0 +1,3 @@
import { getKind } from "npm:@denotest/dual-cjs-esm";
console.log(getKind());

View file

@ -0,0 +1,3 @@
exports.getKind = function() {
return "cjs";
};

View file

@ -0,0 +1,3 @@
export function getKind() {
return "esm";
}

View file

@ -0,0 +1,7 @@
{
"name": "@denotest/dual-cjs-esm",
"version": "1.0.0",
"type": "module",
"main": "./index.cjs",
"module": "./index.mjs"
}

View file

@ -390,8 +390,8 @@
return false; return false;
}; };
Module._nodeModulePaths = function (from) { Module._nodeModulePaths = function (fromPath) {
return ops.op_require_node_module_paths(from); return ops.op_require_node_module_paths(fromPath);
}; };
Module._resolveLookupPaths = function (request, parent) { Module._resolveLookupPaths = function (request, parent) {
@ -728,7 +728,7 @@
const content = ops.op_require_read_file(filename); const content = ops.op_require_read_file(filename);
if (StringPrototypeEndsWith(filename, ".js")) { if (StringPrototypeEndsWith(filename, ".js")) {
const pkg = core.ops.op_require_read_package_scope(filename); const pkg = core.ops.op_require_read_closest_package_json(filename);
if (pkg && pkg.exists && pkg.typ == "module") { if (pkg && pkg.exists && pkg.typ == "module") {
let message = `Trying to import ESM module: ${filename}`; let message = `Trying to import ESM module: ${filename}`;

View file

@ -15,5 +15,6 @@ path = "lib.rs"
[dependencies] [dependencies]
deno_core = { version = "0.148.0", path = "../../core" } deno_core = { version = "0.148.0", path = "../../core" }
path-clean = "=0.1.0"
regex = "1" regex = "1"
serde = "1.0.136" serde = "1.0.136"

View file

@ -56,7 +56,7 @@ pub fn err_invalid_package_target(
key: String, key: String,
target: String, target: String,
is_import: bool, is_import: bool,
maybe_base: Option<String>, maybe_referrer: Option<String>,
) -> AnyError { ) -> AnyError {
let rel_error = !is_import && !target.is_empty() && !target.starts_with("./"); let rel_error = !is_import && !target.is_empty() && !target.starts_with("./");
let mut msg = "[ERR_INVALID_PACKAGE_TARGET]".to_string(); let mut msg = "[ERR_INVALID_PACKAGE_TARGET]".to_string();
@ -69,7 +69,7 @@ pub fn err_invalid_package_target(
msg = format!("{} Invalid \"{}\" target {} defined for '{}' in the package config {}package.json", msg, ie, target, key, pkg_path) msg = format!("{} Invalid \"{}\" target {} defined for '{}' in the package config {}package.json", msg, ie, target, key, pkg_path)
}; };
if let Some(base) = maybe_base { if let Some(base) = maybe_referrer {
msg = format!("{} imported from {}", msg, base); msg = format!("{} imported from {}", msg, base);
}; };
if rel_error { if rel_error {
@ -82,7 +82,7 @@ pub fn err_invalid_package_target(
pub fn err_package_path_not_exported( pub fn err_package_path_not_exported(
pkg_path: String, pkg_path: String,
subpath: String, subpath: String,
maybe_base: Option<String>, maybe_referrer: Option<String>,
) -> AnyError { ) -> AnyError {
let mut msg = "[ERR_PACKAGE_PATH_NOT_EXPORTED]".to_string(); let mut msg = "[ERR_PACKAGE_PATH_NOT_EXPORTED]".to_string();
@ -95,8 +95,8 @@ pub fn err_package_path_not_exported(
msg = format!("{} Package subpath \'{}\' is not defined by \"exports\" in {}package.json", msg, subpath, pkg_path); msg = format!("{} Package subpath \'{}\' is not defined by \"exports\" in {}package.json", msg, subpath, pkg_path);
}; };
if let Some(base) = maybe_base { if let Some(referrer) = maybe_referrer {
msg = format!("{} imported from {}", msg, base); msg = format!("{} imported from {}", msg, referrer);
} }
generic_error(msg) generic_error(msg)

View file

@ -12,11 +12,13 @@ use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
pub use package_json::PackageJson; pub use package_json::PackageJson;
pub use resolution::get_closest_package_json;
pub use resolution::get_package_scope_config; pub use resolution::get_package_scope_config;
pub use resolution::legacy_main_resolve; pub use resolution::legacy_main_resolve;
pub use resolution::package_exports_resolve; pub use resolution::package_exports_resolve;
pub use resolution::package_imports_resolve; pub use resolution::package_imports_resolve;
pub use resolution::package_resolve; pub use resolution::package_resolve;
pub use resolution::NodeModuleKind;
pub use resolution::DEFAULT_CONDITIONS; pub use resolution::DEFAULT_CONDITIONS;
pub trait NodePermissions { pub trait NodePermissions {
@ -77,6 +79,7 @@ pub fn init<P: NodePermissions + 'static>(
op_require_read_file::decl::<P>(), op_require_read_file::decl::<P>(),
op_require_as_file_path::decl(), op_require_as_file_path::decl(),
op_require_resolve_exports::decl(), op_require_resolve_exports::decl(),
op_require_read_closest_package_json::decl::<P>(),
op_require_read_package_scope::decl(), op_require_read_package_scope::decl(),
op_require_package_imports_resolve::decl::<P>(), op_require_package_imports_resolve::decl::<P>(),
]) ])
@ -485,17 +488,18 @@ fn op_require_try_self(
return Ok(None); return Ok(None);
} }
let base = deno_core::url::Url::from_file_path(PathBuf::from("/")).unwrap(); let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap();
if let Some(exports) = &pkg.exports { if let Some(exports) = &pkg.exports {
resolution::package_exports_resolve( resolution::package_exports_resolve(
deno_core::url::Url::from_file_path(&pkg.path).unwrap(), &pkg.path,
expansion, expansion,
exports, exports,
&base, &referrer,
NodeModuleKind::Cjs,
resolution::REQUIRE_CONDITIONS, resolution::REQUIRE_CONDITIONS,
&*resolver, &*resolver,
) )
.map(|r| Some(r.as_str().to_string())) .map(|r| Some(r.to_string_lossy().to_string()))
} else { } else {
Ok(None) Ok(None)
} }
@ -550,21 +554,42 @@ fn op_require_resolve_exports(
)?; )?;
if let Some(exports) = &pkg.exports { if let Some(exports) = &pkg.exports {
let base = Url::from_file_path(parent_path).unwrap(); let referrer = Url::from_file_path(parent_path).unwrap();
resolution::package_exports_resolve( resolution::package_exports_resolve(
deno_core::url::Url::from_directory_path(pkg_path).unwrap(), &pkg.path,
format!(".{}", expansion), format!(".{}", expansion),
exports, exports,
&base, &referrer,
NodeModuleKind::Cjs,
resolution::REQUIRE_CONDITIONS, resolution::REQUIRE_CONDITIONS,
&*resolver, &*resolver,
) )
.map(|r| Some(r.to_file_path().unwrap().to_string_lossy().to_string())) .map(|r| Some(r.to_string_lossy().to_string()))
} else { } else {
Ok(None) Ok(None)
} }
} }
#[op]
fn op_require_read_closest_package_json<P>(
state: &mut OpState,
filename: String,
) -> Result<PackageJson, AnyError>
where
P: NodePermissions + 'static,
{
check_unstable(state);
ensure_read_permission::<P>(
state,
PathBuf::from(&filename).parent().unwrap(),
)?;
let resolver = state.borrow::<Rc<dyn DenoDirNpmResolver>>().clone();
resolution::get_closest_package_json(
&Url::from_file_path(filename).unwrap(),
&*resolver,
)
}
#[op] #[op]
fn op_require_read_package_scope( fn op_require_read_package_scope(
state: &mut OpState, state: &mut OpState,
@ -600,10 +625,11 @@ where
let r = resolution::package_imports_resolve( let r = resolution::package_imports_resolve(
&request, &request,
&referrer, &referrer,
NodeModuleKind::Cjs,
resolution::REQUIRE_CONDITIONS, resolution::REQUIRE_CONDITIONS,
&*resolver, &*resolver,
) )
.map(|r| Some(r.as_str().to_string())); .map(|r| Some(Url::from_file_path(r).unwrap().to_string()));
state.put(resolver); state.put(resolver);
r r
} else { } else {

View file

@ -19,6 +19,7 @@ pub struct PackageJson {
pub imports: Option<Map<String, Value>>, pub imports: Option<Map<String, Value>>,
pub bin: Option<Value>, pub bin: Option<Value>,
pub main: Option<String>, pub main: Option<String>,
pub module: Option<String>,
pub name: Option<String>, pub name: Option<String>,
pub path: PathBuf, pub path: PathBuf,
pub typ: String, pub typ: String,
@ -33,6 +34,7 @@ impl PackageJson {
imports: None, imports: None,
bin: None, bin: None,
main: None, main: None,
module: None,
name: None, name: None,
path, path,
typ: "none".to_string(), typ: "none".to_string(),
@ -66,6 +68,7 @@ impl PackageJson {
let imports_val = package_json.get("imports"); let imports_val = package_json.get("imports");
let main_val = package_json.get("main"); let main_val = package_json.get("main");
let module_val = package_json.get("module");
let name_val = package_json.get("name"); let name_val = package_json.get("name");
let type_val = package_json.get("type"); let type_val = package_json.get("type");
let bin = package_json.get("bin").map(ToOwned::to_owned); let bin = package_json.get("bin").map(ToOwned::to_owned);
@ -79,21 +82,12 @@ impl PackageJson {
} }
}); });
let imports = if let Some(imp) = imports_val { let imports = imports_val
imp.as_object().map(|imp| imp.to_owned()) .and_then(|imp| imp.as_object())
} else { .map(|imp| imp.to_owned());
None let main = main_val.and_then(|s| s.as_str()).map(|s| s.to_string());
}; let name = name_val.and_then(|s| s.as_str()).map(|s| s.to_string());
let main = if let Some(m) = main_val { let module = module_val.and_then(|s| s.as_str()).map(|s| s.to_string());
m.as_str().map(|m| m.to_string())
} else {
None
};
let name = if let Some(n) = name_val {
n.as_str().map(|n| n.to_string())
} else {
None
};
// Ignore unknown types for forwards compatibility // Ignore unknown types for forwards compatibility
let typ = if let Some(t) = type_val { let typ = if let Some(t) = type_val {
@ -121,6 +115,7 @@ impl PackageJson {
path, path,
main, main,
name, name,
module,
typ, typ,
types, types,
exports, exports,

View file

@ -1,13 +1,16 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use deno_core::anyhow::bail;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json::Map; use deno_core::serde_json::Map;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use path_clean::PathClean;
use regex::Regex; use regex::Regex;
use crate::errors; use crate::errors;
@ -17,25 +20,29 @@ use crate::DenoDirNpmResolver;
pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"]; pub static DEFAULT_CONDITIONS: &[&str] = &["deno", "node", "import"];
pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"]; pub static REQUIRE_CONDITIONS: &[&str] = &["require", "node"];
fn to_file_path(url: &ModuleSpecifier) -> PathBuf { #[derive(Clone, Copy, Debug, PartialEq, Eq)]
url pub enum NodeModuleKind {
.to_file_path() Esm,
.unwrap_or_else(|_| panic!("Provided URL was not file:// URL: {}", url)) Cjs,
} }
fn to_file_path_string(url: &ModuleSpecifier) -> String { fn to_specifier_display_string(url: &ModuleSpecifier) -> String {
to_file_path(url).display().to_string() if let Ok(path) = url.to_file_path() {
path.display().to_string()
} else {
url.to_string()
}
} }
fn throw_import_not_defined( fn throw_import_not_defined(
specifier: &str, specifier: &str,
package_json_url: Option<ModuleSpecifier>, package_json_path: Option<&Path>,
base: &ModuleSpecifier, base: &ModuleSpecifier,
) -> AnyError { ) -> AnyError {
errors::err_package_import_not_defined( errors::err_package_import_not_defined(
specifier, specifier,
package_json_url.map(|u| to_file_path_string(&u.join(".").unwrap())), package_json_path.map(|p| p.parent().unwrap().display().to_string()),
&to_file_path_string(base), &to_specifier_display_string(base),
) )
} }
@ -84,30 +91,32 @@ fn pattern_key_compare(a: &str, b: &str) -> i32 {
pub fn package_imports_resolve( pub fn package_imports_resolve(
name: &str, name: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<PathBuf, AnyError> {
if name == "#" || name.starts_with("#/") || name.ends_with('/') { if name == "#" || name.starts_with("#/") || name.ends_with('/') {
let reason = "is not a valid internal imports specifier name"; let reason = "is not a valid internal imports specifier name";
return Err(errors::err_invalid_module_specifier( return Err(errors::err_invalid_module_specifier(
name, name,
reason, reason,
Some(to_file_path_string(referrer)), Some(to_specifier_display_string(referrer)),
)); ));
} }
let package_config = get_package_scope_config(referrer, npm_resolver)?; let package_config = get_package_scope_config(referrer, npm_resolver)?;
let mut package_json_url = None; let mut package_json_path = None;
if package_config.exists { if package_config.exists {
package_json_url = Some(Url::from_file_path(package_config.path).unwrap()); package_json_path = Some(package_config.path.clone());
if let Some(imports) = &package_config.imports { if let Some(imports) = &package_config.imports {
if imports.contains_key(name) && !name.contains('*') { if imports.contains_key(name) && !name.contains('*') {
let maybe_resolved = resolve_package_target( let maybe_resolved = resolve_package_target(
package_json_url.clone().unwrap(), package_json_path.as_ref().unwrap(),
imports.get(name).unwrap().to_owned(), imports.get(name).unwrap().to_owned(),
"".to_string(), "".to_string(),
name.to_string(), name.to_string(),
referrer, referrer,
referrer_kind,
false, false,
true, true,
conditions, conditions,
@ -143,11 +152,12 @@ pub fn package_imports_resolve(
if !best_match.is_empty() { if !best_match.is_empty() {
let target = imports.get(best_match).unwrap().to_owned(); let target = imports.get(best_match).unwrap().to_owned();
let maybe_resolved = resolve_package_target( let maybe_resolved = resolve_package_target(
package_json_url.clone().unwrap(), package_json_path.as_ref().unwrap(),
target, target,
best_match_subpath.unwrap(), best_match_subpath.unwrap(),
best_match.to_string(), best_match.to_string(),
referrer, referrer,
referrer_kind,
true, true,
true, true,
conditions, conditions,
@ -161,41 +171,45 @@ pub fn package_imports_resolve(
} }
} }
Err(throw_import_not_defined(name, package_json_url, referrer)) Err(throw_import_not_defined(
name,
package_json_path.as_deref(),
referrer,
))
} }
fn throw_invalid_package_target( fn throw_invalid_package_target(
subpath: String, subpath: String,
target: String, target: String,
package_json_url: &ModuleSpecifier, package_json_path: &Path,
internal: bool, internal: bool,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> AnyError { ) -> AnyError {
errors::err_invalid_package_target( errors::err_invalid_package_target(
to_file_path_string(&package_json_url.join(".").unwrap()), package_json_path.parent().unwrap().display().to_string(),
subpath, subpath,
target, target,
internal, internal,
Some(base.as_str().to_string()), Some(referrer.as_str().to_string()),
) )
} }
fn throw_invalid_subpath( fn throw_invalid_subpath(
subpath: String, subpath: String,
package_json_url: &ModuleSpecifier, package_json_path: &Path,
internal: bool, internal: bool,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> AnyError { ) -> AnyError {
let ie = if internal { "imports" } else { "exports" }; let ie = if internal { "imports" } else { "exports" };
let reason = format!( let reason = format!(
"request is not a valid subpath for the \"{}\" resolution of {}", "request is not a valid subpath for the \"{}\" resolution of {}",
ie, ie,
to_file_path_string(package_json_url) package_json_path.display(),
); );
errors::err_invalid_module_specifier( errors::err_invalid_module_specifier(
&subpath, &subpath,
&reason, &reason,
Some(to_file_path_string(base)), Some(to_specifier_display_string(referrer)),
) )
} }
@ -204,20 +218,21 @@ fn resolve_package_target_string(
target: String, target: String,
subpath: String, subpath: String,
match_: String, match_: String,
package_json_url: ModuleSpecifier, package_json_path: &Path,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
pattern: bool, pattern: bool,
internal: bool, internal: bool,
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<PathBuf, AnyError> {
if !subpath.is_empty() && !pattern && !target.ends_with('/') { if !subpath.is_empty() && !pattern && !target.ends_with('/') {
return Err(throw_invalid_package_target( return Err(throw_invalid_package_target(
match_, match_,
target, target,
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)); ));
} }
let invalid_segment_re = let invalid_segment_re =
@ -234,9 +249,12 @@ fn resolve_package_target_string(
} else { } else {
format!("{}{}", target, subpath) format!("{}{}", target, subpath)
}; };
let package_json_url =
ModuleSpecifier::from_file_path(package_json_path).unwrap();
return package_resolve( return package_resolve(
&export_target, &export_target,
&package_json_url, &package_json_url,
referrer_kind,
conditions, conditions,
npm_resolver, npm_resolver,
); );
@ -245,35 +263,33 @@ fn resolve_package_target_string(
return Err(throw_invalid_package_target( return Err(throw_invalid_package_target(
match_, match_,
target, target,
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)); ));
} }
if invalid_segment_re.is_match(&target[2..]) { if invalid_segment_re.is_match(&target[2..]) {
return Err(throw_invalid_package_target( return Err(throw_invalid_package_target(
match_, match_,
target, target,
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)); ));
} }
let resolved = package_json_url.join(&target)?; let package_path = package_json_path.parent().unwrap();
let resolved_path = resolved.path(); let resolved_path = package_path.join(&target).clean();
let package_url = package_json_url.join(".").unwrap();
let package_path = package_url.path();
if !resolved_path.starts_with(package_path) { if !resolved_path.starts_with(package_path) {
return Err(throw_invalid_package_target( return Err(throw_invalid_package_target(
match_, match_,
target, target,
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)); ));
} }
if subpath.is_empty() { if subpath.is_empty() {
return Ok(resolved); return Ok(resolved_path);
} }
if invalid_segment_re.is_match(&subpath) { if invalid_segment_re.is_match(&subpath) {
let request = if pattern { let request = if pattern {
@ -283,39 +299,43 @@ fn resolve_package_target_string(
}; };
return Err(throw_invalid_subpath( return Err(throw_invalid_subpath(
request, request,
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)); ));
} }
if pattern { if pattern {
let resolved_path_str = resolved_path.to_string_lossy();
let replaced = pattern_re let replaced = pattern_re
.replace(resolved.as_str(), |_caps: &regex::Captures| subpath.clone()); .replace(&resolved_path_str, |_caps: &regex::Captures| {
let url = Url::parse(&replaced)?; subpath.clone()
return Ok(url); });
return Ok(PathBuf::from(replaced.to_string()));
} }
Ok(resolved.join(&subpath)?) Ok(resolved_path.join(&subpath).clean())
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn resolve_package_target( fn resolve_package_target(
package_json_url: ModuleSpecifier, package_json_path: &Path,
target: Value, target: Value,
subpath: String, subpath: String,
package_subpath: String, package_subpath: String,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
pattern: bool, pattern: bool,
internal: bool, internal: bool,
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<Option<ModuleSpecifier>, AnyError> { ) -> Result<Option<PathBuf>, AnyError> {
if let Some(target) = target.as_str() { if let Some(target) = target.as_str() {
return Ok(Some(resolve_package_target_string( return Ok(Some(resolve_package_target_string(
target.to_string(), target.to_string(),
subpath, subpath,
package_subpath, package_subpath,
package_json_url, package_json_path,
base, referrer,
referrer_kind,
pattern, pattern,
internal, internal,
conditions, conditions,
@ -329,11 +349,12 @@ fn resolve_package_target(
let mut last_error = None; let mut last_error = None;
for target_item in target_arr { for target_item in target_arr {
let resolved_result = resolve_package_target( let resolved_result = resolve_package_target(
package_json_url.clone(), package_json_path,
target_item.to_owned(), target_item.to_owned(),
subpath.clone(), subpath.clone(),
package_subpath.clone(), package_subpath.clone(),
base, referrer,
referrer_kind,
pattern, pattern,
internal, internal,
conditions, conditions,
@ -371,11 +392,12 @@ fn resolve_package_target(
if key == "default" || conditions.contains(&key.as_str()) { if key == "default" || conditions.contains(&key.as_str()) {
let condition_target = target_obj.get(key).unwrap().to_owned(); let condition_target = target_obj.get(key).unwrap().to_owned();
let resolved = resolve_package_target( let resolved = resolve_package_target(
package_json_url.clone(), package_json_path,
condition_target, condition_target,
subpath.clone(), subpath.clone(),
package_subpath.clone(), package_subpath.clone(),
base, referrer,
referrer_kind,
pattern, pattern,
internal, internal,
conditions, conditions,
@ -394,43 +416,45 @@ fn resolve_package_target(
Err(throw_invalid_package_target( Err(throw_invalid_package_target(
package_subpath, package_subpath,
target.to_string(), target.to_string(),
&package_json_url, package_json_path,
internal, internal,
base, referrer,
)) ))
} }
fn throw_exports_not_found( fn throw_exports_not_found(
subpath: String, subpath: String,
package_json_url: &ModuleSpecifier, package_json_path: &Path,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> AnyError { ) -> AnyError {
errors::err_package_path_not_exported( errors::err_package_path_not_exported(
to_file_path_string(&package_json_url.join(".").unwrap()), package_json_path.parent().unwrap().display().to_string(),
subpath, subpath,
Some(to_file_path_string(base)), Some(to_specifier_display_string(referrer)),
) )
} }
pub fn package_exports_resolve( pub fn package_exports_resolve(
package_json_url: ModuleSpecifier, package_json_path: &Path,
package_subpath: String, package_subpath: String,
package_exports: &Map<String, Value>, package_exports: &Map<String, Value>,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<PathBuf, AnyError> {
if package_exports.contains_key(&package_subpath) if package_exports.contains_key(&package_subpath)
&& package_subpath.find('*').is_none() && package_subpath.find('*').is_none()
&& !package_subpath.ends_with('/') && !package_subpath.ends_with('/')
{ {
let target = package_exports.get(&package_subpath).unwrap().to_owned(); let target = package_exports.get(&package_subpath).unwrap().to_owned();
let resolved = resolve_package_target( let resolved = resolve_package_target(
package_json_url.clone(), package_json_path,
target, target,
"".to_string(), "".to_string(),
package_subpath.to_string(), package_subpath.to_string(),
base, referrer,
referrer_kind,
false, false,
false, false,
conditions, conditions,
@ -439,8 +463,8 @@ pub fn package_exports_resolve(
if resolved.is_none() { if resolved.is_none() {
return Err(throw_exports_not_found( return Err(throw_exports_not_found(
package_subpath, package_subpath,
&package_json_url, package_json_path,
base, referrer,
)); ));
} }
return Ok(resolved.unwrap()); return Ok(resolved.unwrap());
@ -483,11 +507,12 @@ pub fn package_exports_resolve(
if !best_match.is_empty() { if !best_match.is_empty() {
let target = package_exports.get(best_match).unwrap().to_owned(); let target = package_exports.get(best_match).unwrap().to_owned();
let maybe_resolved = resolve_package_target( let maybe_resolved = resolve_package_target(
package_json_url.clone(), package_json_path,
target, target,
best_match_subpath.unwrap(), best_match_subpath.unwrap(),
best_match.to_string(), best_match.to_string(),
base, referrer,
referrer_kind,
true, true,
false, false,
conditions, conditions,
@ -498,22 +523,22 @@ pub fn package_exports_resolve(
} else { } else {
return Err(throw_exports_not_found( return Err(throw_exports_not_found(
package_subpath, package_subpath,
&package_json_url, package_json_path,
base, referrer,
)); ));
} }
} }
Err(throw_exports_not_found( Err(throw_exports_not_found(
package_subpath, package_subpath,
&package_json_url, package_json_path,
base, referrer,
)) ))
} }
fn parse_package_name( fn parse_package_name(
specifier: &str, specifier: &str,
base: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> Result<(String, String, bool), AnyError> { ) -> Result<(String, String, bool), AnyError> {
let mut separator_index = specifier.find('/'); let mut separator_index = specifier.find('/');
let mut valid_package_name = true; let mut valid_package_name = true;
@ -547,7 +572,7 @@ fn parse_package_name(
return Err(errors::err_invalid_module_specifier( return Err(errors::err_invalid_module_specifier(
specifier, specifier,
"is not a valid package name", "is not a valid package name",
Some(to_file_path_string(base)), Some(to_specifier_display_string(referrer)),
)); ));
} }
@ -563,27 +588,28 @@ fn parse_package_name(
pub fn package_resolve( pub fn package_resolve(
specifier: &str, specifier: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
referrer_kind: NodeModuleKind,
conditions: &[&str], conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver, npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<PathBuf, AnyError> {
let (package_name, package_subpath, _is_scoped) = let (package_name, package_subpath, _is_scoped) =
parse_package_name(specifier, referrer)?; parse_package_name(specifier, referrer)?;
// ResolveSelf // ResolveSelf
let package_config = get_package_scope_config(referrer, npm_resolver)?; let package_config = get_package_scope_config(referrer, npm_resolver)?;
if package_config.exists { if package_config.exists
let package_json_url = Url::from_file_path(&package_config.path).unwrap(); && package_config.name.as_ref() == Some(&package_name)
if package_config.name.as_ref() == Some(&package_name) { {
if let Some(exports) = &package_config.exports { if let Some(exports) = &package_config.exports {
return package_exports_resolve( return package_exports_resolve(
package_json_url, &package_config.path,
package_subpath, package_subpath,
exports, exports,
referrer, referrer,
conditions, referrer_kind,
npm_resolver, conditions,
); npm_resolver,
} );
} }
} }
@ -592,8 +618,6 @@ pub fn package_resolve(
&referrer.to_file_path().unwrap(), &referrer.to_file_path().unwrap(),
)?; )?;
let package_json_path = package_dir_path.join("package.json"); let package_json_path = package_dir_path.join("package.json");
let package_json_url =
ModuleSpecifier::from_file_path(&package_json_path).unwrap();
// todo: error with this instead when can't find package // todo: error with this instead when can't find package
// Err(errors::err_module_not_found( // Err(errors::err_module_not_found(
@ -612,21 +636,20 @@ pub fn package_resolve(
let package_json = PackageJson::load(npm_resolver, package_json_path)?; let package_json = PackageJson::load(npm_resolver, package_json_path)?;
if let Some(exports) = &package_json.exports { if let Some(exports) = &package_json.exports {
return package_exports_resolve( return package_exports_resolve(
package_json_url, &package_json.path,
package_subpath, package_subpath,
exports, exports,
referrer, referrer,
referrer_kind,
conditions, conditions,
npm_resolver, npm_resolver,
); );
} }
if package_subpath == "." { if package_subpath == "." {
return legacy_main_resolve(&package_json_url, &package_json, referrer); return legacy_main_resolve(&package_json, referrer_kind);
} }
package_json_url Ok(package_json.path.parent().unwrap().join(&package_subpath))
.join(&package_subpath)
.map_err(AnyError::from)
} }
pub fn get_package_scope_config( pub fn get_package_scope_config(
@ -635,12 +658,43 @@ pub fn get_package_scope_config(
) -> Result<PackageJson, AnyError> { ) -> Result<PackageJson, AnyError> {
let root_folder = npm_resolver let root_folder = npm_resolver
.resolve_package_folder_from_path(&referrer.to_file_path().unwrap())?; .resolve_package_folder_from_path(&referrer.to_file_path().unwrap())?;
let package_json_path = root_folder.join("./package.json"); let package_json_path = root_folder.join("package.json");
PackageJson::load(npm_resolver, package_json_path) PackageJson::load(npm_resolver, package_json_path)
} }
fn file_exists(path_url: &ModuleSpecifier) -> bool { pub fn get_closest_package_json(
if let Ok(stats) = std::fs::metadata(to_file_path(path_url)) { url: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<PackageJson, AnyError> {
let package_json_path = get_closest_package_json_path(url, npm_resolver)?;
PackageJson::load(npm_resolver, package_json_path)
}
fn get_closest_package_json_path(
url: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<PathBuf, AnyError> {
let file_path = url.to_file_path().unwrap();
let mut current_dir = file_path.parent().unwrap();
let package_json_path = current_dir.join("package.json");
if package_json_path.exists() {
return Ok(package_json_path);
}
let root_pkg_folder = npm_resolver
.resolve_package_folder_from_path(&url.to_file_path().unwrap())?;
while current_dir.starts_with(&root_pkg_folder) {
current_dir = current_dir.parent().unwrap();
let package_json_path = current_dir.join("package.json");
if package_json_path.exists() {
return Ok(package_json_path);
}
}
bail!("did not find package.json in {}", root_pkg_folder.display())
}
fn file_exists(path: &Path) -> bool {
if let Ok(stats) = std::fs::metadata(path) {
stats.is_file() stats.is_file()
} else { } else {
false false
@ -648,28 +702,56 @@ fn file_exists(path_url: &ModuleSpecifier) -> bool {
} }
pub fn legacy_main_resolve( pub fn legacy_main_resolve(
package_json_url: &ModuleSpecifier,
package_json: &PackageJson, package_json: &PackageJson,
_base: &ModuleSpecifier, referrer_kind: NodeModuleKind,
) -> Result<ModuleSpecifier, AnyError> { ) -> Result<PathBuf, AnyError> {
let maybe_main =
if referrer_kind == NodeModuleKind::Esm && package_json.typ == "module" {
&package_json.module
} else {
&package_json.main
};
let mut guess; let mut guess;
if let Some(main) = &package_json.main { if let Some(main) = maybe_main {
guess = package_json_url.join(&format!("./{}", main))?; guess = package_json.path.parent().unwrap().join(main).clean();
if file_exists(&guess) { if file_exists(&guess) {
return Ok(guess); return Ok(guess);
} }
let mut found = false; let mut found = false;
for ext in [ // todo(dsherret): investigate exactly how node handles this
".js", let endings = match referrer_kind {
".json", NodeModuleKind::Cjs => vec![
".node", ".js",
"/index.js", ".cjs",
"/index.json", ".json",
"/index.node", ".node",
] { "/index.js",
guess = package_json_url.join(&format!("./{}{}", main, ext))?; "/index.cjs",
"/index.json",
"/index.node",
],
NodeModuleKind::Esm => vec![
".js",
".mjs",
".json",
".node",
"/index.js",
"/index.mjs",
".cjs",
"/index.cjs",
"/index.json",
"/index.node",
],
};
for ending in endings {
guess = package_json
.path
.parent()
.unwrap()
.join(&format!("{}{}", main, ending))
.clean();
if file_exists(&guess) { if file_exists(&guess) {
found = true; found = true;
break; break;
@ -682,8 +764,25 @@ pub fn legacy_main_resolve(
} }
} }
for p in ["./index.js", "./index.json", "./index.node"] { let index_file_names = match referrer_kind {
guess = package_json_url.join(p)?; NodeModuleKind::Cjs => {
vec!["index.js", "index.cjs", "index.json", "index.node"]
}
NodeModuleKind::Esm => vec![
"index.js",
"index.mjs",
"index.cjs",
"index.json",
"index.node",
],
};
for index_file_name in index_file_names {
guess = package_json
.path
.parent()
.unwrap()
.join(index_file_name)
.clean();
if file_exists(&guess) { if file_exists(&guess) {
// TODO(bartlomieju): emitLegacyIndexDeprecation() // TODO(bartlomieju): emitLegacyIndexDeprecation()
return Ok(guess); return Ok(guess);