feat(napi/transform): add inject plugin (#6250)

This commit is contained in:
Boshen 2024-10-02 15:15:51 +00:00
parent d085c2cd25
commit f98e12c13a
6 changed files with 112 additions and 20 deletions

1
Cargo.lock generated
View file

@ -1986,6 +1986,7 @@ dependencies = [
"oxc_sourcemap",
"oxc_span",
"oxc_transformer",
"rustc-hash",
]
[[package]]

View file

@ -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 }

View file

@ -205,6 +205,8 @@ export interface TransformOptions {
es2015?: ES2015BindingOptions
/** Define Plugin */
define?: Record<string, string>
/** Inject Plugin */
inject?: Record<string, string | [string, string]>
}
export interface TransformResult {

View file

@ -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<ES2015BindingOptions>,
/// Define Plugin
pub define: Option<HashMap<String, String>>,
#[napi(ts_type = "Record<string, string>")]
pub define: Option<FxHashMap<String, String>>,
/// Inject Plugin
#[napi(ts_type = "Record<string, string | [string, string]>")]
pub inject: Option<FxHashMap<String, Either<String, Vec<String>>>>,
}
impl From<TransformOptions> for oxc_transformer::TransformOptions {

View file

@ -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<TransformOptions>) -> 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<TransformOptions>) -> C
scopes = ret.scopes;
if let Some(define) = define {
let define = define.into_iter().collect::<Vec<_>>();
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<String, String>,
symbols: SymbolTable,
scopes: ScopeTree,
) -> (SymbolTable, ScopeTree) {
let define = define.into_iter().collect::<Vec<_>>();
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<String, Either<String, Vec<String>>>,
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::<Result<Vec<_>, ()>>()
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)
}

View file

@ -83,4 +83,29 @@ 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.');