From 60b82aa34196e68d24a10c2eaa4cc3e2198c6ca9 Mon Sep 17 00:00:00 2001 From: ahabhgk Date: Fri, 7 Jul 2023 15:32:38 +0800 Subject: [PATCH 1/7] wip --- crates/rspack_plugin_runtime/src/lib.rs | 1 + .../src/react_refresh.rs | 38 +++++++++++++++++++ .../src/runtime_module/mod.rs | 2 + .../src/runtime_module/react_refresh.rs | 32 ++++++++++++++++ .../runtime_module/runtime/react_refresh.js | 30 +++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 crates/rspack_plugin_runtime/src/react_refresh.rs create mode 100644 crates/rspack_plugin_runtime/src/runtime_module/react_refresh.rs create mode 100644 crates/rspack_plugin_runtime/src/runtime_module/runtime/react_refresh.js diff --git a/crates/rspack_plugin_runtime/src/lib.rs b/crates/rspack_plugin_runtime/src/lib.rs index 20419ad1390..1cd3750a838 100644 --- a/crates/rspack_plugin_runtime/src/lib.rs +++ b/crates/rspack_plugin_runtime/src/lib.rs @@ -1,5 +1,6 @@ #![feature(get_mut_unchecked)] mod helpers; +mod react_refresh; pub use helpers::*; mod lazy_compilation; pub use lazy_compilation::LazyCompilationPlugin; diff --git a/crates/rspack_plugin_runtime/src/react_refresh.rs b/crates/rspack_plugin_runtime/src/react_refresh.rs new file mode 100644 index 00000000000..a563c118823 --- /dev/null +++ b/crates/rspack_plugin_runtime/src/react_refresh.rs @@ -0,0 +1,38 @@ +use async_trait::async_trait; +use rspack_core::{ + AdditionalChunkRuntimeRequirementsArgs, Plugin, PluginAdditionalChunkRuntimeRequirementsOutput, + PluginContext, RuntimeGlobals, RuntimeModuleExt, +}; +use rspack_error::Result; + +use crate::runtime_module::ReactRefreshRuntimeModule; + +#[derive(Debug)] +pub struct ReactRefreshPlugin; + +#[async_trait] +impl Plugin for ReactRefreshPlugin { + fn name(&self) -> &'static str { + "ReactRefreshPlugin" + } + + fn apply(&self, _ctx: rspack_core::PluginContext<&mut rspack_core::ApplyContext>) -> Result<()> { + Ok(()) + } + + fn additional_tree_runtime_requirements( + &self, + _ctx: PluginContext, + args: &mut AdditionalChunkRuntimeRequirementsArgs, + ) -> PluginAdditionalChunkRuntimeRequirementsOutput { + let compilation = &mut args.compilation; + let chunk = args.chunk; + let runtime_requirements = &mut args.runtime_requirements; + + runtime_requirements.insert(RuntimeGlobals::INTERCEPT_MODULE_EXECUTION); + runtime_requirements.insert(RuntimeGlobals::MODULE_CACHE); + compilation.add_runtime_module(chunk, ReactRefreshRuntimeModule::default().boxed()); + + Ok(()) + } +} diff --git a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs index 7ea24471a13..28d280ae4be 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs +++ b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs @@ -27,6 +27,7 @@ mod node_module_decorator; mod normal; mod on_chunk_loaded; mod public_path; +mod react_refresh; mod readfile_chunk_loading; mod require_js_chunk_loading; mod startup_chunk_dependencies; @@ -60,6 +61,7 @@ pub use node_module_decorator::NodeModuleDecoratorRuntimeModule; pub use normal::NormalRuntimeModule; pub use on_chunk_loaded::OnChunkLoadedRuntimeModule; pub use public_path::PublicPathRuntimeModule; +pub use react_refresh::ReactRefreshRuntimeModule; pub use readfile_chunk_loading::ReadFileChunkLoadingRuntimeModule; pub use require_js_chunk_loading::RequireChunkLoadingRuntimeModule; pub use startup_chunk_dependencies::StartupChunkDependenciesRuntimeModule; diff --git a/crates/rspack_plugin_runtime/src/runtime_module/react_refresh.rs b/crates/rspack_plugin_runtime/src/runtime_module/react_refresh.rs new file mode 100644 index 00000000000..2d78fe24cdd --- /dev/null +++ b/crates/rspack_plugin_runtime/src/runtime_module/react_refresh.rs @@ -0,0 +1,32 @@ +use rspack_core::{ + rspack_sources::{BoxSource, RawSource, SourceExt}, + Compilation, RuntimeModule, +}; +use rspack_identifier::Identifier; + +use crate::impl_runtime_module; + +#[derive(Debug, Eq)] +pub struct ReactRefreshRuntimeModule { + id: Identifier, +} + +impl Default for ReactRefreshRuntimeModule { + fn default() -> Self { + Self { + id: Identifier::from("webpack/runtime/react_refresh"), + } + } +} + +impl RuntimeModule for ReactRefreshRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, _compilation: &Compilation) -> BoxSource { + RawSource::from(include_str!("runtime/react_refresh.js")).boxed() + } +} + +impl_runtime_module!(ReactRefreshRuntimeModule); diff --git a/crates/rspack_plugin_runtime/src/runtime_module/runtime/react_refresh.js b/crates/rspack_plugin_runtime/src/runtime_module/runtime/react_refresh.js new file mode 100644 index 00000000000..35f2ff975d3 --- /dev/null +++ b/crates/rspack_plugin_runtime/src/runtime_module/runtime/react_refresh.js @@ -0,0 +1,30 @@ +// Thanks https://github.com/pmmmwh/react-refresh-webpack-plugin + +const RefreshUtils = require("@pmmmwh/react-refresh-webpack-plugin/lib/runtime/RefreshUtils"); +const RefreshRuntime = require("react-refresh/runtime"); + +RefreshRuntime.injectIntoGlobalHook(globalThis); + +// Port from https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/loader/utils/getRefreshModuleRuntime.js#L29 +function refresh(moduleId, webpackHot) { + const currentExports = RefreshUtils.getModuleExports(moduleId); + const fn = exports => { + RefreshUtils.executeRuntime(exports, moduleId, webpackHot); + }; + if (typeof Promise !== "undefined" && currentExports instanceof Promise) { + currentExports.then(fn); + } else { + fn(currentExports); + } +} + +// Injected global react refresh runtime + +globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; + +__webpack_modules__.$ReactRefreshRuntime$ = { + refresh, + register: RefreshRuntime.register, + createSignatureFunctionForTransform: + RefreshRuntime.createSignatureFunctionForTransform +}; From 42aebb1cc202061bed402e5c6ed301a2c6b1d1e9 Mon Sep 17 00:00:00 2001 From: ahabhgk Date: Fri, 7 Jul 2023 21:00:48 +0800 Subject: [PATCH 2/7] fix: worker with react refresh --- .../rspack_plugin_javascript/src/ast/mod.rs | 1 - .../rspack_plugin_javascript/src/ast/parse.rs | 26 +-- .../src/visitors/mod.rs | 2 +- .../src/visitors/swc_visitor/react.rs | 172 +++++++++++++++--- .../rspack-dev-client/src/reactRefresh.ts | 2 +- packages/rspack-dev-server/src/server.ts | 10 +- 6 files changed, 155 insertions(+), 58 deletions(-) diff --git a/crates/rspack_plugin_javascript/src/ast/mod.rs b/crates/rspack_plugin_javascript/src/ast/mod.rs index 201c08ddfa1..1b5e6142a00 100644 --- a/crates/rspack_plugin_javascript/src/ast/mod.rs +++ b/crates/rspack_plugin_javascript/src/ast/mod.rs @@ -4,6 +4,5 @@ mod stringify; pub use minify::minify; pub use parse::parse; -pub use parse::parse_js_code; pub use stringify::print; pub use stringify::stringify; diff --git a/crates/rspack_plugin_javascript/src/ast/parse.rs b/crates/rspack_plugin_javascript/src/ast/parse.rs index 5a2dfb34eef..faefea1c5ac 100644 --- a/crates/rspack_plugin_javascript/src/ast/parse.rs +++ b/crates/rspack_plugin_javascript/src/ast/parse.rs @@ -1,4 +1,3 @@ -use std::path::Path; use std::sync::Arc; use rspack_core::{ast::javascript::Ast, ModuleType}; @@ -11,7 +10,7 @@ use swc_core::ecma::parser::{ }; use swc_node_comments::SwcComments; -use crate::utils::{ecma_parse_error_to_rspack_error, syntax_by_module_type}; +use crate::utils::ecma_parse_error_to_rspack_error; use crate::IsModule; fn module_type_to_is_module(value: &ModuleType) -> IsModule { @@ -96,26 +95,3 @@ pub fn parse( )), } } - -pub fn parse_js_code(js_code: String, module_type: &ModuleType) -> Result { - let filename = "".to_string(); - let syntax = syntax_by_module_type(Path::new(&filename), module_type, false); - let cm: Arc = Default::default(); - let fm = cm.new_source_file(FileName::Custom(filename), js_code); - - match parse_js( - fm.clone(), - ast::EsVersion::Es2022, - syntax, - module_type_to_is_module(module_type), - None, - ) { - Ok(program) => Ok(program), - Err(errs) => Err(Error::BatchErrors( - errs - .into_iter() - .map(|err| ecma_parse_error_to_rspack_error(err, &fm, module_type)) - .collect::>(), - )), - } -} diff --git a/crates/rspack_plugin_javascript/src/visitors/mod.rs b/crates/rspack_plugin_javascript/src/visitors/mod.rs index 7256ba06f0d..7e04fce62ad 100644 --- a/crates/rspack_plugin_javascript/src/visitors/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/mod.rs @@ -93,7 +93,7 @@ pub fn run_before_pass( should_transform_by_react ), Optional::new( - swc_visitor::fold_react_refresh(), + swc_visitor::fold_react_refresh(unresolved_mark), should_transform_by_react && options.builtins.react.refresh.is_some() ), either!( diff --git a/crates/rspack_plugin_javascript/src/visitors/swc_visitor/react.rs b/crates/rspack_plugin_javascript/src/visitors/swc_visitor/react.rs index dbb9061ce2e..40c7608fcfd 100644 --- a/crates/rspack_plugin_javascript/src/visitors/swc_visitor/react.rs +++ b/crates/rspack_plugin_javascript/src/visitors/swc_visitor/react.rs @@ -1,15 +1,17 @@ use std::sync::Arc; -use once_cell::sync::Lazy; -use rspack_core::{ModuleType, ReactOptions}; +use rspack_core::ReactOptions; +use swc_core::common::DUMMY_SP; use swc_core::common::{comments::SingleThreadedComments, Mark, SourceMap}; -use swc_core::ecma::ast::{CallExpr, Callee, Expr, Ident, ModuleItem, Program, Script}; +use swc_core::ecma::ast::{ + BinExpr, BinaryOp, BlockStmt, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, + Function, Ident, MemberExpr, ModuleItem, Program, Stmt, +}; use swc_core::ecma::transforms::react::RefreshOptions; use swc_core::ecma::transforms::react::{react as swc_react, Options}; +use swc_core::ecma::utils::{member_expr, quote_ident, quote_str}; use swc_core::ecma::visit::{noop_visit_type, Fold, Visit, VisitWith}; -use crate::ast::parse_js_code; - pub fn react<'a>( top_level_mark: Mark, comments: Option<&'a SingleThreadedComments>, @@ -41,8 +43,8 @@ pub fn react<'a>( ) } -pub fn fold_react_refresh() -> impl Fold { - ReactHmrFolder {} +pub fn fold_react_refresh(unresolved_mark: Mark) -> impl Fold { + ReactHmrFolder { unresolved_mark } } #[derive(Default)] @@ -80,24 +82,146 @@ impl Visit for ReactRefreshUsageFinder { } } -// __webpack_require__.$ReactRefreshRuntime$ is injected by the react-refresh additional entry -// See https://github.com/web-infra-dev/rspack/pull/2714 why we have a promise here -static RUNTIME_CODE: &str = r#" -function $RefreshReg$(type, id) { - __webpack_modules__.$ReactRefreshRuntime$.register(type, __webpack_module__.id+ "_" + id); +// $ReactRefreshRuntime$ is injected by provide +fn create_react_refresh_runtime_stmts(unresolved_mark: Mark) -> Vec { + fn create_react_refresh_runtime_ident(unresolved_mark: Mark) -> Box { + Box::new(Expr::Ident(Ident { + span: DUMMY_SP.apply_mark(unresolved_mark), + sym: "$ReactRefreshRuntime$".into(), + optional: false, + })) + } + vec![ + FnDecl { + ident: quote_ident!("$RefreshReg$"), + declare: false, + function: Box::new(Function { + params: vec![quote_ident!("type").into(), quote_ident!("id").into()], + decorators: Vec::new(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: CallExpr { + span: DUMMY_SP, + callee: Callee::Expr( + MemberExpr { + span: DUMMY_SP, + obj: create_react_refresh_runtime_ident(unresolved_mark), + prop: quote_ident!("register").into(), + } + .into(), + ), + args: vec![ + ExprOrSpread { + spread: None, + expr: quote_ident!("type").into(), + }, + ExprOrSpread { + spread: None, + expr: BinExpr { + span: DUMMY_SP, + op: BinaryOp::Add, + left: BinExpr { + span: DUMMY_SP, + op: BinaryOp::Add, + left: member_expr!(DUMMY_SP, __webpack_module__), + right: quote_str!("_").into(), + } + .into(), + right: quote_ident!("id").into(), + } + .into(), + }, + ], + type_args: None, + } + .into(), + })], + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + } + .into(), + ExprStmt { + span: DUMMY_SP, + expr: CallExpr { + span: DUMMY_SP, + callee: Callee::Expr( + MemberExpr { + span: DUMMY_SP, + obj: CallExpr { + span: DUMMY_SP, + // See https://github.com/web-infra-dev/rspack/pull/2714 why we have a promise here + callee: member_expr!(DUMMY_SP, Promise.resolve).into(), + args: vec![], + type_args: None, + } + .into(), + prop: quote_ident!("then").into(), + } + .into(), + ), + args: vec![ExprOrSpread { + spread: None, + expr: FnExpr { + ident: None, + function: Box::new(Function { + params: Vec::new(), + decorators: Vec::new(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: CallExpr { + span: DUMMY_SP, + callee: Callee::Expr( + MemberExpr { + span: DUMMY_SP, + obj: create_react_refresh_runtime_ident(unresolved_mark), + prop: quote_ident!("refresh").into(), + } + .into(), + ), + args: vec![ + ExprOrSpread { + spread: None, + expr: member_expr!(DUMMY_SP, __webpack_module__.id), + }, + ExprOrSpread { + spread: None, + expr: member_expr!(DUMMY_SP, __webpack_module__.hot), + }, + ], + type_args: None, + } + .into(), + })], + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + } + .into(), + }], + type_args: None, + } + .into(), + } + .into(), + ] } -Promise.resolve().then(function(){ - __webpack_modules__.$ReactRefreshRuntime$.refresh(__webpack_module__.id, __webpack_module__.hot); -}) -"#; - -static RUNTIME_CODE_AST: Lazy