/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
libs/core/extensions.rs
1 077 строк
38 KB
Nathan Whitaker
perf(core): reduce native callback snapshot overhead (#35121)
11 июн 2026, 07:21
Не верифицирован
11 июн 2026, 07:21
1eebdde
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::borrow::Cow; use std::marker::PhantomData; use std::sync::Arc; use v8::MapFnTo; use v8::fast_api::CFunction; use v8::fast_api::CFunctionInfo; use v8::fast_api::Int64Representation; use v8::fast_api::Type; use crate::FastStaticString; use crate::OpState; use crate::modules::IntoModuleCodeString; use crate::modules::ModuleCodeString; use crate::ops::OpMetadata; use crate::runtime::bindings; #[derive(Clone)] pub enum ExtensionFileSourceCode { /// Source code is included in the binary produced. Either by being defined /// inline, or included using `include_str!()`. If you are snapshotting, this /// will result in two copies of the source code being included - one in the /// snapshot, the other the static string in the `Extension`. #[deprecated = "Use ExtensionFileSource::new"] IncludedInBinary(FastStaticString), // Source code is loaded from a file on disk. It's meant to be used if the // embedder is creating snapshots. Files will be loaded from the filesystem // during the build time and they will only be present in the V8 snapshot. LoadedFromFsDuringSnapshot(&'static str), // <- Path // Source code was loaded from memory. It's meant to be used if the // embedder is creating snapshots. Files will be loaded from memory // during the build time and they will only be present in the V8 snapshot. LoadedFromMemoryDuringSnapshot(FastStaticString), /// Source code may be computed at runtime. Computed(Arc<str>), } #[allow(deprecated, reason = "needed to match on deprecated variants")] impl std::fmt::Debug for ExtensionFileSourceCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { Self::IncludedInBinary(..) => write!(f, "IncludedInBinary(..)"), Self::LoadedFromFsDuringSnapshot(path) => { write!(f, "LoadedFromFsDuringSnapshot({path})") } Self::LoadedFromMemoryDuringSnapshot(..) => { write!(f, "LoadedFromMemoryDuringSnapshot(..)") } Self::Computed(..) => write!(f, "Computed(..)"), } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ExtensionSourceType { LazyEsm, LazyJs, Js, Esm, } #[derive(Clone, Debug)] pub struct ExtensionFileSource { pub specifier: &'static str, pub code: ExtensionFileSourceCode, _unconstructable_use_new: PhantomData<()>, } impl ExtensionFileSource { pub const fn new(specifier: &'static str, code: FastStaticString) -> Self { #[allow(deprecated, reason = "constructing deprecated variant internally")] Self { specifier, code: ExtensionFileSourceCode::IncludedInBinary(code), _unconstructable_use_new: PhantomData, } } pub const fn new_computed(specifier: &'static str, code: Arc<str>) -> Self { #[allow(deprecated, reason = "constructing deprecated variant internally")] Self { specifier, code: ExtensionFileSourceCode::Computed(code), _unconstructable_use_new: PhantomData, } } pub const fn loaded_during_snapshot( specifier: &'static str, path: &'static str, ) -> Self { #[allow(deprecated, reason = "constructing deprecated variant internally")] Self { specifier, code: ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path), _unconstructable_use_new: PhantomData, } } pub const fn loaded_from_memory_during_snapshot( specifier: &'static str, code: FastStaticString, ) -> Self { #[allow(deprecated, reason = "constructing deprecated variant internally")] Self { specifier, code: ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code), _unconstructable_use_new: PhantomData, } } /// Whether this source's bytes are reachable at runtime without an external /// table (e.g. from a snapshot build's residual sources). True for inline /// or compile-time embedded sources, false for paths that were only valid /// on the build machine. pub fn is_runtime_loadable(&self) -> bool { #[allow( deprecated, reason = "matching deprecated variant we still inspect" )] match &self.code { ExtensionFileSourceCode::IncludedInBinary(_) | ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(_) | ExtensionFileSourceCode::Computed(_) => true, ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(_) => false, } } fn find_non_ascii(s: &str) -> String { s.chars().filter(|c| !c.is_ascii()).collect::<String>() } pub fn load(&self) -> Result<ModuleCodeString, std::io::Error> { #[allow(deprecated, reason = "needed to match on deprecated variants")] match &self.code { ExtensionFileSourceCode::LoadedFromMemoryDuringSnapshot(code) | ExtensionFileSourceCode::IncludedInBinary(code) => { debug_assert!( code.is_ascii(), "Extension code must be 7-bit ASCII: {} (found {})", self.specifier, Self::find_non_ascii(code) ); Ok(IntoModuleCodeString::into_module_code(*code)) } ExtensionFileSourceCode::LoadedFromFsDuringSnapshot(path) => { #[allow( clippy::disallowed_methods, reason = "load extensions from real fs" )] let s = std::fs::read_to_string(path)?; debug_assert!( s.is_ascii(), "Extension code must be 7-bit ASCII: {} (found {})", self.specifier, Self::find_non_ascii(&s) ); Ok(s.into()) } ExtensionFileSourceCode::Computed(code) => { debug_assert!( code.is_ascii(), "Extension code must be 7-bit ASCII: {} (found {})", self.specifier, Self::find_non_ascii(code) ); Ok(ModuleCodeString::from(code.clone())) } } } } pub type OpFnRef = v8::FunctionCallback; /// Function pointer type for the slow op implementation that returns a status code. /// /// Status codes: /// - `0`: op completed synchronously without error. /// - `1`: op completed with an error/exception. /// - `2`: async op was deferred; completion/error metrics will be emitted later. /// /// Sync ops only return `0` or `1`. Async ops may return `0`, `1`, or `2`. pub type SlowFnImplRef = fn(*const v8::FunctionCallbackInfo) -> usize; pub type OpMiddlewareFn = dyn Fn(OpDecl) -> OpDecl; pub type OpStateFn = dyn FnOnce(&mut OpState); /// Trait implemented by all generated ops. pub trait Op { const NAME: &'static str; const DECL: OpDecl; } pub type GlobalTemplateMiddlewareFn = for<'s, 'i> fn( &mut v8::PinScope<'s, 'i, ()>, v8::Local<'s, v8::ObjectTemplate>, ) -> v8::Local<'s, v8::ObjectTemplate>; pub type GlobalObjectMiddlewareFn = for<'s, 'i> fn(&mut v8::PinScope<'s, 'i>, v8::Local<'s, v8::Object>); extern "C" fn noop() {} const NOOP_FN: CFunction = CFunction::new( noop as _, &CFunctionInfo::new(Type::Void.as_info(), &[], Int64Representation::Number), ); // Declaration for object wrappers. #[derive(Clone, Copy)] pub struct OpMethodDecl { pub type_name: fn() -> &'static str, pub name: (&'static str, FastStaticString), pub constructor: Option<OpDecl>, pub methods: &'static [OpDecl], pub static_methods: &'static [OpDecl], pub inherits_type_name: fn() -> Option<&'static str>, } #[derive(Clone, Copy, PartialEq)] pub enum AccessorType { Getter, Setter, None, } #[derive(Clone, Copy)] pub struct OpDecl { pub name: &'static str, pub name_fast: FastStaticString, pub is_async: bool, pub is_reentrant: bool, pub symbol_for: bool, pub accessor_type: AccessorType, pub arg_count: u8, pub no_side_effects: bool, pub constructable: bool, /// The slow dispatch call. If metrics are disabled, the `v8::Function` is created with this callback. pub(crate) slow_fn: OpFnRef, /// The slow dispatch implementation, returning a status code. Used by the shared /// metrics wrapper to call the op and check success/error. pub(crate) slow_fn_impl: SlowFnImplRef, /// The slow dispatch call with metrics enabled. If metrics are enabled, the `v8::Function` is created with this callback. pub(crate) slow_fn_with_metrics: OpFnRef, /// The fast dispatch call. If metrics are disabled, the `v8::Function`'s fastcall is created with this callback. pub(crate) fast_fn: Option<CFunction>, /// The fast dispatch call with metrics enabled. If metrics are enabled, the `v8::Function`'s fastcall is created with this callback. pub(crate) fast_fn_with_metrics: Option<CFunction>, /// Any metadata associated with this op. pub metadata: OpMetadata, } impl OpDecl { /// For use by internal op implementation only. #[doc(hidden)] #[allow(clippy::too_many_arguments, reason = "all arguments are needed")] pub const fn new_internal_op2( name: (&'static str, FastStaticString), is_async: bool, is_reentrant: bool, symbol_for: bool, arg_count: u8, no_side_effects: bool, constructable: bool, slow_fn: OpFnRef, slow_fn_impl: SlowFnImplRef, accessor_type: AccessorType, fast_fn: Option<CFunction>, fast_fn_with_metrics: Option<CFunction>, metadata: OpMetadata, ) -> Self { #[allow(deprecated, reason = "constructing deprecated fields internally")] Self { name: name.0, name_fast: name.1, is_async, is_reentrant, symbol_for, arg_count, no_side_effects, constructable, slow_fn, slow_fn_impl, slow_fn_with_metrics: crate::ops_metrics::slow_metrics_dispatch, accessor_type, fast_fn, fast_fn_with_metrics, metadata, } } pub fn is_accessor(&self) -> bool { self.accessor_type != AccessorType::None } /// Returns a copy of this `OpDecl` that replaces underlying functions /// with noops. pub fn disable(self) -> Self { Self { slow_fn: bindings::op_disabled_fn.map_fn_to(), slow_fn_with_metrics: bindings::op_disabled_fn.map_fn_to(), // TODO(bartlomieju): Currently this fast fn won't throw like `op_disabled_fn`; // ideally we would add a fallback that would throw, but it's unclear // if disabled op (that throws in JS) would ever get optimized to become // a fast function. fast_fn: self.fast_fn.map(|_| NOOP_FN), fast_fn_with_metrics: self.fast_fn_with_metrics.map(|_| NOOP_FN), ..self } } /// Returns a copy of this `OpDecl` with the implementation function set to the function from another /// `OpDecl`. pub const fn with_implementation_from(mut self, from: &Self) -> Self { self.slow_fn = from.slow_fn; self.slow_fn_impl = from.slow_fn_impl; self.slow_fn_with_metrics = from.slow_fn_with_metrics; self.fast_fn = from.fast_fn; self.fast_fn_with_metrics = from.fast_fn_with_metrics; self } #[doc(hidden)] pub const fn fast_fn(&self) -> CFunction { let Some(f) = self.fast_fn else { panic!("Not a fast function"); }; f } #[doc(hidden)] pub const fn fast_fn_with_metrics(&self) -> CFunction { let Some(f) = self.fast_fn_with_metrics else { panic!("Not a fast function"); }; f } } /// Declares a block of Deno `#[op]`s. The first parameter determines the name of the /// op declaration block, and is usually `deno_ops`. This block generates a function that /// returns a [`Vec<OpDecl>`]. /// /// This can be either a compact form like: /// /// ```no_compile /// # use deno_core::*; /// #[op] /// fn op_xyz() {} /// /// deno_core::ops!(deno_ops, [ /// op_xyz /// ]); /// /// // Use the ops: /// deno_ops() /// ``` /// /// ... or a parameterized form like so that allows passing a number of type parameters /// to each `#[op]`: /// /// ```no_compile /// # use deno_core::*; /// #[op] /// fn op_xyz<P>() where P: Clone {} /// /// deno_core::ops!(deno_ops, /// parameters = [P: Clone], /// ops = [ /// op_xyz<P> /// ] /// ); /// /// // Use the ops, with `String` as the parameter `P`: /// deno_ops::<String>() /// ``` #[macro_export] macro_rules! ops { ($name:ident, parameters = [ $( $param:ident : $type:ident ),+ ], ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $op_param:ident > )? ),+ $(,)? ]) => { pub(crate) fn $name < $( $param : $type + 'static ),+ > () -> ::std::vec::Vec<$crate::OpDecl> { vec![ $( $( #[ $m ] )* $( $op )+ $( :: <$op_param> )? () , )+ ] } }; ($name:ident, [ $( $(#[$m:meta])* $( $op:ident )::+ ),+ $(,)? ] ) => { pub(crate) fn $name() -> ::std::Vec<$crate::OpDecl> { use $crate::Op; vec![ $( $( #[ $m ] )* $( $op )+() , )+ ] } } } /// Return the first argument if not empty, otherwise the second. #[macro_export] macro_rules! or { ($e:expr_2021, $fallback:expr_2021) => { $e }; (, $fallback:expr_2021) => { $fallback }; } /// Defines a Deno extension. The first parameter is the name of the extension symbol namespace to create. This is the symbol you /// will use to refer to the extension. /// /// Most extensions will define a combination of ops and ESM files, like so: /// /// ```no_compile /// #[op] /// fn op_xyz() { /// } /// /// deno_core::extension!( /// my_extension, /// ops = [ op_xyz ], /// esm = [ "my_script.js" ], /// docs = "A small sample extension" /// ); /// ``` /// /// The following options are available for the [`extension`] macro: /// /// * deps: a comma-separated list of module dependencies, eg: `deps = [ my_other_extension ]` /// * parameters: a comma-separated list of parameters and base traits, eg: `parameters = [ P: MyTrait ]` /// * bounds: a comma-separated list of additional type bounds, eg: `bounds = [ P::MyAssociatedType: MyTrait ]` /// * ops: a comma-separated list of [`OpDecl`]s to provide, eg: `ops = [ op_foo, op_bar ]` /// * esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), eg: `esm = [ dir "dir", "my_file.js" ]` /// * lazy_loaded_esm: a comma-separated list of ESM module filenames (see [`include_js_files`]), that will be included in /// the produced binary, but not automatically evaluated. Eg: `lazy_loaded_esm = [ dir "dir", "my_file.js" ]` /// * lazy_loaded_js: a comma-separated list of JS script filenames that will be included in /// the produced binary, but not automatically evaluated. Loaded on demand via `Deno.core.loadExtScript()`. /// Eg: `lazy_loaded_js = [ dir "dir", "my_file.js" ]` /// * synthetic_esm: a comma-separated list of `"module_specifier" = "backing_script_specifier"` pairs. /// Imports of `module_specifier` resolve to a synthetic ESM module whose exports are derived from /// the exports object returned by the IIFE in `backing_script_specifier` (which must be declared /// in `lazy_loaded_js`). Eg: `synthetic_esm = [ "node:zlib" = "ext:deno_node/zlib.js" ]` /// * js: a comma-separated list of JS filenames (see [`include_js_files`]), eg: `js = [ dir "dir", "my_file.js" ]` /// * config: a structure-like definition for configuration parameters which will be required when initializing this extension, eg: `config = { my_param: Option<usize> }` /// * middleware: an [`OpDecl`] middleware function with the signature `fn (OpDecl) -> OpDecl` /// * state: a state initialization function, with the signature `fn (&mut OpState, ...) -> ()`, where `...` are parameters matching the fields of the config struct /// * global_template_middleware: a global template middleware function (see [`Extension::global_template_middleware`]) /// * global_object_middleware: a global object middleware function (see [`Extension::global_object_middleware`]) /// * docs: comma separated list of toplevel #[doc=...] tags to be applied to the extension's resulting struct #[macro_export] macro_rules! extension { ( $name:ident $(, deps = [ $( $dep:ident ),* ] )? $(, parameters = [ $( $param:ident : $type:ident ),+ ] )? $(, bounds = [ $( $bound:path : $bound_type:ident ),+ ] )? $(, ops_fn = $ops_symbol:ident $( < $ops_param:ident > )? )? $(, ops = [ $( $(#[$m:meta])* $( $op:ident )::+ $( < $( $op_param:ident ),* > )? ),+ $(,)? ] )? $(, objects = [ $( $(#[$masd:meta])* $( $object:ident )::+ ),+ $(,)? ] )? $(, esm_entry_point = $esm_entry_point:expr_2021 )? $(, esm = [ $($esm:tt)* ] )? $(, lazy_loaded_esm = [ $($lazy_loaded_esm:tt)* ] )? $(, lazy_loaded_js = [ $($lazy_loaded_js:tt)* ] )? $(, synthetic_esm = [ $( $synthetic_esm_module:literal = $synthetic_esm_backing:literal ),* $(,)? ] )? $(, js = [ $($js:tt)* ] )? $(, options = { $( $options_id:ident : $options_type:ty ),* $(,)? } )? $(, middleware = $middleware_fn:expr_2021 )? $(, state = $state_fn:expr_2021 )? $(, global_template_middleware = $global_template_middleware_fn:expr_2021 )? $(, global_object_middleware = $global_object_middleware_fn:expr_2021 )? $(, external_references = [ $( $external_reference:expr_2021 ),* $(,)? ] )? $(, customizer = $customizer_fn:expr_2021 )? $(, docs = $($docblocks:expr_2021),+)? $(,)? ) => { $( $(#[doc = $docblocks])+ )? /// /// An extension for use with the Deno JS runtime. /// To use it, provide it as an argument when instantiating your runtime: /// /// ```rust,ignore /// use deno_core::{ JsRuntime, RuntimeOptions }; /// #[doc = concat!("let mut extensions = vec![", stringify!($name), "::init()];")] /// let mut js_runtime = JsRuntime::new(RuntimeOptions { /// extensions, /// ..Default::default() /// }); /// ``` /// #[allow(non_camel_case_types, reason = "matches extension naming convention")] pub struct $name { } impl $name { fn ext $( < $( $param : $type + 'static ),+ > )?() -> $crate::Extension { #[allow(unused_imports, reason = "used when ops are specified")] use $crate::Op; $crate::Extension { // Computed at compile-time, may be modified at runtime with `Cow`: name: ::std::stringify!($name), deps: &[ $( $( ::std::stringify!($dep) ),* )? ], // Use intermediary `const`s here to disable user expressions which // can't be evaluated at compile-time. js_files: { const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($js)*)? ); ::std::borrow::Cow::Borrowed(JS) }, esm_files: { const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_js_files!( $name $($($esm)*)? ); ::std::borrow::Cow::Borrowed(JS) }, lazy_loaded_esm_files: { const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_lazy_loaded_js_files!( $name $($($lazy_loaded_esm)*)? ); ::std::borrow::Cow::Borrowed(JS) }, lazy_loaded_js_files: { const JS: &'static [$crate::ExtensionFileSource] = &$crate::include_lazy_loaded_js_files!( $name $($($lazy_loaded_js)*)? ); ::std::borrow::Cow::Borrowed(JS) }, synthetic_esm_modules: { const MAPPINGS: &'static [(&'static str, &'static str)] = &[ $($(($synthetic_esm_module, $synthetic_esm_backing)),*)? ]; ::std::borrow::Cow::Borrowed(MAPPINGS) }, esm_entry_point: { const V: ::std::option::Option<&'static ::std::primitive::str> = $crate::or!($(::std::option::Option::Some($esm_entry_point))?, ::std::option::Option::None); V }, ops: ::std::borrow::Cow::Owned(vec![$($({ $( #[ $m ] )* $( $op )::+ $( :: < $($op_param),* > )? () }),+)?]), objects: ::std::borrow::Cow::Borrowed(&[$($({ $( $object )::+::DECL }),+)?]), external_references: ::std::borrow::Cow::Borrowed(&[ $( $external_reference ),* ]), global_template_middleware: ::std::option::Option::None, global_object_middleware: ::std::option::Option::None, // Computed at runtime: op_state_fn: ::std::option::Option::None, needs_lazy_init: false, middleware_fn: ::std::option::Option::None, enabled: true, } } // If ops were specified, add those ops to the extension. #[inline(always)] #[allow(unused_variables, reason = "ext may be unused depending on macro expansion")] fn with_ops_fn $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension) $( where $( $bound : $bound_type ),+ )? { // Use the ops_fn, if provided $crate::extension!(! __ops__ ext $( $ops_symbol $( < $ops_param > )? )? __eot__); } // Includes the state and middleware functions, if defined. #[inline(always)] #[allow(unused_variables, reason = "ext may be unused depending on macro expansion")] fn with_middleware $( < $( $param : $type + 'static ),+ > )?(ext: &mut $crate::Extension) { $( ext.global_template_middleware = ::std::option::Option::Some($global_template_middleware_fn); )? $( ext.global_object_middleware = ::std::option::Option::Some($global_object_middleware_fn); )? $( ext.middleware_fn = ::std::option::Option::Some(::std::boxed::Box::new($middleware_fn)); )? } #[inline(always)] #[allow(unused_variables, reason = "ext may be unused depending on macro expansion")] #[allow(clippy::redundant_closure_call, reason = "generated by macro")] fn with_customizer(ext: &mut $crate::Extension) { $( ($customizer_fn)(ext); )? } /// Initialize this extension for runtime or snapshot creation. /// /// # Returns /// an Extension object that can be used during instantiation of a JsRuntime #[allow(dead_code, reason = "generated by macro, may not be called")] pub fn init $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::Extension $( where $( $bound : $bound_type ),+ )? { let mut ext = Self::ext $( ::< $( $param ),+ > )?(); Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext); $crate::extension!(! __config__ ext $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? ); Self::with_middleware $( ::< $( $param ),+ > )?(&mut ext); Self::with_customizer(&mut ext); ext } /// Initialize this extension for runtime or snapshot creation. /// /// If this method is used, you must later call `JsRuntime::lazy_init_extensions` /// with the result of this extension's `args` method. /// /// # Returns /// an Extension object that can be used during instantiation of a JsRuntime #[allow(dead_code, reason = "generated by macro, may not be called")] pub fn lazy_init $( < $( $param : $type + 'static ),+ > )? () -> $crate::Extension $( where $( $bound : $bound_type ),+ )? { let mut ext = Self::ext $( ::< $( $param ),+ > )?(); Self::with_ops_fn $( ::< $( $param ),+ > )?(&mut ext); ext.needs_lazy_init = true; Self::with_middleware$( ::< $( $param ),+ > )?(&mut ext); Self::with_customizer(&mut ext); ext } /// Create an `ExtensionArguments` value which must be passed to /// `JsRuntime::lazy_init_extensions`. #[allow(dead_code, unused_mut, reason = "generated by macro, may not be called")] pub fn args $( < $( $param : $type + 'static ),+ > )? ( $( $( $options_id : $options_type ),* )? ) -> $crate::ExtensionArguments $( where $( $bound : $bound_type ),+ )? { let mut args = $crate::ExtensionArguments { name: ::std::stringify!($name), op_state_fn: ::std::option::Option::None, }; $crate::extension!(! __config__ args $( parameters = [ $( $param : $type ),* ] )? $( config = { $( $options_id : $options_type ),* } )? $( state_fn = $state_fn )? ); args } } }; // This branch of the macro generates a config object that calls the state function with itself. (! __config__ $args:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? config = { $( $options_id:ident : $options_type:ty ),* } $( state_fn = $state_fn:expr_2021 )? ) => { { #[doc(hidden)] struct Config $( < $( $param : $type + 'static ),+ > )? { $( pub $options_id : $options_type , )* $( __phantom_data: ::std::marker::PhantomData<($( $param ),+)>, )? } let config = Config { $( $options_id , )* $( __phantom_data: ::std::marker::PhantomData::<($( $param ),+)>::default() )? }; let state_fn: fn(&mut $crate::OpState, Config $( < $( $param ),+ > )? ) = $( $state_fn )?; $args.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new(move |state: &mut $crate::OpState| { state_fn(state, config); })); } }; (! __config__ $args:ident $( parameters = [ $( $param:ident : $type:ident ),+ ] )? $( state_fn = $state_fn:expr_2021 )? ) => { $( $args.op_state_fn = ::std::option::Option::Some(::std::boxed::Box::new($state_fn)); )? }; (! __ops__ $ext:ident __eot__) => { }; (! __ops__ $ext:ident $ops_symbol:ident __eot__) => { $ext.ops.to_mut().extend($ops_symbol()) }; (! __ops__ $ext:ident $ops_symbol:ident < $ops_param:ident > __eot__) => { $ext.ops.to_mut().extend($ops_symbol::<$ops_param>()) }; } pub struct Extension { pub name: &'static str, pub deps: &'static [&'static str], pub js_files: Cow<'static, [ExtensionFileSource]>, pub esm_files: Cow<'static, [ExtensionFileSource]>, pub lazy_loaded_esm_files: Cow<'static, [ExtensionFileSource]>, pub lazy_loaded_js_files: Cow<'static, [ExtensionFileSource]>, /// `(module_specifier, backing_script_specifier)` pairs. At extension /// init, each pair is registered so that imports of `module_specifier` /// resolve to a synthetic ESM module built from the exports object /// returned by the IIFE in `backing_script_specifier` (which must be /// declared in `lazy_loaded_js_files`). pub synthetic_esm_modules: Cow<'static, [(&'static str, &'static str)]>, pub esm_entry_point: Option<&'static str>, pub ops: Cow<'static, [OpDecl]>, pub objects: Cow<'static, [OpMethodDecl]>, pub external_references: Cow<'static, [v8::ExternalReference]>, pub global_template_middleware: Option<GlobalTemplateMiddlewareFn>, pub global_object_middleware: Option<GlobalObjectMiddlewareFn>, pub op_state_fn: Option<Box<OpStateFn>>, pub needs_lazy_init: bool, pub middleware_fn: Option<Box<OpMiddlewareFn>>, pub enabled: bool, } impl Extension { // Produces a new extension that is suitable for use during the warmup phase. // // JS sources are not included, and ops are include for external references only. pub(crate) fn for_warmup(&self) -> Extension { Self { op_state_fn: None, needs_lazy_init: self.needs_lazy_init, middleware_fn: None, name: self.name, deps: self.deps, js_files: Cow::Borrowed(&[]), esm_files: Cow::Borrowed(&[]), lazy_loaded_esm_files: Cow::Borrowed(&[]), lazy_loaded_js_files: Cow::Borrowed(&[]), synthetic_esm_modules: Cow::Borrowed(&[]), esm_entry_point: None, ops: self.ops.clone(), objects: self.objects.clone(), external_references: self.external_references.clone(), global_template_middleware: self.global_template_middleware, global_object_middleware: self.global_object_middleware, enabled: self.enabled, } } } impl Default for Extension { fn default() -> Self { Self { name: "DEFAULT", deps: &[], js_files: Cow::Borrowed(&[]), esm_files: Cow::Borrowed(&[]), lazy_loaded_esm_files: Cow::Borrowed(&[]), lazy_loaded_js_files: Cow::Borrowed(&[]), synthetic_esm_modules: Cow::Borrowed(&[]), esm_entry_point: None, ops: Cow::Borrowed(&[]), objects: Cow::Borrowed(&[]), external_references: Cow::Borrowed(&[]), global_template_middleware: None, global_object_middleware: None, op_state_fn: None, needs_lazy_init: false, middleware_fn: None, enabled: true, } } } // Note: this used to be a trait, but we "downgraded" it to a single concrete type // for the initial iteration, it will likely become a trait in the future impl Extension { /// Check if dependencies have been loaded, and errors if either: /// - The extension is depending on itself or an extension with the same name. /// - A dependency hasn't been loaded yet. pub fn check_dependencies(&self, previous_exts: &[Extension]) { 'dep_loop: for dep in self.deps { if dep == &self.name { panic!( "Extension '{}' is either depending on itself or there is another extension with the same name", self.name ); } for ext in previous_exts { if dep == &ext.name { continue 'dep_loop; } } panic!("Extension '{}' is missing dependency '{dep}'", self.name); } } /// returns JS source code to be loaded into the isolate (either at snapshotting, /// or at startup). as a vector of a tuple of the file name, and the source code. pub fn get_js_sources(&self) -> &[ExtensionFileSource] { &self.js_files } pub fn get_esm_sources(&self) -> &[ExtensionFileSource] { &self.esm_files } pub fn get_lazy_loaded_esm_sources(&self) -> &[ExtensionFileSource] { &self.lazy_loaded_esm_files } pub fn get_esm_entry_point(&self) -> Option<&'static str> { self.esm_entry_point } pub fn op_count(&self) -> usize { self.ops.len() } pub fn method_op_count(&self) -> usize { self.objects.len() } /// Called at JsRuntime startup to initialize ops in the isolate. pub fn init_ops(&mut self) -> &[OpDecl] { if !self.enabled { for op in self.ops.to_mut() { op.disable(); } } self.ops.as_ref() } /// Called at JsRuntime startup to initialize method ops in the isolate. pub fn init_method_ops(&self) -> &[OpMethodDecl] { self.objects.as_ref() } /// Allows setting up the initial op-state of an isolate at startup. pub fn take_state(&mut self, state: &mut OpState) { if let Some(op_fn) = self.op_state_fn.take() { op_fn(state); } } /// Middleware should be called before init pub fn take_middleware(&mut self) -> Option<Box<OpMiddlewareFn>> { self.middleware_fn.take() } pub fn get_global_template_middleware( &mut self, ) -> Option<GlobalTemplateMiddlewareFn> { self.global_template_middleware } pub fn get_global_object_middleware( &mut self, ) -> Option<GlobalObjectMiddlewareFn> { self.global_object_middleware } pub fn get_external_references(&mut self) -> &[v8::ExternalReference] { self.external_references.as_ref() } pub fn enabled(self, enabled: bool) -> Self { Self { enabled, ..self } } pub fn disable(self) -> Self { self.enabled(false) } } /// Holds configuration needed to initialize an extension. Must be passed to /// `JsRuntime::lazy_init_extensions`. pub struct ExtensionArguments { #[doc(hidden)] pub name: &'static str, #[doc(hidden)] pub op_state_fn: Option<Box<OpStateFn>>, } /// Helps embed JS files in an extension. Returns a vector of /// [`ExtensionFileSource`], that represents the filename and source code. /// /// ``` /// # use deno_core::include_js_files_doctest as include_js_files; /// // Example (for "my_extension"): /// let files = include_js_files!( /// my_extension /// "01_hello.js", /// "02_goodbye.js", /// ); /// /// // Produces following specifiers: /// // - "ext:my_extension/01_hello.js" /// // - "ext:my_extension/02_goodbye.js" /// ``` /// /// An optional "dir" option can be specified to prefix all files with a /// directory name. /// /// ``` /// # use deno_core::include_js_files_doctest as include_js_files; /// // Example with "dir" option (for "my_extension"): /// include_js_files!( /// my_extension /// dir "js", /// "01_hello.js", /// "02_goodbye.js", /// ); /// // Produces following specifiers: /// // - "ext:my_extension/js/01_hello.js" /// // - "ext:my_extension/js/02_goodbye.js" /// ``` /// /// You may also override the specifiers for each file like so: /// /// ``` /// # use deno_core::include_js_files_doctest as include_js_files; /// // Example with "dir" option (for "my_extension"): /// include_js_files!( /// my_extension /// "module:hello" = "01_hello.js", /// "module:goodbye" = "02_goodbye.js", /// ); /// // Produces following specifiers: /// // - "module:hello" /// // - "module:goodbye" /// ``` #[macro_export] macro_rules! include_js_files { // Valid inputs: // - "file" // - "file" with_specifier "specifier" // - "specifier" = "file" // - "specifier" = { source = "source" } ($name:ident $( dir $dir:literal, )? $( $s1:literal $(with_specifier $s2:literal)? $(= $config:tt)? ),* $(,)?) => { $crate::__extension_include_js_files_detect!(name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([ // These entries will be parsed in __extension_include_js_files_inner $s1 $(with_specifier $s2)? $(= $config)? ]),*) }; } /// Helps embed JS files in an extension. Returns a vector of /// `ExtensionFileSource`, that represent the filename and source code. All /// specified files are rewritten into "ext:<extension_name>/<file_name>". /// /// An optional "dir" option can be specified to prefix all files with a /// directory name. /// /// See [`include_js_files!`] for details on available options. #[macro_export] macro_rules! include_lazy_loaded_js_files { ($name:ident $( dir $dir:literal, )? $( $s1:literal $(with_specifier $s2:literal)? $(= $config:tt)? ),* $(,)?) => { $crate::__extension_include_js_files_inner!(mode=loaded, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([ // These entries will be parsed in __extension_include_js_files_inner $s1 $(with_specifier $s2)? $(= $config)? ]),*) }; } /// Used for doctests only. Won't try to load anything from disk. #[doc(hidden)] #[macro_export] macro_rules! include_js_files_doctest { ($name:ident $( dir $dir:literal, )? $( $s1:literal $(with_specifier $s2:literal)? $(= $config:tt)? ),* $(,)?) => { $crate::__extension_include_js_files_inner!(mode=loaded, name=$name, dir=$crate::__extension_root_dir!($($dir)?), $([ $s1 $(with_specifier $s2)? $(= $config)? ]),*) }; } /// Source files declared on an extension are recorded by absolute path; their /// bytes are read from disk during snapshot creation and never embedded in the /// final binary. With a startup snapshot, the source is reachable via the /// snapshot bytes (for `esm`/`js`) or the residual lazy table emitted by the /// snapshot build (for `lazy_loaded_*`). #[doc(hidden)] #[macro_export] macro_rules! __extension_include_js_files_detect { ($($rest:tt)*) => { $crate::__extension_include_js_files_inner!(mode=loaded, $($rest)*) }; } /// This is the core of the [`include_js_files!`] and [`include_lazy_loaded_js_files`] macros. The first /// rule is the entry point that receives a list of unparsed file entries. Each entry is extracted and /// then parsed with the `@parse_item` rules. #[doc(hidden)] #[macro_export] macro_rules! __extension_include_js_files_inner { // Entry point: (mode=, name=, dir=, [... files]) (mode=$mode:ident, name=$name:ident, dir=$dir:expr_2021, $([ $s1:literal $(with_specifier $s2:literal)? $(= $config:tt)? ]),*) => { [ $( $crate::__extension_include_js_files_inner!( @parse_item mode=$mode, name=$name, dir=$dir, $s1 $(with_specifier $s2)? $(= $config)? ) ),* ] }; // @parse_item macros will parse a single file entry, and then call @item macros with the destructured data // "file" -> Include a file, use the generated specifier (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr_2021, $file:literal) => { $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=concat!("ext:", stringify!($name), "/", $file), file=$file) }; // "file" with_specifier "specifier" -> Include a file, use the provided specifier (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr_2021, $file:literal with_specifier $specifier:literal) => { { #[deprecated="When including JS files 'file with_specifier specifier' is deprecated: use 'specifier = file' instead"] struct WithSpecifierIsDeprecated {} _ = WithSpecifierIsDeprecated {}; $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file) } }; // "specifier" = "file" -> Include a file, use the provided specifier (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr_2021, $specifier:literal = $file:literal) => { $crate::__extension_include_js_files_inner!(@item mode=$mode, dir=$dir, specifier=$specifier, file=$file) }; // "specifier" = { source = "source" } -> Include a file, use the provided specifier (@parse_item mode=$mode:ident, name=$name:ident, dir=$dir:expr_2021, $specifier:literal = { source = $source:literal }) => { $crate::__extension_include_js_files_inner!(@item mode=$mode, specifier=$specifier, source=$source) }; // @item macros generate the final output // loaded, source (@item mode=loaded, specifier=$specifier:expr_2021, source=$source:expr_2021) => { $crate::ExtensionFileSource::loaded_from_memory_during_snapshot($specifier, $crate::ascii_str!($source)) }; // loaded, file (@item mode=loaded, dir=$dir:expr_2021, specifier=$specifier:expr_2021, file=$file:literal) => { $crate::ExtensionFileSource::loaded_during_snapshot($specifier, concat!($dir, "/", $file)) }; // included, source (@item mode=included, specifier=$specifier:expr_2021, source=$source:expr_2021) => { $crate::ExtensionFileSource::new($specifier, $crate::ascii_str!($source)) }; // included, file (@item mode=included, dir=$dir:expr_2021, specifier=$specifier:expr_2021, file=$file:literal) => { $crate::ExtensionFileSource::new($specifier, $crate::ascii_str_include!(concat!($dir, "/", $file))) }; } /// Given an optional `$dir`, generates a crate-relative root directory. #[doc(hidden)] #[macro_export] macro_rules! __extension_root_dir { () => { env!("CARGO_MANIFEST_DIR") }; ($dir:expr_2021) => { concat!(env!("CARGO_MANIFEST_DIR"), "/", $dir) }; } #[cfg(test)] mod tests { #[test] fn test_include_js() { let files = include_js_files!(prefix "00_infra.js", "01_core.js",); assert_eq!("ext:prefix/00_infra.js", files[0].specifier); assert_eq!("ext:prefix/01_core.js", files[1].specifier); let files = include_js_files!(prefix dir ".", "00_infra.js", "01_core.js",); assert_eq!("ext:prefix/00_infra.js", files[0].specifier); assert_eq!("ext:prefix/01_core.js", files[1].specifier); let files = include_js_files!(prefix "a" = { source = "b" } ); assert_eq!("a", files[0].specifier); } }