mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
feat(ext/node): properly segregate node globals (#19307)
Code run within Deno-mode and Node-mode should have access to a slightly different set of globals. Previously this was done through a compile time code-transform for Node-mode, but this is not ideal and has many edge cases, for example Node's globalThis having a different identity than Deno's globalThis. This commit makes the `globalThis` of the entire runtime a semi-proxy. This proxy returns a different set of globals depending on the caller's mode. This is not a full proxy, because it is shadowed by "real" properties on globalThis. This is done to avoid the overhead of a full proxy for all globalThis operations. The globals between Deno-mode and Node-mode are now properly segregated. This means that code running in Deno-mode will not have access to Node's globals, and vice versa. Deleting a managed global in Deno-mode will NOT delete the corresponding global in Node-mode, and vice versa. --------- Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com> Co-authored-by: Aapo Alasuutari <aapo.alasuutari@gmail.com>
This commit is contained in:
parent
30feee81e1
commit
b8021744b3
21 changed files with 671 additions and 475 deletions
106
cli/cache/node.rs
vendored
106
cli/cache/node.rs
vendored
|
@ -12,26 +12,12 @@ use super::FastInsecureHasher;
|
||||||
|
|
||||||
pub static NODE_ANALYSIS_CACHE_DB: CacheDBConfiguration =
|
pub static NODE_ANALYSIS_CACHE_DB: CacheDBConfiguration =
|
||||||
CacheDBConfiguration {
|
CacheDBConfiguration {
|
||||||
table_initializer: concat!(
|
table_initializer: "CREATE TABLE IF NOT EXISTS cjsanalysiscache (
|
||||||
"CREATE TABLE IF NOT EXISTS cjsanalysiscache (
|
|
||||||
specifier TEXT PRIMARY KEY,
|
specifier TEXT PRIMARY KEY,
|
||||||
source_hash TEXT NOT NULL,
|
source_hash TEXT NOT NULL,
|
||||||
data TEXT NOT NULL
|
data TEXT NOT NULL
|
||||||
);",
|
);",
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS cjsanalysiscacheidx
|
on_version_change: "DELETE FROM cjsanalysiscache;",
|
||||||
ON cjsanalysiscache(specifier);",
|
|
||||||
"CREATE TABLE IF NOT EXISTS esmglobalscache (
|
|
||||||
specifier TEXT PRIMARY KEY,
|
|
||||||
source_hash TEXT NOT NULL,
|
|
||||||
data TEXT NOT NULL
|
|
||||||
);",
|
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS esmglobalscacheidx
|
|
||||||
ON esmglobalscache(specifier);",
|
|
||||||
),
|
|
||||||
on_version_change: concat!(
|
|
||||||
"DELETE FROM cjsanalysiscache;",
|
|
||||||
"DELETE FROM esmglobalscache;",
|
|
||||||
),
|
|
||||||
preheat_queries: &[],
|
preheat_queries: &[],
|
||||||
on_failure: CacheFailure::InMemory,
|
on_failure: CacheFailure::InMemory,
|
||||||
};
|
};
|
||||||
|
@ -91,29 +77,6 @@ impl NodeAnalysisCache {
|
||||||
cjs_analysis,
|
cjs_analysis,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_esm_analysis(
|
|
||||||
&self,
|
|
||||||
specifier: &str,
|
|
||||||
expected_source_hash: &str,
|
|
||||||
) -> Option<Vec<String>> {
|
|
||||||
Self::ensure_ok(
|
|
||||||
self.inner.get_esm_analysis(specifier, expected_source_hash),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_esm_analysis(
|
|
||||||
&self,
|
|
||||||
specifier: &str,
|
|
||||||
source_hash: &str,
|
|
||||||
top_level_decls: &Vec<String>,
|
|
||||||
) {
|
|
||||||
Self::ensure_ok(self.inner.set_esm_analysis(
|
|
||||||
specifier,
|
|
||||||
source_hash,
|
|
||||||
top_level_decls,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -172,54 +135,6 @@ impl NodeAnalysisCacheInner {
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_esm_analysis(
|
|
||||||
&self,
|
|
||||||
specifier: &str,
|
|
||||||
expected_source_hash: &str,
|
|
||||||
) -> Result<Option<Vec<String>>, AnyError> {
|
|
||||||
let query = "
|
|
||||||
SELECT
|
|
||||||
data
|
|
||||||
FROM
|
|
||||||
esmglobalscache
|
|
||||||
WHERE
|
|
||||||
specifier=?1
|
|
||||||
AND source_hash=?2
|
|
||||||
LIMIT 1";
|
|
||||||
let res = self.conn.query_row(
|
|
||||||
query,
|
|
||||||
params![specifier, &expected_source_hash],
|
|
||||||
|row| {
|
|
||||||
let top_level_decls: String = row.get(0)?;
|
|
||||||
let decls: Vec<String> = serde_json::from_str(&top_level_decls)?;
|
|
||||||
Ok(decls)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_esm_analysis(
|
|
||||||
&self,
|
|
||||||
specifier: &str,
|
|
||||||
source_hash: &str,
|
|
||||||
top_level_decls: &Vec<String>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
let sql = "
|
|
||||||
INSERT OR REPLACE INTO
|
|
||||||
esmglobalscache (specifier, source_hash, data)
|
|
||||||
VALUES
|
|
||||||
(?1, ?2, ?3)";
|
|
||||||
self.conn.execute(
|
|
||||||
sql,
|
|
||||||
params![
|
|
||||||
specifier,
|
|
||||||
&source_hash,
|
|
||||||
&serde_json::to_string(top_level_decls)?,
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -245,23 +160,10 @@ mod test {
|
||||||
assert_eq!(actual_cjs_analysis.exports, cjs_analysis.exports);
|
assert_eq!(actual_cjs_analysis.exports, cjs_analysis.exports);
|
||||||
assert_eq!(actual_cjs_analysis.reexports, cjs_analysis.reexports);
|
assert_eq!(actual_cjs_analysis.reexports, cjs_analysis.reexports);
|
||||||
|
|
||||||
assert!(cache.get_esm_analysis("file.js", "2").unwrap().is_none());
|
|
||||||
let esm_analysis = vec!["esm1".to_string()];
|
|
||||||
cache
|
|
||||||
.set_esm_analysis("file.js", "2", &esm_analysis)
|
|
||||||
.unwrap();
|
|
||||||
assert!(cache.get_esm_analysis("file.js", "3").unwrap().is_none()); // different hash
|
|
||||||
let actual_esm_analysis =
|
|
||||||
cache.get_esm_analysis("file.js", "2").unwrap().unwrap();
|
|
||||||
assert_eq!(actual_esm_analysis, esm_analysis);
|
|
||||||
|
|
||||||
// adding when already exists should not cause issue
|
// adding when already exists should not cause issue
|
||||||
cache
|
cache
|
||||||
.set_cjs_analysis("file.js", "2", &cjs_analysis)
|
.set_cjs_analysis("file.js", "2", &cjs_analysis)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cache
|
|
||||||
.set_esm_analysis("file.js", "2", &esm_analysis)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// recreating with same cli version should still have it
|
// recreating with same cli version should still have it
|
||||||
let conn = cache.conn.recreate_with_version("1.0.0");
|
let conn = cache.conn.recreate_with_version("1.0.0");
|
||||||
|
@ -270,14 +172,10 @@ mod test {
|
||||||
cache.get_cjs_analysis("file.js", "2").unwrap().unwrap();
|
cache.get_cjs_analysis("file.js", "2").unwrap().unwrap();
|
||||||
assert_eq!(actual_analysis.exports, cjs_analysis.exports);
|
assert_eq!(actual_analysis.exports, cjs_analysis.exports);
|
||||||
assert_eq!(actual_analysis.reexports, cjs_analysis.reexports);
|
assert_eq!(actual_analysis.reexports, cjs_analysis.reexports);
|
||||||
let actual_esm_analysis =
|
|
||||||
cache.get_esm_analysis("file.js", "2").unwrap().unwrap();
|
|
||||||
assert_eq!(actual_esm_analysis, esm_analysis);
|
|
||||||
|
|
||||||
// now changing the cli version should clear it
|
// now changing the cli version should clear it
|
||||||
let conn = cache.conn.recreate_with_version("2.0.0");
|
let conn = cache.conn.recreate_with_version("2.0.0");
|
||||||
let cache = NodeAnalysisCacheInner::new(conn);
|
let cache = NodeAnalysisCacheInner::new(conn);
|
||||||
assert!(cache.get_cjs_analysis("file.js", "2").unwrap().is_none());
|
assert!(cache.get_cjs_analysis("file.js", "2").unwrap().is_none());
|
||||||
assert!(cache.get_esm_analysis("file.js", "2").unwrap().is_none());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ use crate::module_loader::CjsResolutionStore;
|
||||||
use crate::module_loader::CliModuleLoaderFactory;
|
use crate::module_loader::CliModuleLoaderFactory;
|
||||||
use crate::module_loader::ModuleLoadPreparer;
|
use crate::module_loader::ModuleLoadPreparer;
|
||||||
use crate::module_loader::NpmModuleLoader;
|
use crate::module_loader::NpmModuleLoader;
|
||||||
use crate::node::CliCjsEsmCodeAnalyzer;
|
use crate::node::CliCjsCodeAnalyzer;
|
||||||
use crate::node::CliNodeCodeTranslator;
|
use crate::node::CliNodeCodeTranslator;
|
||||||
use crate::npm::create_npm_fs_resolver;
|
use crate::npm::create_npm_fs_resolver;
|
||||||
use crate::npm::CliNpmRegistryApi;
|
use crate::npm::CliNpmRegistryApi;
|
||||||
|
@ -475,7 +475,7 @@ impl CliFactory {
|
||||||
let caches = self.caches()?;
|
let caches = self.caches()?;
|
||||||
let node_analysis_cache =
|
let node_analysis_cache =
|
||||||
NodeAnalysisCache::new(caches.node_analysis_db());
|
NodeAnalysisCache::new(caches.node_analysis_db());
|
||||||
let cjs_esm_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache);
|
let cjs_esm_analyzer = CliCjsCodeAnalyzer::new(node_analysis_cache);
|
||||||
|
|
||||||
Ok(Arc::new(NodeCodeTranslator::new(
|
Ok(Arc::new(NodeCodeTranslator::new(
|
||||||
cjs_esm_analyzer,
|
cjs_esm_analyzer,
|
||||||
|
|
|
@ -28,7 +28,6 @@ mod tests {
|
||||||
if (!(bootstrap.mainRuntime && bootstrap.workerRuntime)) {
|
if (!(bootstrap.mainRuntime && bootstrap.workerRuntime)) {
|
||||||
throw Error("bad");
|
throw Error("bad");
|
||||||
}
|
}
|
||||||
console.log("we have console.log!!!");
|
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -786,10 +786,8 @@ impl NpmModuleLoader {
|
||||||
permissions,
|
permissions,
|
||||||
)?
|
)?
|
||||||
} else {
|
} else {
|
||||||
// only inject node globals for esm
|
// esm code is untouched
|
||||||
self
|
code
|
||||||
.node_code_translator
|
|
||||||
.esm_code_with_node_globals(specifier, &code)?
|
|
||||||
};
|
};
|
||||||
Ok(ModuleCodeSource {
|
Ok(ModuleCodeSource {
|
||||||
code: code.into(),
|
code: code.into(),
|
||||||
|
|
117
cli/node.rs
117
cli/node.rs
|
@ -1,24 +1,17 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use deno_ast::swc::common::SyntaxContext;
|
|
||||||
use deno_ast::view::Node;
|
|
||||||
use deno_ast::view::NodeTrait;
|
|
||||||
use deno_ast::CjsAnalysis;
|
use deno_ast::CjsAnalysis;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_ast::ParsedSource;
|
|
||||||
use deno_ast::SourceRanged;
|
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_runtime::deno_node::analyze::CjsAnalysis as ExtNodeCjsAnalysis;
|
use deno_runtime::deno_node::analyze::CjsAnalysis as ExtNodeCjsAnalysis;
|
||||||
use deno_runtime::deno_node::analyze::CjsEsmCodeAnalyzer;
|
use deno_runtime::deno_node::analyze::CjsCodeAnalyzer;
|
||||||
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
|
use deno_runtime::deno_node::analyze::NodeCodeTranslator;
|
||||||
|
|
||||||
use crate::cache::NodeAnalysisCache;
|
use crate::cache::NodeAnalysisCache;
|
||||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||||
|
|
||||||
pub type CliNodeCodeTranslator = NodeCodeTranslator<CliCjsEsmCodeAnalyzer>;
|
pub type CliNodeCodeTranslator = NodeCodeTranslator<CliCjsCodeAnalyzer>;
|
||||||
|
|
||||||
/// Resolves a specifier that is pointing into a node_modules folder.
|
/// Resolves a specifier that is pointing into a node_modules folder.
|
||||||
///
|
///
|
||||||
|
@ -39,11 +32,11 @@ pub fn resolve_specifier_into_node_modules(
|
||||||
.unwrap_or_else(|| specifier.clone())
|
.unwrap_or_else(|| specifier.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CliCjsEsmCodeAnalyzer {
|
pub struct CliCjsCodeAnalyzer {
|
||||||
cache: NodeAnalysisCache,
|
cache: NodeAnalysisCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliCjsEsmCodeAnalyzer {
|
impl CliCjsCodeAnalyzer {
|
||||||
pub fn new(cache: NodeAnalysisCache) -> Self {
|
pub fn new(cache: NodeAnalysisCache) -> Self {
|
||||||
Self { cache }
|
Self { cache }
|
||||||
}
|
}
|
||||||
|
@ -86,7 +79,7 @@ impl CliCjsEsmCodeAnalyzer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CjsEsmCodeAnalyzer for CliCjsEsmCodeAnalyzer {
|
impl CjsCodeAnalyzer for CliCjsCodeAnalyzer {
|
||||||
fn analyze_cjs(
|
fn analyze_cjs(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
|
@ -98,104 +91,4 @@ impl CjsEsmCodeAnalyzer for CliCjsEsmCodeAnalyzer {
|
||||||
reexports: analysis.reexports,
|
reexports: analysis.reexports,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn analyze_esm_top_level_decls(
|
|
||||||
&self,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
source: &str,
|
|
||||||
) -> Result<HashSet<String>, AnyError> {
|
|
||||||
// TODO(dsherret): this code is way more inefficient than it needs to be.
|
|
||||||
//
|
|
||||||
// In the future, we should disable capturing tokens & scope analysis
|
|
||||||
// and instead only use swc's APIs to go through the portions of the tree
|
|
||||||
// that we know will affect the global scope while still ensuring that
|
|
||||||
// `var` decls are taken into consideration.
|
|
||||||
let source_hash = NodeAnalysisCache::compute_source_hash(source);
|
|
||||||
if let Some(decls) = self
|
|
||||||
.cache
|
|
||||||
.get_esm_analysis(specifier.as_str(), &source_hash)
|
|
||||||
{
|
|
||||||
Ok(HashSet::from_iter(decls))
|
|
||||||
} else {
|
|
||||||
let parsed_source = deno_ast::parse_program(deno_ast::ParseParams {
|
|
||||||
specifier: specifier.to_string(),
|
|
||||||
text_info: deno_ast::SourceTextInfo::from_string(source.to_string()),
|
|
||||||
media_type: deno_ast::MediaType::from_specifier(specifier),
|
|
||||||
capture_tokens: true,
|
|
||||||
scope_analysis: true,
|
|
||||||
maybe_syntax: None,
|
|
||||||
})?;
|
|
||||||
let top_level_decls = analyze_top_level_decls(&parsed_source)?;
|
|
||||||
self.cache.set_esm_analysis(
|
|
||||||
specifier.as_str(),
|
|
||||||
&source_hash,
|
|
||||||
&top_level_decls.clone().into_iter().collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
Ok(top_level_decls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn analyze_top_level_decls(
|
|
||||||
parsed_source: &ParsedSource,
|
|
||||||
) -> Result<HashSet<String>, AnyError> {
|
|
||||||
fn visit_children(
|
|
||||||
node: Node,
|
|
||||||
top_level_context: SyntaxContext,
|
|
||||||
results: &mut HashSet<String>,
|
|
||||||
) {
|
|
||||||
if let Node::Ident(ident) = node {
|
|
||||||
if ident.ctxt() == top_level_context && is_local_declaration_ident(node) {
|
|
||||||
results.insert(ident.sym().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in node.children() {
|
|
||||||
visit_children(child, top_level_context, results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let top_level_context = parsed_source.top_level_context();
|
|
||||||
|
|
||||||
parsed_source.with_view(|program| {
|
|
||||||
let mut results = HashSet::new();
|
|
||||||
visit_children(program.into(), top_level_context, &mut results);
|
|
||||||
Ok(results)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_local_declaration_ident(node: Node) -> bool {
|
|
||||||
if let Some(parent) = node.parent() {
|
|
||||||
match parent {
|
|
||||||
Node::BindingIdent(decl) => decl.id.range().contains(&node.range()),
|
|
||||||
Node::ClassDecl(decl) => decl.ident.range().contains(&node.range()),
|
|
||||||
Node::ClassExpr(decl) => decl
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.map(|i| i.range().contains(&node.range()))
|
|
||||||
.unwrap_or(false),
|
|
||||||
Node::TsInterfaceDecl(decl) => decl.id.range().contains(&node.range()),
|
|
||||||
Node::FnDecl(decl) => decl.ident.range().contains(&node.range()),
|
|
||||||
Node::FnExpr(decl) => decl
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.map(|i| i.range().contains(&node.range()))
|
|
||||||
.unwrap_or(false),
|
|
||||||
Node::TsModuleDecl(decl) => decl.id.range().contains(&node.range()),
|
|
||||||
Node::TsNamespaceDecl(decl) => decl.id.range().contains(&node.range()),
|
|
||||||
Node::VarDeclarator(decl) => decl.name.range().contains(&node.range()),
|
|
||||||
Node::ImportNamedSpecifier(decl) => {
|
|
||||||
decl.local.range().contains(&node.range())
|
|
||||||
}
|
|
||||||
Node::ImportDefaultSpecifier(decl) => {
|
|
||||||
decl.local.range().contains(&node.range())
|
|
||||||
}
|
|
||||||
Node::ImportStarAsSpecifier(decl) => decl.range().contains(&node.range()),
|
|
||||||
Node::KeyValuePatProp(decl) => decl.key.range().contains(&node.range()),
|
|
||||||
Node::AssignPatProp(decl) => decl.key.range().contains(&node.range()),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::file_fetcher::get_source_from_data_url;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
use crate::module_loader::CjsResolutionStore;
|
use crate::module_loader::CjsResolutionStore;
|
||||||
use crate::module_loader::NpmModuleLoader;
|
use crate::module_loader::NpmModuleLoader;
|
||||||
use crate::node::CliCjsEsmCodeAnalyzer;
|
use crate::node::CliCjsCodeAnalyzer;
|
||||||
use crate::npm::create_npm_fs_resolver;
|
use crate::npm::create_npm_fs_resolver;
|
||||||
use crate::npm::CliNpmRegistryApi;
|
use crate::npm::CliNpmRegistryApi;
|
||||||
use crate::npm::CliNpmResolver;
|
use crate::npm::CliNpmResolver;
|
||||||
|
@ -366,7 +366,7 @@ pub async fn run(
|
||||||
let cjs_resolutions = Arc::new(CjsResolutionStore::default());
|
let cjs_resolutions = Arc::new(CjsResolutionStore::default());
|
||||||
let cache_db = Caches::new(deno_dir_provider.clone());
|
let cache_db = Caches::new(deno_dir_provider.clone());
|
||||||
let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db());
|
let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db());
|
||||||
let cjs_esm_code_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache);
|
let cjs_esm_code_analyzer = CliCjsCodeAnalyzer::new(node_analysis_cache);
|
||||||
let node_code_translator = Arc::new(NodeCodeTranslator::new(
|
let node_code_translator = Arc::new(NodeCodeTranslator::new(
|
||||||
cjs_esm_code_analyzer,
|
cjs_esm_code_analyzer,
|
||||||
fs.clone(),
|
fs.clone(),
|
||||||
|
|
25
cli/tests/node_compat/polyfill_globals.js
Normal file
25
cli/tests/node_compat/polyfill_globals.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import process from "node:process";
|
||||||
|
import { Buffer } from "node:buffer";
|
||||||
|
import {
|
||||||
|
clearImmediate,
|
||||||
|
clearInterval,
|
||||||
|
clearTimeout,
|
||||||
|
setImmediate,
|
||||||
|
setInterval,
|
||||||
|
setTimeout,
|
||||||
|
} from "node:timers";
|
||||||
|
import { performance } from "node:perf_hooks";
|
||||||
|
import console from "node:console";
|
||||||
|
globalThis.Buffer = Buffer;
|
||||||
|
globalThis.clearImmediate = clearImmediate;
|
||||||
|
globalThis.clearInterval = clearInterval;
|
||||||
|
globalThis.clearTimeout = clearTimeout;
|
||||||
|
globalThis.console = console;
|
||||||
|
globalThis.global = globalThis;
|
||||||
|
globalThis.performance = performance;
|
||||||
|
globalThis.process = process;
|
||||||
|
globalThis.setImmediate = setImmediate;
|
||||||
|
globalThis.setInterval = setInterval;
|
||||||
|
globalThis.setTimeout = setTimeout;
|
||||||
|
delete globalThis.window;
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import "./polyfill_globals.js";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
const file = Deno.args[0];
|
const file = Deno.args[0];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
|
17
cli/tests/testdata/npm/compare_globals/main.out
vendored
17
cli/tests/testdata/npm/compare_globals/main.out
vendored
|
@ -4,7 +4,20 @@ Download http://localhost:4545/npm/registry/@denotest/globals/1.0.0.tgz
|
||||||
Download http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz
|
Download http://localhost:4545/npm/registry/@types/node/node-18.8.2.tgz
|
||||||
Check file:///[WILDCARD]/npm/compare_globals/main.ts
|
Check file:///[WILDCARD]/npm/compare_globals/main.ts
|
||||||
true
|
true
|
||||||
|
true
|
||||||
[]
|
[]
|
||||||
5
|
false
|
||||||
undefined
|
function
|
||||||
|
function
|
||||||
|
function
|
||||||
undefined
|
undefined
|
||||||
|
false
|
||||||
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
false
|
||||||
|
bar
|
||||||
|
bar
|
||||||
|
|
39
cli/tests/testdata/npm/compare_globals/main.ts
vendored
39
cli/tests/testdata/npm/compare_globals/main.ts
vendored
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import * as globals from "npm:@denotest/globals";
|
import * as globals from "npm:@denotest/globals";
|
||||||
console.log(globals.global === globals.globalThis);
|
console.log(globals.global === globals.globalThis);
|
||||||
|
// @ts-expect-error even though these are the same object, they have different types
|
||||||
|
console.log(globals.globalThis === globalThis);
|
||||||
console.log(globals.process.execArgv);
|
console.log(globals.process.execArgv);
|
||||||
|
|
||||||
type AssertTrue<T extends true> = never;
|
type AssertTrue<T extends true> = never;
|
||||||
|
@ -13,15 +15,36 @@ type _TestHasNodeJsGlobal = NodeJS.Architecture;
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
controller.abort("reason"); // in the NodeJS declaration it doesn't have a reason
|
controller.abort("reason"); // in the NodeJS declaration it doesn't have a reason
|
||||||
|
|
||||||
|
// Some globals are not the same between Node and Deno.
|
||||||
|
// @ts-expect-error incompatible types between Node and Deno
|
||||||
|
console.log(globalThis.setTimeout === globals.getSetTimeout());
|
||||||
|
|
||||||
// Super edge case where some Node code deletes a global where the
|
// Super edge case where some Node code deletes a global where the
|
||||||
// Node code has its own global and the Deno code has the same global,
|
// Node code has its own global and the Deno code has the same global,
|
||||||
// but it's different. Basically if some Node code deletes
|
// but it's different. Basically if some Node code deletes
|
||||||
// one of these globals then we don't want it to suddenly inherit
|
// one of these globals then we don't want it to suddenly inherit
|
||||||
// the Deno global.
|
// the Deno global (or touch the Deno global at all).
|
||||||
globals.withNodeGlobalThis((nodeGlobalThis: any) => {
|
console.log(typeof globalThis.setTimeout);
|
||||||
(globalThis as any).setTimeout = 5;
|
console.log(typeof globals.getSetTimeout());
|
||||||
console.log(setTimeout);
|
globals.deleteSetTimeout();
|
||||||
delete nodeGlobalThis["setTimeout"];
|
console.log(typeof globalThis.setTimeout);
|
||||||
console.log(nodeGlobalThis["setTimeout"]); // should be undefined
|
console.log(typeof globals.getSetTimeout());
|
||||||
console.log(globalThis["setTimeout"]); // should be undefined
|
|
||||||
});
|
// In Deno, the process global is not defined, but in Node it is.
|
||||||
|
console.log("process" in globalThis);
|
||||||
|
console.log(
|
||||||
|
Object.getOwnPropertyDescriptor(globalThis, "process") !== undefined,
|
||||||
|
);
|
||||||
|
globals.checkProcessGlobal();
|
||||||
|
|
||||||
|
// In Deno, the window global is defined, but in Node it is not.
|
||||||
|
console.log("window" in globalThis);
|
||||||
|
console.log(
|
||||||
|
Object.getOwnPropertyDescriptor(globalThis, "window") !== undefined,
|
||||||
|
);
|
||||||
|
globals.checkWindowGlobal();
|
||||||
|
|
||||||
|
// "Non-managed" globals are shared between Node and Deno.
|
||||||
|
(globalThis as any).foo = "bar";
|
||||||
|
console.log((globalThis as any).foo);
|
||||||
|
console.log(globals.getFoo());
|
||||||
|
|
|
@ -12,4 +12,10 @@ type _TestHasProcessGlobal = AssertTrue<
|
||||||
typeof globalThis extends { process: any } ? true : false
|
typeof globalThis extends { process: any } ? true : false
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export function withNodeGlobalThis(action: (global: typeof globalThis) => void): void;
|
export function deleteSetTimeout(): void;
|
||||||
|
export function getSetTimeout(): typeof setTimeout;
|
||||||
|
|
||||||
|
export function checkProcessGlobal(): void;
|
||||||
|
export function checkWindowGlobal(): void;
|
||||||
|
|
||||||
|
export function getFoo(): string;
|
|
@ -2,6 +2,24 @@ exports.globalThis = globalThis;
|
||||||
exports.global = global;
|
exports.global = global;
|
||||||
exports.process = process;
|
exports.process = process;
|
||||||
|
|
||||||
exports.withNodeGlobalThis = function (action) {
|
exports.deleteSetTimeout = function () {
|
||||||
action(globalThis);
|
delete globalThis.setTimeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.getSetTimeout = function () {
|
||||||
|
return globalThis.setTimeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkProcessGlobal = function () {
|
||||||
|
console.log("process" in globalThis);
|
||||||
|
console.log(Object.getOwnPropertyDescriptor(globalThis, "process") !== undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.checkWindowGlobal = function () {
|
||||||
|
console.log("window" in globalThis);
|
||||||
|
console.log(Object.getOwnPropertyDescriptor(globalThis, "window") !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getFoo = function () {
|
||||||
|
return globalThis.foo;
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
[WILDCARD]package.json file found at '[WILDCARD]with_package_json[WILDCARD]npm_binary[WILDCARD]package.json'
|
[WILDCARD]package.json file found at '[WILDCARD]with_package_json[WILDCARD]npm_binary[WILDCARD]package.json'
|
||||||
[WILDCARD]
|
[WILDCARD]
|
||||||
this
|
this
|
||||||
|
[WILDCARD]
|
||||||
is
|
is
|
||||||
|
[WILDCARD]
|
||||||
a
|
a
|
||||||
|
[WILDCARD]
|
||||||
test
|
test
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -19,21 +18,6 @@ use crate::NodeResolutionMode;
|
||||||
use crate::NpmResolverRc;
|
use crate::NpmResolverRc;
|
||||||
use crate::PackageJson;
|
use crate::PackageJson;
|
||||||
use crate::PathClean;
|
use crate::PathClean;
|
||||||
use crate::NODE_GLOBAL_THIS_NAME;
|
|
||||||
|
|
||||||
static NODE_GLOBALS: &[&str] = &[
|
|
||||||
"Buffer",
|
|
||||||
"clearImmediate",
|
|
||||||
"clearInterval",
|
|
||||||
"clearTimeout",
|
|
||||||
"console",
|
|
||||||
"global",
|
|
||||||
"process",
|
|
||||||
"setImmediate",
|
|
||||||
"setInterval",
|
|
||||||
"setTimeout",
|
|
||||||
"performance",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CjsAnalysis {
|
pub struct CjsAnalysis {
|
||||||
|
@ -42,7 +26,7 @@ pub struct CjsAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Code analyzer for CJS and ESM files.
|
/// Code analyzer for CJS and ESM files.
|
||||||
pub trait CjsEsmCodeAnalyzer {
|
pub trait CjsCodeAnalyzer {
|
||||||
/// Analyzes CommonJs code for exports and reexports, which is
|
/// Analyzes CommonJs code for exports and reexports, which is
|
||||||
/// then used to determine the wrapper ESM module exports.
|
/// then used to determine the wrapper ESM module exports.
|
||||||
fn analyze_cjs(
|
fn analyze_cjs(
|
||||||
|
@ -50,58 +34,30 @@ pub trait CjsEsmCodeAnalyzer {
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Result<CjsAnalysis, AnyError>;
|
) -> Result<CjsAnalysis, AnyError>;
|
||||||
|
|
||||||
/// Analyzes ESM code for top level declarations. This is used
|
|
||||||
/// to help inform injecting node specific globals into Node ESM
|
|
||||||
/// code. For example, if a top level `setTimeout` function exists
|
|
||||||
/// then we don't want to inject a `setTimeout` declaration.
|
|
||||||
///
|
|
||||||
/// Note: This will go away in the future once we do this all in v8.
|
|
||||||
fn analyze_esm_top_level_decls(
|
|
||||||
&self,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
source: &str,
|
|
||||||
) -> Result<HashSet<String>, AnyError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NodeCodeTranslator<TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer> {
|
pub struct NodeCodeTranslator<TCjsCodeAnalyzer: CjsCodeAnalyzer> {
|
||||||
cjs_esm_code_analyzer: TCjsEsmCodeAnalyzer,
|
cjs_code_analyzer: TCjsCodeAnalyzer,
|
||||||
fs: deno_fs::FileSystemRc,
|
fs: deno_fs::FileSystemRc,
|
||||||
node_resolver: NodeResolverRc,
|
node_resolver: NodeResolverRc,
|
||||||
npm_resolver: NpmResolverRc,
|
npm_resolver: NpmResolverRc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer>
|
impl<TCjsCodeAnalyzer: CjsCodeAnalyzer> NodeCodeTranslator<TCjsCodeAnalyzer> {
|
||||||
NodeCodeTranslator<TCjsEsmCodeAnalyzer>
|
|
||||||
{
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
cjs_esm_code_analyzer: TCjsEsmCodeAnalyzer,
|
cjs_code_analyzer: TCjsCodeAnalyzer,
|
||||||
fs: deno_fs::FileSystemRc,
|
fs: deno_fs::FileSystemRc,
|
||||||
node_resolver: NodeResolverRc,
|
node_resolver: NodeResolverRc,
|
||||||
npm_resolver: NpmResolverRc,
|
npm_resolver: NpmResolverRc,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cjs_esm_code_analyzer,
|
cjs_code_analyzer,
|
||||||
fs,
|
fs,
|
||||||
node_resolver,
|
node_resolver,
|
||||||
npm_resolver,
|
npm_resolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the code to be used when executing Node specific ESM code.
|
|
||||||
///
|
|
||||||
/// Note: This will go away in the future once we do this all in v8.
|
|
||||||
pub fn esm_code_with_node_globals(
|
|
||||||
&self,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
source: &str,
|
|
||||||
) -> Result<String, AnyError> {
|
|
||||||
let top_level_decls = self
|
|
||||||
.cjs_esm_code_analyzer
|
|
||||||
.analyze_esm_top_level_decls(specifier, source)?;
|
|
||||||
Ok(esm_code_from_top_level_decls(source, &top_level_decls))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Translates given CJS module into ESM. This function will perform static
|
/// Translates given CJS module into ESM. This function will perform static
|
||||||
/// analysis on the file to find defined exports and reexports.
|
/// analysis on the file to find defined exports and reexports.
|
||||||
///
|
///
|
||||||
|
@ -117,7 +73,7 @@ impl<TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer>
|
||||||
let mut temp_var_count = 0;
|
let mut temp_var_count = 0;
|
||||||
let mut handled_reexports: HashSet<String> = HashSet::default();
|
let mut handled_reexports: HashSet<String> = HashSet::default();
|
||||||
|
|
||||||
let analysis = self.cjs_esm_code_analyzer.analyze_cjs(specifier, source)?;
|
let analysis = self.cjs_code_analyzer.analyze_cjs(specifier, source)?;
|
||||||
|
|
||||||
let mut source = vec![
|
let mut source = vec![
|
||||||
r#"import {createRequire as __internalCreateRequire} from "node:module";
|
r#"import {createRequire as __internalCreateRequire} from "node:module";
|
||||||
|
@ -169,7 +125,7 @@ impl<TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer>
|
||||||
})?;
|
})?;
|
||||||
{
|
{
|
||||||
let analysis = self
|
let analysis = self
|
||||||
.cjs_esm_code_analyzer
|
.cjs_code_analyzer
|
||||||
.analyze_cjs(&reexport_specifier, &reexport_file_text)?;
|
.analyze_cjs(&reexport_specifier, &reexport_file_text)?;
|
||||||
|
|
||||||
for reexport in analysis.reexports {
|
for reexport in analysis.reexports {
|
||||||
|
@ -328,42 +284,6 @@ impl<TCjsEsmCodeAnalyzer: CjsEsmCodeAnalyzer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn esm_code_from_top_level_decls(
|
|
||||||
file_text: &str,
|
|
||||||
top_level_decls: &HashSet<String>,
|
|
||||||
) -> String {
|
|
||||||
let mut globals = Vec::with_capacity(NODE_GLOBALS.len());
|
|
||||||
let has_global_this = top_level_decls.contains("globalThis");
|
|
||||||
for global in NODE_GLOBALS.iter() {
|
|
||||||
if !top_level_decls.contains(&global.to_string()) {
|
|
||||||
globals.push(*global);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = String::new();
|
|
||||||
let global_this_expr = NODE_GLOBAL_THIS_NAME;
|
|
||||||
let global_this_expr = if has_global_this {
|
|
||||||
global_this_expr
|
|
||||||
} else {
|
|
||||||
write!(result, "var globalThis = {global_this_expr};").unwrap();
|
|
||||||
"globalThis"
|
|
||||||
};
|
|
||||||
for global in globals {
|
|
||||||
write!(result, "var {global} = {global_this_expr}.{global};").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip the shebang
|
|
||||||
let file_text = if file_text.starts_with("#!/") {
|
|
||||||
let start_index = file_text.find('\n').unwrap_or(file_text.len());
|
|
||||||
&file_text[start_index..]
|
|
||||||
} else {
|
|
||||||
file_text
|
|
||||||
};
|
|
||||||
result.push_str(file_text);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
|
static RESERVED_WORDS: Lazy<HashSet<&str>> = Lazy::new(|| {
|
||||||
HashSet::from([
|
HashSet::from([
|
||||||
"abstract",
|
"abstract",
|
||||||
|
@ -526,43 +446,6 @@ fn escape_for_double_quote_string(text: &str) -> String {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_esm_code_with_node_globals() {
|
|
||||||
let r = esm_code_from_top_level_decls(
|
|
||||||
"export const x = 1;",
|
|
||||||
&HashSet::from(["x".to_string()]),
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
r.contains(&format!("var globalThis = {};", NODE_GLOBAL_THIS_NAME,))
|
|
||||||
);
|
|
||||||
assert!(r.contains("var process = globalThis.process;"));
|
|
||||||
assert!(r.contains("export const x = 1;"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_esm_code_with_node_globals_with_shebang() {
|
|
||||||
let r = esm_code_from_top_level_decls(
|
|
||||||
"#!/usr/bin/env node\nexport const x = 1;",
|
|
||||||
&HashSet::from(["x".to_string()]),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
r,
|
|
||||||
format!(
|
|
||||||
concat!(
|
|
||||||
"var globalThis = {}",
|
|
||||||
";var Buffer = globalThis.Buffer;",
|
|
||||||
"var clearImmediate = globalThis.clearImmediate;var clearInterval = globalThis.clearInterval;",
|
|
||||||
"var clearTimeout = globalThis.clearTimeout;var console = globalThis.console;",
|
|
||||||
"var global = globalThis.global;var process = globalThis.process;",
|
|
||||||
"var setImmediate = globalThis.setImmediate;var setInterval = globalThis.setInterval;",
|
|
||||||
"var setTimeout = globalThis.setTimeout;var performance = globalThis.performance;\n",
|
|
||||||
"export const x = 1;"
|
|
||||||
),
|
|
||||||
NODE_GLOBAL_THIS_NAME,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_export() {
|
fn test_add_export() {
|
||||||
let mut temp_var_count = 0;
|
let mut temp_var_count = 0;
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// we use a changing variable name to make it harder to depend on this
|
|
||||||
let crate_version = env!("CARGO_PKG_VERSION");
|
|
||||||
println!(
|
|
||||||
"cargo:rustc-env=NODE_GLOBAL_THIS_NAME=__DENO_NODE_GLOBAL_THIS_{}__",
|
|
||||||
crate_version.replace('.', "_")
|
|
||||||
);
|
|
||||||
}
|
|
483
ext/node/global.rs
Normal file
483
ext/node/global.rs
Normal file
|
@ -0,0 +1,483 @@
|
||||||
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use deno_core::v8;
|
||||||
|
use deno_core::v8::GetPropertyNamesArgs;
|
||||||
|
use deno_core::v8::MapFnTo;
|
||||||
|
|
||||||
|
use crate::NodeResolver;
|
||||||
|
|
||||||
|
// NOTE(bartlomieju): somehow calling `.map_fn_to()` multiple times on a function
|
||||||
|
// returns two different pointers. That shouldn't be the case as `.map_fn_to()`
|
||||||
|
// creates a thin wrapper that is a pure function. @piscisaureus suggests it
|
||||||
|
// might be a bug in Rust compiler; so for now we just create and store
|
||||||
|
// these mapped functions per-thread. We should revisit it in the future and
|
||||||
|
// ideally remove altogether.
|
||||||
|
thread_local! {
|
||||||
|
pub static GETTER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = getter.map_fn_to();
|
||||||
|
pub static SETTER_MAP_FN: v8::GenericNamedPropertySetterCallback<'static> = setter.map_fn_to();
|
||||||
|
pub static QUERY_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = query.map_fn_to();
|
||||||
|
pub static DELETER_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = deleter.map_fn_to();
|
||||||
|
pub static ENUMERATOR_MAP_FN: v8::GenericNamedPropertyEnumeratorCallback<'static> = enumerator.map_fn_to();
|
||||||
|
pub static DEFINER_MAP_FN: v8::GenericNamedPropertyDefinerCallback<'static> = definer.map_fn_to();
|
||||||
|
pub static DESCRIPTOR_MAP_FN: v8::GenericNamedPropertyGetterCallback<'static> = descriptor.map_fn_to();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert an ASCII string to a UTF-16 byte encoding of the string.
|
||||||
|
const fn str_to_utf16<const N: usize>(s: &str) -> [u16; N] {
|
||||||
|
let mut out = [0_u16; N];
|
||||||
|
let mut i = 0;
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
assert!(N == bytes.len());
|
||||||
|
while i < bytes.len() {
|
||||||
|
assert!(bytes[i] < 128, "only works for ASCII strings");
|
||||||
|
out[i] = bytes[i] as u16;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ext/node changes the global object to be a proxy object that intercepts all
|
||||||
|
// property accesses for globals that are different between Node and Deno and
|
||||||
|
// dynamically returns a different value depending on if the accessing code is
|
||||||
|
// in node_modules/ or not.
|
||||||
|
//
|
||||||
|
// To make this performant, a v8 named property handler is used, that only
|
||||||
|
// intercepts property accesses for properties that are not already present on
|
||||||
|
// the global object (it is non-masking). This means that in the common case,
|
||||||
|
// when a user accesses a global that is the same between Node and Deno (like
|
||||||
|
// Uint8Array or fetch), the proxy overhead is avoided.
|
||||||
|
//
|
||||||
|
// The Deno and Node specific globals are stored in objects in the internal
|
||||||
|
// fields of the proxy object. The first internal field is the object storing
|
||||||
|
// the Deno specific globals, the second internal field is the object storing
|
||||||
|
// the Node specific globals.
|
||||||
|
//
|
||||||
|
// These are the globals that are handled:
|
||||||
|
// - Buffer (node only)
|
||||||
|
// - clearImmediate (node only)
|
||||||
|
// - clearInterval (both, but different implementation)
|
||||||
|
// - clearTimeout (both, but different implementation)
|
||||||
|
// - console (both, but different implementation)
|
||||||
|
// - global (node only)
|
||||||
|
// - performance (both, but different implementation)
|
||||||
|
// - process (node only)
|
||||||
|
// - setImmediate (node only)
|
||||||
|
// - setInterval (both, but different implementation)
|
||||||
|
// - setTimeout (both, but different implementation)
|
||||||
|
// - window (deno only)
|
||||||
|
|
||||||
|
// UTF-16 encodings of the managed globals. THIS LIST MUST BE SORTED.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const MANAGED_GLOBALS: [&[u16]; 12] = [
|
||||||
|
&str_to_utf16::<6>("Buffer"),
|
||||||
|
&str_to_utf16::<14>("clearImmediate"),
|
||||||
|
&str_to_utf16::<13>("clearInterval"),
|
||||||
|
&str_to_utf16::<12>("clearTimeout"),
|
||||||
|
&str_to_utf16::<7>("console"),
|
||||||
|
&str_to_utf16::<6>("global"),
|
||||||
|
&str_to_utf16::<11>("performance"),
|
||||||
|
&str_to_utf16::<7>("process"),
|
||||||
|
&str_to_utf16::<12>("setImmediate"),
|
||||||
|
&str_to_utf16::<11>("setInterval"),
|
||||||
|
&str_to_utf16::<10>("setTimeout"),
|
||||||
|
&str_to_utf16::<6>("window"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const SHORTEST_MANAGED_GLOBAL: usize = 6;
|
||||||
|
const LONGEST_MANAGED_GLOBAL: usize = 14;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum Mode {
|
||||||
|
Deno,
|
||||||
|
Node,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global_template_middleware<'s>(
|
||||||
|
_scope: &mut v8::HandleScope<'s, ()>,
|
||||||
|
template: v8::Local<'s, v8::ObjectTemplate>,
|
||||||
|
) -> v8::Local<'s, v8::ObjectTemplate> {
|
||||||
|
// The internal field layout is as follows:
|
||||||
|
// 0: Reflect.get
|
||||||
|
// 1: Reflect.set
|
||||||
|
// 2: An object containing the Deno specific globals
|
||||||
|
// 3: An object containing the Node specific globals
|
||||||
|
assert_eq!(template.internal_field_count(), 0);
|
||||||
|
template.set_internal_field_count(4);
|
||||||
|
|
||||||
|
let mut config = v8::NamedPropertyHandlerConfiguration::new().flags(
|
||||||
|
v8::PropertyHandlerFlags::NON_MASKING
|
||||||
|
| v8::PropertyHandlerFlags::HAS_NO_SIDE_EFFECT,
|
||||||
|
);
|
||||||
|
|
||||||
|
config = GETTER_MAP_FN.with(|getter| config.getter_raw(*getter));
|
||||||
|
config = SETTER_MAP_FN.with(|setter| config.setter_raw(*setter));
|
||||||
|
config = QUERY_MAP_FN.with(|query| config.query_raw(*query));
|
||||||
|
config = DELETER_MAP_FN.with(|deleter| config.deleter_raw(*deleter));
|
||||||
|
config =
|
||||||
|
ENUMERATOR_MAP_FN.with(|enumerator| config.enumerator_raw(*enumerator));
|
||||||
|
config = DEFINER_MAP_FN.with(|definer| config.definer_raw(*definer));
|
||||||
|
config =
|
||||||
|
DESCRIPTOR_MAP_FN.with(|descriptor| config.descriptor_raw(*descriptor));
|
||||||
|
|
||||||
|
template.set_named_property_handler(config);
|
||||||
|
|
||||||
|
template
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global_object_middleware<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
global: v8::Local<'s, v8::Object>,
|
||||||
|
) {
|
||||||
|
// ensure the global object is not Object.prototype
|
||||||
|
let object_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"Object").unwrap();
|
||||||
|
let object = global
|
||||||
|
.get(scope, object_key.into())
|
||||||
|
.unwrap()
|
||||||
|
.to_object(scope)
|
||||||
|
.unwrap();
|
||||||
|
let prototype_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"prototype").unwrap();
|
||||||
|
let object_prototype = object
|
||||||
|
.get(scope, prototype_key.into())
|
||||||
|
.unwrap()
|
||||||
|
.to_object(scope)
|
||||||
|
.unwrap();
|
||||||
|
assert_ne!(global, object_prototype);
|
||||||
|
|
||||||
|
// get the Reflect.get and Reflect.set functions
|
||||||
|
let reflect_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"Reflect").unwrap();
|
||||||
|
let reflect = global
|
||||||
|
.get(scope, reflect_key.into())
|
||||||
|
.unwrap()
|
||||||
|
.to_object(scope)
|
||||||
|
.unwrap();
|
||||||
|
let get_key = v8::String::new_external_onebyte_static(scope, b"get").unwrap();
|
||||||
|
let reflect_get = reflect.get(scope, get_key.into()).unwrap();
|
||||||
|
assert!(reflect_get.is_function());
|
||||||
|
let set_key = v8::String::new_external_onebyte_static(scope, b"set").unwrap();
|
||||||
|
let reflect_set = reflect.get(scope, set_key.into()).unwrap();
|
||||||
|
assert!(reflect_set.is_function());
|
||||||
|
|
||||||
|
// globalThis.__bootstrap.ext_node_denoGlobals and
|
||||||
|
// globalThis.__bootstrap.ext_node_nodeGlobals are the objects that contain
|
||||||
|
// the Deno and Node specific globals respectively. If they do not yet exist
|
||||||
|
// on the global object, create them as null prototype objects.
|
||||||
|
let bootstrap_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"__bootstrap").unwrap();
|
||||||
|
let bootstrap = match global.get(scope, bootstrap_key.into()) {
|
||||||
|
Some(value) if value.is_object() => value.to_object(scope).unwrap(),
|
||||||
|
Some(value) if value.is_undefined() => {
|
||||||
|
let null = v8::null(scope);
|
||||||
|
let obj =
|
||||||
|
v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[]);
|
||||||
|
global.set(scope, bootstrap_key.into(), obj.into());
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
_ => panic!("__bootstrap should not be tampered with"),
|
||||||
|
};
|
||||||
|
let deno_globals_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"ext_node_denoGlobals")
|
||||||
|
.unwrap();
|
||||||
|
let deno_globals = match bootstrap.get(scope, deno_globals_key.into()) {
|
||||||
|
Some(value) if value.is_object() => value,
|
||||||
|
Some(value) if value.is_undefined() => {
|
||||||
|
let null = v8::null(scope);
|
||||||
|
let obj =
|
||||||
|
v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[])
|
||||||
|
.into();
|
||||||
|
bootstrap.set(scope, deno_globals_key.into(), obj);
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
_ => panic!("__bootstrap.ext_node_denoGlobals should not be tampered with"),
|
||||||
|
};
|
||||||
|
let node_globals_key =
|
||||||
|
v8::String::new_external_onebyte_static(scope, b"ext_node_nodeGlobals")
|
||||||
|
.unwrap();
|
||||||
|
let node_globals = match bootstrap.get(scope, node_globals_key.into()) {
|
||||||
|
Some(value) if value.is_object() => value,
|
||||||
|
Some(value) if value.is_undefined() => {
|
||||||
|
let null = v8::null(scope);
|
||||||
|
let obj =
|
||||||
|
v8::Object::with_prototype_and_properties(scope, null.into(), &[], &[])
|
||||||
|
.into();
|
||||||
|
bootstrap.set(scope, node_globals_key.into(), obj);
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
_ => panic!("__bootstrap.ext_node_nodeGlobals should not be tampered with"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// set the internal fields
|
||||||
|
assert!(global.set_internal_field(0, reflect_get));
|
||||||
|
assert!(global.set_internal_field(1, reflect_set));
|
||||||
|
assert!(global.set_internal_field(2, deno_globals));
|
||||||
|
assert!(global.set_internal_field(3, node_globals));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_managed_key(
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
key: v8::Local<v8::Name>,
|
||||||
|
) -> bool {
|
||||||
|
let Ok(str): Result<v8::Local<v8::String>, _> = key.try_into() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let len = str.length();
|
||||||
|
|
||||||
|
#[allow(clippy::manual_range_contains)]
|
||||||
|
if len < SHORTEST_MANAGED_GLOBAL || len > LONGEST_MANAGED_GLOBAL {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let buf = &mut [0u16; LONGEST_MANAGED_GLOBAL];
|
||||||
|
let written = str.write(
|
||||||
|
scope,
|
||||||
|
buf.as_mut_slice(),
|
||||||
|
0,
|
||||||
|
v8::WriteOptions::NO_NULL_TERMINATION,
|
||||||
|
);
|
||||||
|
assert_eq!(written, len);
|
||||||
|
MANAGED_GLOBALS.binary_search(&&buf[..len]).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_mode(scope: &mut v8::HandleScope) -> Mode {
|
||||||
|
let Some(v8_string) = v8::StackTrace::current_script_name_or_source_url(scope) else {
|
||||||
|
return Mode::Deno;
|
||||||
|
};
|
||||||
|
let string = v8_string.to_rust_string_lossy(scope);
|
||||||
|
// TODO: don't require parsing the specifier
|
||||||
|
let Ok(specifier) = deno_core::ModuleSpecifier::parse(&string) else {
|
||||||
|
return Mode::Deno;
|
||||||
|
};
|
||||||
|
let op_state = deno_core::JsRuntime::op_state_from(scope);
|
||||||
|
let op_state = op_state.borrow();
|
||||||
|
let Some(node_resolver) = op_state.try_borrow::<Rc<NodeResolver>>() else {
|
||||||
|
return Mode::Deno;
|
||||||
|
};
|
||||||
|
if node_resolver.in_npm_package(&specifier) {
|
||||||
|
Mode::Node
|
||||||
|
} else {
|
||||||
|
Mode::Deno
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_object<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
real_global_object: v8::Local<'s, v8::Object>,
|
||||||
|
mode: Mode,
|
||||||
|
) -> v8::Local<'s, v8::Object> {
|
||||||
|
let value = match mode {
|
||||||
|
Mode::Deno => real_global_object.get_internal_field(scope, 2).unwrap(),
|
||||||
|
Mode::Node => real_global_object.get_internal_field(scope, 3).unwrap(),
|
||||||
|
};
|
||||||
|
v8::Local::<v8::Object>::try_from(value).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_global_object<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
) -> v8::Local<'s, v8::Object> {
|
||||||
|
let context = scope.get_current_context();
|
||||||
|
let global = context.global(scope);
|
||||||
|
let global = global
|
||||||
|
.get_prototype(scope)
|
||||||
|
.unwrap()
|
||||||
|
.to_object(scope)
|
||||||
|
.unwrap();
|
||||||
|
global
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getter<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let this = args.this();
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let reflect_get: v8::Local<v8::Function> = real_global_object
|
||||||
|
.get_internal_field(scope, 0)
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
let undefined = v8::undefined(scope);
|
||||||
|
let Some(value) = reflect_get.call(
|
||||||
|
scope,
|
||||||
|
undefined.into(),
|
||||||
|
&[inner.into(), key.into(), this.into()],
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
rv.set(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setter<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
value: v8::Local<'s, v8::Value>,
|
||||||
|
args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let this = args.this();
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let reflect_set: v8::Local<v8::Function> = real_global_object
|
||||||
|
.get_internal_field(scope, 1)
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
let undefined = v8::undefined(scope);
|
||||||
|
|
||||||
|
let Some(success) = reflect_set.call(
|
||||||
|
scope,
|
||||||
|
undefined.into(),
|
||||||
|
&[inner.into(), key.into(), value, this.into()],
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
rv.set(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
_args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
|
||||||
|
let Some(true) = inner.has_own_property(scope, key) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(attributes) = inner.get_property_attributes(scope, key.into()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
rv.set_uint32(attributes.as_u32());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleter<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
|
||||||
|
let Some(success) = inner.delete(scope, key.into()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.should_throw_on_error() && !success {
|
||||||
|
let message = v8::String::new(scope, "Cannot delete property").unwrap();
|
||||||
|
let exception = v8::Exception::type_error(scope, message);
|
||||||
|
scope.throw_exception(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.set_bool(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enumerator<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
_args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
|
||||||
|
let Some(array) = inner.get_property_names(scope, GetPropertyNamesArgs::default()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
rv.set(array.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn definer<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
descriptor: &v8::PropertyDescriptor,
|
||||||
|
args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
let Some(success) = inner.define_property(scope, key, descriptor) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.should_throw_on_error() && !success {
|
||||||
|
let message = v8::String::new(scope, "Cannot define property").unwrap();
|
||||||
|
let exception = v8::Exception::type_error(scope, message);
|
||||||
|
scope.throw_exception(exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.set_bool(success);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descriptor<'s>(
|
||||||
|
scope: &mut v8::HandleScope<'s>,
|
||||||
|
key: v8::Local<'s, v8::Name>,
|
||||||
|
_args: v8::PropertyCallbackArguments<'s>,
|
||||||
|
mut rv: v8::ReturnValue,
|
||||||
|
) {
|
||||||
|
if !is_managed_key(scope, key) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let real_global_object = real_global_object(scope);
|
||||||
|
let mode = current_mode(scope);
|
||||||
|
|
||||||
|
let scope = &mut v8::TryCatch::new(scope);
|
||||||
|
|
||||||
|
let inner = inner_object(scope, real_global_object, mode);
|
||||||
|
let Some(descriptor) = inner.get_own_property_descriptor(scope, key) else {
|
||||||
|
scope.rethrow().expect("to have caught an exception");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if descriptor.is_undefined() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.set(descriptor);
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ use deno_core::serde_v8;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use deno_core::v8;
|
use deno_core::v8;
|
||||||
|
use deno_core::v8::ExternalReference;
|
||||||
use deno_core::JsRuntime;
|
use deno_core::JsRuntime;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_fs::sync::MaybeSend;
|
use deno_fs::sync::MaybeSend;
|
||||||
|
@ -25,6 +26,7 @@ use once_cell::sync::Lazy;
|
||||||
|
|
||||||
pub mod analyze;
|
pub mod analyze;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
mod global;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod package_json;
|
mod package_json;
|
||||||
mod path;
|
mod path;
|
||||||
|
@ -41,6 +43,9 @@ pub use resolution::NodeResolution;
|
||||||
pub use resolution::NodeResolutionMode;
|
pub use resolution::NodeResolutionMode;
|
||||||
pub use resolution::NodeResolver;
|
pub use resolution::NodeResolver;
|
||||||
|
|
||||||
|
use crate::global::global_object_middleware;
|
||||||
|
use crate::global::global_template_middleware;
|
||||||
|
|
||||||
pub trait NodePermissions {
|
pub trait NodePermissions {
|
||||||
fn check_net_url(
|
fn check_net_url(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -112,8 +117,6 @@ pub trait NpmResolver: std::fmt::Debug + MaybeSend + MaybeSync {
|
||||||
) -> Result<(), AnyError>;
|
) -> Result<(), AnyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const NODE_GLOBAL_THIS_NAME: &str = env!("NODE_GLOBAL_THIS_NAME");
|
|
||||||
|
|
||||||
pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {
|
pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||||
// The full list of environment variables supported by Node.js is available
|
// The full list of environment variables supported by Node.js is available
|
||||||
// at https://nodejs.org/api/cli.html#environment-variables
|
// at https://nodejs.org/api/cli.html#environment-variables
|
||||||
|
@ -510,7 +513,49 @@ deno_core::extension!(deno_node,
|
||||||
npm_resolver,
|
npm_resolver,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
global_template_middleware = global_template_middleware,
|
||||||
|
global_object_middleware = global_object_middleware,
|
||||||
|
customizer = |ext: &mut deno_core::ExtensionBuilder| {
|
||||||
|
let mut external_references = Vec::with_capacity(7);
|
||||||
|
|
||||||
|
global::GETTER_MAP_FN.with(|getter| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_getter: *getter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global::SETTER_MAP_FN.with(|setter| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_setter: *setter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global::QUERY_MAP_FN.with(|query| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_getter: *query,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global::DELETER_MAP_FN.with(|deleter| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_getter: *deleter,
|
||||||
|
},);
|
||||||
|
});
|
||||||
|
global::ENUMERATOR_MAP_FN.with(|enumerator| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
enumerator: *enumerator,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global::DEFINER_MAP_FN.with(|definer| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_definer: *definer,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
global::DESCRIPTOR_MAP_FN.with(|descriptor| {
|
||||||
|
external_references.push(ExternalReference {
|
||||||
|
named_getter: *descriptor,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ext.external_references(external_references);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn initialize_runtime(
|
pub fn initialize_runtime(
|
||||||
|
@ -524,16 +569,12 @@ pub fn initialize_runtime(
|
||||||
"undefined".to_string()
|
"undefined".to_string()
|
||||||
};
|
};
|
||||||
let source_code = format!(
|
let source_code = format!(
|
||||||
r#"(function loadBuiltinNodeModules(nodeGlobalThisName, usesLocalNodeModulesDir, argv0) {{
|
r#"(function loadBuiltinNodeModules(usesLocalNodeModulesDir, argv0) {{
|
||||||
Deno[Deno.internal].node.initialize(
|
Deno[Deno.internal].node.initialize(
|
||||||
nodeGlobalThisName,
|
|
||||||
usesLocalNodeModulesDir,
|
usesLocalNodeModulesDir,
|
||||||
argv0
|
argv0
|
||||||
);
|
);
|
||||||
// Make the nodeGlobalThisName unconfigurable here.
|
}})({uses_local_node_modules_dir}, {argv0});"#,
|
||||||
Object.defineProperty(globalThis, nodeGlobalThisName, {{ configurable: false }});
|
|
||||||
}})('{}', {}, {});"#,
|
|
||||||
NODE_GLOBAL_THIS_NAME, uses_local_node_modules_dir, argv0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
js_runtime.execute_script(located_script_name!(), source_code.into())?;
|
js_runtime.execute_script(located_script_name!(), source_code.into())?;
|
||||||
|
|
|
@ -2,71 +2,5 @@
|
||||||
|
|
||||||
// deno-lint-ignore-file
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
const primordials = globalThis.__bootstrap.primordials;
|
export const denoGlobals = globalThis.__bootstrap.ext_node_denoGlobals;
|
||||||
const {
|
export const nodeGlobals = globalThis.__bootstrap.ext_node_nodeGlobals;
|
||||||
ArrayPrototypeFilter,
|
|
||||||
Proxy,
|
|
||||||
ReflectDefineProperty,
|
|
||||||
ReflectDeleteProperty,
|
|
||||||
ReflectGet,
|
|
||||||
ReflectGetOwnPropertyDescriptor,
|
|
||||||
ReflectHas,
|
|
||||||
ReflectOwnKeys,
|
|
||||||
ReflectSet,
|
|
||||||
Set,
|
|
||||||
SetPrototypeHas,
|
|
||||||
} = primordials;
|
|
||||||
|
|
||||||
const nodeGlobals = {};
|
|
||||||
const nodeGlobalThis = new Proxy(globalThis, {
|
|
||||||
get(target, prop) {
|
|
||||||
if (ReflectHas(nodeGlobals, prop)) {
|
|
||||||
return ReflectGet(nodeGlobals, prop);
|
|
||||||
} else {
|
|
||||||
return ReflectGet(target, prop);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set(target, prop, value) {
|
|
||||||
if (ReflectHas(nodeGlobals, prop)) {
|
|
||||||
return ReflectSet(nodeGlobals, prop, value);
|
|
||||||
} else {
|
|
||||||
return ReflectSet(target, prop, value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
has(target, prop) {
|
|
||||||
return ReflectHas(nodeGlobals, prop) || ReflectHas(target, prop);
|
|
||||||
},
|
|
||||||
deleteProperty(target, prop) {
|
|
||||||
const nodeDeleted = ReflectDeleteProperty(nodeGlobals, prop);
|
|
||||||
const targetDeleted = ReflectDeleteProperty(target, prop);
|
|
||||||
return nodeDeleted || targetDeleted;
|
|
||||||
},
|
|
||||||
ownKeys(target) {
|
|
||||||
const targetKeys = ReflectOwnKeys(target);
|
|
||||||
const nodeGlobalsKeys = ReflectOwnKeys(nodeGlobals);
|
|
||||||
const nodeGlobalsKeySet = new Set(nodeGlobalsKeys);
|
|
||||||
return [
|
|
||||||
...ArrayPrototypeFilter(
|
|
||||||
targetKeys,
|
|
||||||
(k) => !SetPrototypeHas(nodeGlobalsKeySet, k),
|
|
||||||
),
|
|
||||||
...nodeGlobalsKeys,
|
|
||||||
];
|
|
||||||
},
|
|
||||||
defineProperty(target, prop, desc) {
|
|
||||||
if (ReflectHas(nodeGlobals, prop)) {
|
|
||||||
return ReflectDefineProperty(nodeGlobals, prop, desc);
|
|
||||||
} else {
|
|
||||||
return ReflectDefineProperty(target, prop, desc);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getOwnPropertyDescriptor(target, prop) {
|
|
||||||
if (ReflectHas(nodeGlobals, prop)) {
|
|
||||||
return ReflectGetOwnPropertyDescriptor(nodeGlobals, prop);
|
|
||||||
} else {
|
|
||||||
return ReflectGetOwnPropertyDescriptor(target, prop);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { nodeGlobals, nodeGlobalThis };
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ const {
|
||||||
Error,
|
Error,
|
||||||
TypeError,
|
TypeError,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
import { nodeGlobalThis } from "ext:deno_node/00_globals.js";
|
|
||||||
import _httpAgent from "ext:deno_node/_http_agent.mjs";
|
import _httpAgent from "ext:deno_node/_http_agent.mjs";
|
||||||
import _httpOutgoing from "ext:deno_node/_http_outgoing.ts";
|
import _httpOutgoing from "ext:deno_node/_http_outgoing.ts";
|
||||||
import _streamDuplex from "ext:deno_node/internal/streams/duplex.mjs";
|
import _streamDuplex from "ext:deno_node/internal/streams/duplex.mjs";
|
||||||
|
@ -342,7 +341,7 @@ function tryPackage(requestPath, exts, isMain, originalPath) {
|
||||||
err.requestPath = originalPath;
|
err.requestPath = originalPath;
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
nodeGlobalThis.process.emitWarning(
|
process.emitWarning(
|
||||||
`Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` +
|
`Invalid 'main' field in '${packageJsonPath}' of '${pkg}'. ` +
|
||||||
"Please either fix that or report it to the module author",
|
"Please either fix that or report it to the module author",
|
||||||
"DeprecationWarning",
|
"DeprecationWarning",
|
||||||
|
@ -414,7 +413,7 @@ function getExportsForCircularRequire(module) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitCircularRequireWarning(prop) {
|
function emitCircularRequireWarning(prop) {
|
||||||
nodeGlobalThis.process.emitWarning(
|
process.emitWarning(
|
||||||
`Accessing non-existent property '${String(prop)}' of module exports ` +
|
`Accessing non-existent property '${String(prop)}' of module exports ` +
|
||||||
"inside circular dependency",
|
"inside circular dependency",
|
||||||
);
|
);
|
||||||
|
@ -704,7 +703,7 @@ Module._load = function (request, parent, isMain) {
|
||||||
const module = cachedModule || new Module(filename, parent);
|
const module = cachedModule || new Module(filename, parent);
|
||||||
|
|
||||||
if (isMain) {
|
if (isMain) {
|
||||||
nodeGlobalThis.process.mainModule = module;
|
process.mainModule = module;
|
||||||
mainModule = module;
|
mainModule = module;
|
||||||
module.id = ".";
|
module.id = ".";
|
||||||
}
|
}
|
||||||
|
@ -913,9 +912,7 @@ Module.prototype.require = function (id) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Module.wrapper = [
|
Module.wrapper = [
|
||||||
// We provide the non-standard APIs in the CommonJS wrapper
|
"(function (exports, require, module, __filename, __dirname) { (function () {",
|
||||||
// to avoid exposing them in global namespace.
|
|
||||||
"(function (exports, require, module, __filename, __dirname, globalThis) { const { Buffer, clearImmediate, clearInterval, clearTimeout, console, global, process, setImmediate, setInterval, setTimeout, performance} = globalThis; var window = undefined; (function () {",
|
|
||||||
"\n}).call(this); })",
|
"\n}).call(this); })",
|
||||||
];
|
];
|
||||||
Module.wrap = function (script) {
|
Module.wrap = function (script) {
|
||||||
|
@ -950,7 +947,7 @@ function wrapSafe(
|
||||||
const wrapper = Module.wrap(content);
|
const wrapper = Module.wrap(content);
|
||||||
const [f, err] = core.evalContext(wrapper, `file://${filename}`);
|
const [f, err] = core.evalContext(wrapper, `file://${filename}`);
|
||||||
if (err) {
|
if (err) {
|
||||||
if (nodeGlobalThis.process.mainModule === cjsModuleInstance) {
|
if (process.mainModule === cjsModuleInstance) {
|
||||||
enrichCJSError(err.thrown);
|
enrichCJSError(err.thrown);
|
||||||
}
|
}
|
||||||
if (isEsmSyntaxError(err.thrown)) {
|
if (isEsmSyntaxError(err.thrown)) {
|
||||||
|
@ -988,7 +985,6 @@ Module.prototype._compile = function (content, filename) {
|
||||||
this,
|
this,
|
||||||
filename,
|
filename,
|
||||||
dirname,
|
dirname,
|
||||||
nodeGlobalThis,
|
|
||||||
);
|
);
|
||||||
if (requireDepth === 0) {
|
if (requireDepth === 0) {
|
||||||
statCache = null;
|
statCache = null;
|
||||||
|
@ -1050,7 +1046,7 @@ Module._extensions[".node"] = function (module, filename) {
|
||||||
if (filename.endsWith("fsevents.node")) {
|
if (filename.endsWith("fsevents.node")) {
|
||||||
throw new Error("Using fsevents module is currently not supported");
|
throw new Error("Using fsevents module is currently not supported");
|
||||||
}
|
}
|
||||||
module.exports = ops.op_napi_open(filename, nodeGlobalThis);
|
module.exports = ops.op_napi_open(filename, globalThis);
|
||||||
};
|
};
|
||||||
|
|
||||||
function createRequireFromPath(filename) {
|
function createRequireFromPath(filename) {
|
||||||
|
|
|
@ -4,15 +4,12 @@
|
||||||
|
|
||||||
const internals = globalThis.__bootstrap.internals;
|
const internals = globalThis.__bootstrap.internals;
|
||||||
const requireImpl = internals.requireImpl;
|
const requireImpl = internals.requireImpl;
|
||||||
const primordials = globalThis.__bootstrap.primordials;
|
import { nodeGlobals } from "ext:deno_node/00_globals.js";
|
||||||
const { ObjectDefineProperty } = primordials;
|
|
||||||
import { nodeGlobals, nodeGlobalThis } from "ext:deno_node/00_globals.js";
|
|
||||||
import "node:module";
|
import "node:module";
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
function initialize(
|
function initialize(
|
||||||
nodeGlobalThisName,
|
|
||||||
usesLocalNodeModulesDir,
|
usesLocalNodeModulesDir,
|
||||||
argv0,
|
argv0,
|
||||||
) {
|
) {
|
||||||
|
@ -29,20 +26,13 @@ function initialize(
|
||||||
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
|
nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval;
|
||||||
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
|
nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout;
|
||||||
nodeGlobals.console = nativeModuleExports["console"];
|
nodeGlobals.console = nativeModuleExports["console"];
|
||||||
nodeGlobals.global = nodeGlobalThis;
|
nodeGlobals.global = globalThis;
|
||||||
nodeGlobals.process = nativeModuleExports["process"];
|
nodeGlobals.process = nativeModuleExports["process"];
|
||||||
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
|
nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate;
|
||||||
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
|
nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval;
|
||||||
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
|
nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout;
|
||||||
nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance;
|
nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance;
|
||||||
|
|
||||||
// add a hidden global for the esm code to use in order to reliably
|
|
||||||
// get node's globalThis
|
|
||||||
ObjectDefineProperty(globalThis, nodeGlobalThisName, {
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true,
|
|
||||||
value: nodeGlobalThis,
|
|
||||||
});
|
|
||||||
// FIXME(bartlomieju): not nice to depend on `Deno` namespace here
|
// FIXME(bartlomieju): not nice to depend on `Deno` namespace here
|
||||||
// but it's the only way to get `args` and `version` and this point.
|
// but it's the only way to get `args` and `version` and this point.
|
||||||
internals.__bootstrapNodeProcess(argv0, Deno.args, Deno.version);
|
internals.__bootstrapNodeProcess(argv0, Deno.args, Deno.version);
|
||||||
|
|
|
@ -413,14 +413,16 @@ function promiseRejectMacrotaskCallback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasBootstrapped = false;
|
let hasBootstrapped = false;
|
||||||
|
// Delete the `console` object that V8 automaticaly adds onto the global wrapper
|
||||||
|
// object on context creation. We don't want this console object to shadow the
|
||||||
|
// `console` object exposed by the ext/node globalThis proxy.
|
||||||
|
delete globalThis.console;
|
||||||
// Set up global properties shared by main and worker runtime.
|
// Set up global properties shared by main and worker runtime.
|
||||||
ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope);
|
ObjectDefineProperties(globalThis, windowOrWorkerGlobalScope);
|
||||||
// FIXME(bartlomieju): temporarily add whole `Deno.core` to
|
// FIXME(bartlomieju): temporarily add whole `Deno.core` to
|
||||||
// `Deno[Deno.internal]` namespace. It should be removed and only necessary
|
// `Deno[Deno.internal]` namespace. It should be removed and only necessary
|
||||||
// methods should be left there.
|
// methods should be left there.
|
||||||
ObjectAssign(internals, {
|
ObjectAssign(internals, { core });
|
||||||
core,
|
|
||||||
});
|
|
||||||
const internalSymbol = Symbol("Deno.internal");
|
const internalSymbol = Symbol("Deno.internal");
|
||||||
const finalDenoNs = {
|
const finalDenoNs = {
|
||||||
internal: internalSymbol,
|
internal: internalSymbol,
|
||||||
|
|
Loading…
Reference in a new issue