Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Deno.test("should resolve, load and get graph", async () => {
assertEquals(diagnostics.length, 0);
const graph = loader.getGraphUnstable();
assertEquals((graph as any).roots[0], modFileUrl);
const resolvedUrl = loader.resolve(
const resolvedUrl = loader.resolveSync(
"./mod.test.ts",
modFileUrl,
ResolutionMode.Import,
Expand Down
41 changes: 37 additions & 4 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
* if (diagnostics.length > 0) {
* throw new Error(diagnostics[0].message);
* }
* const resolvedUrl = loader.resolve(
* // alternatively use resolve to resolve npm/jsr specifiers not found
* // in the entrypoints or if not being able to provide entrypoints
* const resolvedUrl = loader.resolveSync(
* "./mod.test.ts",
* "https://deno.land/mod.ts", // referrer
* ResolutionMode.Import,
Expand Down Expand Up @@ -208,8 +210,8 @@ export class Loader implements Disposable {
return messages.map((message) => ({ message }));
}

/** Resolves a specifier using the given referrer and resolution mode. */
resolve(
/** Synchronously resolves a specifier using the given referrer and resolution mode. */
resolveSync(
specifier: string,
referrer: string | undefined,
resolutionMode: ResolutionMode,
Expand All @@ -221,7 +223,38 @@ export class Loader implements Disposable {
})`,
);
}
const value = this.#inner.resolve(specifier, referrer, resolutionMode);
const value = this.#inner.resolve_sync(specifier, referrer, resolutionMode);
if (this.#debug) {
console.error(`Resolved to '${value}'`);
}
return value;
}

/** Asynchronously resolves a specifier using the given referrer and resolution mode.
*
* This is useful for resolving `jsr:` and `npm:` specifiers on the fly when they can't
* be figured out from entrypoints, but it may cause multiple "npm install"s and different
* npm or jsr resolution than Deno. For that reason it's better to provide the list of
* entrypoints up front so the loader can create the npm and jsr graph, and then after use
* synchronous resolution to resolve jsr and npm specifiers.
*/
resolve(
specifier: string,
referrer: string | undefined,
resolutionMode: ResolutionMode,
): Promise<string> {
if (this.#debug) {
console.error(
`Resolving '${specifier}' from '${referrer ?? "<undefined>"}' (${
resolutionModeToString(resolutionMode)
})`,
);
}
const value = this.#inner.resolve(
specifier,
referrer,
resolutionMode,
);
if (this.#debug) {
console.error(`Resolved to '${value}'`);
}
Expand Down
164 changes: 119 additions & 45 deletions src/rs_lib/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod http_client;

use std::borrow::Cow;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
Expand Down Expand Up @@ -49,6 +50,8 @@ use deno_resolver::loader::ModuleLoader;
use deno_resolver::loader::RequestedModuleType;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_semver::SmallStackString;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use js_sys::Object;
use js_sys::Uint8Array;
use node_resolver::NodeConditionOptions;
Expand Down Expand Up @@ -356,10 +359,32 @@ impl DenoLoader {
&mut self,
entrypoints: Vec<String>,
) -> Result<Vec<String>, anyhow::Error> {
let roots = entrypoints
let urls = entrypoints
.into_iter()
.map(|e| self.resolve_entrypoint(e))
.map(|e| self.resolve_entrypoint(Cow::Owned(e)))
.collect::<Result<Vec<_>, _>>()?;
self.add_entrypoint_urls(urls.clone()).await?;
let errors = self
.graph
.walk(
urls.iter(),
WalkOptions {
check_js: CheckJsOption::True,
kind: GraphKind::CodeOnly,
follow_dynamic: false,
prefer_fast_check_graph: false,
},
)
.errors()
.map(|e| e.to_string_with_range())
.collect();
Ok(errors)
}

async fn add_entrypoint_urls(
&mut self,
entrypoints: Vec<Url>,
) -> Result<(), anyhow::Error> {
let npm_package_info_provider = self
.npm_installer_factory
.lockfile_npm_package_info_provider()?;
Expand Down Expand Up @@ -396,7 +421,7 @@ impl DenoLoader {
self
.graph
.build(
roots.clone(),
entrypoints,
Vec::new(),
&loader,
deno_graph::BuildOptions {
Expand All @@ -417,81 +442,122 @@ impl DenoLoader {
},
)
.await;
let errors = self
.graph
.walk(
roots.iter(),
WalkOptions {
check_js: CheckJsOption::True,
kind: GraphKind::CodeOnly,
follow_dynamic: false,
prefer_fast_check_graph: false,
},
)
.errors()
.map(|e| e.to_string_with_range())
.collect();
Ok(errors)
Ok(())
}

pub fn resolve(
pub fn resolve_sync(
&self,
specifier: String,
importer: Option<String>,
resolution_mode: u8,
) -> Result<String, JsValue> {
let resolution_mode = match resolution_mode {
1 => node_resolver::ResolutionMode::Require,
_ => node_resolver::ResolutionMode::Import,
};
self
.resolve_inner(specifier, importer, resolution_mode)
.resolve_sync_inner(
&specifier,
importer,
parse_resolution_mode(resolution_mode),
)
.map_err(create_js_error)
}

fn resolve_inner(
fn resolve_sync_inner(
&self,
specifier: &str,
importer: Option<String>,
resolution_mode: node_resolver::ResolutionMode,
) -> Result<String, anyhow::Error> {
let (specifier, referrer) =
self.resolve_specifier_and_referrer(specifier, importer)?;
let resolved = self.resolver.resolve_with_graph(
&self.graph,
&specifier,
&referrer,
deno_graph::Position::zeroed(),
ResolveWithGraphOptions {
mode: resolution_mode,
kind: node_resolver::NodeResolutionKind::Execution,
maintain_npm_specifiers: false,
},
)?;
Ok(resolved.into())
}

pub async fn resolve(
&mut self,
specifier: String,
importer: Option<String>,
resolution_mode: u8,
) -> Result<String, JsValue> {
self
.resolve_inner(
&specifier,
importer,
parse_resolution_mode(resolution_mode),
)
.await
.map_err(create_js_error)
}

async fn resolve_inner(
&mut self,
specifier: &str,
importer: Option<String>,
resolution_mode: node_resolver::ResolutionMode,
) -> Result<String, anyhow::Error> {
let (specifier, referrer) =
self.resolve_specifier_and_referrer(&specifier, importer.clone())?;
let resolved = self.resolver.resolve_with_graph(
&self.graph,
&specifier,
&referrer,
deno_graph::Position::zeroed(),
ResolveWithGraphOptions {
mode: resolution_mode,
kind: node_resolver::NodeResolutionKind::Execution,
maintain_npm_specifiers: true,
},
)?;
if NpmPackageReqReference::from_specifier(&resolved).is_ok()
|| JsrPackageReqReference::from_specifier(&resolved).is_ok()
{
self.add_entrypoint_urls(vec![resolved.clone()]).await?;
self.resolve_sync_inner(&specifier, importer, resolution_mode)
} else {
Ok(resolved.into())
}
}

fn resolve_specifier_and_referrer<'a>(
&self,
specifier: &'a str,
importer: Option<String>,
) -> Result<(Cow<'a, str>, Url), anyhow::Error> {
let importer = importer.filter(|v| !v.is_empty());
let (specifier, referrer) = match importer {
Ok(match importer {
Some(referrer)
if referrer.starts_with("http:")
|| referrer.starts_with("https:")
|| referrer.starts_with("file:") =>
{
(specifier, Url::parse(&referrer)?)
(Cow::Borrowed(specifier), Url::parse(&referrer)?)
}
Some(referrer) => (
specifier,
Cow::Borrowed(specifier),
deno_path_util::url_from_file_path(
&sys_traits::impls::wasm_string_to_path(referrer),
)?,
),
None => {
let entrypoint = self.resolve_entrypoint(specifier)?.to_string();
let entrypoint =
Cow::Owned(self.resolve_entrypoint(Cow::Borrowed(specifier))?.into());
(
entrypoint,
deno_path_util::url_from_file_path(
deno_path_util::url_from_directory_path(
self.workspace_factory.initial_cwd(),
)?,
)
}
};
let resolved = self.resolver.resolve_with_graph(
&self.graph,
&specifier,
&referrer,
deno_graph::Position::zeroed(),
ResolveWithGraphOptions {
mode: resolution_mode,
kind: node_resolver::NodeResolutionKind::Execution,
maintain_npm_specifiers: false,
},
)?;
Ok(resolved.into())
})
}

pub async fn load(
Expand Down Expand Up @@ -634,12 +700,13 @@ impl DenoLoader {

fn resolve_entrypoint(
&self,
specifier: String,
specifier: Cow<str>,
) -> Result<Url, anyhow::Error> {
let cwd = self.workspace_factory.initial_cwd();
if specifier.contains('\\') {
return Ok(deno_path_util::url_from_file_path(&resolve_absolute_path(
specifier, cwd,
specifier.into_owned(),
cwd,
))?);
}
let referrer = deno_path_util::url_from_directory_path(cwd)?;
Expand Down Expand Up @@ -702,6 +769,13 @@ fn create_js_error(err: anyhow::Error) -> JsValue {
wasm_bindgen::JsError::new(&err.to_string()).into()
}

fn parse_resolution_mode(resolution_mode: u8) -> node_resolver::ResolutionMode {
match resolution_mode {
1 => node_resolver::ResolutionMode::Require,
_ => node_resolver::ResolutionMode::Import,
}
}

fn media_type_to_u8(media_type: MediaType) -> u8 {
match media_type {
MediaType::JavaScript => 0,
Expand Down
8 changes: 6 additions & 2 deletions tests/bytes_and_text/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ Deno.test("loads jsx transpiled", async () => {
};
const { loader } = await createWorkspace();

const mainTsUrl = loader.resolve(mainTs, undefined, ResolutionMode.Import);
const dataFileUrl = loader.resolve(
const mainTsUrl = loader.resolveSync(
mainTs,
undefined,
ResolutionMode.Import,
);
const dataFileUrl = loader.resolveSync(
"./data_utf8_bom.txt",
mainTsUrl,
ResolutionMode.Import,
Expand Down
8 changes: 6 additions & 2 deletions tests/bytes_and_text_npm/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ Deno.test("loads jsx transpiled", async () => {
};
const { loader } = await createWorkspace();

const mainTsUrl = loader.resolve(mainTs, undefined, ResolutionMode.Import);
const dataFileUrl = loader.resolve(
const mainTsUrl = loader.resolveSync(
mainTs,
undefined,
ResolutionMode.Import,
);
const dataFileUrl = loader.resolveSync(
"package/data.txt",
mainTsUrl,
ResolutionMode.Import,
Expand Down
14 changes: 11 additions & 3 deletions tests/jsx/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ Deno.test("loads jsx transpiled", async () => {
"//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanN4Il0sInNvdXJjZXNDb250ZW50IjpbImNvbnNvbGUubG9nKDxkaXYgLz4pO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSxRQUFRLEdBQUcifQ==";
const mainTsxSourceMappingURL =
"//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4udHN4Il0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IHZhbHVlOiBzdHJpbmcgPSBcIlwiO1xuY29uc29sZS5sb2coPGRpdiAvPiwgdmFsdWUpO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE1BQU0sUUFBZ0I7QUFDdEIsUUFBUSxHQUFHLEVBQUUsT0FBUSJ9";
const mainJsxUrl = loader.resolve(mainJsx, undefined, ResolutionMode.Import);
const mainTsxUrl = loader.resolve(mainTsx, undefined, ResolutionMode.Import);
const mainJsxUrl = loader.resolveSync(
mainJsx,
undefined,
ResolutionMode.Import,
);
const mainTsxUrl = loader.resolveSync(
mainTsx,
undefined,
ResolutionMode.Import,
);

assertResponseText(
await loader.load(mainJsxUrl, RequestedModuleType.Default),
Expand All @@ -38,7 +46,7 @@ ${mainJsxSourceMappingURL}`,
);

// resolves jsx-dev
const jsx = loader.resolve(
const jsx = loader.resolveSync(
"react/jsx-dev-runtime",
mainTsx,
ResolutionMode.Import,
Expand Down
2 changes: 1 addition & 1 deletion tests/link_jsr_entrypoint/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Deno.test("loads linked entrypoint", async () => {
});

const response = await loader.load(
loader.resolve("@denotest/add", undefined, ResolutionMode.Import),
loader.resolveSync("@denotest/add", undefined, ResolutionMode.Import),
RequestedModuleType.Default,
);
assertResponseText(
Expand Down
Loading