use std::{
sync::Arc,
time::{Duration, Instant},
};
use rspack_core::{
AssetEmittedInfo, BuildModuleGraphArtifact, ChunkUkey, Compilation,
CompilationOptimizeDependencies, CompilationParams, CompilerAssetEmitted, CompilerCompilation,
DependencyType, ExportsInfoArtifact, ModuleType, NormalModuleFactoryParser, ParserAndGenerator,
ParserOptions, Plugin, RuntimeCodeTemplate, SideEffectsOptimizeArtifact, get_module_directives,
get_module_hashbang,
rspack_sources::{ConcatSource, RawStringSource, Source, SourceExt},
};
use rspack_error::{Diagnostic, Result};
use rspack_hook::{plugin, plugin_hook};
use rspack_plugin_asset::AssetParserAndGenerator;
use rspack_plugin_externals::EsmNodeTargetPlugin;
use rspack_plugin_javascript::{
BoxJavascriptParserPlugin, JavascriptModulesRender, JsPlugin, RenderSource,
parser_and_generator::JavaScriptParserAndGenerator,
};
use crate::{
asset::RslibAssetParserAndGenerator,
dyn_import_external::{
ExportImportedDependencyTemplate, ImportDependencyTemplate, cutout_dyn_import_externals,
cutout_star_re_export_externals,
},
hashbang_parser_plugin::HashbangParserPlugin,
parser_plugin::RslibParserPlugin,
react_directives_parser_plugin::ReactDirectivesParserPlugin,
};
#[derive(Debug)]
pub struct RslibPluginOptions {
pub intercept_api_plugin: bool,
pub force_node_shims: bool,
pub auto_cjs_node_builtin: bool,
}
#[derive(Debug)]
pub struct ProgressPluginStateInfo {
pub value: String,
pub time: Instant,
pub duration: Option<Duration>,
}
#[plugin]
#[derive(Debug)]
pub struct RslibPlugin {
options: RslibPluginOptions,
}
impl RslibPlugin {
pub fn new(options: RslibPluginOptions) -> Self {
Self::new_inner(options)
}
}
#[plugin_hook(NormalModuleFactoryParser for RslibPlugin)]
async fn nmf_parser(
&self,
module_type: &ModuleType,
parser: &mut Box<dyn ParserAndGenerator>,
_parser_options: Option<&ParserOptions>,
) -> Result<()> {
if let Some(parser) = parser.downcast_mut::<JavaScriptParserAndGenerator>() {
if module_type.is_js_like() {
parser.add_parser_plugin(Box::new(HashbangParserPlugin) as BoxJavascriptParserPlugin);
parser.add_parser_plugin(Box::new(ReactDirectivesParserPlugin) as BoxJavascriptParserPlugin);
parser.add_parser_plugin(
Box::new(RslibParserPlugin::new(self.options.intercept_api_plugin))
as BoxJavascriptParserPlugin,
);
}
if module_type.is_js_esm() && self.options.force_node_shims {
parser.add_parser_plugin(Box::new(
rspack_plugin_javascript::node_stuff_plugin::NodeStuffPlugin::new(true, false),
) as BoxJavascriptParserPlugin);
}
} else if parser.is::<AssetParserAndGenerator>() {
*parser = Box::new(RslibAssetParserAndGenerator(
parser
.downcast_ref::<AssetParserAndGenerator>()
.expect("is AssetParser")
.clone(),
))
}
Ok(())
}
#[plugin_hook(CompilerCompilation for RslibPlugin, stage=10)]
async fn compilation(
&self,
compilation: &mut Compilation,
_params: &mut CompilationParams,
) -> Result<()> {
let import_template = compilation.get_dependency_template(
rspack_core::DependencyTemplateType::Dependency(DependencyType::DynamicImport),
);
compilation.set_dependency_template(
rspack_core::DependencyTemplateType::Dependency(DependencyType::DynamicImport),
Arc::new(ImportDependencyTemplate {
template: import_template,
}),
);
let export_template = compilation.get_dependency_template(
rspack_core::DependencyTemplateType::Dependency(DependencyType::EsmExportImportedSpecifier),
);
compilation.set_dependency_template(
rspack_core::DependencyTemplateType::Dependency(DependencyType::EsmExportImportedSpecifier),
Arc::new(ExportImportedDependencyTemplate {
template: export_template,
}),
);
let hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
let mut hooks = hooks.write().await;
hooks.render.tap(render::new(self));
drop(hooks);
Ok(())
}
#[plugin_hook(JavascriptModulesRender for RslibPlugin)]
async fn render(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
render_source: &mut RenderSource,
_runtime_template: &RuntimeCodeTemplate<'_>,
) -> Result<()> {
let entry_modules = compilation
.build_chunk_graph_artifact
.chunk_graph
.get_chunk_entry_modules(chunk_ukey);
if entry_modules.is_empty() {
return Ok(());
}
let module_graph = compilation.get_module_graph();
for entry_module_id in &entry_modules {
let hashbang = get_module_hashbang(module_graph, entry_module_id);
let directives = get_module_directives(module_graph, entry_module_id);
if hashbang.is_none() && directives.is_none() {
continue;
}
let original_source_str = render_source.source.source().into_string_lossy();
let mut new_source = ConcatSource::default();
if let Some(hashbang) = hashbang {
new_source.add(RawStringSource::from(format!("{hashbang}\n")));
}
if let Some(directives) = directives {
let use_strict_prefix = "\"use strict\";\n";
if let Some(rest) = original_source_str.strip_prefix(use_strict_prefix) {
new_source.add(RawStringSource::from(use_strict_prefix));
for directive in directives {
new_source.add(RawStringSource::from(format!("{directive}\n")));
}
new_source.add(RawStringSource::from(rest));
} else {
for directive in directives {
new_source.add(RawStringSource::from(format!("{directive}\n")));
}
new_source.add(render_source.source.clone());
}
} else {
new_source.add(render_source.source.clone());
}
render_source.source = new_source.boxed();
break;
}
Ok(())
}
#[plugin_hook(CompilationOptimizeDependencies for RslibPlugin)]
async fn optimize_dependencies(
&self,
compilation: &Compilation,
_side_effects_optimize_artifact: &mut SideEffectsOptimizeArtifact,
build_module_graph_artifact: &mut BuildModuleGraphArtifact,
exports_info_artifact: &mut ExportsInfoArtifact,
_diagnostics: &mut Vec<Diagnostic>,
) -> Result<Option<bool>> {
cutout_dyn_import_externals(build_module_graph_artifact);
cutout_star_re_export_externals(
compilation,
build_module_graph_artifact,
exports_info_artifact,
);
Ok(None)
}
#[plugin_hook(CompilerAssetEmitted for RslibPlugin)]
async fn asset_emitted(
&self,
compilation: &Compilation,
_filename: &str,
info: &AssetEmittedInfo,
) -> Result<()> {
use rspack_fs::FilePermissions;
let content = info.source.source().into_string_lossy();
if content.starts_with("#!") {
let output_fs = &compilation.output_filesystem;
let permissions = FilePermissions::from_mode(0o755);
output_fs
.set_permissions(&info.target_path, permissions)
.await?;
}
Ok(())
}
impl Plugin for RslibPlugin {
fn name(&self) -> &'static str {
"rslib"
}
fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
ctx.compiler_hooks.compilation.tap(compilation::new(self));
ctx
.normal_module_factory_hooks
.parser
.tap(nmf_parser::new(self));
ctx
.compilation_hooks
.optimize_dependencies
.tap(optimize_dependencies::new(self));
ctx
.compiler_hooks
.asset_emitted
.tap(asset_emitted::new(self));
if self.options.auto_cjs_node_builtin {
EsmNodeTargetPlugin::new().apply(ctx)?;
}
Ok(())
}
}