Repository: rolldown/rolldown
Pull request #4042: refactor(rust): improve `rolldown_plugin_module_preload_polyfill`
Author: login: shulaoda
Labels: name: needs-triage
Created: 2025-04-07T03:25:17Z | Merged: 2025-04-07T05:30:50Z

### Description

Related to #3968 


Diff:
diff --git a/crates/rolldown_binding/src/options/plugin/binding_builtin_plugin.rs b/crates/rolldown_binding/src/options/plugin/binding_builtin_plugin.rs
index 04806cb1bbfb..59fd42d4b613 100644
--- a/crates/rolldown_binding/src/options/plugin/binding_builtin_plugin.rs
+++ b/crates/rolldown_binding/src/options/plugin/binding_builtin_plugin.rs
@@ -331,15 +331,7 @@ impl TryFrom<BindingBuiltinPlugin> for Arc<dyn Pluginable> {
         Arc::new(ImportGlobPlugin { config })
       }
       BindingBuiltinPluginName::DynamicImportVars => Arc::new(DynamicImportVarsPlugin {}),
-      BindingBuiltinPluginName::ModulePreloadPolyfill => {
-        let skip = if let Some(options) = plugin.options {
-          let config = BindingModulePreloadPolyfillPluginConfig::from_unknown(options)?;
-          config.skip.unwrap_or_default()
-        } else {
-          false
-        };
-        Arc::new(ModulePreloadPolyfillPlugin { skip })
-      }
+      BindingBuiltinPluginName::ModulePreloadPolyfill => Arc::new(ModulePreloadPolyfillPlugin {}),
       BindingBuiltinPluginName::Manifest => {
         let config = if let Some(options) = plugin.options {
           BindingManifestPluginConfig::from_unknown(options)?.into()
diff --git a/crates/rolldown_plugin/src/plugin_context.rs b/crates/rolldown_plugin/src/plugin_context.rs
index b92eac41a7b0..f8da797e664b 100644
--- a/crates/rolldown_plugin/src/plugin_context.rs
+++ b/crates/rolldown_plugin/src/plugin_context.rs
@@ -10,8 +10,8 @@ use anyhow::Context;
 use arcstr::ArcStr;
 use derive_more::Debug;
 use rolldown_common::{
-  ModuleDefFormat, ModuleInfo, ModuleLoaderMsg, ResolvedId, SharedFileEmitter,
-  SharedNormalizedBundlerOptions, side_effects::HookSideEffects,
+  ModuleDefFormat, ModuleInfo, ModuleLoaderMsg, NormalizedBundlerOptions, ResolvedId,
+  SharedFileEmitter, SharedNormalizedBundlerOptions, side_effects::HookSideEffects,
 };
 use rolldown_resolver::{ResolveError, Resolver};
 use rolldown_utils::dashmap::{FxDashMap, FxDashSet};
@@ -48,6 +48,10 @@ impl PluginContext {
       tx: Arc::clone(&self.tx),
     }))
   }
