refactor(plugin/runner): Avoid redundant filesystem reads (#3186)

This commit is contained in:
OJ Kwon 2022-01-03 23:10:39 -08:00 committed by GitHub
parent 7d0a8a12f1
commit b61c49fe39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 61 deletions

View File

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

View File

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

View File

@ -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(())