mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
feat(core) deno_core::extension! macro to simplify extension registration (#18210)
This implements two macros to simplify extension registration and centralize a lot of the boilerplate as a base for future improvements: * `deno_core::ops!` registers a block of `#[op]`s, optionally with type parameters, useful for places where we share lists of ops * `deno_core::extension!` is used to register an extension, and creates two methods that can be used at runtime/snapshot generation time: `init_ops` and `init_ops_and_esm`. --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
parent
0bc6bf5d33
commit
e55b448730
53 changed files with 1690 additions and 1851 deletions
295
cli/build.rs
295
cli/build.rs
|
@ -1,11 +1,13 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use deno_core::include_js_files;
|
||||
use deno_core::snapshot_util::*;
|
||||
use deno_core::Extension;
|
||||
use deno_core::ExtensionBuilder;
|
||||
use deno_core::ExtensionFileSource;
|
||||
use deno_core::ExtensionFileSourceCode;
|
||||
use deno_runtime::deno_cache::SqliteBackedCache;
|
||||
|
@ -33,6 +35,105 @@ mod ts {
|
|||
specifier: String,
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_build_info(state: &mut OpState) -> Value {
|
||||
let build_specifier = "asset:///bootstrap.ts";
|
||||
|
||||
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
|
||||
.iter()
|
||||
.map(|s| s.name)
|
||||
.collect::<Vec<&str>>();
|
||||
let build_libs = state.borrow::<Vec<&str>>();
|
||||
json!({
|
||||
"buildSpecifier": build_specifier,
|
||||
"libs": build_libs,
|
||||
"nodeBuiltInModuleNames": node_built_in_module_names,
|
||||
})
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_is_node_file() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_script_version(
|
||||
_state: &mut OpState,
|
||||
_args: Value,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
Ok(Some("1".to_string()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
// using the same op that is used in `tsc.rs` for loading modules and reading
|
||||
// files, but a slightly different implementation at build time.
|
||||
fn op_load(state: &mut OpState, args: LoadArgs) -> Result<Value, AnyError> {
|
||||
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
|
||||
let path_dts = state.borrow::<PathBuf>();
|
||||
let re_asset =
|
||||
Regex::new(r"asset:/{3}lib\.(\S+)\.d\.ts").expect("bad regex");
|
||||
let build_specifier = "asset:///bootstrap.ts";
|
||||
|
||||
// we need a basic file to send to tsc to warm it up.
|
||||
if args.specifier == build_specifier {
|
||||
Ok(json!({
|
||||
"data": r#"Deno.writeTextFile("hello.txt", "hello deno!");"#,
|
||||
"version": "1",
|
||||
// this corresponds to `ts.ScriptKind.TypeScript`
|
||||
"scriptKind": 3
|
||||
}))
|
||||
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
|
||||
// parse out just the name so we can lookup the asset.
|
||||
} else if let Some(caps) = re_asset.captures(&args.specifier) {
|
||||
if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
|
||||
// if it comes from an op crate, we were supplied with the path to the
|
||||
// file.
|
||||
let path = if let Some(op_crate_lib) = op_crate_libs.get(lib) {
|
||||
PathBuf::from(op_crate_lib).canonicalize()?
|
||||
// otherwise we will generate the path ourself
|
||||
} else {
|
||||
path_dts.join(format!("lib.{lib}.d.ts"))
|
||||
};
|
||||
let data = std::fs::read_to_string(path)?;
|
||||
Ok(json!({
|
||||
"data": data,
|
||||
"version": "1",
|
||||
// this corresponds to `ts.ScriptKind.TypeScript`
|
||||
"scriptKind": 3
|
||||
}))
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"InvalidSpecifier",
|
||||
format!("An invalid specifier was requested: {}", args.specifier),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"InvalidSpecifier",
|
||||
format!("An invalid specifier was requested: {}", args.specifier),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
deno_core::extension!(deno_tsc,
|
||||
ops = [op_build_info, op_is_node_file, op_load, op_script_version],
|
||||
js = [
|
||||
dir "tsc",
|
||||
"00_typescript.js",
|
||||
"99_main_compiler.js",
|
||||
],
|
||||
config = {
|
||||
op_crate_libs: HashMap<&'static str, PathBuf>,
|
||||
build_libs: Vec<&'static str>,
|
||||
path_dts: PathBuf,
|
||||
},
|
||||
state = |state, op_crate_libs, build_libs, path_dts| {
|
||||
state.put(op_crate_libs);
|
||||
state.put(build_libs);
|
||||
state.put(path_dts);
|
||||
},
|
||||
);
|
||||
|
||||
pub fn create_compiler_snapshot(snapshot_path: PathBuf, cwd: &Path) {
|
||||
// libs that are being provided by op crates.
|
||||
let mut op_crate_libs = HashMap::new();
|
||||
|
@ -158,110 +259,15 @@ mod ts {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
#[op]
|
||||
fn op_build_info(state: &mut OpState) -> Value {
|
||||
let build_specifier = "asset:///bootstrap.ts";
|
||||
|
||||
let node_built_in_module_names = SUPPORTED_BUILTIN_NODE_MODULES
|
||||
.iter()
|
||||
.map(|s| s.name)
|
||||
.collect::<Vec<&str>>();
|
||||
let build_libs = state.borrow::<Vec<&str>>();
|
||||
json!({
|
||||
"buildSpecifier": build_specifier,
|
||||
"libs": build_libs,
|
||||
"nodeBuiltInModuleNames": node_built_in_module_names,
|
||||
})
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_is_node_file() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[op]
|
||||
fn op_script_version(
|
||||
_state: &mut OpState,
|
||||
_args: Value,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
Ok(Some("1".to_string()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
// using the same op that is used in `tsc.rs` for loading modules and reading
|
||||
// files, but a slightly different implementation at build time.
|
||||
fn op_load(state: &mut OpState, args: LoadArgs) -> Result<Value, AnyError> {
|
||||
let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
|
||||
let path_dts = state.borrow::<PathBuf>();
|
||||
let re_asset =
|
||||
Regex::new(r"asset:/{3}lib\.(\S+)\.d\.ts").expect("bad regex");
|
||||
let build_specifier = "asset:///bootstrap.ts";
|
||||
|
||||
// we need a basic file to send to tsc to warm it up.
|
||||
if args.specifier == build_specifier {
|
||||
Ok(json!({
|
||||
"data": r#"Deno.writeTextFile("hello.txt", "hello deno!");"#,
|
||||
"version": "1",
|
||||
// this corresponds to `ts.ScriptKind.TypeScript`
|
||||
"scriptKind": 3
|
||||
}))
|
||||
// specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
|
||||
// parse out just the name so we can lookup the asset.
|
||||
} else if let Some(caps) = re_asset.captures(&args.specifier) {
|
||||
if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
|
||||
// if it comes from an op crate, we were supplied with the path to the
|
||||
// file.
|
||||
let path = if let Some(op_crate_lib) = op_crate_libs.get(lib) {
|
||||
PathBuf::from(op_crate_lib).canonicalize()?
|
||||
// otherwise we are will generate the path ourself
|
||||
} else {
|
||||
path_dts.join(format!("lib.{lib}.d.ts"))
|
||||
};
|
||||
let data = std::fs::read_to_string(path)?;
|
||||
Ok(json!({
|
||||
"data": data,
|
||||
"version": "1",
|
||||
// this corresponds to `ts.ScriptKind.TypeScript`
|
||||
"scriptKind": 3
|
||||
}))
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"InvalidSpecifier",
|
||||
format!("An invalid specifier was requested: {}", args.specifier),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"InvalidSpecifier",
|
||||
format!("An invalid specifier was requested: {}", args.specifier),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let tsc_extension = Extension::builder("deno_tsc")
|
||||
.ops(vec![
|
||||
op_build_info::decl(),
|
||||
op_is_node_file::decl(),
|
||||
op_load::decl(),
|
||||
op_script_version::decl(),
|
||||
])
|
||||
.js(include_js_files! {
|
||||
dir "tsc",
|
||||
"00_typescript.js",
|
||||
"99_main_compiler.js",
|
||||
})
|
||||
.state(move |state| {
|
||||
state.put(op_crate_libs.clone());
|
||||
state.put(build_libs.clone());
|
||||
state.put(path_dts.clone());
|
||||
})
|
||||
.build();
|
||||
|
||||
create_snapshot(CreateSnapshotOptions {
|
||||
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
||||
snapshot_path,
|
||||
startup_snapshot: None,
|
||||
extensions: vec![tsc_extension],
|
||||
extensions: vec![deno_tsc::init_ops_and_esm(
|
||||
op_crate_libs,
|
||||
build_libs,
|
||||
path_dts,
|
||||
)],
|
||||
|
||||
// NOTE(bartlomieju): Compressing the TSC snapshot in debug build took
|
||||
// ~45s on M1 MacBook Pro; without compression it took ~1s.
|
||||
|
@ -304,61 +310,66 @@ mod ts {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME(bartlomieju): information about which extensions were
|
||||
// already snapshotted is not preserved in the snapshot. This should be
|
||||
// fixed, so we can reliably depend on that information.
|
||||
// deps = [runtime]
|
||||
deno_core::extension!(
|
||||
cli,
|
||||
esm = [
|
||||
dir "js",
|
||||
"40_testing.js"
|
||||
],
|
||||
customizer = |ext: &mut ExtensionBuilder| {
|
||||
ext.esm(vec![ExtensionFileSource {
|
||||
specifier: "runtime/js/99_main.js".to_string(),
|
||||
code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(
|
||||
std::path::PathBuf::from(deno_runtime::js::PATH_FOR_99_MAIN_JS),
|
||||
),
|
||||
}]);
|
||||
}
|
||||
);
|
||||
|
||||
fn create_cli_snapshot(snapshot_path: PathBuf) {
|
||||
// NOTE(bartlomieju): ordering is important here, keep it in sync with
|
||||
// `runtime/worker.rs`, `runtime/web_worker.rs` and `runtime/build.rs`!
|
||||
let mut extensions: Vec<Extension> = vec![
|
||||
deno_webidl::init(),
|
||||
deno_console::init(),
|
||||
deno_url::init_ops(),
|
||||
deno_web::init_ops::<PermissionsContainer>(
|
||||
let extensions: Vec<Extension> = vec![
|
||||
deno_webidl::deno_webidl::init_ops(),
|
||||
deno_console::deno_console::init_ops(),
|
||||
deno_url::deno_url::init_ops(),
|
||||
deno_web::deno_web::init_ops::<PermissionsContainer>(
|
||||
deno_web::BlobStore::default(),
|
||||
Default::default(),
|
||||
),
|
||||
deno_fetch::init_ops::<PermissionsContainer>(Default::default()),
|
||||
deno_cache::init_ops::<SqliteBackedCache>(None),
|
||||
deno_websocket::init_ops::<PermissionsContainer>("".to_owned(), None, None),
|
||||
deno_webstorage::init_ops(None),
|
||||
deno_crypto::init_ops(None),
|
||||
deno_broadcast_channel::init_ops(
|
||||
deno_fetch::deno_fetch::init_ops::<PermissionsContainer>(Default::default()),
|
||||
deno_cache::deno_cache::init_ops::<SqliteBackedCache>(None),
|
||||
deno_websocket::deno_websocket::init_ops::<PermissionsContainer>(
|
||||
"".to_owned(),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
deno_webstorage::deno_webstorage::init_ops(None),
|
||||
deno_crypto::deno_crypto::init_ops(None),
|
||||
deno_broadcast_channel::deno_broadcast_channel::init_ops(
|
||||
deno_broadcast_channel::InMemoryBroadcastChannel::default(),
|
||||
false, // No --unstable.
|
||||
),
|
||||
deno_ffi::init_ops::<PermissionsContainer>(false),
|
||||
deno_net::init_ops::<PermissionsContainer>(
|
||||
deno_ffi::deno_ffi::init_ops::<PermissionsContainer>(false),
|
||||
deno_net::deno_net::init_ops::<PermissionsContainer>(
|
||||
None, false, // No --unstable.
|
||||
None,
|
||||
),
|
||||
deno_tls::init_ops(),
|
||||
deno_napi::init_ops::<PermissionsContainer>(),
|
||||
deno_http::init_ops(),
|
||||
deno_io::init_ops(Default::default()),
|
||||
deno_fs::init_ops::<PermissionsContainer>(false),
|
||||
deno_flash::init_ops::<PermissionsContainer>(false), // No --unstable
|
||||
deno_node::init_ops::<PermissionsContainer>(None), // No --unstable.
|
||||
deno_node::init_polyfill_ops(),
|
||||
deno_tls::deno_tls::init_ops(),
|
||||
deno_napi::deno_napi::init_ops::<PermissionsContainer>(),
|
||||
deno_http::deno_http::init_ops(),
|
||||
deno_io::deno_io::init_ops(Rc::new(RefCell::new(Some(Default::default())))),
|
||||
deno_fs::deno_fs::init_ops::<PermissionsContainer>(false),
|
||||
deno_flash::deno_flash::init_ops::<PermissionsContainer>(false), // No --unstable
|
||||
deno_node::deno_node_loading::init_ops::<PermissionsContainer>(None), // No --unstable.
|
||||
deno_node::deno_node::init_ops(),
|
||||
cli::init_ops_and_esm(), // NOTE: This needs to be init_ops_and_esm!
|
||||
];
|
||||
|
||||
let mut esm_files = include_js_files!(
|
||||
dir "js",
|
||||
"40_testing.js",
|
||||
);
|
||||
esm_files.push(ExtensionFileSource {
|
||||
specifier: "runtime/js/99_main.js".to_string(),
|
||||
code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(
|
||||
std::path::PathBuf::from(deno_runtime::js::PATH_FOR_99_MAIN_JS),
|
||||
),
|
||||
});
|
||||
extensions.push(
|
||||
Extension::builder("cli")
|
||||
// FIXME(bartlomieju): information about which extensions were
|
||||
// already snapshotted is not preserved in the snapshot. This should be
|
||||
// fixed, so we can reliably depend on that information.
|
||||
// .dependencies(vec!["runtime"])
|
||||
.esm(esm_files)
|
||||
.build(),
|
||||
);
|
||||
|
||||
create_snapshot(CreateSnapshotOptions {
|
||||
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
||||
snapshot_path,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue