mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 21:54:36 +03:00
feat(plugin): Add PluginContext
(#3568)
This commit is contained in:
parent
a5b3dd815b
commit
a96217feaa
@ -60,7 +60,11 @@ use swc_ecma_transforms_optimization::{inline_globals2, GlobalExprMap};
|
||||
use swc_ecma_visit::{Fold, VisitMutWith};
|
||||
|
||||
use self::util::BoolOrObject;
|
||||
use crate::{builder::PassBuilder, plugin::PluginConfig, SwcComments, SwcImportResolver};
|
||||
use crate::{
|
||||
builder::PassBuilder,
|
||||
plugin::{PluginConfig, PluginContext},
|
||||
SwcComments, SwcImportResolver,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -425,6 +429,15 @@ impl Options {
|
||||
NodeModulesResolver::new(TargetEnv::Node, Default::default(), true),
|
||||
);
|
||||
|
||||
let transform_filename = match base {
|
||||
FileName::Real(path) => path.as_os_str().to_str().map(String::from),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let plugin_context = PluginContext {
|
||||
filename: transform_filename,
|
||||
};
|
||||
|
||||
let pass = chain!(
|
||||
lint_to_fold(swc_ecma_lints::rules::all(LintParams {
|
||||
program: &program,
|
||||
@ -457,7 +470,7 @@ impl Options {
|
||||
),
|
||||
syntax.typescript()
|
||||
),
|
||||
crate::plugin::plugins(plugin_resolver, experimental),
|
||||
crate::plugin::plugins(plugin_resolver, experimental, plugin_context),
|
||||
custom_before_pass(&program),
|
||||
// handle jsx
|
||||
Optional::new(
|
||||
|
@ -11,13 +11,37 @@ use swc_ecma_loader::resolvers::{lru::CachingResolver, node::NodeModulesResolver
|
||||
use swc_ecma_transforms::pass::noop;
|
||||
use swc_ecma_visit::{noop_fold_type, Fold};
|
||||
|
||||
/// A tuple represents a plugin.
|
||||
/// First element is a resolvable name to the plugin, second is a JSON object
|
||||
/// that represents configuration option for those plugin.
|
||||
/// Type of plugin's configuration is up to each plugin - swc/core does not have
|
||||
/// strong type and it'll be serialized into plain string when it's passed to
|
||||
/// plugin's entrypoint function.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct PluginConfig(String, serde_json::Value);
|
||||
|
||||
/// Struct represents arbitary `context` or `state` to be passed to plugin's
|
||||
/// entrypoint.
|
||||
/// While internally this is strongly typed, it is not exposed as public
|
||||
/// interface to plugin's entrypoint but instead will be passed as JSON string.
|
||||
/// First, not all of plugins will need to deserialize this - plugin may opt in
|
||||
/// to access when it's needed. Secondly, we do not have way to avoid breaking
|
||||
/// changes when adding a new property. We may change this to typed
|
||||
/// deserialization in the future if we have add-only schema support with
|
||||
/// serialization / deserialization.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct PluginContext {
|
||||
/// The path of the file being processed. This includes all of the path as
|
||||
/// much as possible.
|
||||
pub filename: Option<String>,
|
||||
}
|
||||
|
||||
pub fn plugins(
|
||||
resolver: CachingResolver<NodeModulesResolver>,
|
||||
config: crate::config::JscExperimental,
|
||||
plugin_context: PluginContext,
|
||||
) -> impl Fold {
|
||||
#[cfg(feature = "plugin")]
|
||||
{
|
||||
@ -28,6 +52,7 @@ pub fn plugins(
|
||||
resolver,
|
||||
plugins: config.plugins,
|
||||
plugin_cache: cache_root,
|
||||
plugin_context,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +69,7 @@ struct RustPlugins {
|
||||
/// swc, as well as cache.
|
||||
#[cfg(feature = "plugin")]
|
||||
plugin_cache: Option<swc_plugin_runner::resolve::PluginCache>,
|
||||
plugin_context: PluginContext,
|
||||
}
|
||||
|
||||
impl RustPlugins {
|
||||
@ -66,8 +92,12 @@ impl RustPlugins {
|
||||
if let Some(plugins) = &self.plugins {
|
||||
for p in plugins {
|
||||
let config_json = serde_json::to_string(&p.1)
|
||||
.context("failed to serialize plugin config as json")?;
|
||||
let config_json = Serialized::serialize(&config_json)?;
|
||||
.context("Failed to serialize plugin config as json")
|
||||
.and_then(|value| Serialized::serialize(&value))?;
|
||||
|
||||
let context_json = serde_json::to_string(&self.plugin_context)
|
||||
.context("Failed to serialize plugin context as json")
|
||||
.and_then(|value| Serialized::serialize(&value))?;
|
||||
|
||||
let resolved_path = self
|
||||
.resolver
|
||||
@ -83,8 +113,9 @@ impl RustPlugins {
|
||||
&p.0,
|
||||
&path,
|
||||
&mut self.plugin_cache,
|
||||
config_json,
|
||||
serialized,
|
||||
config_json,
|
||||
context_json,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
@ -222,26 +222,27 @@ impl VisitMut for TransformVisitor {
|
||||
// Implement necessary visit_mut_* methods for actual custom transform.
|
||||
}
|
||||
|
||||
/// An entrypoint to the SWC's transform plugin.
|
||||
/// `plugin_transform` macro handles necessary interop to communicate with the host,
|
||||
/// and entrypoint function name (`process_transform`) can be anything else.
|
||||
///
|
||||
/// If plugin need to handle low-level ptr directly,
|
||||
/// it is possible to opt out from macro by writing transform fn manually via raw interface
|
||||
/// An example plugin function with macro support.
|
||||
/// `plugin_transform` macro interop pointers into deserialized structs, as well
|
||||
/// as returning ptr back to host.
|
||||
///
|
||||
/// It is possible to opt out from macro by writing transform fn manually via
|
||||
/// `__plugin_process_impl(
|
||||
/// ast_ptr: *const u8,
|
||||
/// ast_ptr_len: i32,
|
||||
/// config_str_ptr: *const u8,
|
||||
/// config_str_ptr_len: i32) ->
|
||||
/// config_str_ptr_len: i32,
|
||||
/// context_str_ptr: *const u8,
|
||||
/// context_str_ptr_len: i32) ->
|
||||
/// i32 /* 0 for success, fail otherwise.
|
||||
/// Note this is only for internal pointer interop result,
|
||||
/// not actual transform result */
|
||||
///
|
||||
/// However, this means plugin author need to handle all of serialization/deserialization
|
||||
/// steps with communicating with host. Refer `swc_plugin_macro` for more details.
|
||||
/// if plugin need to handle low-level ptr directly. However, there are
|
||||
/// important steps manually need to be performed like sending transformed
|
||||
/// results back to host. Refer swc_plugin_macro how does it work internally.
|
||||
#[plugin_transform]
|
||||
pub fn process_transform(program: Program, _plugin_config: String) -> Program {
|
||||
pub fn process_transform(program: Program, _plugin_config: String, _context: String) -> Program {
|
||||
program.fold_with(&mut as_folder(TransformVisitor))
|
||||
}
|
||||
"#
|
||||
|
@ -61,9 +61,10 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
// There are some cases error won't be wrapped up however - for example, we expect
|
||||
// serialization of PluginError itself should succeed.
|
||||
#[no_mangle]
|
||||
pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32) -> i32 {
|
||||
pub fn #process_impl_ident(ast_ptr: *const u8, ast_ptr_len: i32, config_str_ptr: *const u8, config_str_ptr_len: i32, context_str_ptr: *const u8, context_str_ptr_len: i32) -> i32 {
|
||||
let ast_ptr_len_usize: Result<usize, std::num::TryFromIntError> = std::convert::TryInto::try_into(ast_ptr_len);
|
||||
let config_str_ptr_len_usize: Result<usize, std::num::TryFromIntError> = std::convert::TryInto::try_into(config_str_ptr_len);
|
||||
let context_str_ptr_len_usize: Result<usize, std::num::TryFromIntError> = std::convert::TryInto::try_into(context_str_ptr_len);
|
||||
|
||||
if ast_ptr_len_usize.is_err() {
|
||||
let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of AST pointer".to_string());
|
||||
@ -75,21 +76,29 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
if context_str_ptr_len_usize.is_err() {
|
||||
let err = swc_plugin::PluginError::SizeInteropFailure("Failed to convert size of context string pointer".to_string());
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
|
||||
// Read raw serialized bytes from wasm's memory space. Host (SWC) should
|
||||
// allocate memory, copy bytes and pass ptr to plugin.
|
||||
let raw_ast_serialized_bytes =
|
||||
unsafe { std::slice::from_raw_parts(ast_ptr, ast_ptr_len_usize.unwrap()) };
|
||||
let raw_config_serialized_bytes =
|
||||
unsafe { std::slice::from_raw_parts(config_str_ptr, config_str_ptr_len_usize.unwrap()) };
|
||||
|
||||
let raw_context_serialized_bytes =
|
||||
unsafe { std::slice::from_raw_parts(context_str_ptr, context_str_ptr_len_usize.unwrap()) };
|
||||
|
||||
// Reconstruct SerializedProgram from raw bytes
|
||||
let serialized_program = swc_plugin::Serialized::new_for_plugin(raw_ast_serialized_bytes, ast_ptr_len);
|
||||
let serialized_config = swc_plugin::Serialized::new_for_plugin(raw_config_serialized_bytes, config_str_ptr_len);
|
||||
let serialized_context = swc_plugin::Serialized::new_for_plugin(raw_context_serialized_bytes, context_str_ptr_len);
|
||||
|
||||
// Reconstruct `Program` & config string from serialized program
|
||||
let program = swc_plugin::Serialized::deserialize(&serialized_program);
|
||||
let config = swc_plugin::Serialized::deserialize(&serialized_config);
|
||||
let context = swc_plugin::Serialized::deserialize(&serialized_context);
|
||||
|
||||
if program.is_err() {
|
||||
let err = swc_plugin::PluginError::Deserialize(
|
||||
@ -109,6 +118,14 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
}
|
||||
let config: String = config.expect("Should be a string");
|
||||
|
||||
if context.is_err() {
|
||||
let err = swc_plugin::PluginError::Deserialize(
|
||||
("Failed to deserialize context string received from host".to_string(),
|
||||
raw_context_serialized_bytes.to_vec())
|
||||
);
|
||||
return construct_error_ptr(err);
|
||||
}
|
||||
let context: String = context.expect("Should be a string");
|
||||
|
||||
// Create a handler wired with plugin's diagnostic emitter, set it for global context.
|
||||
let handler = swc_plugin::errors::Handler::with_emitter(
|
||||
@ -126,7 +143,7 @@ fn handle_func(func: ItemFn) -> TokenStream {
|
||||
}
|
||||
|
||||
// Take original plugin fn ident, then call it with interop'ed args
|
||||
let transformed_program = #ident(program, config);
|
||||
let transformed_program = #ident(program, config, context);
|
||||
|
||||
// Serialize transformed result, return back to the host.
|
||||
let serialized_result = swc_plugin::Serialized::serialize(&transformed_program);
|
||||
|
@ -417,7 +417,7 @@ fn load_plugin(
|
||||
/// teardown
|
||||
struct PluginTransformTracker {
|
||||
// Main transform interface plugin exports
|
||||
exported_plugin_transform: wasmer::NativeFunc<(i32, i32, i32, i32), i32>,
|
||||
exported_plugin_transform: wasmer::NativeFunc<(i32, i32, i32, i32, i32, i32), i32>,
|
||||
// `__free` function automatically exported via swc_plugin sdk to allow deallocation in guest
|
||||
// memory space
|
||||
exported_plugin_free: wasmer::NativeFunc<(i32, i32), i32>,
|
||||
@ -437,7 +437,9 @@ impl PluginTransformTracker {
|
||||
let tracker = PluginTransformTracker {
|
||||
exported_plugin_transform: instance
|
||||
.exports
|
||||
.get_native_function::<(i32, i32, i32, i32), i32>("__plugin_process_impl")?,
|
||||
.get_native_function::<(i32, i32, i32, i32, i32, i32), i32>(
|
||||
"__plugin_process_impl",
|
||||
)?,
|
||||
exported_plugin_free: instance
|
||||
.exports
|
||||
.get_native_function::<(i32, i32), i32>("__free")?,
|
||||
@ -500,15 +502,19 @@ impl PluginTransformTracker {
|
||||
&mut self,
|
||||
program: &Serialized,
|
||||
config: &Serialized,
|
||||
context: &Serialized,
|
||||
) -> Result<Serialized, Error> {
|
||||
let guest_program_ptr = self.write_bytes_into_guest(program)?;
|
||||
let config_str_ptr = self.write_bytes_into_guest(config)?;
|
||||
let context_str_ptr = self.write_bytes_into_guest(context)?;
|
||||
|
||||
let result = self.exported_plugin_transform.call(
|
||||
guest_program_ptr.0,
|
||||
guest_program_ptr.1,
|
||||
config_str_ptr.0,
|
||||
config_str_ptr.1,
|
||||
context_str_ptr.0,
|
||||
context_str_ptr.1,
|
||||
)?;
|
||||
|
||||
self.read_bytes_from_guest(result)
|
||||
@ -529,13 +535,14 @@ pub fn apply_js_plugin(
|
||||
plugin_name: &str,
|
||||
path: &Path,
|
||||
cache: &mut Option<PluginCache>,
|
||||
config_json: Serialized,
|
||||
program: Serialized,
|
||||
config_json: Serialized,
|
||||
context_json: Serialized,
|
||||
) -> Result<Serialized, Error> {
|
||||
(|| -> Result<_, Error> {
|
||||
let mut transform_tracker = PluginTransformTracker::new(path, cache)?;
|
||||
|
||||
transform_tracker.transform(&program, &config_json)
|
||||
transform_tracker.transform(&program, &config_json, &context_json)
|
||||
})()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
|
@ -82,10 +82,18 @@ fn internal() -> Result<(), Error> {
|
||||
|
||||
let program = Serialized::serialize(&program).expect("Should serializable");
|
||||
let config = Serialized::serialize(&"{}".to_string()).expect("Should serializable");
|
||||
let context = Serialized::serialize(&"{sourceFileName: 'single_plugin_test'}".to_string())
|
||||
.expect("Should serializable");
|
||||
|
||||
let program_bytes =
|
||||
swc_plugin_runner::apply_js_plugin("internal-test", &path, &mut None, config, program)
|
||||
.expect("Plugin should apply transform");
|
||||
let program_bytes = swc_plugin_runner::apply_js_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&mut None,
|
||||
program,
|
||||
config,
|
||||
context,
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
let program: Program =
|
||||
Serialized::deserialize(&program_bytes).expect("Should able to deserialize");
|
||||
@ -119,10 +127,20 @@ fn internal() -> Result<(), Error> {
|
||||
|
||||
let program = Serialized::serialize(&program).expect("Should serializable");
|
||||
let config = Serialized::serialize(&"{}".to_string()).expect("Should serializable");
|
||||
let context =
|
||||
Serialized::serialize(&"{sourceFileName: 'single_plugin_handler_test'}".to_string())
|
||||
.expect("Should serializable");
|
||||
|
||||
let _res = HANDLER.set(&handler, || {
|
||||
swc_plugin_runner::apply_js_plugin("internal-test", &path, &mut None, config, program)
|
||||
.expect("Plugin should apply transform")
|
||||
swc_plugin_runner::apply_js_plugin(
|
||||
"internal-test",
|
||||
&path,
|
||||
&mut None,
|
||||
program,
|
||||
config,
|
||||
context,
|
||||
)
|
||||
.expect("Plugin should apply transform")
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@ -151,8 +169,10 @@ fn internal() -> Result<(), Error> {
|
||||
"internal-test",
|
||||
&path,
|
||||
&mut None,
|
||||
Serialized::serialize(&"{}".to_string()).expect("Should serializable"),
|
||||
serialized_program,
|
||||
Serialized::serialize(&"{}".to_string()).expect("Should serializable"),
|
||||
Serialized::serialize(&"{sourceFileName: 'multiple_plugin_test'}".to_string())
|
||||
.expect("Should serializable"),
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
@ -161,8 +181,10 @@ fn internal() -> Result<(), Error> {
|
||||
"internal-test",
|
||||
&path,
|
||||
&mut None,
|
||||
Serialized::serialize(&"{}".to_string()).expect("Should serializable"),
|
||||
serialized_program,
|
||||
Serialized::serialize(&"{}".to_string()).expect("Should serializable"),
|
||||
Serialized::serialize(&"{sourceFileName: 'multiple_plugin_test2'}".to_string())
|
||||
.expect("Should serializable"),
|
||||
)
|
||||
.expect("Plugin should apply transform");
|
||||
|
||||
|
@ -712,7 +712,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_ast"
|
||||
version = "0.65.3"
|
||||
version = "0.65.4"
|
||||
dependencies = [
|
||||
"is-macro",
|
||||
"num-bigint",
|
||||
@ -766,7 +766,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_plugin"
|
||||
version = "0.25.0"
|
||||
version = "0.26.1"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"swc_atoms",
|
||||
@ -778,7 +778,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_plugin_macro"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -32,7 +32,9 @@ impl VisitMut for ConsoleOutputReplacer {
|
||||
/// ast_ptr: *const u8,
|
||||
/// ast_ptr_len: i32,
|
||||
/// config_str_ptr: *const u8,
|
||||
/// config_str_ptr_len: i32) ->
|
||||
/// config_str_ptr_len: i32,
|
||||
/// context_str_ptr: *const u8,
|
||||
/// context_str_ptr_len: i32) ->
|
||||
/// i32 /* 0 for success, fail otherwise.
|
||||
/// Note this is only for internal pointer interop result,
|
||||
/// not actual transform result */
|
||||
@ -41,7 +43,7 @@ impl VisitMut for ConsoleOutputReplacer {
|
||||
/// important steps manually need to be performed like sending transformed
|
||||
/// results back to host. Refer swc_plugin_macro how does it work internally.
|
||||
#[plugin_transform]
|
||||
pub fn process(program: Program, _plugin_config: String) -> Program {
|
||||
pub fn process(program: Program, _plugin_config: String, _context: String) -> Program {
|
||||
HANDLER.with(|handler| {
|
||||
handler
|
||||
.struct_span_err(DUMMY_SP, "Test diagnostics from plugin")
|
||||
|
Loading…
Reference in New Issue
Block a user