+
+  pub fn options(&self) -> &NormalizedBundlerOptions {
+    &self.options
+  }
 }
 
 impl Deref for PluginContext {
diff --git a/crates/rolldown_plugin_module_preload_polyfill/README.md b/crates/rolldown_plugin_module_preload_polyfill/README.md
new file mode 100644
index 000000000000..fa00c549eddc
--- /dev/null
+++ b/crates/rolldown_plugin_module_preload_polyfill/README.md
@@ -0,0 +1,61 @@
+# Maintenance Guide
+
+A plugin for [rolldown-vite](https://github.com/vitejs/rolldown-vite) that injects a `modulepreload` polyfill for legacy browsers, ported from [Vite's `modulePreloadPolyfillPlugin`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34).
+
+**This plugin is exclusive to `rolldown-vite` and is currently considered unstable for external use.**
+
+## 📦 What it does
+
+This plugin resolves the special import:
+
+```js
+import 'vite/modulepreload-polyfill';
+```
+
+By default, it injects a polyfill for `rel="modulepreload"` into each entry module, ensuring compatibility with browsers that don't support `modulepreload` natively.
+
+## 🚀 Debug Usage
+
+```js
+import { defineConfig } from 'rolldown';
+import { modulePreloadPolyfillPlugin } from 'rolldown/experimental';
+
+export default defineConfig({
+  input: {
+    entry: './index.ts',
+  },
+  plugins: [modulePreloadPolyfillPlugin()],
+});
+```
+
+## 🧪 Porting Differences
+
+Unlike `Vite`, which uses `__VITE_IS_MODERN__` to control the polyfill, this plugin automatically applies the polyfill only when `output.format` is `esm`, matching `Vite`’s behavior.
+
+## 📄 License
+
+### Polyfill License
+
+The `module-preload-polyfill.js` polyfill code is based on
+[es-module-shims](https://github.com/guybedford/es-module-shims) by `Guy Bedford`:
+
+```
+MIT License
+
+Copyright (C) 2018-2021 Guy Bedford
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+```
diff --git a/crates/rolldown_plugin_module_preload_polyfill/src/lib.rs b/crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
index aff72077fe67..e269dcda1b1d 100644
--- a/crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
+++ b/crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
@@ -1,20 +1,16 @@
-use rolldown_common::side_effects::HookSideEffects;
+use std::borrow::Cow;
+
+use rolldown_common::{OutputFormat, side_effects::HookSideEffects};
 use rolldown_plugin::{
   HookLoadArgs, HookLoadOutput, HookLoadReturn, HookResolveIdArgs, HookResolveIdOutput,
   HookResolveIdReturn, Plugin, PluginContext,
 };
-use std::borrow::Cow;
 
 const MODULE_PRELOAD_POLYFILL: &str = "vite/modulepreload-polyfill";
-
 const RESOLVED_MODULE_PRELOAD_POLYFILL_ID: &str = "\0vite/modulepreload-polyfill.js";
 
-const IS_MODERN_FLAG: &str = "__VITE_IS_MODERN__";
-
 #[derive(Debug)]
-pub struct ModulePreloadPolyfillPlugin {
-  pub skip: bool,
-}
+pub struct ModulePreloadPolyfillPlugin;
 
 impl Plugin for ModulePreloadPolyfillPlugin {
   fn name(&self) -> Cow<'static, str> {
@@ -26,25 +22,23 @@ impl Plugin for ModulePreloadPolyfillPlugin {
     _ctx: &PluginContext,
     args: &HookResolveIdArgs<'_>,
   ) -> HookResolveIdReturn {
-    Ok((args.specifier == MODULE_PRELOAD_POLYFILL).then(|| HookResolveIdOutput {
+    Ok((args.specifier == MODULE_PRELOAD_POLYFILL).then_some(HookResolveIdOutput {
       id: arcstr::literal!(RESOLVED_MODULE_PRELOAD_POLYFILL_ID),
       ..Default::default()
     }))
   }
 
-  async fn load(&self, _ctx: &PluginContext, args: &HookLoadArgs<'_>) -> HookLoadReturn {
-    if args.id == RESOLVED_MODULE_PRELOAD_POLYFILL_ID {
-      if self.skip {
-        return Ok(Some(HookLoadOutput { code: String::new(), ..Default::default() }));
+  async fn load(&self, ctx: &PluginContext, args: &HookLoadArgs<'_>) -> HookLoadReturn {
+    Ok((args.id == RESOLVED_MODULE_PRELOAD_POLYFILL_ID).then(|| {
+      if matches!(ctx.options().format, OutputFormat::Esm) {
+        HookLoadOutput {
+          code: include_str!("module-preload-polyfill.js").to_string(),
+          side_effects: Some(HookSideEffects::True),
+          ..Default::default()
+        }
+      } else {
+        HookLoadOutput { code: String::new(), ..Default::default() }
       }
-
-      Ok(Some(HookLoadOutput {
-        code: format!("{IS_MODERN_FLAG}&&{}", include_str!("module-preload-polyfill.js")),
-        side_effects: Some(HookSideEffects::True),
-        ..Default::default()
-      }))
-    } else {
-      Ok(None)
-    }
+    }))
   }
 }
diff --git a/crates/rolldown_plugin_module_preload_polyfill/src/module-preload-polyfill.js b/crates/rolldown_plugin_module_preload_polyfill/src/module-preload-polyfill.js
index bb76ded90b08..a02b66c57fa8 100644
--- a/crates/rolldown_plugin_module_preload_polyfill/src/module-preload-polyfill.js
+++ b/crates/rolldown_plugin_module_preload_polyfill/src/module-preload-polyfill.js
@@ -1,26 +1,3 @@
-// Port from https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34
-
-/**
-The following polyfill function is meant to run in the browser and adapted from
-https://github.com/guybedford/es-module-shims
-MIT License
-Copyright (C) 2018-2021 Guy Bedford
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-*/
-
 (function polyfill() {
   const relList = document.createElement('link').relList;
   if (relList && relList.supports && relList.supports('modulepreload')) {
diff --git a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/basic/main.js.snap b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/basic/main.js.snap
index fd55f5d22343..d099fe950a3d 100644
--- a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/basic/main.js.snap
+++ b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/basic/main.js.snap
@@ -1,6 +1,6 @@
 
 //#region \0vite/modulepreload-polyfill.js
-__VITE_IS_MODERN__ && function polyfill() {
+(function polyfill() {
 	const relList = document.createElement("link").relList;
 	if (relList && relList.supports && relList.supports("modulepreload")) return;
 	for (const link of document.querySelectorAll("link[rel=\"modulepreload\"]")) processPreload(link);
@@ -28,6 +28,6 @@ __VITE_IS_MODERN__ && function polyfill() {
 		const fetchOpts = getFetchOpts(link);
 		fetch(link.href, fetchOpts);
 	}
-}();
+})();
 
 //#endregion
\ No newline at end of file
diff --git a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/cjs/_config.ts b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/cjs/_config.ts
index 577ccecc3cc5..62cf23b56b5a 100644
--- a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/cjs/_config.ts
+++ b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/cjs/_config.ts
@@ -7,9 +7,6 @@ export default defineTest({
     output: {
       format: 'cjs',
     },
-    define: {
-      __VITE_IS_MODERN__: 'false',
-    },
     plugins: [modulePreloadPolyfillPlugin()],
   },
   async afterTest(output) {
diff --git a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/_config.ts b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/_config.ts
deleted file mode 100644
index 2427d0eb3095..000000000000
--- a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/_config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { expect } from 'vitest'
-import { defineTest } from 'rolldown-tests'
-import { modulePreloadPolyfillPlugin } from 'rolldown/experimental'
-
-export default defineTest({
-  config: {
-    plugins: [
-      modulePreloadPolyfillPlugin({
-        skip: true,
-      }),
-    ],
-  },
-  afterTest(output) {
-    expect(output.output[0].code.length).toBe(0)
-  },
-})
diff --git a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/main.js b/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/main.js
deleted file mode 100644
index da2e79fdbde9..000000000000
--- a/packages/rolldown/tests/fixtures/builtin-plugin/module-preload-polyfill/skip/main.js
+++ /dev/null
@@ -1 +0,0 @@
-import 'vite/modulepreload-polyfill'
\ No newline at end of file


Reviews:
user: login: sapphi-red
body: Since this plugin wouldn't be used with rollup-vite, relying on rolldown specific behavior (using the outputOptions in transform hook) should be ok.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:13:28Z
user: login: shulaoda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:18:05Z
user: login: shulaoda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:20:02Z
user: login: shulaoda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:20:09Z
user: login: shulaoda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:30:44Z
user: login: sapphi-red
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:48:11Z
user: login: sapphi-red
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:49:52Z
user: login: sapphi-red
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:51:30Z
user: login: shulaoda
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T04:55:01Z
user: login: sapphi-red
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-07T05:14:13Z

Review comments:
diff_hunk: @@ -0,0 +1,63 @@
+# rolldown_plugin_module_preload_polyfill
+
+A plugin for [rolldown-vite](https://github.com/vitejs/rolldown-vite) that injects a `modulepreload` polyfill for legacy browsers, ported from [Vite's `modulePreloadPolyfillPlugin`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34).
+
+## 📦 What it does
+
+This plugin resolves the special import:
+
+```js
+import 'vite/modulepreload-polyfill';
+```
+
+By default, it injects a polyfill for `rel="modulepreload"` into each entry module, ensuring compatibility with browsers that don't support `modulepreload` natively.
+
+## 🚀 Usage
+
+Add this plugin to your `plugins`:
+
+```js
+import { defineConfig } from 'rolldown';
+import { modulePreloadPolyfillPlugin } from 'rolldown/experimental';
+
+export default defineConfig({
+  input: {
+    entry: './index.ts',
+  },
+  plugins: [modulePreloadPolyfillPlugin()],
+});
+```
+
+## 🧪 Compatibility
+
+This plugin is originally designed for `Vite` but is more versatile.
+
+Unlike `Vite`, which uses `__VITE_IS_MODERN__` to control the polyfill, this plugin automatically applies the polyfill only when `output.format` is `esm`, matching `Vite`’s behavior.
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: sapphi-red
body: It currently injects the polyfill only when `output.format` is `esm` because the modern output uses ESM and the legacy output uses SystemJS. But in the future, that may change (for example, if https://github.com/vitejs/rolldown-vite/pull/80 lands)
created_at: 2025-04-07T04:07:26Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,16 @@
-use rolldown_common::side_effects::HookSideEffects;
+use std::borrow::Cow;
+
+use rolldown_common::{OutputFormat, side_effects::HookSideEffects};
 use rolldown_plugin::{
   HookLoadArgs, HookLoadOutput, HookLoadReturn, HookResolveIdArgs, HookResolveIdOutput,
   HookResolveIdReturn, Plugin, PluginContext,
 };
-use std::borrow::Cow;
 
 const MODULE_PRELOAD_POLYFILL: &str = "vite/modulepreload-polyfill";
-
 const RESOLVED_MODULE_PRELOAD_POLYFILL_ID: &str = "\0vite/modulepreload-polyfill.js";
 
-const IS_MODERN_FLAG: &str = "__VITE_IS_MODERN__";
-
 #[derive(Debug)]
-pub struct ModulePreloadPolyfillPlugin {
-  pub skip: bool,
path: crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
user: login: sapphi-red
body: I think we still need this `skip` option to make this part work.
https://github.com/vitejs/rolldown-vite/blob/5f9204a2df876eb4efb97b270c713a955cbc55b8/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L25-L26

created_at: 2025-04-07T04:11:18Z
author_association: MEMBER
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: sapphi-red
body: Do you expect this plugin to be used directly as well? I guess that could make it difficult to make breaking changes while we iterate and would be better to note that it's unstable yet.
created_at: 2025-04-07T04:12:56Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,16 @@
-use rolldown_common::side_effects::HookSideEffects;
+use std::borrow::Cow;
+
+use rolldown_common::{OutputFormat, side_effects::HookSideEffects};
 use rolldown_plugin::{
   HookLoadArgs, HookLoadOutput, HookLoadReturn, HookResolveIdArgs, HookResolveIdOutput,
   HookResolveIdReturn, Plugin, PluginContext,
 };
-use std::borrow::Cow;
 
 const MODULE_PRELOAD_POLYFILL: &str = "vite/modulepreload-polyfill";
-
 const RESOLVED_MODULE_PRELOAD_POLYFILL_ID: &str = "\0vite/modulepreload-polyfill.js";
 
-const IS_MODERN_FLAG: &str = "__VITE_IS_MODERN__";
-
 #[derive(Debug)]
-pub struct ModulePreloadPolyfillPlugin {
-  pub skip: bool,
path: crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
user: login: shulaoda
body: Currently, this is how we use it in `rolldown-vite`, so the `skip` flag is unnecessary and can help reduce redundant hook invocations. What do you think?
```js
modulePreload !== false && modulePreload.polyfill
  ? enableNativePlugin === true
    ? perEnvironmentPlugin(
        'native:modulepreload-polyfill',
        (environment) => {
          if (
            config.command !== 'build' ||
            environment.config.consumer !== 'client'
          )
            return false
          return nativeModulePreloadPolyfillPlugin({
            skip: false,
          })
        },
      )
    : modulePreloadPolyfillPlugin(config)
  : null,
```
created_at: 2025-04-07T04:18:05Z
author_association: MEMBER
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: shulaoda
body: The main goal is to directly support `Vite`’s usage scenarios. I'm currently working on improving and porting the native plugins, and I'll incorporate your suggestions to make the necessary adaptations and adjustments.
created_at: 2025-04-07T04:20:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,63 @@
+# rolldown_plugin_module_preload_polyfill
+
+A plugin for [rolldown-vite](https://github.com/vitejs/rolldown-vite) that injects a `modulepreload` polyfill for legacy browsers, ported from [Vite's `modulePreloadPolyfillPlugin`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34).
+
+## 📦 What it does
+
+This plugin resolves the special import:
+
+```js
+import 'vite/modulepreload-polyfill';
+```
+
+By default, it injects a polyfill for `rel="modulepreload"` into each entry module, ensuring compatibility with browsers that don't support `modulepreload` natively.
+
+## 🚀 Usage
+
+Add this plugin to your `plugins`:
+
+```js
+import { defineConfig } from 'rolldown';
+import { modulePreloadPolyfillPlugin } from 'rolldown/experimental';
+
+export default defineConfig({
+  input: {
+    entry: './index.ts',
+  },
+  plugins: [modulePreloadPolyfillPlugin()],
+});
+```
+
+## 🧪 Compatibility
+
+This plugin is originally designed for `Vite` but is more versatile.
+
+Unlike `Vite`, which uses `__VITE_IS_MODERN__` to control the polyfill, this plugin automatically applies the polyfill only when `output.format` is `esm`, matching `Vite`’s behavior.
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: shulaoda
body: Got it.
created_at: 2025-04-07T04:20:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,63 @@
+# rolldown_plugin_module_preload_polyfill
+
+A plugin for [rolldown-vite](https://github.com/vitejs/rolldown-vite) that injects a `modulepreload` polyfill for legacy browsers, ported from [Vite's `modulePreloadPolyfillPlugin`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34).
+
+## 📦 What it does
+
+This plugin resolves the special import:
+
+```js
+import 'vite/modulepreload-polyfill';
+```
+
+By default, it injects a polyfill for `rel="modulepreload"` into each entry module, ensuring compatibility with browsers that don't support `modulepreload` natively.
+
+## 🚀 Usage
+
+Add this plugin to your `plugins`:
+
+```js
+import { defineConfig } from 'rolldown';
+import { modulePreloadPolyfillPlugin } from 'rolldown/experimental';
+
+export default defineConfig({
+  input: {
+    entry: './index.ts',
+  },
+  plugins: [modulePreloadPolyfillPlugin()],
+});
+```
+
+## 🧪 Compatibility
+
+This plugin is originally designed for `Vite` but is more versatile.
+
+Unlike `Vite`, which uses `__VITE_IS_MODERN__` to control the polyfill, this plugin automatically applies the polyfill only when `output.format` is `esm`, matching `Vite`’s behavior.
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: shulaoda
body: So do you think we should keep the `__VITE_IS_MODERN__` flag, use a `skip` option to control the behavior, or leave it entirely to the user to handle externally?
created_at: 2025-04-07T04:30:44Z
author_association: MEMBER
diff_hunk: @@ -1,20 +1,16 @@
-use rolldown_common::side_effects::HookSideEffects;
+use std::borrow::Cow;
+
+use rolldown_common::{OutputFormat, side_effects::HookSideEffects};
 use rolldown_plugin::{
   HookLoadArgs, HookLoadOutput, HookLoadReturn, HookResolveIdArgs, HookResolveIdOutput,
   HookResolveIdReturn, Plugin, PluginContext,
 };
-use std::borrow::Cow;
 
 const MODULE_PRELOAD_POLYFILL: &str = "vite/modulepreload-polyfill";
-
 const RESOLVED_MODULE_PRELOAD_POLYFILL_ID: &str = "\0vite/modulepreload-polyfill.js";
 
-const IS_MODERN_FLAG: &str = "__VITE_IS_MODERN__";
-
 #[derive(Debug)]
-pub struct ModulePreloadPolyfillPlugin {
-  pub skip: bool,
path: crates/rolldown_plugin_module_preload_polyfill/src/lib.rs
user: login: sapphi-red
body: Ah, now I remember doing so. That's true. Yeah, it should be fine to remove this 👍 
created_at: 2025-04-07T04:48:11Z
author_association: MEMBER
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: sapphi-red
body: Then, I think it's better to document that this plugin's main goal is that and is unstable yet. (or removing the README would serve that purpose as well).
created_at: 2025-04-07T04:49:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,63 @@
+# rolldown_plugin_module_preload_polyfill
+
+A plugin for [rolldown-vite](https://github.com/vitejs/rolldown-vite) that injects a `modulepreload` polyfill for legacy browsers, ported from [Vite's `modulePreloadPolyfillPlugin`](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L34).
+
+## 📦 What it does
+
+This plugin resolves the special import:
+
+```js
+import 'vite/modulepreload-polyfill';
+```
+
+By default, it injects a polyfill for `rel="modulepreload"` into each entry module, ensuring compatibility with browsers that don't support `modulepreload` natively.
+
+## 🚀 Usage
+
+Add this plugin to your `plugins`:
+
+```js
+import { defineConfig } from 'rolldown';
+import { modulePreloadPolyfillPlugin } from 'rolldown/experimental';
+
+export default defineConfig({
+  input: {
+    entry: './index.ts',
+  },
+  plugins: [modulePreloadPolyfillPlugin()],
+});
+```
+
+## 🧪 Compatibility
+
+This plugin is originally designed for `Vite` but is more versatile.
+
+Unlike `Vite`, which uses `__VITE_IS_MODERN__` to control the polyfill, this plugin automatically applies the polyfill only when `output.format` is `esm`, matching `Vite`’s behavior.
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: sapphi-red
body: I think it's ok now. I wanted to point out that this may change and that's one part of the instability I mentioned in https://github.com/rolldown/rolldown/pull/4042#discussion_r2030426976.
created_at: 2025-04-07T04:51:30Z
author_association: MEMBER
path: crates/rolldown_plugin_module_preload_polyfill/README.md
user: login: shulaoda
body: Actually, the `README.md` is intended for developers to clarify the purpose of this plugin and highlight the differences from the `Vite` plugin, making it easier to maintain. I will update it to reflect this point.
created_at: 2025-04-07T04:55:01Z
author_association: MEMBER

PR comments:
user: login: netlify[bot]
created_at: 2025-04-07T03:26:10Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *rolldown-rs* canceled.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | b148de0e333a324c336832672868b2966ca609bf |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/rolldown-rs/deploys/67f35d7951a73300082e92e1 |
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-07T03:39:29Z
body: ### Benchmarks Rust
- target: `main`(021d1e8b85b58d75847089b7f2f57b2ed85540bb)
- pr: `04-06-refactor/rolldown-plugin-module-preload-polyfill`(b148de0e333a324c336832672868b2966ca609bf)
```
group                                                               pr                                     target
-----                                                               --                                     ------
bundle/bundle@multi-duplicated-top-level-symbol                     1.00     72.7±2.49ms        ? ?/sec    1.14     83.2±1.73ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify              1.00     93.0±1.33ms        ? ?/sec    1.12    104.4±2.49ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-minify-sourcemap    1.00    107.3±1.16ms        ? ?/sec    1.12    120.4±4.39ms        ? ?/sec
bundle/bundle@multi-duplicated-top-level-symbol-sourcemap           1.00     86.0±2.75ms        ? ?/sec    1.06     90.9±1.78ms        ? ?/sec
bundle/bundle@rome-ts                                               1.00    121.2±1.21ms        ? ?/sec    1.07    130.0±1.90ms        ? ?/sec
bundle/bundle@rome-ts-minify                                        1.00    195.8±1.91ms        ? ?/sec    1.17    229.5±4.23ms        ? ?/sec
bundle/bundle@rome-ts-minify-sourcemap                              1.00    239.5±8.79ms        ? ?/sec    1.14    272.5±2.69ms        ? ?/sec
bundle/bundle@rome-ts-sourcemap                                     1.00    134.3±1.62ms        ? ?/sec    1.06    141.7±4.20ms        ? ?/sec
bundle/bundle@threejs                                               1.00     40.7±0.48ms        ? ?/sec    1.04     42.4±0.51ms        ? ?/sec
bundle/bundle@threejs-minify                                        1.00     87.6±2.09ms        ? ?/sec    1.01     88.9±2.14ms        ? ?/sec
bundle/bundle@threejs-minify-sourcemap                              1.00    102.1±1.15ms        ? ?/sec    1.02    103.7±1.18ms        ? ?/sec
bundle/bundle@threejs-sourcemap                                     1.03     49.8±1.09ms        ? ?/sec    1.00     48.5±0.86ms        ? ?/sec
bundle/bundle@threejs10x                                            1.00    431.0±4.47ms        ? ?/sec    1.01   436.7±30.75ms        ? ?/sec
bundle/bundle@threejs10x-minify                                     1.00   1076.7±8.09ms        ? ?/sec    1.01  1084.4±10.20ms        ? ?/sec
bundle/bundle@threejs10x-minify-sourcemap                           1.00   1234.6±5.77ms        ? ?/sec    1.04   1285.5±4.10ms        ? ?/sec
bundle/bundle@threejs10x-sourcemap                                  1.01    503.3±4.85ms        ? ?/sec    1.00    500.5±3.28ms        ? ?/sec
remapping/remapping                                                 1.06     26.2±0.76ms        ? ?/sec    1.00     24.7±2.42ms        ? ?/sec
remapping/render-chunk-remapping                                    1.00     71.2±9.80ms        ? ?/sec    1.00     71.5±5.39ms        ? ?/sec
scan/scan@rome-ts                                                   1.00     94.1±1.07ms        ? ?/sec    1.10    103.9±3.21ms        ? ?/sec
scan/scan@threejs                                                   1.00     31.4±1.17ms        ? ?/sec    1.09     34.1±1.48ms        ? ?/sec
scan/scan@threejs10x                                                1.00    316.9±7.86ms        ? ?/sec    1.04    328.3±3.91ms        ? ?/sec
```
author_association: CONTRIBUTOR
user: login: IWANABETHATGUY
created_at: 2025-04-07T03:48:11Z
body: This plugin has some breaking change. Can you have a look @sapphi-red?

author_association: MEMBER