Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions crates/rspack_binding_api/src/swc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use napi::bindgen_prelude::within_runtime_if_available;
use rspack_javascript_compiler::{
JavaScriptCompiler, TransformOutput as CompilerTransformOutput, minify::JsMinifyOptions,
Expand Down Expand Up @@ -57,9 +59,9 @@ fn _transform(source: String, options: String) -> napi::Result<TransformOutput>
compiler
.transform(
source,
Some(swc_core::common::FileName::Real(
Some(Arc::new(swc_core::common::FileName::Real(
options.filename.clone().into(),
)),
))),
options,
Some(module_source_map_kind),
|_, _| {},
Expand Down
25 changes: 25 additions & 0 deletions crates/rspack_core/src/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ pub struct BuildContext {
pub fs: Arc<dyn ReadableFileSystem>,
}

#[cacheable]
#[derive(Debug, Clone)]
pub enum RSCModuleType {
Server,
Client,
}

#[cacheable]
#[derive(Debug, Clone)]
pub enum ClientEntryType {
Cjs,
Auto,
}

#[cacheable]
#[derive(Debug, Clone)]
pub struct RSCMeta {
pub module_type: RSCModuleType,
#[cacheable(with=AsVec<AsPreset>)]
pub client_refs: Vec<Atom>,
pub client_entry_type: Option<ClientEntryType>,
}

#[cacheable]
#[derive(Debug, Clone)]
pub struct BuildInfo {
Expand All @@ -73,6 +96,7 @@ pub struct BuildInfo {
pub assets: BindingCell<HashMap<String, CompilationAsset>>,
pub module: bool,
pub collected_typescript_info: Option<CollectedTypeScriptInfo>,
pub rsc: Option<RSCMeta>,
/// Stores external fields from the JS side (Record<string, any>),
/// while other properties are stored in KnownBuildInfo.
#[cacheable(with=AsPreset)]
Expand Down Expand Up @@ -101,6 +125,7 @@ impl Default for BuildInfo {
assets: Default::default(),
module: false,
collected_typescript_info: None,
rsc: None,
extras: Default::default(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/rspack_javascript_compiler/src/compiler/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl JavaScriptCompiler {
pub fn transform<'a, S, P>(
&self,
source: S,
filename: Option<FileName>,
filename: Option<Arc<FileName>>,
options: SwcOptions,
module_source_map_kind: Option<SourceMapKind>,
inspect_parsed_ast: impl FnOnce(&Program, Mark),
Expand All @@ -69,7 +69,7 @@ impl JavaScriptCompiler {
{
let fm = self
.cm
.new_source_file(filename.unwrap_or(FileName::Anon).into(), source.into());
.new_source_file(filename.unwrap_or(Arc::new(FileName::Anon)), source.into());
let javascript_transformer = JavaScriptTransformer::new(self.cm.clone(), fm, self, options)?;

javascript_transformer.transform(inspect_parsed_ast, before_pass, module_source_map_kind)
Expand Down
6 changes: 3 additions & 3 deletions crates/rspack_loader_swc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ ignored = ["swc"]
[features]
default = []
plugin = [
"rspack_util/plugin",

# plugin_transform_host_native cannot be enabled directly to avoid wasmer dependency
"swc_core/__plugin_transform_host",
"swc_core/__plugin_transform_host_schema_v1",
Expand All @@ -22,6 +20,8 @@ plugin = [
[dependencies]
async-trait = { workspace = true }
either = { workspace = true }
once_cell = { workspace = true }
regex = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
Expand All @@ -30,7 +30,7 @@ rspack_javascript_compiler = { workspace = true }
rspack_loader_runner = { workspace = true }
rspack_swc_plugin_import = { workspace = true }
rspack_swc_plugin_ts_collector = { workspace = true }
rspack_util = { workspace = true, optional = true }
rspack_util = { workspace = true }
rspack_workspace = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
20 changes: 16 additions & 4 deletions crates/rspack_loader_swc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ mod collect_ts_info;
mod options;
mod plugin;
mod transformer;
mod transforms;

use std::{default::Default, path::Path};
use std::{default::Default, path::Path, sync::Arc};

use options::SwcCompilerOptionsWithAdditional;
pub use options::SwcLoaderJsOptions;
Expand Down Expand Up @@ -105,7 +106,7 @@ impl SwcLoader {
};

let javascript_compiler = JavaScriptCompiler::new();
let filename = FileName::Real(resource_path.clone().into_std_path_buf());
let filename = Arc::new(FileName::Real(resource_path.clone().into_std_path_buf()));

let source = content.into_string_lossy();
let is_typescript =
Expand All @@ -118,7 +119,7 @@ impl SwcLoader {
diagnostics,
} = javascript_compiler.transform(
source,
Some(filename),
Some(filename.clone()),
swc_options,
Some(loader_context.context.source_map_kind),
|program, unresolved_mark| {
Expand All @@ -138,7 +139,18 @@ impl SwcLoader {
options,
));
},
|_| transformer::transform(&self.options_with_additional.rspack_experiments),
|_| {
if self.options_with_additional.rspack_experiments.rsc {
transforms::server_components(
filename,
transforms::Config::WithOptions(transforms::Options {
// TODO: 应该当做配置项传入
is_react_server_layer: true,
}),
);
}
transformer::transform(&self.options_with_additional.rspack_experiments)
},
)?;

for diagnostic in diagnostics {
Expand Down
4 changes: 4 additions & 0 deletions crates/rspack_loader_swc/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use swc_core::base::config::{
pub struct RawRspackExperiments {
pub import: Option<Vec<RawImportOptions>>,
pub collect_type_script_info: Option<RawCollectTypeScriptInfoOptions>,
#[serde(default)]
pub rsc: bool,
}

#[derive(Default, Deserialize, Debug)]
Expand All @@ -28,6 +30,7 @@ pub struct RawCollectTypeScriptInfoOptions {
pub(crate) struct RspackExperiments {
pub(crate) import: Option<Vec<ImportOptions>>,
pub(crate) collect_typescript_info: Option<CollectTypeScriptInfoOptions>,
pub(crate) rsc: bool,
}

#[derive(Default, Debug)]
Expand All @@ -50,6 +53,7 @@ impl From<RawRspackExperiments> for RspackExperiments {
.import
.map(|i| i.into_iter().map(|v| v.into()).collect()),
collect_typescript_info: value.collect_type_script_info.map(|v| v.into()),
rsc: value.rsc,
}
}
}
Expand Down
150 changes: 150 additions & 0 deletions crates/rspack_loader_swc/src/transforms/cjs_finder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use swc_core::ecma::{
ast::*,
visit::{Visit, VisitWith},
};

pub fn contains_cjs(m: &Module) -> bool {
let mut v = CjsFinder::default();
m.visit_with(&mut v);
v.found && !v.is_esm
}

#[derive(Copy, Clone, Default)]
struct CjsFinder {
found: bool,
is_esm: bool,
ignore_module: bool,
ignore_exports: bool,
}

impl CjsFinder {
/// If the given pattern contains `module` as a parameter, we don't need to
/// recurse into it because `module` is shadowed.
fn adjust_state<'a, I>(&mut self, iter: I)
where
I: Iterator<Item = &'a Pat>,
{
iter.for_each(|p| {
if let Pat::Ident(i) = p {
if &*i.id.sym == "module" {
self.ignore_module = true;
}
if &*i.id.sym == "exports" {
self.ignore_exports = true;
}
}
})
}
}

/// This visitor implementation supports typescript, because the api of `swc`
/// does not support changing configuration based on content of the file.
impl Visit for CjsFinder {
fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
let old_ignore_module = self.ignore_module;
let old_ignore_exports = self.ignore_exports;

self.adjust_state(n.params.iter());

n.visit_children_with(self);

self.ignore_module = old_ignore_module;
self.ignore_exports = old_ignore_exports;
}

// Detect `Object.defineProperty(exports, "__esModule", ...)`
// Note that `Object.defineProperty(module.exports, ...)` will be handled by
// `visit_member_expr`.
fn visit_call_expr(&mut self, e: &CallExpr) {
if !self.ignore_exports {
if let Callee::Expr(expr) = &e.callee {
if let Expr::Member(member_expr) = &**expr {
if let (Expr::Ident(obj), MemberProp::Ident(prop)) =
(&*member_expr.obj, &member_expr.prop)
{
if &*obj.sym == "Object" && &*prop.sym == "defineProperty" {
if let Some(ExprOrSpread { expr: expr0, .. }) = e.args.first() {
if let Expr::Ident(arg0) = &**expr0 {
if &*arg0.sym == "exports" {
if let Some(ExprOrSpread { expr: expr1, .. }) = e.args.get(1) {
if let Expr::Lit(Lit::Str(arg1)) = &**expr1 {
if &*arg1.value == "__esModule" {
self.found = true;
return;
}
}
}
}
}
}
}
}
}
}
}

e.callee.visit_with(self);
}

fn visit_class_method(&mut self, n: &ClassMethod) {
let old_ignore_module = self.ignore_module;
let old_ignore_exports = self.ignore_exports;

self.adjust_state(n.function.params.iter().map(|v| &v.pat));

n.visit_children_with(self);

self.ignore_module = old_ignore_module;
self.ignore_exports = old_ignore_exports;
}

fn visit_function(&mut self, n: &Function) {
let old_ignore_module = self.ignore_module;
let old_ignore_exports = self.ignore_exports;

self.adjust_state(n.params.iter().map(|v| &v.pat));

n.visit_children_with(self);

self.ignore_module = old_ignore_module;
self.ignore_exports = old_ignore_exports;
}

fn visit_member_expr(&mut self, e: &MemberExpr) {
if let Expr::Ident(obj) = &*e.obj {
if let MemberProp::Ident(prop) = &e.prop {
// Detect `module.exports` and `exports.__esModule`
if (!self.ignore_module && &*obj.sym == "module" && &*prop.sym == "exports")
|| (!self.ignore_exports && &*obj.sym == "exports")
{
self.found = true;
return;
}
}
}

e.obj.visit_with(self);
e.prop.visit_with(self);
}

fn visit_method_prop(&mut self, n: &MethodProp) {
let old_ignore_module = self.ignore_module;
let old_ignore_exports = self.ignore_exports;

self.adjust_state(n.function.params.iter().map(|v| &v.pat));

n.visit_children_with(self);

self.ignore_module = old_ignore_module;
self.ignore_exports = old_ignore_exports;
}

fn visit_module_decl(&mut self, n: &ModuleDecl) {
match n {
ModuleDecl::Import(_) => {}
_ => {
self.is_esm = true;
}
}
}
}
Loading
Loading