mirror of
https://github.com/swc-project/swc.git
synced 2024-12-24 06:05:02 +03:00
refactor(plugin/runner): Avoid redundant filesystem reads (#3186)
This commit is contained in:
parent
7d0a8a12f1
commit
b61c49fe39
@ -14,59 +14,14 @@ use swc_ecma_visit::{noop_fold_type, Fold};
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct PluginConfig(String, serde_json::Value);
|
||||
|
||||
struct PluginBinary(String, Vec<u8>, String);
|
||||
|
||||
pub fn plugins(config: crate::config::JscExperimental) -> impl Fold {
|
||||
#[cfg(feature = "plugin")]
|
||||
{
|
||||
use anyhow::Context;
|
||||
|
||||
let mut plugin_binaries = vec![];
|
||||
let mut plugin_errors = vec![];
|
||||
|
||||
if let Some(plugins) = &config.plugins {
|
||||
for p in plugins {
|
||||
let config_json = serde_json::to_string(&p.1)
|
||||
.context("failed to serialize plugin config as json");
|
||||
|
||||
if config_json.is_err() {
|
||||
plugin_errors.push(config_json.err().unwrap());
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = swc_plugin_runner::resolve::resolve(&p.0);
|
||||
|
||||
if path.is_err() {
|
||||
plugin_errors.push(path.err().unwrap());
|
||||
continue;
|
||||
}
|
||||
|
||||
let module_bytes = std::fs::read(&path.unwrap().as_ref());
|
||||
|
||||
if module_bytes.is_err() {
|
||||
plugin_errors.push(
|
||||
module_bytes
|
||||
.context("Failed to read plugin binary")
|
||||
.err()
|
||||
.unwrap(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
plugin_binaries.push(PluginBinary(
|
||||
p.0.clone(),
|
||||
module_bytes.ok().unwrap(),
|
||||
config_json.ok().unwrap(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let cache_root =
|
||||
swc_plugin_runner::resolve::resolve_plugin_cache_root(config.cache_root).ok();
|
||||
|
||||
RustPlugins {
|
||||
plugins: plugin_binaries,
|
||||
plugin_errors,
|
||||
plugins: config.plugins,
|
||||
plugin_cache: cache_root,
|
||||
}
|
||||
}
|
||||
@ -78,8 +33,7 @@ pub fn plugins(config: crate::config::JscExperimental) -> impl Fold {
|
||||
}
|
||||
|
||||
struct RustPlugins {
|
||||
plugins: Vec<PluginBinary>,
|
||||
plugin_errors: Vec<anyhow::Error>,
|
||||
plugins: Option<Vec<PluginConfig>>,
|
||||
/// TODO: it is unclear how we'll support plugin itself in wasm target of
|
||||
/// swc, as well as cache.
|
||||
#[cfg(feature = "plugin")]
|
||||
@ -89,12 +43,23 @@ struct RustPlugins {
|
||||
impl RustPlugins {
|
||||
#[cfg(feature = "plugin")]
|
||||
fn apply(&mut self, mut n: Program) -> Result<Program, anyhow::Error> {
|
||||
for e in self.plugin_errors.drain(..) {
|
||||
return Err(e);
|
||||
}
|
||||
use anyhow::Context;
|
||||
|
||||
for p in &self.plugins {
|
||||
n = swc_plugin_runner::apply_js_plugin(&p.0, &p.1, &mut self.plugin_cache, &p.2, n)?;
|
||||
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 path = swc_plugin_runner::resolve::resolve(&p.0)?;
|
||||
|
||||
n = swc_plugin_runner::apply_js_plugin(
|
||||
&p.0,
|
||||
&path,
|
||||
&mut self.plugin_cache,
|
||||
&config_json,
|
||||
n,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
|
@ -1,5 +1,13 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use resolve::PluginCache;
|
||||
use swc_common::collections::AHashMap;
|
||||
use swc_ecma_ast::Program;
|
||||
use wasmer::{imports, Instance, Module, Store};
|
||||
use wasmer_cache::{Cache, Hash};
|
||||
@ -9,7 +17,40 @@ pub mod resolve;
|
||||
/// Load plugin from specified path.
|
||||
/// If cache is provided, it'll try to load from cache first to avoid
|
||||
/// compilation.
|
||||
fn load_plugin(module_bytes: &Vec<u8>, cache: &mut Option<PluginCache>) -> Result<Instance, Error> {
|
||||
///
|
||||
/// Since plugin will be initialized per-file transform, this function tries to
|
||||
/// avoid reading filesystem per each initialization via naive in-memory map
|
||||
/// which stores raw bytecodes from file. Unlike compiled bytecode cache for the
|
||||
/// wasm, this is volatile.
|
||||
///
|
||||
/// ### Notes
|
||||
/// [This code](https://github.com/swc-project/swc/blob/fc4c6708f24cda39640fbbfe56123f2f6eeb2474/crates/swc/src/plugin.rs#L19-L44)
|
||||
/// includes previous incorrect attempt to workaround file read issues.
|
||||
/// In actual transform, `plugins` is also being called per each transform.
|
||||
fn load_plugin(plugin_path: &Path, cache: &mut Option<PluginCache>) -> Result<Instance, Error> {
|
||||
static BYTE_CACHE: Lazy<Mutex<AHashMap<PathBuf, Arc<Vec<u8>>>>> =
|
||||
Lazy::new(|| Default::default());
|
||||
|
||||
// TODO: This caching streategy does not consider few edge cases.
|
||||
// 1. If process is long-running (devServer) binary change in the middle of
|
||||
// process won't be reflected.
|
||||
// 2. If reading binary fails somehow it won't bail out but keep retry.
|
||||
let module_bytes_key = plugin_path.to_path_buf();
|
||||
let module_bytes = if let Some(cached_bytes) = BYTE_CACHE.lock().get(&module_bytes_key).cloned()
|
||||
{
|
||||
cached_bytes
|
||||
} else {
|
||||
let fresh_module_bytes = std::fs::read(plugin_path)
|
||||
.map(Arc::new)
|
||||
.context("Cannot read plugin from specified path")?;
|
||||
BYTE_CACHE
|
||||
.lock()
|
||||
.insert(module_bytes_key, fresh_module_bytes.clone());
|
||||
|
||||
fresh_module_bytes
|
||||
};
|
||||
|
||||
// TODO: can we share store instances across each plugin binaries?
|
||||
let wasmer_store = Store::default();
|
||||
|
||||
let load_from_cache = |c: &mut PluginCache, hash: Hash| match c {
|
||||
@ -22,10 +63,10 @@ fn load_plugin(module_bytes: &Vec<u8>, cache: &mut Option<PluginCache>) -> Resul
|
||||
PluginCache::File(filesystem_cache) => filesystem_cache.store(hash, module),
|
||||
};
|
||||
|
||||
let hash = Hash::generate(module_bytes);
|
||||
let hash = Hash::generate(&module_bytes);
|
||||
|
||||
let load_cold_wasm_bytes =
|
||||
|| Module::new(&wasmer_store, module_bytes).context("Cannot compile plugin");
|
||||
|| Module::new(&wasmer_store, module_bytes.as_ref()).context("Cannot compile plugin");
|
||||
|
||||
let module = if let Some(cache) = cache {
|
||||
let cached_module =
|
||||
@ -64,16 +105,21 @@ fn load_plugin(module_bytes: &Vec<u8>, cache: &mut Option<PluginCache>) -> Resul
|
||||
}
|
||||
|
||||
pub fn apply_js_plugin(
|
||||
plugin_name: &str,
|
||||
module_bytes: &Vec<u8>,
|
||||
_plugin_name: &str,
|
||||
path: &Path,
|
||||
cache: &mut Option<PluginCache>,
|
||||
_config_json: &str,
|
||||
program: Program,
|
||||
) -> Result<Program, Error> {
|
||||
(|| -> Result<_, Error> {
|
||||
let _instance = load_plugin(module_bytes, cache)?;
|
||||
let _instance = load_plugin(path, cache)?;
|
||||
// TODO: actually apply transform from plugin
|
||||
Ok(program)
|
||||
})()
|
||||
.with_context(|| format!("failed to invoke `{}` as js transform plugin", plugin_name))
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to invoke `{}` as js transform plugin",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ fn internal() -> Result<(), Error> {
|
||||
let program = parser.parse_program().unwrap();
|
||||
|
||||
let _program =
|
||||
swc_plugin_runner::apply_js_plugin("internal-test", &vec![], &mut None, "{}", program)
|
||||
swc_plugin_runner::apply_js_plugin("internal-test", &path, &mut None, "{}", program)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user