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"
dependencies = [
"deno_core",
"path-clean",
"regex",
"serde",
]

View file

@ -17,11 +17,13 @@ use deno_core::serde_json::Value;
use deno_core::url::Url;
use deno_core::JsRuntime;
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::package_exports_resolve;
use deno_runtime::deno_node::package_imports_resolve;
use deno_runtime::deno_node::package_resolve;
use deno_runtime::deno_node::DenoDirNpmResolver;
use deno_runtime::deno_node::NodeModuleKind;
use deno_runtime::deno_node::PackageJson;
use deno_runtime::deno_node::DEFAULT_CONDITIONS;
use once_cell::sync::Lazy;
@ -342,6 +344,8 @@ pub fn node_resolve(
referrer: &ModuleSpecifier,
npm_resolver: &dyn DenoDirNpmResolver,
) -> 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
// 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));
}
// todo(THIS PR): I think this is handled upstream so can be removed?
// todo(dsherret): this seems wrong
if referrer.scheme() == "data" {
let url = referrer.join(specifier).map_err(AnyError::from)?;
return Ok(Some(ResolveResponse::Specifier(url)));
@ -412,7 +416,7 @@ pub fn node_resolve_npm_reference(
let package_folder = npm_resolver
.resolve_package_from_deno_module(&reference.req)?
.folder_path;
let maybe_url = package_config_resolve(
let resolved_path = package_config_resolve(
&reference
.sub_path
.as_ref()
@ -420,16 +424,13 @@ pub fn node_resolve_npm_reference(
.unwrap_or_else(|| ".".to_string()),
&package_folder,
npm_resolver,
NodeModuleKind::Esm,
)
.map(Some)
.with_context(|| {
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)?;
// TODO(bartlomieju): skipped checking errors for commonJS resolution and
// "preserveSymlinksMain"/"preserveSymlinks" options.
@ -521,33 +522,30 @@ fn package_config_resolve(
package_subpath: &str,
package_dir: &Path,
npm_resolver: &dyn DenoDirNpmResolver,
) -> Result<ModuleSpecifier, AnyError> {
referrer_kind: NodeModuleKind,
) -> Result<PathBuf, AnyError> {
let package_json_path = package_dir.join("package.json");
// todo(dsherret): remove base from this code
let base =
let referrer =
ModuleSpecifier::from_directory_path(package_json_path.parent().unwrap())
.unwrap();
let package_config =
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 {
return package_exports_resolve(
package_json_url,
&package_json_path,
package_subpath.to_string(),
exports,
&base,
&referrer,
referrer_kind,
DEFAULT_CONDITIONS,
npm_resolver,
);
}
if package_subpath == "." {
return legacy_main_resolve(&package_json_url, &package_config, &base);
return legacy_main_resolve(&package_config, referrer_kind);
}
package_json_url
.join(package_subpath)
.map_err(AnyError::from)
Ok(package_dir.join(package_subpath))
}
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(
resolved: ModuleSpecifier,
base: &ModuleSpecifier,
@ -667,25 +634,34 @@ fn module_resolve(
conditions: &[&str],
npm_resolver: &dyn DenoDirNpmResolver,
) -> 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 resolved_specifier = referrer.join(specifier)?;
Some(resolved_specifier)
} else if specifier.starts_with('#') {
Some(package_imports_resolve(
Some(
package_imports_resolve(
specifier,
referrer,
NodeModuleKind::Esm,
conditions,
npm_resolver,
)?)
)
.map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
)
} else if let Ok(resolved) = Url::parse(specifier) {
Some(resolved)
} else {
Some(package_resolve(
Some(
package_resolve(
specifier,
referrer,
NodeModuleKind::Esm,
conditions,
npm_resolver,
)?)
)
.map(|p| ModuleSpecifier::from_file_path(p).unwrap())?,
)
};
Ok(match url {
Some(url) => Some(finalize_resolution(url, referrer)?),

View file

@ -75,6 +75,13 @@ itest!(conditional_exports {
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 {
args: "run --allow-read --allow-env --unstable npm/dynamic_import/main.ts",
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;
};
Module._nodeModulePaths = function (from) {
return ops.op_require_node_module_paths(from);
Module._nodeModulePaths = function (fromPath) {
return ops.op_require_node_module_paths(fromPath);
};
Module._resolveLookupPaths = function (request, parent) {
@ -728,7 +728,7 @@
const content = ops.op_require_read_file(filename);
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") {
let message = `Trying to import ESM module: ${filename}`;

View file

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

View file

@ -56,7 +56,7 @@ pub fn err_invalid_package_target(
key: String,
target: String,
is_import: bool,
maybe_base: Option<String>,
maybe_referrer: Option<String>,
) -> AnyError {
let rel_error = !is_import && !target.is_empty() && !target.starts_with("./");
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)
};
if let Some(base) = maybe_base {
if let Some(base) = maybe_referrer {
msg = format!("{} imported from {}", msg, base);
};
if rel_error {
@ -82,7 +82,7 @@ pub fn err_invalid_package_target(
pub fn err_package_path_not_exported(
pkg_path: String,
subpath: String,
maybe_base: Option<String>,
maybe_referrer: Option<String>,
) -> AnyError {
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);
};
if let Some(base) = maybe_base {
msg = format!("{} imported from {}", msg, base);
if let Some(referrer) = maybe_referrer {
msg = format!("{} imported from {}", msg, referrer);
}
generic_error(msg)

View file

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

View file

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

View file

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