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:
parent
54be07d05e
commit
5f251b283b
14 changed files with 331 additions and 209 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1141,6 +1141,7 @@ name = "deno_node"
|
|||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"path-clean",
|
||||
"regex",
|
||||
"serde",
|
||||
]
|
||||
|
|
|
@ -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)?),
|
||||
|
|
|
@ -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",
|
||||
|
|
1
cli/tests/testdata/npm/dual_cjs_esm/main.out
vendored
Normal file
1
cli/tests/testdata/npm/dual_cjs_esm/main.out
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
esm
|
3
cli/tests/testdata/npm/dual_cjs_esm/main.ts
vendored
Normal file
3
cli/tests/testdata/npm/dual_cjs_esm/main.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { getKind } from "npm:@denotest/dual-cjs-esm";
|
||||
|
||||
console.log(getKind());
|
3
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/index.cjs
vendored
Normal file
3
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/index.cjs
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
exports.getKind = function() {
|
||||
return "cjs";
|
||||
};
|
3
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/index.mjs
vendored
Normal file
3
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/index.mjs
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function getKind() {
|
||||
return "esm";
|
||||
}
|
7
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/package.json
vendored
Normal file
7
cli/tests/testdata/npm/registry/@denotest/dual-cjs-esm/1.0.0/package.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@denotest/dual-cjs-esm",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"main": "./index.cjs",
|
||||
"module": "./index.mjs"
|
||||
}
|
|
@ -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}`;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: ®ex::Captures| subpath.clone());
|
||||
let url = Url::parse(&replaced)?;
|
||||
return Ok(url);
|
||||
.replace(&resolved_path_str, |_caps: ®ex::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);
|
||||
|
|
Loading…
Reference in a new issue