From 8768929c679318da64011584bf61d0696f430641 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 2 Oct 2024 22:25:51 +0800 Subject: [PATCH] feat(napi/transform): add inject plugin --- Cargo.lock | 1 + napi/transform/Cargo.toml | 2 + napi/transform/index.d.ts | 2 + napi/transform/src/options.rs | 11 +++- napi/transform/src/transformer.rs | 91 +++++++++++++++++++++++++------ napi/transform/test.mjs | 19 +++++++ 6 files changed, 106 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c10526c7dc306..9b922f179ba2a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1986,6 +1986,7 @@ dependencies = [ "oxc_sourcemap", "oxc_span", "oxc_transformer", + "rustc-hash", ] [[package]] diff --git a/napi/transform/Cargo.toml b/napi/transform/Cargo.toml index f8891de634c05c..dd641e87f2643e 100644 --- a/napi/transform/Cargo.toml +++ b/napi/transform/Cargo.toml @@ -32,6 +32,8 @@ oxc_sourcemap = { workspace = true } oxc_span = { workspace = true } oxc_transformer = { workspace = true } +rustc-hash = { workspace = true } + napi = { workspace = true } napi-derive = { workspace = true } diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index 6917ffba798f77..9d7b47d988ebde 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -205,6 +205,8 @@ export interface TransformOptions { es2015?: ES2015BindingOptions /** Define Plugin */ define?: Record + /** Inject Plugin */ + inject?: Record } export interface TransformResult { diff --git a/napi/transform/src/options.rs b/napi/transform/src/options.rs index b57357e27fd11b..17d0e0de4880d4 100644 --- a/napi/transform/src/options.rs +++ b/napi/transform/src/options.rs @@ -1,11 +1,11 @@ #![allow(rustdoc::bare_urls)] -#![allow(clippy::disallowed_types)] // allow HashMap -use std::collections::HashMap; use std::path::PathBuf; use napi::Either; use napi_derive::napi; +use rustc_hash::FxHashMap; + use oxc_transformer::{ArrowFunctionsOptions, ES2015Options, JsxRuntime, RewriteExtensionsMode}; use crate::IsolatedDeclarationsOptions; @@ -42,7 +42,12 @@ pub struct TransformOptions { pub es2015: Option, /// Define Plugin - pub define: Option>, + #[napi(ts_type = "Record")] + pub define: Option>, + + /// Inject Plugin + #[napi(ts_type = "Record")] + pub inject: Option>>>, } impl From for oxc_transformer::TransformOptions { diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index 1bb73bd4bf379e..8e175a3f6a1754 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -1,9 +1,15 @@ +use napi::Either; use napi_derive::napi; +use rustc_hash::FxHashMap; + use oxc_allocator::Allocator; use oxc_codegen::CodegenReturn; -use oxc_semantic::SemanticBuilder; +use oxc_semantic::{ScopeTree, SemanticBuilder, SymbolTable}; use oxc_span::SourceType; -use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig, Transformer}; +use oxc_transformer::{ + InjectGlobalVariables, InjectGlobalVariablesConfig, InjectImport, ReplaceGlobalDefines, + ReplaceGlobalDefinesConfig, Transformer, +}; use crate::{context::TransformContext, isolated_declaration, SourceMap, TransformOptions}; @@ -107,6 +113,7 @@ fn transpile(ctx: &TransformContext<'_>, options: Option) -> C let mut options = options; let define = options.as_mut().and_then(|options| options.define.take()); + let inject = options.as_mut().and_then(|options| options.inject.take()); let options = options.map(oxc_transformer::TransformOptions::from).unwrap_or_default(); @@ -126,22 +133,72 @@ fn transpile(ctx: &TransformContext<'_>, options: Option) -> C scopes = ret.scopes; if let Some(define) = define { - let define = define.into_iter().collect::>(); - match ReplaceGlobalDefinesConfig::new(&define) { - Ok(config) => { - let _ret = ReplaceGlobalDefines::new(ctx.allocator, config).build( - symbols, - scopes, - &mut ctx.program_mut(), - ); - // symbols = ret.symbols; - // scopes = ret.scopes; - } - Err(errors) => { - ctx.add_diagnostics(errors); - } - } + (symbols, scopes) = define_plugin(ctx, define, symbols, scopes); + } + + if let Some(inject) = inject { + _ = inject_plugin(ctx, inject, symbols, scopes); } ctx.codegen().build(&ctx.program()) } + +fn define_plugin( + ctx: &TransformContext<'_>, + define: FxHashMap, + symbols: SymbolTable, + scopes: ScopeTree, +) -> (SymbolTable, ScopeTree) { + let define = define.into_iter().collect::>(); + match ReplaceGlobalDefinesConfig::new(&define) { + Ok(config) => { + let ret = ReplaceGlobalDefines::new(ctx.allocator, config).build( + symbols, + scopes, + &mut ctx.program_mut(), + ); + (ret.symbols, ret.scopes) + } + Err(errors) => { + ctx.add_diagnostics(errors); + (symbols, scopes) + } + } +} + +fn inject_plugin( + ctx: &TransformContext<'_>, + inject: FxHashMap>>, + symbols: SymbolTable, + scopes: ScopeTree, +) -> (SymbolTable, ScopeTree) { + let Ok(injects) = inject + .into_iter() + .map(|(local, value)| match value { + Either::A(source) => Ok(InjectImport::default_specifier(&source, &local)), + Either::B(v) => { + if v.len() != 2 { + return Err(()); + } + let source = v[0].to_string(); + Ok(if v[1] == "*" { + InjectImport::namespace_specifier(&source, &local) + } else { + InjectImport::named_specifier(&source, Some(&v[1]), &local) + }) + } + }) + .collect::, ()>>() + else { + return (symbols, scopes); + }; + + let config = InjectGlobalVariablesConfig::new(injects); + let ret = InjectGlobalVariables::new(ctx.allocator, config).build( + symbols, + scopes, + &mut ctx.program_mut(), + ); + + (ret.symbols, ret.scopes) +} diff --git a/napi/transform/test.mjs b/napi/transform/test.mjs index 37d443f325ccbb..50e91b7357cd77 100644 --- a/napi/transform/test.mjs +++ b/napi/transform/test.mjs @@ -83,4 +83,23 @@ test( }, ); +// Test define plugin +// TODO: should be constant folded +test(oxc.transform('test.ts', 'if (process.env.NODE_ENV === "production") { foo; }', { + define: { + 'process.env.NODE_ENV': 'false' + } +}), { + code: 'if (false === "production") {\n\tfoo;\n}\n', +}); + +// Test inject plugin +test(oxc.transform('test.ts', 'let _ = Object.assign', { + inject: { + 'Object.assign': 'foo' + } +}), { + code: 'import $inject_Object_assign from "foo";\nlet _ = $inject_Object_assign;\n', +}); + console.log('Success.');