Skip to content
Open
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
86 changes: 75 additions & 11 deletions cli/snapshot/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
mod shared;

fn main() {
// Trigger a snapshot rebuild whenever the import-graph instrumentation is
// toggled (see libs/core/modules/import_graph.rs).
#[allow(clippy::print_stdout, reason = "build script output")]
{
println!("cargo:rerun-if-env-changed=DENO_SNAPSHOT_IMPORT_GRAPH");
}
#[cfg(not(feature = "disable"))]
{
let o = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
Expand Down Expand Up @@ -42,8 +48,12 @@ fn create_cli_snapshot(
.map(String::as_str)
.collect();

let mut residual_js: Vec<(&str, &std::path::Path)> = Vec::new();
let mut residual_esm: Vec<(&str, &std::path::Path)> = Vec::new();
let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
let residual_sources_dir = out_dir.join("residual_sources");
std::fs::create_dir_all(&residual_sources_dir).unwrap();

let mut residual_js: Vec<(&str, std::path::PathBuf)> = Vec::new();
let mut residual_esm: Vec<(&str, std::path::PathBuf)> = Vec::new();
for file in &output.lazy_extension_files {
if consumed.contains(file.specifier.as_str()) {
continue;
Expand All @@ -53,7 +63,18 @@ fn create_cli_snapshot(
{
println!("cargo:rerun-if-changed={}", file.path.display());
}
let entry = (file.specifier.as_str(), file.path.as_path());
// Both lazy_loaded_js (loadExtScript -> v8::Script::compile on raw
// source) and lazy_loaded_esm (op_lazy_load_esm -> LazyEsmModuleLoader,
// which returns the source unchanged) skip transpilation at runtime.
// The snapshot path applies extension_transpiler for consumed files;
// for residuals we have to pre-transpile here so the runtime gets
// parseable JS rather than TypeScript syntax.
let transpiled_path = transpile_residual_source(
&residual_sources_dir,
&file.specifier,
&file.path,
);
let entry = (file.specifier.as_str(), transpiled_path);
match file.kind {
LazyExtensionFileKind::Js => residual_js.push(entry),
LazyExtensionFileKind::Esm => residual_esm.push(entry),
Expand All @@ -64,25 +85,68 @@ fn create_cli_snapshot(
writeln!(
f,
"// Copyright 2018-2026 the Deno authors. MIT license.\n\
// @generated by cli/snapshot/build.rs do not edit.\n"
// @generated by cli/snapshot/build.rs - do not edit.\n"
)
.unwrap();
write_residual_table(&mut f, "RESIDUAL_LAZY_JS", &residual_js);
write_residual_table(&mut f, "RESIDUAL_LAZY_ESM", &residual_esm);
// Both tables reference files emitted under $OUT_DIR/residual_sources/
// by `transpile_residual_source`, so `include_str!` uses an OUT_DIR-rooted
// concat! literal rather than a source-tree path.
write_residual_table(&mut f, &out_dir, "RESIDUAL_LAZY_JS", &residual_js);
write_residual_table(&mut f, &out_dir, "RESIDUAL_LAZY_ESM", &residual_esm);
}

#[cfg(not(feature = "disable"))]
fn transpile_residual_source(
out_dir: &std::path::Path,
specifier: &str,
src_path: &std::path::Path,
) -> std::path::PathBuf {
use deno_runtime::deno_core::ModuleCodeString;
use deno_runtime::deno_core::ModuleName;
use deno_runtime::transpile::maybe_transpile_source;

let source = std::fs::read_to_string(src_path).unwrap_or_else(|e| {
panic!(
"failed to read residual lazy source {}: {e}",
src_path.display()
)
});
let (transpiled, _source_map) = maybe_transpile_source(
ModuleName::from(specifier.to_string()),
ModuleCodeString::from(source),
)
.unwrap_or_else(|e| {
panic!("failed to transpile residual lazy source {specifier}: {e}")
});

// Sanitize the specifier into a filesystem-safe name. Specifiers look like
// `ext:deno_node/https.ts`, which we want to round-trip into a unique file.
let sanitized: String = specifier
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
.collect();
let out_path = out_dir.join(format!("{sanitized}.js"));
std::fs::write(&out_path, transpiled.as_bytes()).unwrap();
out_path
}

#[cfg(not(feature = "disable"))]
fn write_residual_table(
f: &mut std::fs::File,
out_dir: &std::path::Path,
name: &str,
entries: &[(&str, &std::path::Path)],
entries: &[(&str, std::path::PathBuf)],
) {
use std::io::Write;
writeln!(f, "pub static {name}: &[(&str, &str)] = &[").unwrap();
for (specifier, path) in entries {
// The path comes from CARGO_MANIFEST_DIR-rooted concat! literals, so it is
// already absolute and stable across the workspace.
writeln!(f, " ({specifier:?}, include_str!({path:?})),",).unwrap();
for (specifier, transpiled_path) in entries {
let rel = transpiled_path.strip_prefix(out_dir).unwrap();
writeln!(
f,
" ({specifier:?}, include_str!(concat!(env!(\"OUT_DIR\"), {:?}))),",
format!("/{}", rel.display()),
)
.unwrap();
}
writeln!(f, "];\n").unwrap();
}
25 changes: 16 additions & 9 deletions ext/fs/30_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,14 @@ const { read, readSync, write, writeSync } = core.loadExtScript(
"ext:deno_io/12_io.js",
);
const abortSignal = core.loadExtScript("ext:deno_web/03_abort_signal.js");
const {
readableStreamForRid,
ReadableStreamPrototype,
writableStreamForRid,
} = core.loadExtScript("ext:deno_web/06_streams.js");
// Defer loading the 208 KB `06_streams.js` polyfill: these helpers are only
// used inside File class methods and writeFile-family functions, so pay
// the parse cost on first use rather than at every startup.
let _streamsImpl;
function lazyStreams() {
return _streamsImpl ??
(_streamsImpl = core.loadExtScript("ext:deno_web/06_streams.js"));
}
const { pathFromURL } = core.loadExtScript("ext:deno_web/00_infra.js");

function chmodSync(path, mode) {
Expand Down Expand Up @@ -659,14 +662,14 @@ class FsFile {

get readable() {
if (this.#readable === undefined) {
this.#readable = readableStreamForRid(this.#rid);
this.#readable = lazyStreams().readableStreamForRid(this.#rid);
}
return this.#readable;
}

get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(
this.#writable = lazyStreams().writableStreamForRid(
this.#rid,
true,
undefined,
Expand Down Expand Up @@ -859,7 +862,9 @@ async function writeFile(
options.signal[abortSignal.add](abortHandler);
}
try {
if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) {
if (
ObjectPrototypeIsPrototypeOf(lazyStreams().ReadableStreamPrototype, data)
) {
const file = await open(path, {
mode: options.mode,
append: options.append ?? false,
Expand Down Expand Up @@ -906,7 +911,9 @@ function writeTextFile(
data,
options = { __proto__: null },
) {
if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) {
if (
ObjectPrototypeIsPrototypeOf(lazyStreams().ReadableStreamPrototype, data)
) {
return writeFile(
path,
data.pipeThrough(new TextEncoderStream()),
Expand Down
20 changes: 13 additions & 7 deletions ext/io/12_io.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ const {
TypedArrayPrototypeGetByteLength,
} = primordials;

const {
readableStreamForRid,
writableStreamForRid,
} = core.loadExtScript("ext:deno_web/06_streams.js");
// Defer loading the 208 KB `06_streams.js` polyfill: the two helpers below
// are only used inside the `get readable()` / `get writable()` getters on
// Stdin/Stdout/Stderr, so the cost can be paid the first time someone
// accesses those streams (e.g. `process.stdout.writable`) rather than at
// every startup.
let _streamsImpl;
function lazyStreams() {
return _streamsImpl ??
(_streamsImpl = core.loadExtScript("ext:deno_web/06_streams.js"));
}

// Seek whence values.
// https://golang.org/pkg/io/#pkg-constants
Expand Down Expand Up @@ -145,7 +151,7 @@ class Stdin {

get readable() {
if (this.#readable === undefined) {
this.#readable = readableStreamForRid(this.#rid, false);
this.#readable = lazyStreams().readableStreamForRid(this.#rid, false);
}
return this.#readable;
}
Expand Down Expand Up @@ -199,7 +205,7 @@ class Stdout {

get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(this.#rid);
this.#writable = lazyStreams().writableStreamForRid(this.#rid);
}
return this.#writable;
}
Expand Down Expand Up @@ -234,7 +240,7 @@ class Stderr {

get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(this.#rid);
this.#writable = lazyStreams().writableStreamForRid(this.#rid);
}
return this.#writable;
}
Expand Down
23 changes: 12 additions & 11 deletions ext/net/01_net.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ const {
Uint8Array,
} = primordials;

const {
readableStreamForRidUnrefable,
readableStreamForRidUnrefableRef,
readableStreamForRidUnrefableUnref,
writableStreamForRid,
} = core.loadExtScript("ext:deno_web/06_streams.js");
// All four helpers below are only used inside Conn class methods. Defer
// loading the 208 KB `06_streams.js` polyfill until first stream access.
let _streamsImpl;
function lazyStreams() {
return _streamsImpl ??
(_streamsImpl = core.loadExtScript("ext:deno_web/06_streams.js"));
}
const abortSignal = core.loadExtScript("ext:deno_web/03_abort_signal.js");

async function write(rid, data) {
Expand Down Expand Up @@ -165,25 +166,25 @@ class Conn {

get readable() {
if (this.#readable === undefined) {
this.#readable = readableStreamForRidUnrefable(this.#rid);
this.#readable = lazyStreams().readableStreamForRidUnrefable(this.#rid);
if (this.#unref) {
readableStreamForRidUnrefableUnref(this.#readable);
lazyStreams().readableStreamForRidUnrefableUnref(this.#readable);
}
}
return this.#readable;
}

get writable() {
if (this.#writable === undefined) {
this.#writable = writableStreamForRid(this.#rid);
this.#writable = lazyStreams().writableStreamForRid(this.#rid);
}
return this.#writable;
}

ref() {
this.#unref = false;
if (this.#readable) {
readableStreamForRidUnrefableRef(this.#readable);
lazyStreams().readableStreamForRidUnrefableRef(this.#readable);
}

SetPrototypeForEach(
Expand All @@ -195,7 +196,7 @@ class Conn {
unref() {
this.#unref = true;
if (this.#readable) {
readableStreamForRidUnrefableUnref(this.#readable);
lazyStreams().readableStreamForRidUnrefableUnref(this.#readable);
}
SetPrototypeForEach(
this.#pendingReadPromises,
Expand Down
24 changes: 16 additions & 8 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,20 +412,30 @@ deno_core::extension!(deno_node,
esm_entry_point = "node:module",
esm = [
dir "polyfills",
"internal/streams/compose.js",
"internal/streams/duplexpair.js",
"internal/streams/lazy_transform.js",
"internal/streams/pipeline.js",
"internal_binding/mod.ts",
"internal/streams/operators.js",
"node:module" = "01_require.js",
"node:process" = "process.ts",
"node:repl" = "repl.ts",
// node:stream + node:stream/promises stay eager: every Deno program
// pays their parse/compile cost at runtime startup via
// `__bootstrapNodeProcess` -> `createWritableStdioStream` -> Writable,
// so keeping them in the snapshot is a net startup-time win even
// though most programs never directly require('stream').
"node:stream" = "stream.ts",
"node:stream/promises" = "stream/promises.js",
// node:net and node:tty are needed at every TTY-stdout startup via
// internal/tty.js's TTYWriteStream constructor extending net.Socket.
// Keeping them eager is a startup-time win for interactive runs.
"node:net" = "net_esm.ts",
"node:tty" = "tty_esm.ts",
],
lazy_loaded_esm = [
dir "polyfills",
"internal/streams/compose.js",
"internal/streams/duplexpair.js",
"internal/streams/lazy_transform.js",
"internal/streams/operators.js",
"internal/streams/pipeline.js",
"node:repl" = "repl.ts",
"_fs/_fs_copy.ts",
"_fs/_fs_dir.ts",
"_fs/_fs_exists.ts",
Expand Down Expand Up @@ -489,7 +499,6 @@ deno_core::extension!(deno_node,
"node:timers" = "timers_esm.ts",
"node:timers/promises" = "timers/promises_esm.ts",
"node:tls" = "tls_esm.ts",
"node:tty" = "tty_esm.ts",
"node:v8" = "v8_esm.ts",
"node:child_process" = "child_process_esm.ts",
"node:fs" = "fs_esm.ts",
Expand All @@ -504,7 +513,6 @@ deno_core::extension!(deno_node,
"node:_stream_readable" = "internal/streams/readable_esm.js",
"node:_stream_transform" = "internal/streams/transform_esm.js",
"node:_stream_writable" = "internal/streams/writable_esm.js",
"node:net" = "net_esm.ts",
"node:_tls_common" = "_tls_common_esm.ts",
"node:_tls_wrap" = "_tls_wrap_esm.js",
"node:assert" = "assert_esm.ts",
Expand Down
Loading
Loading