Skip to content

Commit

Permalink
feat: cache next-swc-loader
Browse files Browse the repository at this point in the history
  • Loading branch information
SyMind committed Jan 10, 2025
1 parent fec9b65 commit 725152e
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 20 deletions.
18 changes: 18 additions & 0 deletions crates/rspack_binding_values/src/plugins/js_loader/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ pub fn serde_error_to_miette(
type SwcLoaderCache<'a> = LazyLock<RwLock<FxHashMap<(Cow<'a, str>, Arc<str>), Arc<SwcLoader>>>>;
static SWC_LOADER_CACHE: SwcLoaderCache = LazyLock::new(|| RwLock::new(FxHashMap::default()));

type NextSwcLoaderCache<'a> =
LazyLock<RwLock<FxHashMap<(Cow<'a, str>, Arc<str>), Arc<NextSwcLoader>>>>;
static NEXT_SWC_LOADER_CACHE: NextSwcLoaderCache =
LazyLock::new(|| RwLock::new(FxHashMap::default()));

pub async fn get_builtin_loader(builtin: &str, options: Option<&str>) -> Result<BoxLoader> {
let options: Arc<str> = options.unwrap_or("{}").into();
if builtin.starts_with(SWC_LOADER_IDENTIFIER) {
Expand Down Expand Up @@ -83,6 +88,14 @@ pub async fn get_builtin_loader(builtin: &str, options: Option<&str>) -> Result<
}

if builtin.starts_with(NEXT_SWC_LOADER_IDENTIFIER) {
if let Some(loader) = NEXT_SWC_LOADER_CACHE
.read()
.await
.get(&(Cow::Borrowed(builtin), options.clone()))
{
return Ok(loader.clone());
}

let loader = Arc::new(
rspack_loader_next_swc::NextSwcLoader::new(options.as_ref())
.map_err(|e| {
Expand All @@ -94,6 +107,11 @@ pub async fn get_builtin_loader(builtin: &str, options: Option<&str>) -> Result<
})?
.with_identifier(builtin.into()),
);

NEXT_SWC_LOADER_CACHE.write().await.insert(
(Cow::Owned(builtin.to_owned()), options.clone()),
loader.clone(),
);
return Ok(loader);
}

Expand Down
133 changes: 114 additions & 19 deletions crates/rspack_loader_next_swc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ mod compiler;
mod options;
mod transformer;

use std::collections::HashMap;
use std::default::Default;
use std::path::{Path, PathBuf};
use std::str::FromStr;
Expand All @@ -18,7 +17,6 @@ use next_custom_transforms::transforms::{
use once_cell::sync::Lazy;
use options::NextSwcLoaderJsOptions;
pub use options::SwcLoaderJsOptions;
use preset_env_base::query::QueryOrVersion;
use preset_env_base::version::Version;
use regex::Regex;
use rspack_cacheable::{cacheable, cacheable_dyn};
Expand All @@ -30,7 +28,10 @@ use rspack_plugin_javascript::ast::{self, SourceMapConfig};
use rspack_plugin_javascript::TransformOutput;
use rustc_hash::FxHashMap;
use sugar_path::SugarPath;
use swc::config::{JscConfig, JscExperimental, ModuleConfig};
use swc::config::{
JscConfig, JscExperimental, ModuleConfig, OptimizerConfig, SimplifyOption, TransformConfig,
};
use swc_config::config_types::MergingOption;
use swc_core::base::config::SourceMapsConfig;
use swc_core::base::config::{InputSourceMap, OutputCharset};
use swc_core::ecma::atoms::Atom;
Expand All @@ -40,6 +41,20 @@ use transformer::IdentCollector;

static NODE_MODULES_PATH: Lazy<Regex> = Lazy::new(|| Regex::new("[\\/]node_modules[\\/]").unwrap());

static BABEL_INCLUDE_REGEXES: Lazy<Vec<Regex>> = Lazy::new(|| {
vec![
Regex::new(r"next[\\/]dist[\\/](esm[\\/])?shared[\\/]lib").unwrap(),
Regex::new(r"next[\\/]dist[\\/](esm[\\/])?client").unwrap(),
Regex::new(r"next[\\/]dist[\\/](esm[\\/])?pages").unwrap(),
Regex::new(r"[\\/](strip-ansi|ansi-regex|styled-jsx)[\\/]").unwrap(),
]
});

// these are exact code conditions checked
// for to force transpiling a `node_module`
static FORCE_TRANSPILE_CONDITIONS: Lazy<Regex> =
Lazy::new(|| Regex::new("(next\\/font|next\\/dynamic|use server|use client)").unwrap());

fn is_type_script_file(path: &Path) -> bool {
if let Some(extension) = path.extension() {
let ext = extension.to_string_lossy().to_lowercase();
Expand All @@ -62,6 +77,35 @@ fn should_output_common_js(filename: &Path) -> bool {
is_common_js_file(filename)
}

fn is_resource_in_packages(resource: &str, package_names: &[String]) -> bool {
package_names.iter().any(|p| {
let node_modules_path =
PathBuf::from("node_modules").join(p.replace("/", &std::path::MAIN_SEPARATOR.to_string()));
resource.contains(&format!(
"{}{}{}",
std::path::MAIN_SEPARATOR,
node_modules_path.to_string_lossy(),
std::path::MAIN_SEPARATOR
))
})
}

fn may_be_exclude(exclude_path: &str, transpile_packages: &[String]) -> bool {
if BABEL_INCLUDE_REGEXES
.iter()
.any(|r| r.is_match(exclude_path))
{
return false;
}

let should_be_bundled = is_resource_in_packages(exclude_path, transpile_packages);
if should_be_bundled {
return false;
}

exclude_path.contains("node_modules")
}

#[cacheable]
#[derive(Debug)]
pub struct NextSwcLoader {
Expand Down Expand Up @@ -90,11 +134,13 @@ impl NextSwcLoader {
.resource_path()
.map(|p| p.to_path_buf())
.unwrap_or_default();
let Some(content) = loader_context.take_content() else {
return Ok(());
};

let source = content.into_string_lossy();
let filename = resource_path.clone();

// Ensure `.d.ts` are not processed.
if filename.as_str().ends_with(".d.ts") {
return Ok(());
}

let NextSwcLoaderJsOptions {
root_dir,
Expand All @@ -109,10 +155,16 @@ impl NextSwcLoader {
optimize_server_react,
supported_browsers,
swc_cache_dir,
transpile_packages,
} = &self.options;

let Some(content) = loader_context.take_content() else {
return Ok(());
};

let source = content.into_string_lossy();

let pages_dir = pages_dir.clone().map(Utf8PathBuf::from);
let filename = resource_path.clone();

let is_page_file = match &pages_dir {
Some(pages_dir) => filename.starts_with(pages_dir),
Expand Down Expand Up @@ -203,25 +255,25 @@ impl NextSwcLoader {
let cjs_require_optimizer = Some(cjs_optimizer::Config { packages });

let env = if *is_server {
None
Some(swc_core::ecma::preset_env::Config {
targets: Some(swc_core::ecma::preset_env::Targets::Versions(
preset_env_base::BrowserData {
node: Some(Version::from_str("18.20.4").unwrap()),
..Default::default()
},
)),
..Default::default()
})
} else {
if supported_browsers.is_empty() {
Some(Default::default())
} else {
Some(swc_core::ecma::preset_env::Config {
targets: Some(swc_core::ecma::preset_env::Targets::Query(
supported_browsers.clone().into(),
)),
..Default::default()
})
} else {
Some(swc_core::ecma::preset_env::Config {
targets: Some(swc_core::ecma::preset_env::Targets::HashMap(
HashMap::from_iter(vec![(
"node".to_string(),
QueryOrVersion::Version(Version::from_str("18.20.4").unwrap()),
)]),
)),
..Default::default()
})
}
};

Expand All @@ -242,6 +294,7 @@ impl NextSwcLoader {
Some(Syntax::Es(EsSyntax {
jsx: true,
decorators: true,
import_attributes: true,
..Default::default()
}))
};
Expand Down Expand Up @@ -283,6 +336,34 @@ impl NextSwcLoader {
cache_root: Some(swc_cache_dir.to_string()),
..Default::default()
},
external_helpers: true.into(),
transform: MergingOption::from(Some(TransformConfig {
react: swc_core::ecma::transforms::react::Options {
runtime: Some(swc_core::ecma::transforms::react::Runtime::Automatic),
import_source: Some("react".to_string()),
pragma_frag: Some("React.Fragment".to_string()),
throw_if_namespace: Some(true),
development: Some(is_development),
refresh: if *has_react_refresh {
Some(Default::default())
} else {
None
},
..Default::default()
},
optimizer: Some(OptimizerConfig {
// globals: Some(GlobalPassOption {
// envs: GlobalInliningPassEnvs::Map(HashMap::from_iter(vec![

// ])),
// typeofs: todo!(),
// ..Default::default()
// }),
simplify: Some(SimplifyOption::Bool(false)),
..Default::default()
}),
..Default::default()
})),
..Default::default()
},
module,
Expand Down Expand Up @@ -409,6 +490,20 @@ impl Loader<RunnerContext> for NextSwcLoader {
#[cfg(not(debug_assertions))]
inner()
}

// async fn pitch(&self, loader_context: &mut LoaderContext<RunnerContext>) -> Result<()> {
// let should_maybe_exclude = may_be_exclude(filename.as_str(), transpile_packages);
// if should_maybe_exclude {
// let Some(content) = loader_context.content() else {
// panic!("Invariant might be excluded but missing source");
// };
// if !FORCE_TRANSPILE_CONDITIONS.is_match(&content.clone().into_string_lossy()) {
// return Ok(());
// }
// }

// Ok(())
// }
}

impl Identifiable for NextSwcLoader {
Expand Down
4 changes: 3 additions & 1 deletion crates/rspack_loader_next_swc/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ pub struct NextSwcLoaderJsOptions {

#[serde(default)]
pub esm: bool,
// transpilePackages?: string[]

#[serde(default)]
pub transpile_packages: Vec<String>,
}

impl TryFrom<&str> for NextSwcLoaderJsOptions {
Expand Down

0 comments on commit 725152e

Please sign in to comment.