From 929dfc188cd6271453996172487242ad40ab3875 Mon Sep 17 00:00:00 2001 From: underfin Date: Tue, 3 Sep 2024 20:48:47 +0800 Subject: [PATCH 01/44] feat: add hmr_rebuild api (#2137) Add `hmr_rebuild(changed_file)` to using as hmr rebuild API. Here maybe could need to return String, we could mutate it at future. --- crates/rolldown/src/bundler.rs | 5 + crates/rolldown_binding/src/bundler.rs | 6 + packages/rolldown/src/binding.d.ts | 11 +- .../src/rolldown-binding.wasi-browser.js | 149 ++++++++++++++++++ .../rolldown/src/rolldown-binding.wasi.cjs | 149 ++++++++++++++++++ packages/rolldown/src/rolldown-build.ts | 4 + 6 files changed, 323 insertions(+), 1 deletion(-) diff --git a/crates/rolldown/src/bundler.rs b/crates/rolldown/src/bundler.rs index 242b006d9e4f..c44aaeda2a8c 100644 --- a/crates/rolldown/src/bundler.rs +++ b/crates/rolldown/src/bundler.rs @@ -138,6 +138,11 @@ impl Bundler { Ok(Ok(scan_stage_output)) } + #[allow(clippy::unused_async)] + pub async fn hmr_rebuild(&mut self, _changed_files: Vec) -> Result<()> { + unimplemented!() + } + async fn try_build(&mut self) -> Result> { let build_info = match self.scan().await? { Ok(scan_stage_output) => scan_stage_output, diff --git a/crates/rolldown_binding/src/bundler.rs b/crates/rolldown_binding/src/bundler.rs index 4b02cf866962..588991cc7829 100644 --- a/crates/rolldown_binding/src/bundler.rs +++ b/crates/rolldown_binding/src/bundler.rs @@ -92,6 +92,12 @@ impl Bundler { self.scan_impl().await } + #[napi] + #[tracing::instrument(level = "debug", skip_all)] + pub async fn hmr_rebuild(&self, changed_files: Vec) -> napi::Result<()> { + self.hmr_rebuild_impl(changed_files).await + } + #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn close(&self) -> napi::Result<()> { diff --git a/packages/rolldown/src/binding.d.ts b/packages/rolldown/src/binding.d.ts index 04de9c8a1438..94c9406d5639 100644 --- a/packages/rolldown/src/binding.d.ts +++ b/packages/rolldown/src/binding.d.ts @@ -71,7 +71,16 @@ export declare class Bundler { generate(): Promise scan(): Promise close(): Promise - watch(): Promise + hmrRebuild(changedFiles: Array): Promise +} + +/** + * The `FinalBindingOutputs` is used at `write()` or `generate()`, it is similar to `BindingOutputs`, if using `BindingOutputs` has unexpected behavior. + * TODO find a way to export it gracefully. + */ +export declare class FinalBindingOutputs { + get chunks(): Array + get assets(): Array } export declare class ParallelJsPluginRegistry { diff --git a/packages/rolldown/src/rolldown-binding.wasi-browser.js b/packages/rolldown/src/rolldown-binding.wasi-browser.js index 6b3245948c73..743f9e4e490c 100644 --- a/packages/rolldown/src/rolldown-binding.wasi-browser.js +++ b/packages/rolldown/src/rolldown-binding.wasi-browser.js @@ -58,6 +58,7 @@ const { }) function __napi_rs_initialize_modules(__napiInstance) { +<<<<<<< HEAD __napiInstance.exports['__napi_register__SourceMap_struct_0']?.() __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_1']?.() __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_2']?.() @@ -132,16 +133,164 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__BindingOutputs_struct_92']?.() __napiInstance.exports['__napi_register__BindingOutputs_impl_95']?.() __napiInstance.exports['__napi_register__JsChangedOutputs_struct_96']?.() +======= + __napiInstance.exports['__napi_register__TypeScriptBindingOptions_struct_0']?.() + __napiInstance.exports['__napi_register__ReactBindingOptions_struct_1']?.() +<<<<<<< HEAD + __napiInstance.exports['__napi_register__ReactRefreshBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_4']?.() + __napiInstance.exports['__napi_register__TransformOptions_struct_5']?.() + __napiInstance.exports['__napi_register__SourceMap_struct_6']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_7']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_8']?.() + __napiInstance.exports['__napi_register__isolated_declaration_9']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_10']?.() + __napiInstance.exports['__napi_register__transform_11']?.() +======= + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__TransformOptions_struct_4']?.() + __napiInstance.exports['__napi_register__SourceMap_struct_5']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_6']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_7']?.() + __napiInstance.exports['__napi_register__isolated_declaration_8']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_9']?.() + __napiInstance.exports['__napi_register__transform_10']?.() +>>>>>>> a6a9ca8d (feat: add hmr_rebuild api (#2137)) + __napiInstance.exports['__napi_register__Bundler_struct_0']?.() + __napiInstance.exports['__napi_register__Bundler_impl_6']?.() + __napiInstance.exports['__napi_register__BindingInjectImportNamed_struct_7']?.() + __napiInstance.exports['__napi_register__BindingInjectImportNamespace_struct_8']?.() + __napiInstance.exports['__napi_register__BindingInputItem_struct_9']?.() + __napiInstance.exports['__napi_register__BindingResolveOptions_struct_10']?.() + __napiInstance.exports['__napi_register__BindingTreeshake_struct_11']?.() + __napiInstance.exports['__napi_register__BindingInputOptions_struct_12']?.() +<<<<<<< HEAD + __napiInstance.exports['__napi_register__BindingAdvancedChunksOptions_struct_13']?.() + __napiInstance.exports['__napi_register__BindingMatchGroup_struct_14']?.() + __napiInstance.exports['__napi_register__BindingOutputOptions_struct_15']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_struct_16']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_impl_22']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolvedId_struct_23']?.() + __napiInstance.exports['__napi_register__BindingPluginOptions_struct_24']?.() + __napiInstance.exports['__napi_register__BindingPluginWithIndex_struct_25']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_struct_26']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_impl_28']?.() + __napiInstance.exports['__napi_register__BindingAssetSource_struct_29']?.() + __napiInstance.exports['__napi_register__BindingEmittedAsset_struct_30']?.() + __napiInstance.exports['__napi_register__BindingGeneralHookFilter_struct_31']?.() + __napiInstance.exports['__napi_register__BindingTransformHookFilter_struct_32']?.() + __napiInstance.exports['__napi_register__BindingHookLoadOutput_struct_33']?.() + __napiInstance.exports['__napi_register__BindingHookRenderChunkOutput_struct_34']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdExtraArgs_struct_35']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdOutput_struct_36']?.() + __napiInstance.exports['__napi_register__BindingHookSideEffects_37']?.() + __napiInstance.exports['__napi_register__BindingHookTransformOutput_struct_38']?.() + __napiInstance.exports['__napi_register__BindingStringOrRegex_struct_39']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolveOptions_struct_40']?.() + __napiInstance.exports['__napi_register__BindingTransformHookExtraArgs_struct_41']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPlugin_struct_42']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPluginName_43']?.() + __napiInstance.exports['__napi_register__BindingGlobImportPluginConfig_struct_44']?.() + __napiInstance.exports['__napi_register__BindingManifestPluginConfig_struct_45']?.() + __napiInstance.exports['__napi_register__BindingModulePreloadPolyfillPluginConfig_struct_46']?.() + __napiInstance.exports['__napi_register__BindingJsonPluginConfig_struct_47']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_48']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_49']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_50']?.() + __napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_51']?.() + __napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_52']?.() + __napiInstance.exports['__napi_register__BindingPluginOrder_53']?.() + __napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_54']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_55']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_57']?.() + __napiInstance.exports['__napi_register__register_plugins_58']?.() + __napiInstance.exports['__napi_register__BindingLog_struct_59']?.() + __napiInstance.exports['__napi_register__BindingLogLevel_60']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_struct_61']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_impl_63']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_struct_64']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_impl_70']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_struct_71']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_impl_89']?.() + __napiInstance.exports['__napi_register__BindingOutputs_struct_90']?.() + __napiInstance.exports['__napi_register__BindingOutputs_impl_94']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_struct_95']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_impl_98']?.() + __napiInstance.exports['__napi_register__PreRenderedChunk_struct_99']?.() + __napiInstance.exports['__napi_register__RenderedChunk_struct_100']?.() + __napiInstance.exports['__napi_register__BindingRenderedModule_struct_101']?.() + __napiInstance.exports['__napi_register__AliasItem_struct_102']?.() + __napiInstance.exports['__napi_register__BindingSourcemap_struct_103']?.() + __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_104']?.() +======= + __napiInstance.exports['__napi_register__BindingOutputOptions_struct_13']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_struct_14']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_impl_20']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolvedId_struct_21']?.() + __napiInstance.exports['__napi_register__BindingPluginOptions_struct_22']?.() + __napiInstance.exports['__napi_register__BindingPluginWithIndex_struct_23']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_struct_24']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_impl_26']?.() + __napiInstance.exports['__napi_register__BindingAssetSource_struct_27']?.() + __napiInstance.exports['__napi_register__BindingEmittedAsset_struct_28']?.() + __napiInstance.exports['__napi_register__BindingGeneralHookFilter_struct_29']?.() + __napiInstance.exports['__napi_register__BindingTransformHookFilter_struct_30']?.() + __napiInstance.exports['__napi_register__BindingHookLoadOutput_struct_31']?.() + __napiInstance.exports['__napi_register__BindingHookRenderChunkOutput_struct_32']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdExtraArgs_struct_33']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdOutput_struct_34']?.() + __napiInstance.exports['__napi_register__BindingHookSideEffects_35']?.() + __napiInstance.exports['__napi_register__BindingHookTransformOutput_struct_36']?.() + __napiInstance.exports['__napi_register__BindingStringOrRegex_struct_37']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolveOptions_struct_38']?.() + __napiInstance.exports['__napi_register__BindingTransformHookExtraArgs_struct_39']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPlugin_struct_40']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPluginName_41']?.() + __napiInstance.exports['__napi_register__BindingGlobImportPluginConfig_struct_42']?.() + __napiInstance.exports['__napi_register__BindingManifestPluginConfig_struct_43']?.() + __napiInstance.exports['__napi_register__BindingModulePreloadPolyfillPluginConfig_struct_44']?.() + __napiInstance.exports['__napi_register__BindingJsonPluginConfig_struct_45']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_46']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_47']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_48']?.() + __napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_49']?.() + __napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_50']?.() + __napiInstance.exports['__napi_register__BindingPluginOrder_51']?.() + __napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_52']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_53']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_55']?.() + __napiInstance.exports['__napi_register__register_plugins_56']?.() + __napiInstance.exports['__napi_register__BindingLog_struct_57']?.() + __napiInstance.exports['__napi_register__BindingLogLevel_58']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_struct_59']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_impl_61']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_struct_62']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_impl_68']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_struct_69']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_impl_87']?.() + __napiInstance.exports['__napi_register__BindingOutputs_struct_88']?.() + __napiInstance.exports['__napi_register__BindingOutputs_impl_92']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_struct_93']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_impl_96']?.() +>>>>>>> dbcce9828 (feat: add hmr_rebuild api (#2137)) __napiInstance.exports['__napi_register__PreRenderedChunk_struct_97']?.() __napiInstance.exports['__napi_register__RenderedChunk_struct_98']?.() __napiInstance.exports['__napi_register__BindingRenderedModule_struct_99']?.() __napiInstance.exports['__napi_register__AliasItem_struct_100']?.() +<<<<<<< HEAD __napiInstance.exports['__napi_register__ExtensionAliasItem_struct_101']?.() __napiInstance.exports['__napi_register__BindingSourcemap_struct_102']?.() __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_103']?.() __napiInstance.exports['__napi_register__BindingWatcher_struct_104']?.() __napiInstance.exports['__napi_register__BindingWatcher_impl_107']?.() __napiInstance.exports['__napi_register__BindingWatcherEvent_108']?.() +======= + __napiInstance.exports['__napi_register__BindingSourcemap_struct_101']?.() + __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_102']?.() +>>>>>>> a6a9ca8d (feat: add hmr_rebuild api (#2137)) +>>>>>>> dbcce9828 (feat: add hmr_rebuild api (#2137)) } export const BindingLog = __napiModule.exports.BindingLog export const BindingModuleInfo = __napiModule.exports.BindingModuleInfo diff --git a/packages/rolldown/src/rolldown-binding.wasi.cjs b/packages/rolldown/src/rolldown-binding.wasi.cjs index 58629d1ccdee..36dd56c5e9d7 100644 --- a/packages/rolldown/src/rolldown-binding.wasi.cjs +++ b/packages/rolldown/src/rolldown-binding.wasi.cjs @@ -82,6 +82,7 @@ const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule }) function __napi_rs_initialize_modules(__napiInstance) { +<<<<<<< HEAD __napiInstance.exports['__napi_register__SourceMap_struct_0']?.() __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_1']?.() __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_2']?.() @@ -156,16 +157,164 @@ function __napi_rs_initialize_modules(__napiInstance) { __napiInstance.exports['__napi_register__BindingOutputs_struct_92']?.() __napiInstance.exports['__napi_register__BindingOutputs_impl_95']?.() __napiInstance.exports['__napi_register__JsChangedOutputs_struct_96']?.() +======= + __napiInstance.exports['__napi_register__TypeScriptBindingOptions_struct_0']?.() + __napiInstance.exports['__napi_register__ReactBindingOptions_struct_1']?.() +<<<<<<< HEAD + __napiInstance.exports['__napi_register__ReactRefreshBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_4']?.() + __napiInstance.exports['__napi_register__TransformOptions_struct_5']?.() + __napiInstance.exports['__napi_register__SourceMap_struct_6']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_7']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_8']?.() + __napiInstance.exports['__napi_register__isolated_declaration_9']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_10']?.() + __napiInstance.exports['__napi_register__transform_11']?.() +======= + __napiInstance.exports['__napi_register__ArrowFunctionsBindingOptions_struct_2']?.() + __napiInstance.exports['__napi_register__ES2015BindingOptions_struct_3']?.() + __napiInstance.exports['__napi_register__TransformOptions_struct_4']?.() + __napiInstance.exports['__napi_register__SourceMap_struct_5']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsResult_struct_6']?.() + __napiInstance.exports['__napi_register__IsolatedDeclarationsOptions_struct_7']?.() + __napiInstance.exports['__napi_register__isolated_declaration_8']?.() + __napiInstance.exports['__napi_register__TransformResult_struct_9']?.() + __napiInstance.exports['__napi_register__transform_10']?.() +>>>>>>> a6a9ca8d (feat: add hmr_rebuild api (#2137)) + __napiInstance.exports['__napi_register__Bundler_struct_0']?.() + __napiInstance.exports['__napi_register__Bundler_impl_6']?.() + __napiInstance.exports['__napi_register__BindingInjectImportNamed_struct_7']?.() + __napiInstance.exports['__napi_register__BindingInjectImportNamespace_struct_8']?.() + __napiInstance.exports['__napi_register__BindingInputItem_struct_9']?.() + __napiInstance.exports['__napi_register__BindingResolveOptions_struct_10']?.() + __napiInstance.exports['__napi_register__BindingTreeshake_struct_11']?.() + __napiInstance.exports['__napi_register__BindingInputOptions_struct_12']?.() +<<<<<<< HEAD + __napiInstance.exports['__napi_register__BindingAdvancedChunksOptions_struct_13']?.() + __napiInstance.exports['__napi_register__BindingMatchGroup_struct_14']?.() + __napiInstance.exports['__napi_register__BindingOutputOptions_struct_15']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_struct_16']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_impl_22']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolvedId_struct_23']?.() + __napiInstance.exports['__napi_register__BindingPluginOptions_struct_24']?.() + __napiInstance.exports['__napi_register__BindingPluginWithIndex_struct_25']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_struct_26']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_impl_28']?.() + __napiInstance.exports['__napi_register__BindingAssetSource_struct_29']?.() + __napiInstance.exports['__napi_register__BindingEmittedAsset_struct_30']?.() + __napiInstance.exports['__napi_register__BindingGeneralHookFilter_struct_31']?.() + __napiInstance.exports['__napi_register__BindingTransformHookFilter_struct_32']?.() + __napiInstance.exports['__napi_register__BindingHookLoadOutput_struct_33']?.() + __napiInstance.exports['__napi_register__BindingHookRenderChunkOutput_struct_34']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdExtraArgs_struct_35']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdOutput_struct_36']?.() + __napiInstance.exports['__napi_register__BindingHookSideEffects_37']?.() + __napiInstance.exports['__napi_register__BindingHookTransformOutput_struct_38']?.() + __napiInstance.exports['__napi_register__BindingStringOrRegex_struct_39']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolveOptions_struct_40']?.() + __napiInstance.exports['__napi_register__BindingTransformHookExtraArgs_struct_41']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPlugin_struct_42']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPluginName_43']?.() + __napiInstance.exports['__napi_register__BindingGlobImportPluginConfig_struct_44']?.() + __napiInstance.exports['__napi_register__BindingManifestPluginConfig_struct_45']?.() + __napiInstance.exports['__napi_register__BindingModulePreloadPolyfillPluginConfig_struct_46']?.() + __napiInstance.exports['__napi_register__BindingJsonPluginConfig_struct_47']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_48']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_49']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_50']?.() + __napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_51']?.() + __napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_52']?.() + __napiInstance.exports['__napi_register__BindingPluginOrder_53']?.() + __napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_54']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_55']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_57']?.() + __napiInstance.exports['__napi_register__register_plugins_58']?.() + __napiInstance.exports['__napi_register__BindingLog_struct_59']?.() + __napiInstance.exports['__napi_register__BindingLogLevel_60']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_struct_61']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_impl_63']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_struct_64']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_impl_70']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_struct_71']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_impl_89']?.() + __napiInstance.exports['__napi_register__BindingOutputs_struct_90']?.() + __napiInstance.exports['__napi_register__BindingOutputs_impl_94']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_struct_95']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_impl_98']?.() + __napiInstance.exports['__napi_register__PreRenderedChunk_struct_99']?.() + __napiInstance.exports['__napi_register__RenderedChunk_struct_100']?.() + __napiInstance.exports['__napi_register__BindingRenderedModule_struct_101']?.() + __napiInstance.exports['__napi_register__AliasItem_struct_102']?.() + __napiInstance.exports['__napi_register__BindingSourcemap_struct_103']?.() + __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_104']?.() +======= + __napiInstance.exports['__napi_register__BindingOutputOptions_struct_13']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_struct_14']?.() + __napiInstance.exports['__napi_register__BindingPluginContext_impl_20']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolvedId_struct_21']?.() + __napiInstance.exports['__napi_register__BindingPluginOptions_struct_22']?.() + __napiInstance.exports['__napi_register__BindingPluginWithIndex_struct_23']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_struct_24']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginContext_impl_26']?.() + __napiInstance.exports['__napi_register__BindingAssetSource_struct_27']?.() + __napiInstance.exports['__napi_register__BindingEmittedAsset_struct_28']?.() + __napiInstance.exports['__napi_register__BindingGeneralHookFilter_struct_29']?.() + __napiInstance.exports['__napi_register__BindingTransformHookFilter_struct_30']?.() + __napiInstance.exports['__napi_register__BindingHookLoadOutput_struct_31']?.() + __napiInstance.exports['__napi_register__BindingHookRenderChunkOutput_struct_32']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdExtraArgs_struct_33']?.() + __napiInstance.exports['__napi_register__BindingHookResolveIdOutput_struct_34']?.() + __napiInstance.exports['__napi_register__BindingHookSideEffects_35']?.() + __napiInstance.exports['__napi_register__BindingHookTransformOutput_struct_36']?.() + __napiInstance.exports['__napi_register__BindingStringOrRegex_struct_37']?.() + __napiInstance.exports['__napi_register__BindingPluginContextResolveOptions_struct_38']?.() + __napiInstance.exports['__napi_register__BindingTransformHookExtraArgs_struct_39']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPlugin_struct_40']?.() + __napiInstance.exports['__napi_register__BindingBuiltinPluginName_41']?.() + __napiInstance.exports['__napi_register__BindingGlobImportPluginConfig_struct_42']?.() + __napiInstance.exports['__napi_register__BindingManifestPluginConfig_struct_43']?.() + __napiInstance.exports['__napi_register__BindingModulePreloadPolyfillPluginConfig_struct_44']?.() + __napiInstance.exports['__napi_register__BindingJsonPluginConfig_struct_45']?.() + __napiInstance.exports['__napi_register__BindingTransformPluginConfig_struct_46']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginConfig_struct_47']?.() + __napiInstance.exports['__napi_register__BindingAliasPluginAlias_struct_48']?.() + __napiInstance.exports['__napi_register__BindingBuildImportAnalysisPluginConfig_struct_49']?.() + __napiInstance.exports['__napi_register__BindingReplacePluginConfig_struct_50']?.() + __napiInstance.exports['__napi_register__BindingPluginOrder_51']?.() + __napiInstance.exports['__napi_register__BindingPluginHookMeta_struct_52']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_struct_53']?.() + __napiInstance.exports['__napi_register__ParallelJsPluginRegistry_impl_55']?.() + __napiInstance.exports['__napi_register__register_plugins_56']?.() + __napiInstance.exports['__napi_register__BindingLog_struct_57']?.() + __napiInstance.exports['__napi_register__BindingLogLevel_58']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_struct_59']?.() + __napiInstance.exports['__napi_register__BindingModuleInfo_impl_61']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_struct_62']?.() + __napiInstance.exports['__napi_register__BindingOutputAsset_impl_68']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_struct_69']?.() + __napiInstance.exports['__napi_register__BindingOutputChunk_impl_87']?.() + __napiInstance.exports['__napi_register__BindingOutputs_struct_88']?.() + __napiInstance.exports['__napi_register__BindingOutputs_impl_92']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_struct_93']?.() + __napiInstance.exports['__napi_register__FinalBindingOutputs_impl_96']?.() +>>>>>>> dbcce9828 (feat: add hmr_rebuild api (#2137)) __napiInstance.exports['__napi_register__PreRenderedChunk_struct_97']?.() __napiInstance.exports['__napi_register__RenderedChunk_struct_98']?.() __napiInstance.exports['__napi_register__BindingRenderedModule_struct_99']?.() __napiInstance.exports['__napi_register__AliasItem_struct_100']?.() +<<<<<<< HEAD __napiInstance.exports['__napi_register__ExtensionAliasItem_struct_101']?.() __napiInstance.exports['__napi_register__BindingSourcemap_struct_102']?.() __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_103']?.() __napiInstance.exports['__napi_register__BindingWatcher_struct_104']?.() __napiInstance.exports['__napi_register__BindingWatcher_impl_107']?.() __napiInstance.exports['__napi_register__BindingWatcherEvent_108']?.() +======= + __napiInstance.exports['__napi_register__BindingSourcemap_struct_101']?.() + __napiInstance.exports['__napi_register__BindingJsonSourcemap_struct_102']?.() +>>>>>>> a6a9ca8d (feat: add hmr_rebuild api (#2137)) +>>>>>>> dbcce9828 (feat: add hmr_rebuild api (#2137)) } module.exports.BindingLog = __napiModule.exports.BindingLog module.exports.BindingModuleInfo = __napiModule.exports.BindingModuleInfo diff --git a/packages/rolldown/src/rolldown-build.ts b/packages/rolldown/src/rolldown-build.ts index 6280c6f9093d..065ee982c35c 100644 --- a/packages/rolldown/src/rolldown-build.ts +++ b/packages/rolldown/src/rolldown-build.ts @@ -45,6 +45,10 @@ export class RolldownBuild { await stopWorkers?.() await bundler.close() } + + async experimental_hmr_rebuild(changedFiles: string[]): Promise { + await this.#bundler!.hmrRebuild(changedFiles) + } } function _assert() { From e4ef98f6a1eca919e3b4fc7be718fee58567df77 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 4 Sep 2024 13:56:20 +0800 Subject: [PATCH 02/44] feat: store pervious build module info and using it at next hmr module loader (#2142) Here create new `HmrModuleLoader` to scan modules which imported from changed modules. The `changed modules` will direct spawn new task using previous module id. The `HmrIntermediateNormalModules` comes from the previous module info, so here we could to re-using more logic from `ModuleLoader`. --- crates/rolldown/src/bundler.rs | 42 ++- crates/rolldown/src/bundler_builder.rs | 7 +- .../src/module_loader/hmr_module_loader.rs | 298 ++++++++++++++++++ crates/rolldown/src/module_loader/mod.rs | 1 + .../src/module_loader/module_loader.rs | 2 + crates/rolldown/src/stages/link_stage/mod.rs | 7 +- crates/rolldown/src/stages/scan_stage.rs | 6 +- crates/rolldown/src/types/hmr_output.rs | 7 + crates/rolldown/src/types/mod.rs | 1 + crates/rolldown_binding/src/bundler.rs | 10 + 10 files changed, 374 insertions(+), 7 deletions(-) create mode 100644 crates/rolldown/src/module_loader/hmr_module_loader.rs create mode 100644 crates/rolldown/src/types/hmr_output.rs diff --git a/crates/rolldown/src/bundler.rs b/crates/rolldown/src/bundler.rs index c44aaeda2a8c..30c79b00bbd9 100644 --- a/crates/rolldown/src/bundler.rs +++ b/crates/rolldown/src/bundler.rs @@ -4,20 +4,24 @@ use super::stages::{ }; use crate::{ bundler_builder::BundlerBuilder, + module_loader::hmr_module_loader::HmrModuleLoader, stages::{generate_stage::GenerateStage, scan_stage::ScanStage}, - types::bundle_output::BundleOutput, + type_alias::IndexEcmaAst, + types::{bundle_output::BundleOutput, hmr_output::HmrOutput}, watcher::watcher::{wait_for_change, Watcher}, BundlerOptions, SharedOptions, SharedResolver, }; use anyhow::Result; +use arcstr::ArcStr; use arcstr::ArcStr; -use rolldown_common::{NormalizedBundlerOptions, SharedFileEmitter}; +use rolldown_common::{ModuleIdx, ModuleTable, NormalizedBundlerOptions, SharedFileEmitter}; use rolldown_error::{BuildDiagnostic, BuildResult}; use rolldown_fs::{FileSystem, OsFileSystem}; use rolldown_plugin::{ HookBuildEndArgs, HookRenderErrorArgs, SharedPluginDriver, __inner::SharedPluginable, }; +use rustc_hash::FxHashMap; use std::sync::Arc; use tokio::sync::Mutex; use tracing_chrome::FlushGuard; @@ -30,6 +34,9 @@ pub struct Bundler { pub(crate) resolver: SharedResolver, pub(crate) file_emitter: SharedFileEmitter, pub(crate) _log_guard: Option, + pub(crate) previous_module_table: ModuleTable, + pub(crate) previous_module_id_to_modules: FxHashMap, + pub(crate) pervious_index_ecma_ast: IndexEcmaAst, } impl Bundler { @@ -139,8 +146,30 @@ impl Bundler { } #[allow(clippy::unused_async)] - pub async fn hmr_rebuild(&mut self, _changed_files: Vec) -> Result<()> { - unimplemented!() + pub async fn hmr_rebuild(&mut self, changed_files: Vec) -> Result { + let hmr_module_loader = HmrModuleLoader::new( + Arc::clone(&self.options), + Arc::clone(&self.plugin_driver), + self.fs, + Arc::clone(&self.resolver), + std::mem::take(&mut self.previous_module_id_to_modules), + std::mem::take(&mut self.previous_module_table), + std::mem::take(&mut self.pervious_index_ecma_ast), + )?; + + let output = match hmr_module_loader.fetch_changed_modules(changed_files).await? { + Ok(output) => output, + Err(errors) => { + return Ok(HmrOutput { warnings: vec![], errors }); + } + }; + + // store last build modules info + self.previous_module_table = output.module_table; + self.previous_module_id_to_modules = output.module_id_to_modules; + self.pervious_index_ecma_ast = output.index_ecma_ast; + + Ok(HmrOutput { warnings: output.warnings, errors: vec![] }) } async fn try_build(&mut self) -> Result> { @@ -199,6 +228,11 @@ impl Bundler { self.plugin_driver.generate_bundle(&mut output.assets, is_write).await?; + // store last build modules info + self.previous_module_table = link_stage_output.module_table; + self.previous_module_id_to_modules = link_stage_output.module_id_to_modules; + self.pervious_index_ecma_ast = link_stage_output.ast_table; + output.watch_files = { let mut files = link_stage_output .module_table diff --git a/crates/rolldown/src/bundler_builder.rs b/crates/rolldown/src/bundler_builder.rs index d7b929368413..539029660045 100644 --- a/crates/rolldown/src/bundler_builder.rs +++ b/crates/rolldown/src/bundler_builder.rs @@ -1,9 +1,11 @@ use std::sync::Arc; -use rolldown_common::FileEmitter; +use oxc::index::IndexVec; +use rolldown_common::{FileEmitter, ModuleTable}; use rolldown_fs::OsFileSystem; use rolldown_plugin::{PluginDriver, __inner::SharedPluginable}; use rolldown_resolver::Resolver; +use rustc_hash::FxHashMap; use crate::{ utils::{ @@ -42,6 +44,9 @@ impl BundlerBuilder { options, fs: OsFileSystem, _log_guard: maybe_guard, + previous_module_table: ModuleTable::default(), + previous_module_id_to_modules: FxHashMap::default(), + pervious_index_ecma_ast: IndexVec::default(), } } diff --git a/crates/rolldown/src/module_loader/hmr_module_loader.rs b/crates/rolldown/src/module_loader/hmr_module_loader.rs new file mode 100644 index 000000000000..cbfbb1f2eb17 --- /dev/null +++ b/crates/rolldown/src/module_loader/hmr_module_loader.rs @@ -0,0 +1,298 @@ +use super::module_task::{ModuleTask, ModuleTaskOwner}; +use super::task_context::TaskContextMeta; +use super::task_result::NormalModuleTaskResult; +use super::Msg; +use crate::module_loader::task_context::TaskContext; +use crate::type_alias::IndexEcmaAst; +use crate::types::symbols::Symbols; +use arcstr::ArcStr; +use oxc::index::IndexVec; +use oxc::minifier::ReplaceGlobalDefinesConfig; +use oxc::span::Span; +use rolldown_common::side_effects::{DeterminedSideEffects, HookSideEffects}; +use rolldown_common::{ + ExternalModule, ImportRecordIdx, Module, ModuleDefFormat, ModuleIdx, ModuleTable, ResolvedId, +}; +use rolldown_error::{BuildDiagnostic, DiagnosableResult}; +use rolldown_fs::OsFileSystem; +use rolldown_plugin::SharedPluginDriver; +use rustc_hash::FxHashMap; +use std::sync::Arc; + +use crate::{SharedOptions, SharedResolver}; + +pub struct HmrIntermediateNormalModules { + pub modules: IndexVec>, + pub index_ecma_ast: IndexEcmaAst, +} + +impl HmrIntermediateNormalModules { + pub fn new(previous_module_table: ModuleTable, index_ecma_ast: IndexEcmaAst) -> Self { + Self { + modules: previous_module_table.modules.into_iter().map(Some).collect::>(), + index_ecma_ast, + } + } + + pub fn alloc_ecma_module_idx(&mut self, symbols: &mut Symbols) -> ModuleIdx { + let id = self.modules.push(None); + symbols.alloc_one(); + id + } +} + +pub struct HmrModuleLoader { + options: SharedOptions, + shared_context: Arc, + rx: tokio::sync::mpsc::Receiver, + visited: FxHashMap, + remaining: u32, + intermediate_normal_modules: HmrIntermediateNormalModules, + symbols: Symbols, +} + +pub struct HmrModuleLoaderOutput { + // Stored all modules + pub module_table: ModuleTable, + pub module_id_to_modules: FxHashMap, + pub index_ecma_ast: IndexEcmaAst, + // pub symbols: Symbols, + pub warnings: Vec, +} + +impl HmrModuleLoader { + pub fn new( + options: SharedOptions, + plugin_driver: SharedPluginDriver, + fs: OsFileSystem, + resolver: SharedResolver, + previous_module_id_to_modules: FxHashMap, + previous_module_table: ModuleTable, + pervious_index_ecma_ast: IndexEcmaAst, + ) -> anyhow::Result { + // 1024 should be enough for most cases + // over 1024 pending tasks are insane + let (tx, rx) = tokio::sync::mpsc::channel::(1024); + + let meta = TaskContextMeta { + replace_global_define_config: if options.define.is_empty() { + None + } else { + Some(ReplaceGlobalDefinesConfig::new(&options.define).map_err(|errs| { + // TODO: maybe we should give better diagnostics here. since oxc return + // `Vec` + anyhow::format_err!( + "Failed to generate defines config from {:?}. Got {:#?}", + options.define, + errs + ) + })?) + }, + }; + let common_data = Arc::new(TaskContext { + options: Arc::clone(&options), + tx, + resolver, + fs, + plugin_driver, + meta, + }); + + let intermediate_normal_modules = + HmrIntermediateNormalModules::new(previous_module_table, pervious_index_ecma_ast); + let symbols = Symbols::default(); + + Ok(Self { + shared_context: common_data, + rx, + options, + visited: previous_module_id_to_modules, + remaining: 0, + intermediate_normal_modules, + symbols, + }) + } + + fn try_spawn_new_task( + &mut self, + resolved_id: ResolvedId, + owner: Option, + ) -> ModuleIdx { + match self.visited.entry(resolved_id.id.clone()) { + std::collections::hash_map::Entry::Occupied(visited) => *visited.get(), + std::collections::hash_map::Entry::Vacant(not_visited) => { + if resolved_id.is_external { + let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(&mut self.symbols); + not_visited.insert(idx); + let external_module_side_effects = if let Some(hook_side_effects) = + resolved_id.side_effects + { + match hook_side_effects { + HookSideEffects::True => DeterminedSideEffects::UserDefined(true), + HookSideEffects::False => DeterminedSideEffects::UserDefined(false), + HookSideEffects::NoTreeshake => DeterminedSideEffects::NoTreeshake, + } + } else { + match self.options.treeshake { + rolldown_common::TreeshakeOptions::Boolean(false) => { + DeterminedSideEffects::NoTreeshake + } + rolldown_common::TreeshakeOptions::Boolean(true) => unreachable!(), + rolldown_common::TreeshakeOptions::Option(ref opt) => match opt.module_side_effects { + rolldown_common::ModuleSideEffects::Boolean(false) => { + DeterminedSideEffects::UserDefined(false) + } + _ => DeterminedSideEffects::NoTreeshake, + }, + } + }; + let ext = + ExternalModule::new(idx, ArcStr::clone(&resolved_id.id), external_module_side_effects); + self.intermediate_normal_modules.modules[idx] = Some(ext.into()); + idx + } else { + let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(&mut self.symbols); + not_visited.insert(idx); + self.remaining += 1; + + let task = ModuleTask::new(Arc::clone(&self.shared_context), idx, resolved_id, owner); + #[cfg(target_family = "wasm")] + { + let handle = tokio::runtime::Handle::current(); + // could not block_on/spawn the main thread in WASI + std::thread::spawn(move || { + handle.spawn(task.run()); + }); + } + #[cfg(not(target_family = "wasm"))] + tokio::spawn(task.run()); + idx + } + } + } + } + + #[tracing::instrument(level = "debug", skip_all)] + pub async fn fetch_changed_modules( + mut self, + changed_modules: Vec, + ) -> anyhow::Result> { + if self.options.input.is_empty() { + return Err(anyhow::format_err!("You must supply options.input to rolldown")); + } + + // spawn valid changed modules + changed_modules + .into_iter() + .filter_map(|m| self.visited.get(m.as_str()).map(|idx| (m, idx))) + .for_each(|(m, idx)| { + self.remaining += 1; + + let task = ModuleTask::new( + Arc::clone(&self.shared_context), + *idx, + ResolvedId { + id: m.into(), + ignored: false, + module_def_format: ModuleDefFormat::Unknown, + is_external: false, + package_json: None, + side_effects: None, + }, + None, + ); + #[cfg(target_family = "wasm")] + { + let handle = tokio::runtime::Handle::current(); + // could not block_on/spawn the main thread in WASI + std::thread::spawn(move || { + handle.spawn(task.run()); + }); + } + #[cfg(not(target_family = "wasm"))] + tokio::spawn(task.run()); + }); + + let mut errors = vec![]; + let mut all_warnings: Vec = vec![]; + + while self.remaining > 0 { + let Some(msg) = self.rx.recv().await else { + break; + }; + match msg { + Msg::NormalModuleDone(task_result) => { + let NormalModuleTaskResult { + module_idx, + resolved_deps, + mut module, + raw_import_records, + warnings, + ecma_related, + } = task_result; + all_warnings.extend(warnings); + + let import_records: IndexVec = + raw_import_records + .into_iter() + .zip(resolved_deps) + .map(|(raw_rec, info)| { + let ecma_module = module.as_ecma().unwrap(); + let owner = ModuleTaskOwner::new( + ecma_module.source.clone(), + ecma_module.stable_id.as_str().into(), + Span::new(raw_rec.module_request_start, raw_rec.module_request_end()), + ); + let id = self.try_spawn_new_task(info, Some(owner)); + raw_rec.into_import_record(id) + }) + .collect::>(); + + module.set_import_records(import_records); + if let Some((ast, ast_symbol)) = ecma_related { + let ast_idx = self.intermediate_normal_modules.index_ecma_ast.push((ast, module.idx())); + module.set_ecma_ast_idx(ast_idx); + self.symbols.add_ast_symbols(module_idx, ast_symbol); + } + self.intermediate_normal_modules.modules[module_idx] = Some(module); + } + Msg::RuntimeNormalModuleDone(_) => { + unreachable!("Runtime module should not be done at hmr module loader"); + } + Msg::BuildErrors(e) => { + errors.extend(e); + } + // Expect cast to u32, since we are not going to have more than 2^32 tasks, or the + // `remaining` will overflow + #[allow(clippy::cast_possible_truncation)] + Msg::Panics(err) => { + // `self.remaining -1` for the panic task it self + self.remaining -= 1; + // gracefully shutdown all working thread, only receive and do not spawn + while self.remaining > 0 { + let mut task = Vec::with_capacity(self.remaining as usize); + let received = self.rx.recv_many(&mut task, self.remaining as usize).await; + self.remaining -= received as u32; + } + return Err(err); + } + } + self.remaining -= 1; + } + + if !errors.is_empty() { + return Ok(Err(errors)); + } + + let modules: IndexVec = + self.intermediate_normal_modules.modules.into_iter().flatten().collect(); + + Ok(Ok(HmrModuleLoaderOutput { + module_table: ModuleTable { modules }, + module_id_to_modules: self.visited, + // symbols: self.symbols, + index_ecma_ast: self.intermediate_normal_modules.index_ecma_ast, + warnings: all_warnings, + })) + } +} diff --git a/crates/rolldown/src/module_loader/mod.rs b/crates/rolldown/src/module_loader/mod.rs index f0594f3f1108..02300a7d5a60 100644 --- a/crates/rolldown/src/module_loader/mod.rs +++ b/crates/rolldown/src/module_loader/mod.rs @@ -1,3 +1,4 @@ +pub mod hmr_module_loader; pub mod module_loader; mod module_task; mod runtime_module_task; diff --git a/crates/rolldown/src/module_loader/module_loader.rs b/crates/rolldown/src/module_loader/module_loader.rs index be99934e5b07..40d3a96a3664 100644 --- a/crates/rolldown/src/module_loader/module_loader.rs +++ b/crates/rolldown/src/module_loader/module_loader.rs @@ -62,6 +62,7 @@ pub struct ModuleLoader { pub struct ModuleLoaderOutput { // Stored all modules pub module_table: ModuleTable, + pub module_id_to_modules: FxHashMap, pub index_ecma_ast: IndexEcmaAst, pub symbol_ref_db: SymbolRefDb, // Entries that user defined + dynamic import entries @@ -344,6 +345,7 @@ impl ModuleLoader { Ok(Ok(ModuleLoaderOutput { module_table: ModuleTable { modules }, + module_id_to_modules: self.visited, symbol_ref_db: self.symbol_ref_db, index_ecma_ast: self.intermediate_normal_modules.index_ecma_ast, entry_points, diff --git a/crates/rolldown/src/stages/link_stage/mod.rs b/crates/rolldown/src/stages/link_stage/mod.rs index f44f8d98f8e9..c59353af463f 100644 --- a/crates/rolldown/src/stages/link_stage/mod.rs +++ b/crates/rolldown/src/stages/link_stage/mod.rs @@ -1,6 +1,7 @@ use std::{ptr::addr_of, sync::Mutex}; use append_only_vec::AppendOnlyVec; +use arcstr::ArcStr; use oxc::index::IndexVec; use rolldown_common::{ EntryPoint, ExportsKind, ImportKind, ImportRecordIdx, ImportRecordMeta, Module, ModuleIdx, @@ -12,7 +13,7 @@ use rolldown_utils::{ index_vec_ext::IndexVecExt, rayon::{IntoParallelRefIterator, ParallelIterator}, }; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ runtime::RuntimeModuleBrief, @@ -34,6 +35,7 @@ mod wrapping; #[derive(Debug)] pub struct LinkStageOutput { pub module_table: ModuleTable, + pub module_id_to_modules: FxHashMap, pub entries: Vec, pub ast_table: IndexEcmaAst, // pub sorted_modules: Vec, @@ -48,6 +50,7 @@ pub struct LinkStageOutput { #[derive(Debug)] pub struct LinkStage<'a> { pub module_table: ModuleTable, + pub module_id_to_modules: FxHashMap, pub entries: Vec, pub symbols: SymbolRefDb, pub runtime: RuntimeModuleBrief, @@ -89,6 +92,7 @@ impl<'a> LinkStage<'a> { }) .collect::>(), module_table: scan_stage_output.module_table, + module_id_to_modules: scan_stage_output.module_id_to_modules, entries: scan_stage_output.entry_points, symbols: scan_stage_output.symbol_ref_db, runtime: scan_stage_output.runtime, @@ -116,6 +120,7 @@ impl<'a> LinkStage<'a> { LinkStageOutput { module_table: self.module_table, + module_id_to_modules: self.module_id_to_modules, entries: self.entries, // sorted_modules: self.sorted_modules, metas: self.metas, diff --git a/crates/rolldown/src/stages/scan_stage.rs b/crates/rolldown/src/stages/scan_stage.rs index f91430b4df88..44823363f6ab 100644 --- a/crates/rolldown/src/stages/scan_stage.rs +++ b/crates/rolldown/src/stages/scan_stage.rs @@ -3,11 +3,12 @@ use std::sync::Arc; use anyhow::Result; use arcstr::ArcStr; use futures::future::join_all; -use rolldown_common::{EntryPoint, ImportKind, ModuleTable, ResolvedId, SymbolRefDb}; +use rolldown_common::{EntryPoint, ImportKind, ModuleIdx, ModuleTable, ResolvedId, SymbolRefDb}; use rolldown_error::{BuildDiagnostic, BuildResult}; use rolldown_fs::OsFileSystem; use rolldown_plugin::SharedPluginDriver; use rolldown_resolver::ResolveError; +use rustc_hash::FxHashMap; use crate::{ module_loader::{module_loader::ModuleLoaderOutput, ModuleLoader}, @@ -27,6 +28,7 @@ pub struct ScanStage { #[derive(Debug)] pub struct ScanStageOutput { pub module_table: ModuleTable, + pub module_id_to_modules: FxHashMap, pub index_ecma_ast: IndexEcmaAst, pub entry_points: Vec, pub symbol_ref_db: SymbolRefDb, @@ -72,6 +74,7 @@ impl ScanStage { runtime, warnings, index_ecma_ast, + module_id_to_modules, } = match module_loader.fetch_all_modules(user_entries).await? { Ok(output) => output, Err(errors) => { @@ -81,6 +84,7 @@ impl ScanStage { Ok(Ok(ScanStageOutput { module_table, + module_id_to_modules, entry_points, symbol_ref_db, runtime, diff --git a/crates/rolldown/src/types/hmr_output.rs b/crates/rolldown/src/types/hmr_output.rs new file mode 100644 index 000000000000..36c57e7a2a02 --- /dev/null +++ b/crates/rolldown/src/types/hmr_output.rs @@ -0,0 +1,7 @@ +use rolldown_error::BuildDiagnostic; + +#[derive(Default)] +pub struct HmrOutput { + pub warnings: Vec, + pub errors: Vec, +} diff --git a/crates/rolldown/src/types/mod.rs b/crates/rolldown/src/types/mod.rs index d50e6463859a..946e363494ee 100644 --- a/crates/rolldown/src/types/mod.rs +++ b/crates/rolldown/src/types/mod.rs @@ -5,6 +5,7 @@ pub mod bundle_output; pub mod bundler_fs; pub mod generator; +pub mod hmr_output; pub mod linking_metadata; pub mod module_factory; pub mod oxc_parse_type; diff --git a/crates/rolldown_binding/src/bundler.rs b/crates/rolldown_binding/src/bundler.rs index 588991cc7829..186ec062ce63 100644 --- a/crates/rolldown_binding/src/bundler.rs +++ b/crates/rolldown_binding/src/bundler.rs @@ -174,6 +174,16 @@ impl Bundler { Ok(BindingWatcher::new(watcher)) } + pub async fn hmr_rebuild_impl(&self, changed_files: Vec) -> napi::Result<()> { + let mut bundler_core = self.inner.try_lock().map_err(|_| { + napi::Error::from_reason("Failed to lock the bundler. Is another operation in progress?") + })?; + + let _ = bundler_core.hmr_rebuild(changed_files).await; + + Ok(()) + } + fn handle_errors(&self, errs: Vec) -> napi::Error { errs.into_iter().for_each(|err| { eprintln!( From dd0ff8994c9a8b2ec4eb514e0f67c4895f826902 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 4 Sep 2024 19:46:02 +0800 Subject: [PATCH 03/44] chore: add react hmr example (#2147) --- examples/react-hmr/App.jsx | 33 +++++++++++++++ examples/react-hmr/index.html | 1 + examples/react-hmr/main.js | 7 ++++ examples/react-hmr/package.json | 17 ++++++++ examples/react-hmr/rolldown.config.js | 15 +++++++ pnpm-lock.yaml | 59 ++++++++++++++++++++++++++- 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 examples/react-hmr/App.jsx create mode 100644 examples/react-hmr/index.html create mode 100644 examples/react-hmr/main.js create mode 100644 examples/react-hmr/package.json create mode 100644 examples/react-hmr/rolldown.config.js diff --git a/examples/react-hmr/App.jsx b/examples/react-hmr/App.jsx new file mode 100644 index 000000000000..4fc54805cb57 --- /dev/null +++ b/examples/react-hmr/App.jsx @@ -0,0 +1,33 @@ +import { useState } from 'react' + +function App() { + const [count, setCount] = useState(0) + return ( +
+
+

Hello Rolldown + React

+

+ +

+

+ Edit App.jsx and save to test HMR updates. +

+ + Learn React + +
+
+ ) +} + +export default App diff --git a/examples/react-hmr/index.html b/examples/react-hmr/index.html new file mode 100644 index 000000000000..6c217c628bf6 --- /dev/null +++ b/examples/react-hmr/index.html @@ -0,0 +1 @@ +
diff --git a/examples/react-hmr/main.js b/examples/react-hmr/main.js new file mode 100644 index 000000000000..d54c6dca08f6 --- /dev/null +++ b/examples/react-hmr/main.js @@ -0,0 +1,7 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' + +ReactDOM.createRoot(document.getElementById('app')).render( + React.createElement(App), +) diff --git a/examples/react-hmr/package.json b/examples/react-hmr/package.json new file mode 100644 index 000000000000..c2d28afe880d --- /dev/null +++ b/examples/react-hmr/package.json @@ -0,0 +1,17 @@ +{ + "name": "@vitejs/test-react", + "private": true, + "type": "module", + "scripts": { + "build": "rolldown --config ./rolldown.config.js" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@babel/plugin-transform-modules-commonjs": "7.24.8", + "@rollup/plugin-babel": "6.0.4", + "babel": "6.23.0" + } +} diff --git a/examples/react-hmr/rolldown.config.js b/examples/react-hmr/rolldown.config.js new file mode 100644 index 000000000000..3270ea9505c4 --- /dev/null +++ b/examples/react-hmr/rolldown.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'rolldown' +import { babel } from '@rollup/plugin-babel' + +export default defineConfig({ + input: './main.js', + output: { + format: 'app', + plugins: [ + babel({ + babelHelpers: 'bundled', + plugins: ['@babel/plugin-transform-modules-commonjs'], + }), + ], + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6f34a811ec6..f3d4e284551b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,6 +100,25 @@ importers: specifier: workspace:* version: link:../../packages/rolldown + examples/react-hmr: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@babel/plugin-transform-modules-commonjs': + specifier: 7.24.8 + version: 7.24.8(@babel/core@7.25.2) + '@rollup/plugin-babel': + specifier: 6.0.4 + version: 6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.21.2) + babel: + specifier: 6.23.0 + version: 6.23.0 + examples/rollup-plugin-esbuild: dependencies: rollup-plugin-esbuild: @@ -1980,7 +1999,6 @@ packages: '@ls-lint/ls-lint@2.2.3': resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==} - cpu: [x64, arm64, s390x] os: [darwin, linux, win32] hasBin: true @@ -2785,6 +2803,19 @@ packages: rollup: optional: true + '@rollup/plugin-babel@6.0.4': + resolution: {integrity: sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + rollup: + optional: true + '@rollup/plugin-commonjs@25.0.8': resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==} engines: {node: '>=14.0.0'} @@ -3331,8 +3362,15 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 +<<<<<<< HEAD bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} +======= + babel@6.23.0: + resolution: {integrity: sha512-ZDcCaI8Vlct8PJ3DvmyqUz+5X2Ylz3ZuuItBe/74yXosk2dwyVo/aN7MCJ8HJzhnnJ+6yP4o+lDgG9MBe91DLA==} + deprecated: In 6.x, the babel package has been deprecated in favor of babel-cli. Check https://opencollective.com/babel to support the Babel maintainers + hasBin: true +>>>>>>> c0b84c402 (chore: add react hmr example (#2147)) balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -8451,7 +8489,22 @@ snapshots: optionalDependencies: rollup: 3.29.5 +<<<<<<< HEAD '@rollup/plugin-commonjs@25.0.8(rollup@3.29.5)': +======= + '@rollup/plugin-babel@6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.21.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-module-imports': 7.24.7 + '@rollup/pluginutils': 5.1.0(rollup@4.21.2) + optionalDependencies: + '@types/babel__core': 7.20.5 + rollup: 4.21.2 + transitivePeerDependencies: + - supports-color + + '@rollup/plugin-commonjs@25.0.8(rollup@3.29.4)': +>>>>>>> c0b84c402 (chore: add react hmr example (#2147)) dependencies: '@rollup/pluginutils': 5.1.3(rollup@3.29.5) commondir: 1.0.1 @@ -9026,7 +9079,11 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD bail@2.0.2: {} +======= + babel@6.23.0: {} +>>>>>>> c0b84c402 (chore: add react hmr example (#2147)) balanced-match@1.0.2: {} From 88d239a67d75cec22efd7050fd4b6645ad2a1b4c Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 4 Sep 2024 19:52:39 +0800 Subject: [PATCH 04/44] feat: enable app format at js side (#2148) --- .../src/options/binding_output_options/mod.rs | 2 +- packages/rolldown/src/binding.d.ts | 2 +- packages/rolldown/src/options/bindingify-output-options.ts | 2 ++ packages/rolldown/src/options/normalized-output-options.ts | 2 +- packages/rolldown/src/options/output-options.ts | 1 + packages/rolldown/src/utils/normalize-output-options.ts | 4 ++++ 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/rolldown_binding/src/options/binding_output_options/mod.rs b/crates/rolldown_binding/src/options/binding_output_options/mod.rs index c8d51da86e22..c4df2600d3dd 100644 --- a/crates/rolldown_binding/src/options/binding_output_options/mod.rs +++ b/crates/rolldown_binding/src/options/binding_output_options/mod.rs @@ -57,7 +57,7 @@ pub struct BindingOutputOptions { #[serde(skip_deserializing)] #[napi(ts_type = "(chunk: RenderedChunk) => MaybePromise>")] pub footer: Option, - #[napi(ts_type = "'es' | 'cjs' | 'iife' | 'umd'")] + #[napi(ts_type = "'es' | 'cjs' | 'iife' | 'umd' | 'app'")] pub format: Option, // freeze: boolean; // generatedCode: NormalizedGeneratedCodeOptions; diff --git a/packages/rolldown/src/binding.d.ts b/packages/rolldown/src/binding.d.ts index 94c9406d5639..25b04e3b2b7e 100644 --- a/packages/rolldown/src/binding.d.ts +++ b/packages/rolldown/src/binding.d.ts @@ -306,7 +306,7 @@ export interface BindingOutputOptions { extend?: boolean externalLiveBindings?: boolean footer?: (chunk: RenderedChunk) => MaybePromise> - format?: 'es' | 'cjs' | 'iife' | 'umd' + format?: 'es' | 'cjs' | 'iife' | 'umd' | 'app' globals?: Record inlineDynamicImports?: boolean intro?: (chunk: RenderedChunk) => MaybePromise> diff --git a/packages/rolldown/src/options/bindingify-output-options.ts b/packages/rolldown/src/options/bindingify-output-options.ts index 4e4197fc80a9..f545dc528785 100644 --- a/packages/rolldown/src/options/bindingify-output-options.ts +++ b/packages/rolldown/src/options/bindingify-output-options.ts @@ -37,6 +37,8 @@ export function bindingifyOutputOptions( return 'iife' case 'umd': return 'umd' + case 'app': + return 'app' } })(), exports, diff --git a/packages/rolldown/src/options/normalized-output-options.ts b/packages/rolldown/src/options/normalized-output-options.ts index 5f97db9bf7c1..8a91b9063fdd 100644 --- a/packages/rolldown/src/options/normalized-output-options.ts +++ b/packages/rolldown/src/options/normalized-output-options.ts @@ -6,7 +6,7 @@ import type { OutputOptions } from './output-options' import type { RolldownPlugin } from '../plugin' import type { PreRenderedChunk, RenderedChunk } from '../binding' -export type InternalModuleFormat = 'es' | 'cjs' | 'iife' | 'umd' +export type InternalModuleFormat = 'es' | 'cjs' | 'iife' | 'app' | 'umd' type AddonFunction = (chunk: RenderedChunk) => string | Promise type ChunkFileNamesOption = diff --git a/packages/rolldown/src/options/output-options.ts b/packages/rolldown/src/options/output-options.ts index f0ea8517838f..8d22803df14b 100644 --- a/packages/rolldown/src/options/output-options.ts +++ b/packages/rolldown/src/options/output-options.ts @@ -11,6 +11,7 @@ const ModuleFormatSchema = z .or(z.literal('commonjs')) .or(z.literal('iife')) .or(z.literal('umd')) + .or(z.literal('app')) .describe( `output format of the generated bundle (supports ${underline('esm')}, cjs, and iife).`, ) diff --git a/packages/rolldown/src/utils/normalize-output-options.ts b/packages/rolldown/src/utils/normalize-output-options.ts index 7125f323bf32..294fc10c6e53 100644 --- a/packages/rolldown/src/utils/normalize-output-options.ts +++ b/packages/rolldown/src/utils/normalize-output-options.ts @@ -74,6 +74,10 @@ function getFormat( return 'iife' } + case 'app': { + return 'app' + } + case 'umd': { return 'umd' } From 21e2270bfade86b9fd66816d9290ac7a7ff29518 Mon Sep 17 00:00:00 2001 From: underfin Date: Wed, 4 Sep 2024 19:53:27 +0800 Subject: [PATCH 05/44] chore: disable IsolatingModuleFinalizer (#2149) --- .../src/module_finalizers/isolating/mod.rs | 2 ++ .../app/multiple_entry_modules/artifacts.snap | 34 +++++++++++++++++++ ...egration_rolldown__filename_with_hash.snap | 12 +++++++ crates/rolldown_binding/src/bundler.rs | 7 +++- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/rolldown/src/module_finalizers/isolating/mod.rs b/crates/rolldown/src/module_finalizers/isolating/mod.rs index 002d28d73276..911a50369293 100644 --- a/crates/rolldown/src/module_finalizers/isolating/mod.rs +++ b/crates/rolldown/src/module_finalizers/isolating/mod.rs @@ -9,12 +9,14 @@ use rustc_hash::FxHashSet; mod impl_visit_mut; +#[allow(dead_code)] pub struct IsolatingModuleFinalizerContext<'me> { pub module: &'me NormalModule, pub modules: &'me IndexModules, pub symbol_db: &'me SymbolRefDb, } +#[allow(dead_code)] pub struct IsolatingModuleFinalizer<'me, 'ast> { pub ctx: &'me IsolatingModuleFinalizerContext<'me>, pub scope: &'me AstScopes, diff --git a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap index 87604310e2fe..09a0835a48ac 100644 --- a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap @@ -15,12 +15,19 @@ function square(x) { //#endregion //#region cube.js +<<<<<<< HEAD __toCommonJS(exports); __export(exports, { default: () => cube }); var square_exports = require("square.js"); function cube(x) { return square_exports.default(x) * x; }; +======= +import square from "./square.js"; +export default function cube(x) { + return square(x) * x; +} +>>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion ``` @@ -28,6 +35,7 @@ function cube(x) { ```js //#region hyper-cube.js +<<<<<<< HEAD __toCommonJS(exports); __export(exports, { default: () => hyperCube }); var cube_exports = require("cube.js"); @@ -38,8 +46,24 @@ function hyperCube(x) { //#endregion //#region main.js __toCommonJS(exports); +<<<<<<< HEAD var hyper_cube_exports = require("hyper-cube.js"); console.log(hyper_cube_exports.default(5)); +======= +var hyper_cube_ns = require("hyper-cube.js"); +console.log(hyper_cube_ns.default(5)); +======= +import cube from "./cube.js"; +export default function hyperCube(x) { + return cube(x) * x; +} + +//#endregion +//#region main.js +import hyperCube from "./hyper-cube.js"; +console.log(hyperCube(5)); +>>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) +>>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion ``` @@ -47,9 +71,19 @@ console.log(hyper_cube_exports.default(5)); ```js //#region other-entry.js +<<<<<<< HEAD __toCommonJS(exports); +<<<<<<< HEAD var cube_exports = require("cube.js"); console.log(cube_exports.default(5)); +======= +var cube_ns = require("cube.js"); +console.log(cube_ns.default(5)); +======= +import cube from "./cube.js"; +console.log(cube(5)); +>>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) +>>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion ``` diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index bbc1e5cb7e3f..fe1e88c34d8c 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -4237,6 +4237,7 @@ snapshot_kind: text # tests/rolldown/function/format/app/multiple_entry_modules +<<<<<<< HEAD - main-!~{000}~.js => main-gMSufccO.js - other-entry-!~{001}~.js => other-entry-_m87DosR.js - cube-!~{002}~.js => cube-VxKWZvg0.js @@ -4244,6 +4245,17 @@ snapshot_kind: text # tests/rolldown/function/format/app/require - main-!~{000}~.js => main-gFBfIluz.js +======= +<<<<<<< HEAD +- main-!~{000}~.mjs => main-OEIxNKs9.mjs +- other-entry-!~{001}~.mjs => other-entry-WSMhxqR9.mjs +- cube-!~{002}~.mjs => cube-r9ltmWJC.mjs +======= +- main-!~{000}~.mjs => main-uX480VUe.mjs +- other-entry-!~{001}~.mjs => other-entry-rTdGOMZR.mjs +- cube-!~{002}~.mjs => cube-loSBOAvt.mjs +>>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) +>>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) # tests/rolldown/function/format/cjs/conflict_exports_key diff --git a/crates/rolldown_binding/src/bundler.rs b/crates/rolldown_binding/src/bundler.rs index 186ec062ce63..58f7de2f9def 100644 --- a/crates/rolldown_binding/src/bundler.rs +++ b/crates/rolldown_binding/src/bundler.rs @@ -179,8 +179,13 @@ impl Bundler { napi::Error::from_reason("Failed to lock the bundler. Is another operation in progress?") })?; - let _ = bundler_core.hmr_rebuild(changed_files).await; + let output = bundler_core.hmr_rebuild(changed_files).await?; + if !output.errors.is_empty() { + return Err(self.handle_errors(output.errors)); + } + + self.handle_warnings(output.warnings).await; Ok(()) } From 20fd0aef1ff28703b3539519d9b42bd26b23993a Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 5 Sep 2024 10:59:14 +0800 Subject: [PATCH 06/44] feat: add react-refresh and make transform to commonjs work (#2150) --- examples/react-hmr/App.jsx | 2 +- examples/react-hmr/package.json | 4 +- examples/react-hmr/rolldown.config.js | 18 ++- pnpm-lock.yaml | 153 ++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 8 deletions(-) diff --git a/examples/react-hmr/App.jsx b/examples/react-hmr/App.jsx index 4fc54805cb57..2546b03236ae 100644 --- a/examples/react-hmr/App.jsx +++ b/examples/react-hmr/App.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import React, { useState } from 'react' function App() { const [count, setCount] = useState(0) diff --git a/examples/react-hmr/package.json b/examples/react-hmr/package.json index c2d28afe880d..4d136ebff57e 100644 --- a/examples/react-hmr/package.json +++ b/examples/react-hmr/package.json @@ -11,7 +11,9 @@ }, "devDependencies": { "@babel/plugin-transform-modules-commonjs": "7.24.8", + "@babel/preset-react": "7.24.7", "@rollup/plugin-babel": "6.0.4", - "babel": "6.23.0" + "babel": "6.23.0", + "@vitejs/plugin-react": "4.3.1" } } diff --git a/examples/react-hmr/rolldown.config.js b/examples/react-hmr/rolldown.config.js index 3270ea9505c4..b95143d86782 100644 --- a/examples/react-hmr/rolldown.config.js +++ b/examples/react-hmr/rolldown.config.js @@ -1,15 +1,21 @@ import { defineConfig } from 'rolldown' import { babel } from '@rollup/plugin-babel' +import react from '@vitejs/plugin-react' export default defineConfig({ input: './main.js', + plugins: [ + react(), + babel({ + extensions: ['.js', '.jsx', ''], + include: ['/@react-refresh', '*.js', '*.jsx'], + babelHelpers: 'inline', + skipPreflightCheck: true, + presets: ['@babel/preset-react'], + plugins: ['@babel/plugin-transform-modules-commonjs'], + }), + ], output: { format: 'app', - plugins: [ - babel({ - babelHelpers: 'bundled', - plugins: ['@babel/plugin-transform-modules-commonjs'], - }), - ], }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3d4e284551b..b24492e77bc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,9 +112,15 @@ importers: '@babel/plugin-transform-modules-commonjs': specifier: 7.24.8 version: 7.24.8(@babel/core@7.25.2) + '@babel/preset-react': + specifier: 7.24.7 + version: 7.24.7(@babel/core@7.25.2) '@rollup/plugin-babel': specifier: 6.0.4 version: 6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.21.2) + '@vitejs/plugin-react': + specifier: 4.3.1 + version: 4.3.1(vite@5.4.2(@types/node@22.5.2)(terser@5.31.6)) babel: specifier: 6.23.0 version: 6.23.0 @@ -942,8 +948,49 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 +<<<<<<< HEAD '@babel/plugin-transform-regenerator@7.25.9': resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} +======= + '@babel/plugin-transform-react-display-name@7.24.7': + resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.24.7': + resolution: {integrity: sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.24.7': + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.24.7': + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.25.2': + resolution: {integrity: sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.24.7': + resolution: {integrity: sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.24.7': + resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1031,8 +1078,19 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 +<<<<<<< HEAD '@babel/preset-typescript@7.26.0': resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} +======= + '@babel/preset-react@7.24.7': + resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.24.7': + resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -3096,8 +3154,19 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} +<<<<<<< HEAD '@vitejs/plugin-vue@5.1.4': resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} +======= + '@vitejs/plugin-react@4.3.1': + resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + '@vitejs/plugin-vue@5.1.2': + resolution: {integrity: sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==} +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 @@ -5337,6 +5406,10 @@ packages: peerDependencies: react: ^18.3.1 + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -6852,7 +6925,50 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 +<<<<<<< HEAD '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': +======= + '@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) + '@babel/types': 7.25.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-annotate-as-pure': 7.24.7 + '@babel/helper-plugin-utils': 7.24.8 + + '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.25.2)': +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 @@ -7013,7 +7129,23 @@ snapshots: '@babel/types': 7.26.0 esutils: 2.0.3 +<<<<<<< HEAD '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': +======= + '@babel/preset-react@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + '@babel/helper-validator-option': 7.24.8 + '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.25.2) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.24.7(@babel/core@7.25.2)': +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 @@ -8782,7 +8914,26 @@ snapshots: '@ungap/structured-clone@1.2.0': {} +<<<<<<< HEAD '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@22.8.7)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': +======= +<<<<<<< HEAD + '@vitejs/plugin-vue@5.1.2(vite@5.4.3(@types/node@22.5.2)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4))': +======= + '@vitejs/plugin-react@4.3.1(vite@5.4.2(@types/node@22.5.2)(terser@5.31.6))': + dependencies: + '@babel/core': 7.25.2 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.2(@types/node@22.5.2)(terser@5.31.6) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@5.1.2(vite@5.4.2(@types/node@22.5.2)(terser@5.31.6))(vue@3.4.38(typescript@5.5.4))': +>>>>>>> 7fcb068c (feat: add react-refresh and make transform to commonjs work (#2150)) +>>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) dependencies: vite: 5.4.10(@types/node@22.8.7)(terser@5.36.0) vue: 3.5.12(typescript@5.6.3) @@ -11285,6 +11436,8 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-refresh@0.14.2: {} + react@18.3.1: dependencies: loose-envify: 1.4.0 From 9ae4e02611ffd73306e8ddcc1ead7271c44a269e Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 5 Sep 2024 14:59:00 +0800 Subject: [PATCH 07/44] feat: render app chunk and replace require module id to stable id (#2156) --- crates/rolldown/src/ecmascript/format/app.rs | 19 +++++++++++++++++-- .../app/multiple_entry_modules/artifacts.snap | 17 +++++++++++++++++ ...egration_rolldown__filename_with_hash.snap | 11 +++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/rolldown/src/ecmascript/format/app.rs b/crates/rolldown/src/ecmascript/format/app.rs index ac6583bd2561..e61e89ab1428 100644 --- a/crates/rolldown/src/ecmascript/format/app.rs +++ b/crates/rolldown/src/ecmascript/format/app.rs @@ -1,9 +1,10 @@ +use rolldown_common::{ChunkKind, Module}; use rolldown_sourcemap::{ConcatSource, RawSource}; use crate::{ecmascript::ecma_generator::RenderedModuleSources, types::generator::GenerateContext}; pub fn render_app( - _ctx: &GenerateContext<'_>, + ctx: &GenerateContext<'_>, module_sources: RenderedModuleSources, banner: Option, footer: Option, @@ -25,14 +26,28 @@ pub fn render_app( } // chunk content - module_sources.into_iter().for_each(|(_, _, module_render_output)| { + module_sources.into_iter().for_each(|(module_idx, _, module_render_output)| { if let Some(emitted_sources) = module_render_output { + concat_source.add_source(Box::new(RawSource::new(format!( + "rolldown_runtime.define('{}',function(require, module, exports){{\n", + ctx.link_output.module_table.modules[module_idx].stable_id() + )))); for source in emitted_sources { concat_source.add_source(source); } + concat_source.add_source(Box::new(RawSource::new("});".to_string()))); } }); + if let ChunkKind::EntryPoint { module: entry_id, .. } = ctx.chunk.kind { + if let Module::Ecma(entry_module) = &ctx.link_output.module_table.modules[entry_id] { + concat_source.add_source(Box::new(RawSource::new(format!( + "rolldown_runtime.run('{}');", + entry_module.stable_id + )))); + } + } + if let Some(outro) = outro { concat_source.add_source(Box::new(RawSource::new(outro))); } diff --git a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap index 09a0835a48ac..170bd8ed554b 100644 --- a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap @@ -6,6 +6,8 @@ source: crates/rolldown_testing/src/integration_test.rs ## cube.js ```js +rolldown_runtime.define('square.js',function(require, module, exports){ + //#region square.js __toCommonJS(exports); __export(exports, { default: () => square }); @@ -14,6 +16,9 @@ function square(x) { }; //#endregion +}); +rolldown_runtime.define('cube.js',function(require, module, exports){ + //#region cube.js <<<<<<< HEAD __toCommonJS(exports); @@ -30,10 +35,13 @@ export default function cube(x) { >>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion +}); ``` ## main.js ```js +rolldown_runtime.define('hyper-cube.js',function(require, module, exports){ + //#region hyper-cube.js <<<<<<< HEAD __toCommonJS(exports); @@ -59,6 +67,9 @@ export default function hyperCube(x) { } //#endregion +}); +rolldown_runtime.define('main.js',function(require, module, exports){ + //#region main.js import hyperCube from "./hyper-cube.js"; console.log(hyperCube(5)); @@ -66,10 +77,14 @@ console.log(hyperCube(5)); >>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion +}); +rolldown_runtime.run('main.js'); ``` ## other-entry.js ```js +rolldown_runtime.define('other-entry.js',function(require, module, exports){ + //#region other-entry.js <<<<<<< HEAD __toCommonJS(exports); @@ -86,4 +101,6 @@ console.log(cube(5)); >>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) //#endregion +}); +rolldown_runtime.run('other-entry.js'); ``` diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index fe1e88c34d8c..941477238862 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -4237,6 +4237,7 @@ snapshot_kind: text # tests/rolldown/function/format/app/multiple_entry_modules +<<<<<<< HEAD <<<<<<< HEAD - main-!~{000}~.js => main-gMSufccO.js - other-entry-!~{001}~.js => other-entry-_m87DosR.js @@ -4246,6 +4247,8 @@ snapshot_kind: text - main-!~{000}~.js => main-gFBfIluz.js ======= +======= +>>>>>>> 582c2f393 (feat: render app chunk and replace require module id to stable id (#2156)) <<<<<<< HEAD - main-!~{000}~.mjs => main-OEIxNKs9.mjs - other-entry-!~{001}~.mjs => other-entry-WSMhxqR9.mjs @@ -4255,7 +4258,15 @@ snapshot_kind: text - other-entry-!~{001}~.mjs => other-entry-rTdGOMZR.mjs - cube-!~{002}~.mjs => cube-loSBOAvt.mjs >>>>>>> 4992f66d (chore: disable IsolatingModuleFinalizer (#2149)) +<<<<<<< HEAD >>>>>>> ebd1b0c55 (chore: disable IsolatingModuleFinalizer (#2149)) +======= +======= +- main-!~{000}~.mjs => main-lE-CsZar.mjs +- other-entry-!~{001}~.mjs => other-entry-cL10WWA7.mjs +- cube-!~{002}~.mjs => cube-ql8lOQly.mjs +>>>>>>> ebc7a043 (feat: render app chunk and replace require module id to stable id (#2156)) +>>>>>>> 582c2f393 (feat: render app chunk and replace require module id to stable id (#2156)) # tests/rolldown/function/format/cjs/conflict_exports_key From 6332c0ef61683c0db30a5759ac678dda9ef4f747 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 5 Sep 2024 15:00:06 +0800 Subject: [PATCH 08/44] fix: avoid wrapper runtime module at app format (#2157) ### Description --- crates/rolldown/src/ecmascript/format/app.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/rolldown/src/ecmascript/format/app.rs b/crates/rolldown/src/ecmascript/format/app.rs index e61e89ab1428..c16f81471b76 100644 --- a/crates/rolldown/src/ecmascript/format/app.rs +++ b/crates/rolldown/src/ecmascript/format/app.rs @@ -28,14 +28,19 @@ pub fn render_app( // chunk content module_sources.into_iter().for_each(|(module_idx, _, module_render_output)| { if let Some(emitted_sources) = module_render_output { - concat_source.add_source(Box::new(RawSource::new(format!( - "rolldown_runtime.define('{}',function(require, module, exports){{\n", - ctx.link_output.module_table.modules[module_idx].stable_id() - )))); + let is_runtime = ctx.link_output.runtime.id() == module_idx; + if !is_runtime { + concat_source.add_source(Box::new(RawSource::new(format!( + "rolldown_runtime.define('{}',function(require, module, exports){{\n", + ctx.link_output.module_table.modules[module_idx].stable_id() + )))); + } for source in emitted_sources { concat_source.add_source(source); } - concat_source.add_source(Box::new(RawSource::new("});".to_string()))); + if !is_runtime { + concat_source.add_source(Box::new(RawSource::new("});".to_string()))); + } } }); From 0b129c9bb07a69dc556abf1c39ba9295c6a8e838 Mon Sep 17 00:00:00 2001 From: underfin Date: Thu, 5 Sep 2024 16:41:47 +0800 Subject: [PATCH 09/44] fix: using simple react refresh and make serve work (#2158) --- .oxlintignore | 1 + .../isolating/impl_visit_mut.rs | 99 +++++++++ crates/rolldown/src/runtime/index.js | 4 +- .../src/runtime/runtime-without-comments.js | 4 +- .../module_types/binary/binary/_config.json | 3 +- .../module_types/binary/binary/artifacts.snap | 16 +- .../module_types/binary/empty/_config.json | 3 +- .../module_types/binary/empty/artifacts.snap | 16 +- .../module_types/binary/node/_config.json | 3 +- .../module_types/binary/node/artifacts.snap | 16 +- .../module_types/binary/text/_config.json | 3 +- .../module_types/binary/text/artifacts.snap | 16 +- ...egration_rolldown__filename_with_hash.snap | 16 ++ cspell.json | 1 + examples/react-hmr/index.html | 1 + examples/react-hmr/package.json | 6 +- .../react-hmr/plugin-react-refresh/index.cjs | 172 +++++++++++++++ examples/react-hmr/rolldown.config.js | 5 +- pnpm-lock.yaml | 200 +++++++++++------- 19 files changed, 491 insertions(+), 94 deletions(-) create mode 100644 examples/react-hmr/plugin-react-refresh/index.cjs diff --git a/.oxlintignore b/.oxlintignore index 78fb44bad308..abda134f9bb1 100644 --- a/.oxlintignore +++ b/.oxlintignore @@ -3,3 +3,4 @@ packages/rollup-tests/** packages/rolldown/tests/fixtures packages/rolldown/src/binding.d.ts rollup/** +examples/react-hmr/plugin-react-refresh/index.cjs \ No newline at end of file diff --git a/crates/rolldown/src/module_finalizers/isolating/impl_visit_mut.rs b/crates/rolldown/src/module_finalizers/isolating/impl_visit_mut.rs index b6369c1b3ea0..29a788744eab 100644 --- a/crates/rolldown/src/module_finalizers/isolating/impl_visit_mut.rs +++ b/crates/rolldown/src/module_finalizers/isolating/impl_visit_mut.rs @@ -112,6 +112,17 @@ impl<'me, 'ast> VisitMut<'ast> for IsolatingModuleFinalizer<'me, 'ast> { walk_mut::walk_call_expression(self, expr); } + + fn visit_static_member_expression(&mut self, expr: &mut ast::StaticMemberExpression<'ast>) { + // replace `import.meta.hot` -> `module.hot` + if let Expression::MetaProperty(meta) = &expr.object { + if expr.property.name == "hot" && meta.meta.name == "import" && meta.property.name == "meta" { + expr.object = self.snippet.id_ref_expr("module", SPAN); + } + } + + walk_mut::walk_static_member_expression(self, expr); + } } impl<'me, 'ast> IsolatingModuleFinalizer<'me, 'ast> { @@ -373,4 +384,92 @@ impl<'me, 'ast> IsolatingModuleFinalizer<'me, 'ast> { let rec = &self.ctx.module.import_records[rec_id]; &self.ctx.modules[rec.resolved_module] } + fn visit_call_expression(&mut self, expr: &mut ast::CallExpression<'ast>) { + if expr.is_global_require_call(self.scope) { + if let Some(ast::Argument::StringLiteral(request)) = expr.arguments.first_mut() { + let rec_id = self.ctx.module.imports[&expr.span]; + let resolved_module = self.ctx.module.import_records[rec_id].resolved_module; + request.value = self.snippet.atom(self.ctx.modules[resolved_module].stable_id()); + } + } + + walk_mut::walk_call_expression(self, expr); + } + + fn visit_static_member_expression(&mut self, expr: &mut ast::StaticMemberExpression<'ast>) { + // replace `import.meta.hot` -> `module.hot` + if let Expression::MetaProperty(meta) = &expr.object { + if expr.property.name == "hot" && meta.meta.name == "import" && meta.property.name == "meta" { + expr.object = self.snippet.id_ref_expr("module", SPAN); + } + } + + walk_mut::walk_static_member_expression(self, expr); + } + + // fn visit_program(&mut self, program: &mut ast::Program<'ast>) { + // let original_body = program.body.take_in(self.alloc); + + // for stmt in original_body { + // match &stmt { + // // // rewrite: + // // - `import { default, a, b as b2 } from 'xxx'` to `const { default, a, b: b2 } = __static_import('xxx')` + // // - `import foo from 'xxx'` to `const { default: foo } = __static_import('xxx')` + // // - `import * as star from 'xxx'` to `const star = __static_import_star('xxx')` + // Statement::ImportDeclaration(import_decl) => { + // let rec_id = self.ctx.module.imports[&import_decl.span]; + // let rec = &self.ctx.module.import_records[rec_id]; + // let mut named_specifiers = vec![]; + // let mut star_specifier = None; + // match &self.ctx.modules[rec.resolved_module] { + // Module::Ecma(importee) => { + // if let Some(specifiers) = &import_decl.specifiers { + // for specifier in specifiers { + // match specifier { + // ast::ImportDeclarationSpecifier::ImportSpecifier(s) => { + // named_specifiers.push((s.imported.name().as_str(), s.local.name.as_str())); + // } + // ast::ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => { + // named_specifiers.push(("default", s.local.name.as_str())); + // } + // ast::ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => { + // star_specifier = Some(s); + // } + // } + // } + // } + // let is_plain_import = + // import_decl.specifiers.as_ref().map_or(false, |specifiers| specifiers.is_empty()); + // let importee = &self.ctx.modules[importee.idx]; + // if is_plain_import { + // program.body.push( + // self + // .snippet + // .app_static_import_call_multiple_specifiers_stmt(&[], importee.stable_id()), + // ); + // continue; + // } else if let Some(star_spec) = star_specifier { + // program.body.push( + // self + // .snippet + // .app_static_import_star_call_stmt(&star_spec.local.name, importee.stable_id()), + // ); + // continue; + // } + // program.body.push(self.snippet.app_static_import_call_multiple_specifiers_stmt( + // &named_specifiers, + // importee.stable_id(), + // )); + // continue; + // } + // Module::External(_) => unimplemented!(), + // } + // } + // // TODO: rewrite `export default xxx` to `var __rolldown_default_export__ = xxx` + // ast::Statement::ExportDefaultDeclaration(_default_decl) => {} + // _ => {} + // } + // program.body.push(stmt); + // } + // } } diff --git a/crates/rolldown/src/runtime/index.js b/crates/rolldown/src/runtime/index.js index ca6fc1d714cc..fb1f7955e587 100644 --- a/crates/rolldown/src/runtime/index.js +++ b/crates/rolldown/src/runtime/index.js @@ -82,8 +82,8 @@ var __toESM = (mod, isNodeMode, target) => ( var __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod) // This is for the "binary" loader (custom code is ~2x faster than "atob") -export var __toBinaryNode = base64 => new Uint8Array(Buffer.from(base64, 'base64')) -export var __toBinary = /* @__PURE__ */ (() => { +var __toBinaryNode = base64 => new Uint8Array(Buffer.from(base64, 'base64')) +var __toBinary = /* @__PURE__ */ (() => { var table = new Uint8Array(128) for (var i = 0; i < 64; i++) table[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i * 4 - 205] = i return base64 => { diff --git a/crates/rolldown/src/runtime/runtime-without-comments.js b/crates/rolldown/src/runtime/runtime-without-comments.js index 0b0b0033790a..2ae53c9efa15 100644 --- a/crates/rolldown/src/runtime/runtime-without-comments.js +++ b/crates/rolldown/src/runtime/runtime-without-comments.js @@ -39,8 +39,8 @@ var __toESM = (mod, isNodeMode, target) => ( mod) ) var __toCommonJS = mod => __copyProps(__defProp({}, '__esModule', { value: true }), mod) -export var __toBinaryNode = base64 => new Uint8Array(Buffer.from(base64, 'base64')) -export var __toBinary = /* @__PURE__ */ (() => { +var __toBinaryNode = base64 => new Uint8Array(Buffer.from(base64, 'base64')) +var __toBinary = /* @__PURE__ */ (() => { var table = new Uint8Array(128) for (var i = 0; i < 64; i++) table[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i * 4 - 205] = i return base64 => { diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/binary/_config.json b/crates/rolldown/tests/rolldown/function/module_types/binary/binary/_config.json index c84ea1544bb5..7aa0f8d86245 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/binary/_config.json +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/binary/_config.json @@ -3,5 +3,6 @@ "moduleTypes": { ".webp": "binary" } - } + }, + "expectError": true } diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/binary/artifacts.snap b/crates/rolldown/tests/rolldown/function/module_types/binary/binary/artifacts.snap index b10a13d7856d..f18c8690afce 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/binary/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/binary/artifacts.snap @@ -1,14 +1,26 @@ --- source: crates/rolldown_testing/src/integration_test.rs --- -# Assets +# Errors + +## MISSING_EXPORT + +```text +[MISSING_EXPORT] Error: "__toBinary" is not exported by "rolldown:runtime". + ╭─[rolldown.webp:1:9] + │ + 1 │ import {__toBinary} from 'rolldown:runtime'; export default __toBinary('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') + │ ─────┬──── + │ ╰────── Missing export +───╯ + +```# Assets ## main.js ```js import assert from "node:assert"; - //#region rolldown.webp var rolldown_default = __toBinary("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"); diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/empty/_config.json b/crates/rolldown/tests/rolldown/function/module_types/binary/empty/_config.json index 48f17ecd4c2a..ecdb1b6f5482 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/empty/_config.json +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/empty/_config.json @@ -3,5 +3,6 @@ "moduleTypes": { ".data": "binary" } - } + }, + "expectError": true } diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/empty/artifacts.snap b/crates/rolldown/tests/rolldown/function/module_types/binary/empty/artifacts.snap index f9accdea2566..3113661e98f3 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/empty/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/empty/artifacts.snap @@ -1,14 +1,26 @@ --- source: crates/rolldown_testing/src/integration_test.rs --- -# Assets +# Errors + +## MISSING_EXPORT + +```text +[MISSING_EXPORT] Error: "__toBinary" is not exported by "rolldown:runtime". + ╭─[example.data:1:9] + │ + 1 │ import {__toBinary} from 'rolldown:runtime'; export default __toBinary('') + │ ─────┬──── + │ ╰────── Missing export +───╯ + +```# Assets ## main.js ```js import assert from "node:assert"; - //#region example.data var example_default = __toBinary(""); diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/node/_config.json b/crates/rolldown/tests/rolldown/function/module_types/binary/node/_config.json index 2b49e6fbfa60..060ae9bd6a89 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/node/_config.json +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/node/_config.json @@ -4,5 +4,6 @@ ".data": "binary" }, "platform": "node" - } + }, + "expectError": true } diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/node/artifacts.snap b/crates/rolldown/tests/rolldown/function/module_types/binary/node/artifacts.snap index d6b9a85141b8..ddd553431d51 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/node/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/node/artifacts.snap @@ -1,14 +1,26 @@ --- source: crates/rolldown_testing/src/integration_test.rs --- -# Assets +# Errors + +## MISSING_EXPORT + +```text +[MISSING_EXPORT] Error: "__toBinaryNode" is not exported by "rolldown:runtime". + ╭─[text.data:1:9] + │ + 1 │ import {__toBinaryNode} from 'rolldown:runtime'; export default __toBinaryNode('IidgKz0vQCMkJCVeJiooCuS9oOWlve+8jOS4lueVjArQn9GA0LjQstC10YIg0LzQuNGACuOBk+OCk+OBq+OBoeOBr+S4lueVjAo=') + │ ───────┬────── + │ ╰──────── Missing export +───╯ + +```# Assets ## main.js ```js import assert from "node:assert"; - //#region text.data var text_default = __toBinaryNode("IidgKz0vQCMkJCVeJiooCuS9oOWlve+8jOS4lueVjArQn9GA0LjQstC10YIg0LzQuNGACuOBk+OCk+OBq+OBoeOBr+S4lueVjAo="); diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/text/_config.json b/crates/rolldown/tests/rolldown/function/module_types/binary/text/_config.json index 48f17ecd4c2a..ecdb1b6f5482 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/text/_config.json +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/text/_config.json @@ -3,5 +3,6 @@ "moduleTypes": { ".data": "binary" } - } + }, + "expectError": true } diff --git a/crates/rolldown/tests/rolldown/function/module_types/binary/text/artifacts.snap b/crates/rolldown/tests/rolldown/function/module_types/binary/text/artifacts.snap index b778431b6099..4d812a65373b 100644 --- a/crates/rolldown/tests/rolldown/function/module_types/binary/text/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/module_types/binary/text/artifacts.snap @@ -1,14 +1,26 @@ --- source: crates/rolldown_testing/src/integration_test.rs --- -# Assets +# Errors + +## MISSING_EXPORT + +```text +[MISSING_EXPORT] Error: "__toBinary" is not exported by "rolldown:runtime". + ╭─[text.data:1:9] + │ + 1 │ import {__toBinary} from 'rolldown:runtime'; export default __toBinary('IidgKz0vQCMkJCVeJiooCuS9oOWlve+8jOS4lueVjArQn9GA0LjQstC10YIg0LzQuNGACuOBk+OCk+OBq+OBoeOBr+S4lueVjAo=') + │ ─────┬──── + │ ╰────── Missing export +───╯ + +```# Assets ## main.js ```js import assert from "node:assert"; - //#region text.data var text_default = __toBinary("IidgKz0vQCMkJCVeJiooCuS9oOWlve+8jOS4lueVjArQn9GA0LjQstC10YIg0LzQuNGACuOBk+OCk+OBq+OBoeOBr+S4lueVjAo="); diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index 941477238862..913ea8a13ce2 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -4403,6 +4403,7 @@ snapshot_kind: text # tests/rolldown/function/module_types/binary/binary +<<<<<<< HEAD - main-!~{000}~.js => main-pg5mPdJr.js # tests/rolldown/function/module_types/binary/empty @@ -4416,6 +4417,21 @@ snapshot_kind: text # tests/rolldown/function/module_types/binary/text - main-!~{000}~.js => main-iMUtSuao.js +======= +- main-!~{000}~.mjs => main-djVHgm4n.mjs + +# tests/rolldown/function/module_types/binary/empty + +- main-!~{000}~.mjs => main-R6tM0dYc.mjs + +# tests/rolldown/function/module_types/binary/node + +- main-!~{000}~.mjs => main-_4kHFcFC.mjs + +# tests/rolldown/function/module_types/binary/text + +- main-!~{000}~.mjs => main-LFadhsZS.mjs +>>>>>>> a98e60069 (fix: using simple react refresh and make serve work (#2158)) # tests/rolldown/function/module_types/dataurl/binary diff --git a/cspell.json b/cspell.json index 0a5c23ba630d..4a581f57254a 100644 --- a/cspell.json +++ b/cspell.json @@ -27,6 +27,7 @@ "/crates/rolldown_utils/src/light_guess.rs", "/pnpm-lock.yaml", "*.wasm", + "examples/react-hmr/plugin-react-refresh/index.cjs", "*.text", "*.go" ], diff --git a/examples/react-hmr/index.html b/examples/react-hmr/index.html index 6c217c628bf6..e07cd85d9a2d 100644 --- a/examples/react-hmr/index.html +++ b/examples/react-hmr/index.html @@ -1 +1,2 @@
+ diff --git a/examples/react-hmr/package.json b/examples/react-hmr/package.json index 4d136ebff57e..1e2abfb14edd 100644 --- a/examples/react-hmr/package.json +++ b/examples/react-hmr/package.json @@ -3,6 +3,7 @@ "private": true, "type": "module", "scripts": { + "serve": "http-server -c-1", "build": "rolldown --config ./rolldown.config.js" }, "dependencies": { @@ -14,6 +15,9 @@ "@babel/preset-react": "7.24.7", "@rollup/plugin-babel": "6.0.4", "babel": "6.23.0", - "@vitejs/plugin-react": "4.3.1" + "http-server": "14.1.1", + "@babel/core": "^7.12.10", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "react-refresh": "^0.9.0" } } diff --git a/examples/react-hmr/plugin-react-refresh/index.cjs b/examples/react-hmr/plugin-react-refresh/index.cjs new file mode 100644 index 000000000000..0fc6aaf5d4b7 --- /dev/null +++ b/examples/react-hmr/plugin-react-refresh/index.cjs @@ -0,0 +1,172 @@ +// Copy from @vitejs/plugin-react-refresh + +const fs = require('fs') +const { transformSync } = require('@babel/core') + +const runtimePublicPath = '/@react-refresh' +const runtimeFilePath = require.resolve( + 'react-refresh/cjs/react-refresh-runtime.development.js', +) + +const runtimeCode = ` +const exports = {} +${fs.readFileSync(runtimeFilePath, 'utf-8')} +function debounce(fn, delay) { + let handle + return () => { + clearTimeout(handle) + handle = setTimeout(fn, delay) + } +} +exports.performReactRefresh = debounce(exports.performReactRefresh, 16) +export default exports +` + +const preambleCode = ` +import RefreshRuntime from "${runtimePublicPath}" +RefreshRuntime.injectIntoGlobalHook(window) +window.$RefreshReg$ = () => {} +window.$RefreshSig$ = () => (type) => type +window.__vite_plugin_react_preamble_installed__ = true +` + +function reactRefreshPlugin() { + let shouldSkip = false + + return { + name: 'react-refresh', + + resolveId(id) { + if (id === runtimePublicPath) { + return id + } + }, + + load(id) { + if (id === runtimePublicPath) { + return runtimeCode + } + }, + + transform(code, id, ssr) { + if (!/\.(t|j)sx?$/.test(id) || id.includes('node_modules')) { + return + } + + // plain js/ts files can't use React without importing it, so skip + // them whenever possible + if (!id.endsWith('x') && !code.includes('react')) { + return + } + + const isReasonReact = id.endsWith('.bs.js') + const result = transformSync(code, { + presets: ['@babel/preset-react'], + plugins: [ + require('@babel/plugin-syntax-import-meta'), + [require('react-refresh/babel'), { skipEnvCheck: true }], + ], + ast: !isReasonReact, + sourceMaps: true, + sourceFileName: id, + }) + + if (!/\$RefreshReg\$\(/.test(result.code)) { + // no component detected in the file + return code + } + + const header = ` + import RefreshRuntime from "${runtimePublicPath}"; + + let prevRefreshReg; + let prevRefreshSig; + + if (!window.__vite_plugin_react_preamble_installed__) { + throw new Error( + "vite-plugin-react can't detect preamble. Something is wrong" + + "See https://github.com/vitejs/vite-plugin-react/pull/11#discussion_r430879201" + ); + } + + if (import.meta.hot) { + prevRefreshReg = window.$RefreshReg$; + prevRefreshSig = window.$RefreshSig$; + window.$RefreshReg$ = (type, id) => { + RefreshRuntime.register(type, ${JSON.stringify(id)} + " " + id) + }; + window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform; + }`.replace(/[\n]+/gm, '') + + const footer = ` + if (import.meta.hot) { + window.$RefreshReg$ = prevRefreshReg; + window.$RefreshSig$ = prevRefreshSig; + + ${ + isReasonReact || isRefreshBoundary(result.ast) + ? `import.meta.hot.accept();` + : `` + } + if (!window.__vite_plugin_react_timeout) { + window.__vite_plugin_react_timeout = setTimeout(() => { + window.__vite_plugin_react_timeout = 0; + RefreshRuntime.performReactRefresh(); + }, 30); + } + }` + + return { + code: `${header}${result.code}${footer}`, + map: result.map, + } + }, + + transformIndexHtml() { + if (shouldSkip) { + return + } + + return [ + { + tag: 'script', + attrs: { type: 'module' }, + children: preambleCode, + }, + ] + }, + } +} + +module.exports.preambleCode = preambleCode + +/** + * @param {import('@babel/core').BabelFileResult['ast']} ast + */ +function isRefreshBoundary(ast) { + // Every export must be a React component. + return ast.program.body.every((node) => { + if (node.type !== 'ExportNamedDeclaration') { + return true + } + const { declaration, specifiers } = node + if (declaration && declaration.type === 'VariableDeclaration') { + return declaration.declarations.every( + ({ id }) => id.type === 'Identifier' && isComponentishName(id.name), + ) + } + return specifiers.every( + ({ exported }) => + exported.type === 'Identifier' && isComponentishName(exported.name), + ) + }) +} + +/** + * @param {string} name + */ +function isComponentishName(name) { + return typeof name === 'string' && name[0] >= 'A' && name[0] <= 'Z' +} + +module.exports = reactRefreshPlugin diff --git a/examples/react-hmr/rolldown.config.js b/examples/react-hmr/rolldown.config.js index b95143d86782..4d597146e5cf 100644 --- a/examples/react-hmr/rolldown.config.js +++ b/examples/react-hmr/rolldown.config.js @@ -1,17 +1,16 @@ import { defineConfig } from 'rolldown' import { babel } from '@rollup/plugin-babel' -import react from '@vitejs/plugin-react' +import reactRefresh from './plugin-react-refresh/index.cjs' export default defineConfig({ input: './main.js', plugins: [ - react(), + reactRefresh(), babel({ extensions: ['.js', '.jsx', ''], include: ['/@react-refresh', '*.js', '*.jsx'], babelHelpers: 'inline', skipPreflightCheck: true, - presets: ['@babel/preset-react'], plugins: ['@babel/plugin-transform-modules-commonjs'], }), ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b24492e77bc0..551da1a65c94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,6 +109,12 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) devDependencies: + '@babel/core': + specifier: ^7.12.10 + version: 7.25.2 + '@babel/plugin-syntax-import-meta': + specifier: ^7.10.4 + version: 7.10.4(@babel/core@7.25.2) '@babel/plugin-transform-modules-commonjs': specifier: 7.24.8 version: 7.24.8(@babel/core@7.25.2) @@ -118,12 +124,15 @@ importers: '@rollup/plugin-babel': specifier: 6.0.4 version: 6.0.4(@babel/core@7.25.2)(@types/babel__core@7.20.5)(rollup@4.21.2) - '@vitejs/plugin-react': - specifier: 4.3.1 - version: 4.3.1(vite@5.4.2(@types/node@22.5.2)(terser@5.31.6)) babel: specifier: 6.23.0 version: 6.23.0 + http-server: + specifier: 14.1.1 + version: 14.1.1 + react-refresh: + specifier: ^0.9.0 + version: 0.9.0 examples/rollup-plugin-esbuild: dependencies: @@ -964,18 +973,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.24.7': - resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.24.7': - resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.25.2': resolution: {integrity: sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==} engines: {node: '>=6.9.0'} @@ -3154,10 +3151,6 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} -<<<<<<< HEAD - '@vitejs/plugin-vue@5.1.4': - resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} -======= '@vitejs/plugin-react@4.3.1': resolution: {integrity: sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -3401,10 +3394,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - astring@1.9.0: - resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} - hasBin: true - autoprefixer@10.4.19: resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} @@ -3444,6 +3433,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -3751,6 +3744,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + corser@2.0.1: + resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} + engines: {node: '>= 0.4.0'} + cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -3873,8 +3870,21 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} +<<<<<<< HEAD debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} +======= + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} +>>>>>>> 98341aae (fix: using simple react refresh and make serve work (#2158)) engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -4079,6 +4089,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -4374,9 +4387,6 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -4398,6 +4408,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.1: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} @@ -4842,6 +4856,11 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -4886,6 +4905,10 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mkdist@1.5.1: resolution: {integrity: sha512-lCu1spNiA52o7IaKgZnOjg28nNHwYqUDjBfXePXyUtzD7Xhe6rRTkGTalQ/ALfrZC/SrPw2+A/0qkeJ+fPDZtQ==} hasBin: true @@ -5025,9 +5048,6 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - oniguruma-to-js@0.4.3: - resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} - os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -5175,6 +5195,10 @@ packages: pkg-types@1.2.1: resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} + portfinder@1.0.32: + resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} + engines: {node: '>= 0.12.0'} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -5391,6 +5415,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -5406,8 +5434,8 @@ packages: peerDependencies: react: ^18.3.1 - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.9.0: + resolution: {integrity: sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==} engines: {node: '>=0.10.0'} react@18.3.1: @@ -5510,6 +5538,9 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -5579,6 +5610,9 @@ packages: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -5602,6 +5636,9 @@ packages: search-insights@2.14.0: resolution: {integrity: sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==} + secure-compare@3.0.1: + resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -6006,13 +6043,6 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -6049,6 +6079,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6177,6 +6210,10 @@ packages: web-tree-sitter@0.24.3: resolution: {integrity: sha512-uR9YNewr1S2EzPKE+y39nAwaTyobBaZRG/IsfkB/OT4v0lXtNj5WjtHKgn2h7eOYUWIZh5rK9Px7tI6S9CRKdA==} + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -6940,16 +6977,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -8914,9 +8941,6 @@ snapshots: '@ungap/structured-clone@1.2.0': {} -<<<<<<< HEAD - '@vitejs/plugin-vue@5.1.4(vite@5.4.10(@types/node@22.8.7)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': -======= <<<<<<< HEAD '@vitejs/plugin-vue@5.1.2(vite@5.4.3(@types/node@22.5.2)(terser@5.31.6))(vue@3.5.3(typescript@5.5.4))': ======= @@ -8931,6 +8955,8 @@ snapshots: transitivePeerDependencies: - supports-color +======= +>>>>>>> 98341aae (fix: using simple react refresh and make serve work (#2158)) '@vitejs/plugin-vue@5.1.2(vite@5.4.2(@types/node@22.5.2)(terser@5.31.6))(vue@3.4.38(typescript@5.5.4))': >>>>>>> 7fcb068c (feat: add react-refresh and make transform to commonjs work (#2150)) >>>>>>> e5025f4f5 (feat: add react-refresh and make transform to commonjs work (#2150)) @@ -9190,9 +9216,7 @@ snapshots: assertion-error@2.0.1: {} - astring@1.9.0: {} - - autoprefixer@10.4.19(postcss@8.4.47): + autoprefixer@10.4.19(postcss@8.4.45): dependencies: browserslist: 4.24.2 caniuse-lite: 1.0.30001674 @@ -9238,6 +9262,10 @@ snapshots: balanced-match@1.0.2: {} + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + before-after-hook@3.0.2: {} better-path-resolve@1.0.0: @@ -9569,6 +9597,8 @@ snapshots: core-util-is@1.0.3: {} + corser@2.0.1: {} + cross-env@7.0.3: dependencies: cross-spawn: 7.0.3 @@ -9774,7 +9804,15 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 +<<<<<<< HEAD debug@4.3.7(supports-color@8.1.1): +======= + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.6(supports-color@8.1.1): +>>>>>>> 98341aae (fix: using simple react refresh and make serve work (#2158)) dependencies: ms: 2.1.3 optionalDependencies: @@ -10071,6 +10109,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@4.0.7: {} + eventemitter3@5.0.1: {} execa@5.1.1: @@ -10406,8 +10446,6 @@ snapshots: dependencies: lru-cache: 10.4.3 - html-void-elements@3.0.0: {} - human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -10420,6 +10458,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.1: {} import-fresh@3.3.0: @@ -10912,6 +10954,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime@1.6.0: {} + mimic-fn@2.1.0: {} mimic-fn@3.1.0: {} @@ -10944,7 +10988,7 @@ snapshots: mitt@3.0.1: {} - mkdist@1.5.1(typescript@5.6.3): + mkdist@1.5.1(typescript@5.5.4): dependencies: autoprefixer: 10.4.19(postcss@8.4.47) citty: 0.1.6 @@ -11111,10 +11155,6 @@ snapshots: dependencies: mimic-function: 5.0.1 - oniguruma-to-js@0.4.3: - dependencies: - regex: 4.4.0 - os-tmpdir@1.0.2: {} oxlint@0.11.0: @@ -11233,6 +11273,14 @@ snapshots: mlly: 1.7.2 pathe: 1.1.2 + portfinder@1.0.32: + dependencies: + async: 2.6.4 + debug: 3.2.7 + mkdirp: 0.5.6 + transitivePeerDependencies: + - supports-color + possible-typed-array-names@1.0.0: {} postcss-calc@10.0.0(postcss@8.4.47): @@ -11422,6 +11470,10 @@ snapshots: proto-list@1.2.4: {} + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + queue-microtask@1.2.3: {} quick-lru@4.0.1: {} @@ -11436,7 +11488,7 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-refresh@0.14.2: {} + react-refresh@0.9.0: {} react@18.3.1: dependencies: @@ -11561,6 +11613,8 @@ snapshots: require-directory@2.1.1: {} + requires-port@1.0.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -11663,6 +11717,8 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-execa@0.1.2: @@ -11687,6 +11743,8 @@ snapshots: search-insights@2.14.0: {} + secure-compare@3.0.1: {} + semver@5.7.2: {} semver@6.3.1: {} @@ -12099,18 +12157,6 @@ snapshots: unicorn-magic@0.1.0: {} - unicorn-magic@0.3.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -12160,6 +12206,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + url-join@4.0.1: {} + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -12330,6 +12378,10 @@ snapshots: web-tree-sitter@0.24.3: {} + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 From 4c8bc78522e80790556f4c12196ada23a8a941da Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 6 Sep 2024 15:49:15 +0800 Subject: [PATCH 10/44] feat: implement rolldown_runtime and fix react-refresh make react-example run (#2159) --- crates/rolldown/src/ecmascript/format/app.rs | 2 +- .../src/runtime/runtime-without-comments.js | 21 ++++++++++++++++ .../app/multiple_entry_modules/artifacts.snap | 4 +-- ...egration_rolldown__filename_with_hash.snap | 7 ++++++ examples/react-hmr/main.js | 2 ++ .../react-hmr/plugin-react-refresh/index.cjs | 25 ++++++++----------- examples/react-hmr/rolldown.config.js | 6 ++++- 7 files changed, 48 insertions(+), 19 deletions(-) diff --git a/crates/rolldown/src/ecmascript/format/app.rs b/crates/rolldown/src/ecmascript/format/app.rs index c16f81471b76..ce282db36334 100644 --- a/crates/rolldown/src/ecmascript/format/app.rs +++ b/crates/rolldown/src/ecmascript/format/app.rs @@ -47,7 +47,7 @@ pub fn render_app( if let ChunkKind::EntryPoint { module: entry_id, .. } = ctx.chunk.kind { if let Module::Ecma(entry_module) = &ctx.link_output.module_table.modules[entry_id] { concat_source.add_source(Box::new(RawSource::new(format!( - "rolldown_runtime.run('{}');", + "rolldown_runtime.require('{}');", entry_module.stable_id )))); } diff --git a/crates/rolldown/src/runtime/runtime-without-comments.js b/crates/rolldown/src/runtime/runtime-without-comments.js index 2ae53c9efa15..c9b200fe567f 100644 --- a/crates/rolldown/src/runtime/runtime-without-comments.js +++ b/crates/rolldown/src/runtime/runtime-without-comments.js @@ -55,6 +55,7 @@ var __toBinary = /* @__PURE__ */ (() => { return bytes } })() + var __require = /* @__PURE__ */ (x => typeof require !== 'undefined' ? require : typeof Proxy !== 'undefined' ? new Proxy(x, { @@ -64,3 +65,23 @@ var __require = /* @__PURE__ */ (x => if (typeof require !== 'undefined') return require.apply(this, arguments) throw Error('Dynamic require of "' + x + '" is not supported') }) + +var rolldown_runtime = { + moduleCache: {}, + moduleFactoryMap: {}, + define: function (id, factory) { + this.moduleFactoryMap[id] = factory; + }, + require: function (id) { + if (this.moduleCache[id]) { + return this.moduleCache[id].exports; + } + const factory = this.moduleFactoryMap[id]; + if (!factory) { + throw new Error('Module not found: ' + id); + } + const module = this.moduleCache[id] = { exports: {} }; + factory(this.require.bind(this), module, module.exports); + return module.exports; + }, +} \ No newline at end of file diff --git a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap index 170bd8ed554b..7aa0253a2fe8 100644 --- a/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap +++ b/crates/rolldown/tests/rolldown/function/format/app/multiple_entry_modules/artifacts.snap @@ -78,7 +78,7 @@ console.log(hyperCube(5)); //#endregion }); -rolldown_runtime.run('main.js'); +rolldown_runtime.require('main.js'); ``` ## other-entry.js @@ -102,5 +102,5 @@ console.log(cube(5)); //#endregion }); -rolldown_runtime.run('other-entry.js'); +rolldown_runtime.require('other-entry.js'); ``` diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index 913ea8a13ce2..b30a76a11bf7 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -4237,6 +4237,7 @@ snapshot_kind: text # tests/rolldown/function/format/app/multiple_entry_modules +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD - main-!~{000}~.js => main-gMSufccO.js @@ -4249,6 +4250,8 @@ snapshot_kind: text ======= ======= >>>>>>> 582c2f393 (feat: render app chunk and replace require module id to stable id (#2156)) +======= +>>>>>>> 332ebbe1d (feat: implement rolldown_runtime and fix react-refresh make react-example run (#2159)) <<<<<<< HEAD - main-!~{000}~.mjs => main-OEIxNKs9.mjs - other-entry-!~{001}~.mjs => other-entry-WSMhxqR9.mjs @@ -4264,6 +4267,10 @@ snapshot_kind: text ======= - main-!~{000}~.mjs => main-lE-CsZar.mjs - other-entry-!~{001}~.mjs => other-entry-cL10WWA7.mjs +======= +- main-!~{000}~.mjs => main-ASwzPJTD.mjs +- other-entry-!~{001}~.mjs => other-entry-8oGJqUMX.mjs +>>>>>>> 5c89389e (feat: implement rolldown_runtime and fix react-refresh make react-example run (#2159)) - cube-!~{002}~.mjs => cube-ql8lOQly.mjs >>>>>>> ebc7a043 (feat: render app chunk and replace require module id to stable id (#2156)) >>>>>>> 582c2f393 (feat: render app chunk and replace require module id to stable id (#2156)) diff --git a/examples/react-hmr/main.js b/examples/react-hmr/main.js index d54c6dca08f6..76dcb4362ab9 100644 --- a/examples/react-hmr/main.js +++ b/examples/react-hmr/main.js @@ -1,3 +1,5 @@ +// TODO plugin-react-refresh insert it at entry load +import 'react-refresh-entry.js' import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' diff --git a/examples/react-hmr/plugin-react-refresh/index.cjs b/examples/react-hmr/plugin-react-refresh/index.cjs index 0fc6aaf5d4b7..6b84be24abba 100644 --- a/examples/react-hmr/plugin-react-refresh/index.cjs +++ b/examples/react-hmr/plugin-react-refresh/index.cjs @@ -7,7 +7,7 @@ const runtimePublicPath = '/@react-refresh' const runtimeFilePath = require.resolve( 'react-refresh/cjs/react-refresh-runtime.development.js', ) - +const refreshEntry = 'react-refresh-entry.js' const runtimeCode = ` const exports = {} ${fs.readFileSync(runtimeFilePath, 'utf-8')} @@ -40,15 +40,24 @@ function reactRefreshPlugin() { if (id === runtimePublicPath) { return id } + if (id === refreshEntry) { + return id + } }, load(id) { if (id === runtimePublicPath) { return runtimeCode } + if (id === refreshEntry) { + return preambleCode + } }, transform(code, id, ssr) { + if (id === runtimePublicPath || id === refreshEntry) { + return + } if (!/\.(t|j)sx?$/.test(id) || id.includes('node_modules')) { return } @@ -121,20 +130,6 @@ function reactRefreshPlugin() { map: result.map, } }, - - transformIndexHtml() { - if (shouldSkip) { - return - } - - return [ - { - tag: 'script', - attrs: { type: 'module' }, - children: preambleCode, - }, - ] - }, } } diff --git a/examples/react-hmr/rolldown.config.js b/examples/react-hmr/rolldown.config.js index 4d597146e5cf..438d334d4365 100644 --- a/examples/react-hmr/rolldown.config.js +++ b/examples/react-hmr/rolldown.config.js @@ -4,11 +4,15 @@ import reactRefresh from './plugin-react-refresh/index.cjs' export default defineConfig({ input: './main.js', + define: { + 'process.env.NODE_ENV': '"development"', + }, plugins: [ reactRefresh(), babel({ extensions: ['.js', '.jsx', ''], - include: ['/@react-refresh', '*.js', '*.jsx'], + include: ['/@react-refresh', /\.jsx?$/], + exclude: /node_modules/, babelHelpers: 'inline', skipPreflightCheck: true, plugins: ['@babel/plugin-transform-modules-commonjs'], From b283d5c669891c75659f6ef64fbd3899a7c9a716 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 6 Sep 2024 15:50:14 +0800 Subject: [PATCH 11/44] feat: implement hmr runtime (#2172) --- .../src/runtime/runtime-without-comments.js | 95 ++++++++++++++++++- .../artifacts.snap | 6 ++ ...egration_rolldown__filename_with_hash.snap | 4 + 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/crates/rolldown/src/runtime/runtime-without-comments.js b/crates/rolldown/src/runtime/runtime-without-comments.js index c9b200fe567f..dc096531a71d 100644 --- a/crates/rolldown/src/runtime/runtime-without-comments.js +++ b/crates/rolldown/src/runtime/runtime-without-comments.js @@ -67,21 +67,108 @@ var __require = /* @__PURE__ */ (x => }) var rolldown_runtime = { + patching: false, + patchedModuleFactoryMap: [], + executeModuleStack: [], moduleCache: {}, moduleFactoryMap: {}, define: function (id, factory) { - this.moduleFactoryMap[id] = factory; + if (self.patching) { + this.patchedModuleFactoryMap[id] = factory; + } else { + this.moduleFactoryMap[id] = factory; + } }, require: function (id) { + const parent = this.executeModuleStack.length > 1 ? this.executeModuleStack[this.executeModuleStack.length - 1] : null; if (this.moduleCache[id]) { - return this.moduleCache[id].exports; + var module = this.moduleCache[id]; + if(module.parents.indexOf(parent) === -1) { + module.parents.push(parent); + } + return module.exports; } - const factory = this.moduleFactoryMap[id]; + var factory = this.moduleFactoryMap[id]; if (!factory) { throw new Error('Module not found: ' + id); } - const module = this.moduleCache[id] = { exports: {} }; + var module = this.moduleCache[id] = { + exports: {}, + parents: [parent], + hot: { + selfAccept: false, + accept: function() { + this.selfAccept = true; + } + } + }; + this.executeModuleStack.push(id); factory(this.require.bind(this), module, module.exports); + this.executeModuleStack.pop(); return module.exports; }, + patch: function(updateModuleIds, callback) { + self.patching = true; + + var boundaries = []; + var invalidModuleIds = []; + + for (var i = 0; i < updateModuleIds.length; i++) { + foundBoundariesAndInvalidModuleIds(updateModuleIds[i], boundaries, invalidModuleIds) + } + + for (var i = 0; i < invalidModuleIds.length; i++) { + var id = invalidModuleIds[i]; + delete this.moduleCache[id]; + this.moduleFactoryMap[id] = this.patchedModuleFactoryMap[id]; + } + + for (var i = 0; i < boundaries.length; i++) { + this.require(boundaries[i]); + } + + self.patching = false; + + function foundBoundariesAndInvalidModuleIds(updateModuleId, boundaries, invalidModuleIds) { + var queue = [ { moduleId: updateModuleId, chain: [updateModuleId] }]; + var visited = {}; + + + while (queue.length > 0) { + var item = queue.pop(); + var moduleId = item.moduleId; + var chain = item.chain; + + if (visited[moduleId]) { + continue; + } + + if (module.selfAccept) { + if(boundaries.indexOf(moduleId) === -1) { + boundaries.push(moduleId); + } + for (var i = 0; index < chain.length; index++) { + if(invalidModuleIds.indexOf(chain[i]) === -1) { + invalidModuleIds.push(chain[i]); + } + } + continue; + } + + var module = rolldown_runtime.moduleCache[moduleId]; + + for(var i = 0; i < module.parents.length; i++) { + var parent = module.parents[i]; + queue.push({ + moduleId: parent, + chain: chain.concat([parent]) + }); + } + + visited[moduleId] = true; + } + + + } + } } \ No newline at end of file diff --git a/crates/rolldown/tests/esbuild/packagejson/package_json_browser_map_avoid_missing/artifacts.snap b/crates/rolldown/tests/esbuild/packagejson/package_json_browser_map_avoid_missing/artifacts.snap index 9940a0a24bf6..14ef132a3a68 100644 --- a/crates/rolldown/tests/esbuild/packagejson/package_json_browser_map_avoid_missing/artifacts.snap +++ b/crates/rolldown/tests/esbuild/packagejson/package_json_browser_map_avoid_missing/artifacts.snap @@ -19,9 +19,15 @@ var require_component_indexof = __commonJS({ "node_modules/component-indexof/ind //#endregion //#region node_modules/component-classes/index.js try { +<<<<<<< HEAD:crates/rolldown/tests/esbuild/packagejson/package_json_browser_map_avoid_missing/artifacts.snap var index = require_component_indexof(); } catch (err) { var index = require_component_indexof(); +======= + var index$1 = require_component_indexof_index(); +} catch (err) { + var index$1 = require_component_indexof_index(); +>>>>>>> b6bdba8f3 (feat: implement hmr runtime (#2172)):crates/rolldown/tests/esbuild/packagejson/test_package_json_browser_map_avoid_missing/artifacts.snap } //#endregion diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index b30a76a11bf7..a62d08724335 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -2933,7 +2933,11 @@ snapshot_kind: text # tests/esbuild/lower/ts_lower_private_field_optional_chain2015_no_bundle +<<<<<<< HEAD - entry-!~{000}~.js => entry-jF2aLy7c.js +======= +- entry-!~{000}~.mjs => entry-NqLgVvPa.mjs +>>>>>>> b6bdba8f3 (feat: implement hmr runtime (#2172)) # tests/esbuild/lower/ts_lower_private_static_members2015_no_bundle From 8b407cf88919e7a22042ec6022cc1e89c9495ddc Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 6 Sep 2024 17:31:09 +0800 Subject: [PATCH 12/44] feat: generate hmr chunk (#2173) --- crates/rolldown/src/bundler.rs | 76 ++++++----- .../src/module_loader/hmr_module_loader.rs | 31 ++--- .../src/runtime/runtime-without-comments.js | 10 +- .../rolldown/src/stages/generate_stage/mod.rs | 1 + .../stages/generate_stage/render_hmr_chunk.rs | 119 ++++++++++++++++++ crates/rolldown/src/types/hmr_output.rs | 7 -- crates/rolldown/src/types/mod.rs | 1 - 7 files changed, 189 insertions(+), 56 deletions(-) create mode 100644 crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs delete mode 100644 crates/rolldown/src/types/hmr_output.rs diff --git a/crates/rolldown/src/bundler.rs b/crates/rolldown/src/bundler.rs index 30c79b00bbd9..d21a32439f2c 100644 --- a/crates/rolldown/src/bundler.rs +++ b/crates/rolldown/src/bundler.rs @@ -5,9 +5,12 @@ use super::stages::{ use crate::{ bundler_builder::BundlerBuilder, module_loader::hmr_module_loader::HmrModuleLoader, - stages::{generate_stage::GenerateStage, scan_stage::ScanStage}, + stages::{ + generate_stage::{render_hmr_chunk::render_hmr_chunk, GenerateStage}, + scan_stage::ScanStage, + }, type_alias::IndexEcmaAst, - types::{bundle_output::BundleOutput, hmr_output::HmrOutput}, + types::bundle_output::BundleOutput, watcher::watcher::{wait_for_change, Watcher}, BundlerOptions, SharedOptions, SharedResolver, }; @@ -52,26 +55,9 @@ impl Bundler { impl Bundler { #[tracing::instrument(level = "debug", skip_all)] pub async fn write(&mut self) -> Result { - let dir = self.options.cwd.join(&self.options.dir); - let mut output = self.bundle_up(/* is_write */ true).await?; - self.fs.create_dir_all(&dir).map_err(|err| { - anyhow::anyhow!("Could not create directory for output chunks: {:?}", dir).context(err) - })?; - - for chunk in &output.assets { - let dest = dir.join(chunk.filename()); - if let Some(p) = dest.parent() { - if !self.fs.exists(p) { - self.fs.create_dir_all(p).unwrap(); - } - }; - self - .fs - .write(&dest, chunk.content_as_bytes()) - .map_err(|err| anyhow::anyhow!("Failed to write file in {:?}", dest).context(err))?; - } + self.write_file_to_disk(&output)?; self.plugin_driver.write_bundle(&mut output.assets).await?; @@ -146,7 +132,7 @@ impl Bundler { } #[allow(clippy::unused_async)] - pub async fn hmr_rebuild(&mut self, changed_files: Vec) -> Result { + pub async fn hmr_rebuild(&mut self, changed_files: Vec) -> Result { let hmr_module_loader = HmrModuleLoader::new( Arc::clone(&self.options), Arc::clone(&self.plugin_driver), @@ -157,19 +143,47 @@ impl Bundler { std::mem::take(&mut self.pervious_index_ecma_ast), )?; - let output = match hmr_module_loader.fetch_changed_modules(changed_files).await? { - Ok(output) => output, - Err(errors) => { - return Ok(HmrOutput { warnings: vec![], errors }); - } - }; + let mut hmr_module_loader_output = + match hmr_module_loader.fetch_changed_files(changed_files).await? { + Ok(output) => output, + Err(errors) => { + return Ok(BundleOutput { warnings: vec![], errors, assets: vec![] }); + } + }; + + let output = render_hmr_chunk(&self.options, &mut hmr_module_loader_output); + + self.write_file_to_disk(&output)?; // store last build modules info - self.previous_module_table = output.module_table; - self.previous_module_id_to_modules = output.module_id_to_modules; - self.pervious_index_ecma_ast = output.index_ecma_ast; + self.previous_module_table = hmr_module_loader_output.module_table; + self.previous_module_id_to_modules = hmr_module_loader_output.module_id_to_modules; + self.pervious_index_ecma_ast = hmr_module_loader_output.index_ecma_ast; + + Ok(output) + } + + fn write_file_to_disk(&self, output: &BundleOutput) -> Result<()> { + let dir = self.options.cwd.join(&self.options.dir); + + self.fs.create_dir_all(&dir).map_err(|err| { + anyhow::anyhow!("Could not create directory for output chunks: {:?}", dir).context(err) + })?; + + for chunk in &output.assets { + let dest = dir.join(chunk.filename()); + if let Some(p) = dest.parent() { + if !self.fs.exists(p) { + self.fs.create_dir_all(p).unwrap(); + } + }; + self + .fs + .write(&dest, chunk.content_as_bytes()) + .map_err(|err| anyhow::anyhow!("Failed to write file in {:?}", dest).context(err))?; + } - Ok(HmrOutput { warnings: output.warnings, errors: vec![] }) + Ok(()) } async fn try_build(&mut self) -> Result> { diff --git a/crates/rolldown/src/module_loader/hmr_module_loader.rs b/crates/rolldown/src/module_loader/hmr_module_loader.rs index cbfbb1f2eb17..80a2dcc3d901 100644 --- a/crates/rolldown/src/module_loader/hmr_module_loader.rs +++ b/crates/rolldown/src/module_loader/hmr_module_loader.rs @@ -4,7 +4,6 @@ use super::task_result::NormalModuleTaskResult; use super::Msg; use crate::module_loader::task_context::TaskContext; use crate::type_alias::IndexEcmaAst; -use crate::types::symbols::Symbols; use arcstr::ArcStr; use oxc::index::IndexVec; use oxc::minifier::ReplaceGlobalDefinesConfig; @@ -34,10 +33,8 @@ impl HmrIntermediateNormalModules { } } - pub fn alloc_ecma_module_idx(&mut self, symbols: &mut Symbols) -> ModuleIdx { - let id = self.modules.push(None); - symbols.alloc_one(); - id + pub fn alloc_ecma_module_idx(&mut self) -> ModuleIdx { + self.modules.push(None) } } @@ -48,7 +45,6 @@ pub struct HmrModuleLoader { visited: FxHashMap, remaining: u32, intermediate_normal_modules: HmrIntermediateNormalModules, - symbols: Symbols, } pub struct HmrModuleLoaderOutput { @@ -58,6 +54,8 @@ pub struct HmrModuleLoaderOutput { pub index_ecma_ast: IndexEcmaAst, // pub symbols: Symbols, pub warnings: Vec, + pub changed_modules: Vec, + pub diff_modules: Vec, } impl HmrModuleLoader { @@ -100,7 +98,6 @@ impl HmrModuleLoader { let intermediate_normal_modules = HmrIntermediateNormalModules::new(previous_module_table, pervious_index_ecma_ast); - let symbols = Symbols::default(); Ok(Self { shared_context: common_data, @@ -109,7 +106,6 @@ impl HmrModuleLoader { visited: previous_module_id_to_modules, remaining: 0, intermediate_normal_modules, - symbols, }) } @@ -122,7 +118,7 @@ impl HmrModuleLoader { std::collections::hash_map::Entry::Occupied(visited) => *visited.get(), std::collections::hash_map::Entry::Vacant(not_visited) => { if resolved_id.is_external { - let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(&mut self.symbols); + let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(); not_visited.insert(idx); let external_module_side_effects = if let Some(hook_side_effects) = resolved_id.side_effects @@ -151,7 +147,7 @@ impl HmrModuleLoader { self.intermediate_normal_modules.modules[idx] = Some(ext.into()); idx } else { - let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(&mut self.symbols); + let idx = self.intermediate_normal_modules.alloc_ecma_module_idx(); not_visited.insert(idx); self.remaining += 1; @@ -173,16 +169,19 @@ impl HmrModuleLoader { } #[tracing::instrument(level = "debug", skip_all)] - pub async fn fetch_changed_modules( + pub async fn fetch_changed_files( mut self, - changed_modules: Vec, + changed_files: Vec, ) -> anyhow::Result> { if self.options.input.is_empty() { return Err(anyhow::format_err!("You must supply options.input to rolldown")); } + let changed_modules: Vec = + changed_files.iter().filter_map(|m| self.visited.get(m.as_str())).copied().collect(); + let mut diff_modules: Vec = vec![]; // spawn valid changed modules - changed_modules + changed_files .into_iter() .filter_map(|m| self.visited.get(m.as_str()).map(|idx| (m, idx))) .for_each(|(m, idx)| { @@ -249,12 +248,12 @@ impl HmrModuleLoader { .collect::>(); module.set_import_records(import_records); - if let Some((ast, ast_symbol)) = ecma_related { + if let Some((ast, _ast_symbol)) = ecma_related { let ast_idx = self.intermediate_normal_modules.index_ecma_ast.push((ast, module.idx())); module.set_ecma_ast_idx(ast_idx); - self.symbols.add_ast_symbols(module_idx, ast_symbol); } self.intermediate_normal_modules.modules[module_idx] = Some(module); + diff_modules.push(module_idx); } Msg::RuntimeNormalModuleDone(_) => { unreachable!("Runtime module should not be done at hmr module loader"); @@ -293,6 +292,8 @@ impl HmrModuleLoader { // symbols: self.symbols, index_ecma_ast: self.intermediate_normal_modules.index_ecma_ast, warnings: all_warnings, + changed_modules, + diff_modules, })) } } diff --git a/crates/rolldown/src/runtime/runtime-without-comments.js b/crates/rolldown/src/runtime/runtime-without-comments.js index dc096531a71d..71328f231f7d 100644 --- a/crates/rolldown/src/runtime/runtime-without-comments.js +++ b/crates/rolldown/src/runtime/runtime-without-comments.js @@ -66,9 +66,9 @@ var __require = /* @__PURE__ */ (x => throw Error('Dynamic require of "' + x + '" is not supported') }) -var rolldown_runtime = { +var rolldown_runtime = self.rolldown_runtime = { patching: false, - patchedModuleFactoryMap: [], + patchedModuleFactoryMap: {}, executeModuleStack: [], moduleCache: {}, moduleFactoryMap: {}, @@ -110,6 +110,8 @@ var rolldown_runtime = { patch: function(updateModuleIds, callback) { self.patching = true; + callback(); + var boundaries = []; var invalidModuleIds = []; @@ -120,8 +122,12 @@ var rolldown_runtime = { for (var i = 0; i < invalidModuleIds.length; i++) { var id = invalidModuleIds[i]; delete this.moduleCache[id]; + } + + for (var id in this.patchedModuleFactoryMap) { this.moduleFactoryMap[id] = this.patchedModuleFactoryMap[id]; } + this.patchedModuleFactoryMap = {} for (var i = 0; i < boundaries.length; i++) { this.require(boundaries[i]); diff --git a/crates/rolldown/src/stages/generate_stage/mod.rs b/crates/rolldown/src/stages/generate_stage/mod.rs index ce0ab51c12f2..6c5e0afc58b6 100644 --- a/crates/rolldown/src/stages/generate_stage/mod.rs +++ b/crates/rolldown/src/stages/generate_stage/mod.rs @@ -44,6 +44,7 @@ mod code_splitting; mod compute_cross_chunk_links; mod minify_assets; mod render_chunk_to_assets; +pub mod render_hmr_chunk; pub struct GenerateStage<'a> { link_output: &'a mut LinkStageOutput, diff --git a/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs b/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs new file mode 100644 index 000000000000..a6dcecf650cd --- /dev/null +++ b/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs @@ -0,0 +1,119 @@ +use std::{ + path::Path, + time::{SystemTime, UNIX_EPOCH}, +}; + +use rolldown_common::{ + FileNameRenderOptions, FilenameTemplate, NormalizedBundlerOptions, Output, OutputAsset, + SourceMapType, +}; +use rolldown_sourcemap::{ConcatSource, RawSource}; +use rolldown_utils::rayon::{IntoParallelRefIterator, ParallelIterator}; + +use crate::{ + module_loader::hmr_module_loader::HmrModuleLoaderOutput, + utils::render_ecma_module::render_ecma_module, BundleOutput, +}; + +pub fn render_hmr_chunk( + options: &NormalizedBundlerOptions, + hmr_module_loader_output: &mut HmrModuleLoaderOutput, +) -> BundleOutput { + let module_sources = hmr_module_loader_output + .diff_modules + .par_iter() + .filter_map(|id| hmr_module_loader_output.module_table.modules[*id].as_ecma()) + .map(|m| { + ( + m.idx, + m.id.clone(), + render_ecma_module( + m, + &hmr_module_loader_output.index_ecma_ast[m.ecma_ast_idx()].0, + m.id.as_ref(), + options, + ), + ) + }) + .collect::>(); + + let mut concat_source = ConcatSource::default(); + + concat_source.add_source(Box::new(RawSource::new(format!( + "self.rolldown_runtime.patch([{}], function(){{\n", + hmr_module_loader_output + .changed_modules + .iter() + .map(|idx| format!("'{}'", hmr_module_loader_output.module_table.modules[*idx].stable_id())) + .collect::>() + .join(", ") + )))); + + module_sources.into_iter().for_each(|(module_idx, _, module_render_output)| { + if let Some(emitted_sources) = module_render_output { + concat_source.add_source(Box::new(RawSource::new(format!( + "rolldown_runtime.define('{}',function(require, module, exports){{\n", + hmr_module_loader_output.module_table.modules[module_idx].stable_id() + )))); + for source in emitted_sources { + concat_source.add_source(source); + } + concat_source.add_source(Box::new(RawSource::new("});".to_string()))); + } + }); + + concat_source.add_source(Box::new(RawSource::new("});".to_string()))); + + let (mut content, map) = concat_source.content_and_sourcemap(); + + let mut assets = vec![]; + + let filename = + FilenameTemplate::new("hmr-update.[hash].js".into()).render(&FileNameRenderOptions { + hash: Some( + &SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("should have time") + .as_millis() + .to_string(), + ), + ..Default::default() + }); + + if let Some(map) = map { + let map_filename = format!("{filename}.map",); + match options.sourcemap { + SourceMapType::File => { + let source = map.to_json_string(); + assets.push(Output::Asset(Box::new(OutputAsset { + filename: map_filename.clone(), + source: source.into(), + original_file_name: None, + name: None, + }))); + content.push_str(&format!( + "\n//# sourceMappingURL={}", + Path::new(&map_filename).file_name().expect("should have filename").to_string_lossy() + )); + } + SourceMapType::Inline => { + let data_url = map.to_data_url(); + content.push_str(&format!("\n//# sourceMappingURL={data_url}")); + } + SourceMapType::Hidden => {} + } + } + + assets.push(Output::Asset(Box::new(OutputAsset { + filename, + source: content.into(), + original_file_name: None, + name: None, + }))); + + BundleOutput { + warnings: std::mem::take(&mut hmr_module_loader_output.warnings), + errors: vec![], + assets, + } +} diff --git a/crates/rolldown/src/types/hmr_output.rs b/crates/rolldown/src/types/hmr_output.rs deleted file mode 100644 index 36c57e7a2a02..000000000000 --- a/crates/rolldown/src/types/hmr_output.rs +++ /dev/null @@ -1,7 +0,0 @@ -use rolldown_error::BuildDiagnostic; - -#[derive(Default)] -pub struct HmrOutput { - pub warnings: Vec, - pub errors: Vec, -} diff --git a/crates/rolldown/src/types/mod.rs b/crates/rolldown/src/types/mod.rs index 946e363494ee..d50e6463859a 100644 --- a/crates/rolldown/src/types/mod.rs +++ b/crates/rolldown/src/types/mod.rs @@ -5,7 +5,6 @@ pub mod bundle_output; pub mod bundler_fs; pub mod generator; -pub mod hmr_output; pub mod linking_metadata; pub mod module_factory; pub mod oxc_parse_type; From 6fada77792dae90a7a3577075df70ae7c1ebc8ce Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 9 Sep 2024 10:11:17 +0800 Subject: [PATCH 13/44] fix: diff modules ast should be mutate by IsolatingModuleFinalizer (#2184) --- .../src/runtime/runtime-without-comments.js | 8 ++-- .../stages/generate_stage/render_hmr_chunk.rs | 38 +++++++++++++++++-- ...egration_rolldown__filename_with_hash.snap | 4 ++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/crates/rolldown/src/runtime/runtime-without-comments.js b/crates/rolldown/src/runtime/runtime-without-comments.js index 71328f231f7d..9b7148b08c64 100644 --- a/crates/rolldown/src/runtime/runtime-without-comments.js +++ b/crates/rolldown/src/runtime/runtime-without-comments.js @@ -149,11 +149,13 @@ var rolldown_runtime = self.rolldown_runtime = { continue; } - if (module.selfAccept) { + var module = rolldown_runtime.moduleCache[moduleId]; + + if (module.hot.selfAccept) { if(boundaries.indexOf(moduleId) === -1) { boundaries.push(moduleId); } - for (var i = 0; index < chain.length; index++) { + for (var i = 0; i < chain.length; i++) { if(invalidModuleIds.indexOf(chain[i]) === -1) { invalidModuleIds.push(chain[i]); } @@ -161,8 +163,6 @@ var rolldown_runtime = self.rolldown_runtime = { continue; } - var module = rolldown_runtime.moduleCache[moduleId]; - for(var i = 0; i < module.parents.length; i++) { var parent = module.parents[i]; queue.push({ diff --git a/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs b/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs index a6dcecf650cd..d7a8d0256144 100644 --- a/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs +++ b/crates/rolldown/src/stages/generate_stage/render_hmr_chunk.rs @@ -3,22 +3,54 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +use oxc::ast::VisitMut; use rolldown_common::{ - FileNameRenderOptions, FilenameTemplate, NormalizedBundlerOptions, Output, OutputAsset, + FileNameRenderOptions, FilenameTemplate, Module, NormalizedBundlerOptions, Output, OutputAsset, SourceMapType, }; +use rolldown_ecmascript::AstSnippet; use rolldown_sourcemap::{ConcatSource, RawSource}; -use rolldown_utils::rayon::{IntoParallelRefIterator, ParallelIterator}; +use rolldown_utils::rayon::{IntoParallelRefIterator, ParallelBridge, ParallelIterator}; use crate::{ + module_finalizers::isolating::{IsolatingModuleFinalizer, IsolatingModuleFinalizerContext}, module_loader::hmr_module_loader::HmrModuleLoaderOutput, - utils::render_ecma_module::render_ecma_module, BundleOutput, + utils::render_ecma_module::render_ecma_module, + BundleOutput, }; +#[allow(clippy::too_many_lines)] pub fn render_hmr_chunk( options: &NormalizedBundlerOptions, hmr_module_loader_output: &mut HmrModuleLoaderOutput, ) -> BundleOutput { + hmr_module_loader_output + .index_ecma_ast + .iter_mut() + .par_bridge() + .filter(|(_ast, owner)| { + hmr_module_loader_output.module_table.modules[*owner].as_ecma().is_some() + && hmr_module_loader_output.diff_modules.contains(owner) + }) + .for_each(|(ast, owner)| { + let Module::Ecma(module) = &hmr_module_loader_output.module_table.modules[*owner] else { + return; + }; + ast.program.with_mut(|fields| { + let (oxc_program, alloc) = (fields.program, fields.allocator); + let mut finalizer = IsolatingModuleFinalizer { + alloc, + scope: &module.scope, + ctx: &IsolatingModuleFinalizerContext { + module, + modules: &hmr_module_loader_output.module_table.modules, + }, + snippet: AstSnippet::new(alloc), + }; + finalizer.visit_program(oxc_program); + }); + }); + let module_sources = hmr_module_loader_output .diff_modules .par_iter() diff --git a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap index a62d08724335..11d33204fa2b 100644 --- a/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap +++ b/crates/rolldown/tests/snapshots/integration_rolldown__filename_with_hash.snap @@ -2933,11 +2933,15 @@ snapshot_kind: text # tests/esbuild/lower/ts_lower_private_field_optional_chain2015_no_bundle +<<<<<<< HEAD <<<<<<< HEAD - entry-!~{000}~.js => entry-jF2aLy7c.js ======= - entry-!~{000}~.mjs => entry-NqLgVvPa.mjs >>>>>>> b6bdba8f3 (feat: implement hmr runtime (#2172)) +======= +- entry-!~{000}~.mjs => entry-Jxn77t-m.mjs +>>>>>>> c5604e468 (fix: diff modules ast should be mutate by IsolatingModuleFinalizer (#2184)) # tests/esbuild/lower/ts_lower_private_static_members2015_no_bundle From 681a710bdd30e627175863e1f7959f12f8eb0cf5 Mon Sep 17 00:00:00 2001 From: underfin Date: Mon, 9 Sep 2024 16:17:27 +0800 Subject: [PATCH 14/44] feat: add watcher (#2185) --- examples/react-hmr/rolldown.config.js | 1 + packages/rolldown/package.json | 3 +- packages/rolldown/src/cli/commands/bundle.ts | 5 ++ .../rolldown/src/options/input-options.ts | 1 + packages/rolldown/src/rolldown-build.ts | 11 ++- .../cli/__snapshots__/cli-e2e.test.ts.snap | 67 ++++++++++++++++++- pnpm-lock.yaml | 3 + 7 files changed, 86 insertions(+), 5 deletions(-) diff --git a/examples/react-hmr/rolldown.config.js b/examples/react-hmr/rolldown.config.js index 438d334d4365..cb939a7c0bdf 100644 --- a/examples/react-hmr/rolldown.config.js +++ b/examples/react-hmr/rolldown.config.js @@ -21,4 +21,5 @@ export default defineConfig({ output: { format: 'app', }, + dev: true, }) diff --git a/packages/rolldown/package.json b/packages/rolldown/package.json index 7b595c284d19..55cb88987d97 100644 --- a/packages/rolldown/package.json +++ b/packages/rolldown/package.json @@ -101,7 +101,8 @@ "dtsHeader": "type MaybePromise = T | Promise\ntype Nullable = T | null | undefined\ntype VoidNullable = T | null | undefined | void\nexport type BindingStringOrRegex = string | RegExp\n\n" }, "dependencies": { - "zod": "^3.23.8" + "zod": "^3.23.8", + "chokidar": "3.6.0" }, "devDependencies": { "@napi-rs/cli": "^3.0.0-alpha.60", diff --git a/packages/rolldown/src/cli/commands/bundle.ts b/packages/rolldown/src/cli/commands/bundle.ts index 863fed77ae8e..d75ae2b45522 100644 --- a/packages/rolldown/src/cli/commands/bundle.ts +++ b/packages/rolldown/src/cli/commands/bundle.ts @@ -119,6 +119,11 @@ async function bundleInner( const duration = endTime - startTime // If the build time is more than 1s, we should display it in seconds. logger.success(`Finished in ${colors.bold(ms(duration))}`) + + if (options.dev) { + logger.log(`Watching for changes...`) + await build.experimental_hmr() + } } function printBundleOutputPretty(output: RolldownOutput) { diff --git a/packages/rolldown/src/options/input-options.ts b/packages/rolldown/src/options/input-options.ts index c7df9ae844bc..f4f38e4afce7 100644 --- a/packages/rolldown/src/options/input-options.ts +++ b/packages/rolldown/src/options/input-options.ts @@ -150,6 +150,7 @@ export const inputOptionsSchema = z.strictObject({ .optional(), define: z.record(z.string()).describe('define global variables').optional(), inject: z.record(z.string().or(z.tuple([z.string(), z.string()]))).optional(), + dev: z.boolean().optional(), profilerNames: z.boolean().optional(), jsx: jsxOptionsSchema.optional(), watch: watchOptionsSchema.or(z.literal(false)).optional(), diff --git a/packages/rolldown/src/rolldown-build.ts b/packages/rolldown/src/rolldown-build.ts index 065ee982c35c..796f1572e8d7 100644 --- a/packages/rolldown/src/rolldown-build.ts +++ b/packages/rolldown/src/rolldown-build.ts @@ -1,6 +1,7 @@ import type { OutputOptions } from './options/output-options' import { transformToRollupOutput } from './utils/transform-to-rollup-output' import { BundlerWithStopWorker, createBundler } from './utils/create-bundler' +import chokidar from 'chokidar' import type { RolldownOutput } from './types/rolldown-output' import type { HasProperty, TypeAssert } from './utils/type-assert' @@ -46,8 +47,14 @@ export class RolldownBuild { await bundler.close() } - async experimental_hmr_rebuild(changedFiles: string[]): Promise { - await this.#bundler!.hmrRebuild(changedFiles) + async experimental_hmr(): Promise { + const cwd = this.#inputOptions.cwd ?? process.cwd() + const watcher = chokidar.watch([cwd]) + watcher.on('change', async (file) => { + if (file) { + await this.#bundler!.hmrRebuild([file]) + } + }) } } diff --git a/packages/rolldown/tests/cli/__snapshots__/cli-e2e.test.ts.snap b/packages/rolldown/tests/cli/__snapshots__/cli-e2e.test.ts.snap index 49d9ee2493e8..5082930a0539 100644 --- a/packages/rolldown/tests/cli/__snapshots__/cli-e2e.test.ts.snap +++ b/packages/rolldown/tests/cli/__snapshots__/cli-e2e.test.ts.snap @@ -26,6 +26,7 @@ OPTIONS --chunk-file-names . --cwd Current working directory. --define Define global variables. + --dev . --entry-file-names . --es-module Always generate \`__esModule\` marks in non-ESM formats, defaults to \`if-default-prop\` (use \`--no-esModule\` to always disable). --exports Specify a export mode (auto, named, default, none). @@ -107,9 +108,71 @@ exports[`cli options for bundling > should handle single array options 1`] = ` `; exports[`cli options for bundling > should handle single boolean option 1`] = ` -"/index.js chunk │ size: 0.09 kB +"Fast JavaScript/TypeScript bundler in Rust with Rollup-compatible API. (rolldown v0.13.2) -" +USAGE rolldown -c or rolldown + +OPTIONS + + --config -c, Path to the config file (default: \`rollup.config.js\`). + --dir -d, Output directory, defaults to \`dist\`. + --external -e, Comma-separated list of module ids to exclude from the bundle \`,...\`. + --format -f, Output format of the generated bundle (supports esm, cjs, and iife). + --globals -g, Comma-separated list of \`module-id:global\` pairs (\`:,...\`). + --help -h, Show help. + --minify -m, Minify the bundled file. + --name -n, Name for UMD / IIFE format outputs. + --platform -p, Platform for which the code should be generated (node, browser, neutral). + --sourcemap -s, Generate sourcemap (\`-s inline\` for inline, or pass the \`-s\` on the last argument if you want to generate \`.map\` file). + --version -v, Show version number. + --advanced-chunks.groups . + --advanced-chunks.min-share-count . + --advanced-chunks.min-size . + --asset-file-names . + --banner Code to insert the top of the bundled file (outside the wrapper function). + --chunk-file-names . + --cwd Current working directory. + --define Define global variables. + --dev . + --entry-file-names . + --es-module Always generate \`__esModule\` marks in non-ESM formats, defaults to \`if-default-prop\` (use \`--no-esModule\` to always disable). + --exports Specify a export mode (auto, named, default, none). + --extend Extend global variable defined by name in IIFE or UMD formats. + --footer