diff --git a/e2e/cases/html/combined/static/bar.html b/e2e/cases/html/combined/static/bar.html index b4739cb11d..2f79b165d7 100644 --- a/e2e/cases/html/combined/static/bar.html +++ b/e2e/cases/html/combined/static/bar.html @@ -9,6 +9,6 @@
bar
-
+
diff --git a/e2e/cases/html/combined/static/foo.html b/e2e/cases/html/combined/static/foo.html index 4603aa48cb..a04e0690ee 100644 --- a/e2e/cases/html/combined/static/foo.html +++ b/e2e/cases/html/combined/static/foo.html @@ -9,6 +9,6 @@
foo
-
+
diff --git a/e2e/cases/html/combined/static/index.html b/e2e/cases/html/combined/static/index.html index 696850ede9..0a09f2d7d0 100644 --- a/e2e/cases/html/combined/static/index.html +++ b/e2e/cases/html/combined/static/index.html @@ -9,6 +9,6 @@
text
-
+
diff --git a/e2e/cases/html/inject/static/index.html b/e2e/cases/html/inject/static/index.html index 8cac2c082d..4edade04be 100644 --- a/e2e/cases/html/inject/static/index.html +++ b/e2e/cases/html/inject/static/index.html @@ -8,7 +8,7 @@
text
-
+
<%= htmlPlugin.tags.bodyTags %> diff --git a/e2e/cases/html/minify/static/index.html b/e2e/cases/html/minify/static/index.html index 4992d2269f..3f97f3594f 100644 --- a/e2e/cases/html/minify/static/index.html +++ b/e2e/cases/html/minify/static/index.html @@ -7,6 +7,6 @@
bar
-
+
diff --git a/e2e/cases/html/template/static/bar.html b/e2e/cases/html/template/static/bar.html index b4739cb11d..2f79b165d7 100644 --- a/e2e/cases/html/template/static/bar.html +++ b/e2e/cases/html/template/static/bar.html @@ -9,6 +9,6 @@
bar
-
+
diff --git a/e2e/cases/html/template/static/foo.html b/e2e/cases/html/template/static/foo.html index 4603aa48cb..a04e0690ee 100644 --- a/e2e/cases/html/template/static/foo.html +++ b/e2e/cases/html/template/static/foo.html @@ -9,6 +9,6 @@
foo
-
+
diff --git a/e2e/cases/html/template/static/index.html b/e2e/cases/html/template/static/index.html index 696850ede9..0a09f2d7d0 100644 --- a/e2e/cases/html/template/static/index.html +++ b/e2e/cases/html/template/static/index.html @@ -9,6 +9,6 @@
text
-
+
diff --git a/e2e/cases/plugin-api/plugin-process-assets-with-html-child-compiler/static/index.html b/e2e/cases/plugin-api/plugin-process-assets-with-html-child-compiler/static/index.html index bf3dd1dbf5..5836d90f92 100644 --- a/e2e/cases/plugin-api/plugin-process-assets-with-html-child-compiler/static/index.html +++ b/e2e/cases/plugin-api/plugin-process-assets-with-html-child-compiler/static/index.html @@ -7,6 +7,6 @@
test
-
+
diff --git a/e2e/cases/server/reload-html/src/index.html b/e2e/cases/server/reload-html/src/index.html index 84e7020ccb..1c3006fdd1 100644 --- a/e2e/cases/server/reload-html/src/index.html +++ b/e2e/cases/server/reload-html/src/index.html @@ -4,6 +4,6 @@ Foo -
+
diff --git a/packages/core/src/defaultConfig.ts b/packages/core/src/defaultConfig.ts index 05a7d65309..37e831843b 100644 --- a/packages/core/src/defaultConfig.ts +++ b/packages/core/src/defaultConfig.ts @@ -122,6 +122,7 @@ const getDefaultHtmlConfig = (): NormalizedHtmlConfig => ({ crossorigin: false, outputStructure: 'flat', scriptLoading: 'defer', + implementation: 'js', }); const getDefaultSecurityConfig = (): NormalizedSecurityConfig => ({ diff --git a/packages/core/src/pluginHelper.ts b/packages/core/src/pluginHelper.ts index 7e28c621fc..65e79fa4e0 100644 --- a/packages/core/src/pluginHelper.ts +++ b/packages/core/src/pluginHelper.ts @@ -4,7 +4,7 @@ import { requireCompiledPackage } from './helpers/vendors'; import { rspack } from './rspack'; -import type { HtmlRspackPlugin } from './types'; +import type { HtmlRspackPlugin, NormalizedEnvironmentConfig } from './types'; let htmlPlugin: typeof HtmlRspackPlugin; @@ -17,7 +17,13 @@ export const setHTMLPlugin = (plugin: typeof HtmlRspackPlugin): void => { } }; -export const getHTMLPlugin = (): typeof HtmlRspackPlugin => { +export const getHTMLPlugin = ( + config?: NormalizedEnvironmentConfig, +): typeof HtmlRspackPlugin => { + if (config?.html.implementation === 'native') { + // TODO: remove type assertion + return rspack.HtmlRspackPlugin as unknown as typeof HtmlRspackPlugin; + } if (!htmlPlugin) { htmlPlugin = requireCompiledPackage('html-rspack-plugin'); } diff --git a/packages/core/src/plugins/html.ts b/packages/core/src/plugins/html.ts index 5e1a9c918e..f5d4ff3024 100644 --- a/packages/core/src/plugins/html.ts +++ b/packages/core/src/plugins/html.ts @@ -355,10 +355,11 @@ export const pluginHtml = (context: InternalContext): RsbuildPlugin => ({ }); const getExtraData = (entryName: string) => extraDataMap.get(entryName); + const getHTMLPlugin = () => HtmlPlugin; chain .plugin('rsbuild-html-plugin') - .use(RsbuildHtmlPlugin, [getExtraData]); + .use(RsbuildHtmlPlugin, [getExtraData, getHTMLPlugin]); if (config.html) { const { crossorigin } = config.html; diff --git a/packages/core/src/plugins/resourceHints.ts b/packages/core/src/plugins/resourceHints.ts index e5364d76a2..05df3830ba 100644 --- a/packages/core/src/plugins/resourceHints.ts +++ b/packages/core/src/plugins/resourceHints.ts @@ -1,5 +1,6 @@ import { isRegExp } from 'node:util/types'; import { castArray } from '../helpers'; +import { getHTMLPlugin } from '../pluginHelper'; import { HtmlResourceHintsPlugin } from '../rspack-plugins/resource-hints/HtmlResourceHintsPlugin'; import type { HtmlBasicTag, @@ -90,6 +91,7 @@ export const pluginResourceHints = (): RsbuildPlugin => ({ 'prefetch', HTMLCount, isDev, + () => getHTMLPlugin(config), ]); } @@ -104,7 +106,13 @@ export const pluginResourceHints = (): RsbuildPlugin => ({ chain .plugin(CHAIN_ID.PLUGIN.HTML_PRELOAD) - .use(HtmlResourceHintsPlugin, [options, 'preload', HTMLCount, isDev]); + .use(HtmlResourceHintsPlugin, [ + options, + 'preload', + HTMLCount, + isDev, + () => getHTMLPlugin(config), + ]); } }); }, diff --git a/packages/core/src/provider/rspackConfig.ts b/packages/core/src/provider/rspackConfig.ts index 523ef3d568..8d7dcf9f72 100644 --- a/packages/core/src/provider/rspackConfig.ts +++ b/packages/core/src/provider/rspackConfig.ts @@ -143,7 +143,7 @@ export function getChainUtils( isServer: target === 'node', isWebWorker: target === 'web-worker', CHAIN_ID, - HtmlPlugin: getHTMLPlugin(), + HtmlPlugin: getHTMLPlugin(environment.config), }; } diff --git a/packages/core/src/rspack-plugins/RsbuildHtmlPlugin.ts b/packages/core/src/rspack-plugins/RsbuildHtmlPlugin.ts index 1189985e6b..8cbbceeeb4 100644 --- a/packages/core/src/rspack-plugins/RsbuildHtmlPlugin.ts +++ b/packages/core/src/rspack-plugins/RsbuildHtmlPlugin.ts @@ -5,7 +5,6 @@ import { color, isFunction, partition } from '../helpers'; import { addCompilationError } from '../helpers/compiler'; import { ensureAssetPrefix, isURL } from '../helpers/url'; import { logger } from '../logger'; -import { getHTMLPlugin } from '../pluginHelper'; import type { EnvironmentContext, HtmlBasicTag, @@ -245,9 +244,15 @@ export class RsbuildHtmlPlugin { readonly getExtraData: (entryName: string) => HtmlExtraData | undefined; - constructor(getExtraData: (entryName: string) => HtmlExtraData | undefined) { + readonly getHTMLPlugin: () => typeof HtmlRspackPlugin; + + constructor( + getExtraData: (entryName: string) => HtmlExtraData | undefined, + getHTMLPlugin: () => typeof HtmlRspackPlugin, + ) { this.name = 'RsbuildHtmlPlugin'; this.getExtraData = getExtraData; + this.getHTMLPlugin = getHTMLPlugin; } apply(compiler: Compiler): void { @@ -370,7 +375,7 @@ export class RsbuildHtmlPlugin { }; compiler.hooks.compilation.tap(this.name, (compilation: Compilation) => { - const hooks = getHTMLPlugin().getCompilationHooks(compilation); + const hooks = this.getHTMLPlugin().getCompilationHooks(compilation); hooks.alterAssetTagGroups.tapPromise(this.name, async (data) => { const extraData = getExtraDataByPlugin(data.plugin); diff --git a/packages/core/src/rspack-plugins/resource-hints/HtmlResourceHintsPlugin.ts b/packages/core/src/rspack-plugins/resource-hints/HtmlResourceHintsPlugin.ts index 952077ec96..11f41abab8 100644 --- a/packages/core/src/rspack-plugins/resource-hints/HtmlResourceHintsPlugin.ts +++ b/packages/core/src/rspack-plugins/resource-hints/HtmlResourceHintsPlugin.ts @@ -24,7 +24,6 @@ import type { } from '@rspack/core'; import { castArray, isFunction, upperFirst } from '../../helpers'; import { ensureAssetPrefix } from '../../helpers/url'; -import { getHTMLPlugin } from '../../pluginHelper'; import type { HtmlRspackPlugin, ResourceHintsFilter, @@ -242,21 +241,25 @@ export class HtmlResourceHintsPlugin implements RspackPluginInstance { isDev: boolean; + getHTMLPlugin: () => typeof HtmlRspackPlugin; + constructor( options: ResourceHintsOptions, type: LinkType, HTMLCount: number, isDev: boolean, + getHTMLPlugin: () => typeof HtmlRspackPlugin, ) { this.options = { ...defaultOptions, ...options }; this.type = type; this.HTMLCount = HTMLCount; this.isDev = isDev; + this.getHTMLPlugin = getHTMLPlugin; } apply(compiler: Compiler): void { compiler.hooks.compilation.tap(this.name, (compilation) => { - const pluginHooks = getHTMLPlugin().getCompilationHooks(compilation); + const pluginHooks = this.getHTMLPlugin().getCompilationHooks(compilation); const pluginName = `HTML${upperFirst(this.type)}Plugin`; pluginHooks.beforeAssetTagGeneration.tap(pluginName, (data) => { diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index 157707d8f1..8c7b57ecd0 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -1505,6 +1505,8 @@ export type AppIcon = { filename?: string; }; +type HtmlImplementation = 'js' | 'native'; + export interface HtmlConfig { /** * Configure the `` tag of the HTML. @@ -1591,6 +1593,19 @@ export interface HtmlConfig { * @default 'defer'. If `output.module` is enabled, the value is always `'module'`. */ scriptLoading?: ScriptLoading; + /** + * Specifies which implementation to use for generating HTML files. + * + * - `'js'` (default) - Use the JavaScript-based `html-rspack-plugin`. + * - `'native'` - Use Rspack's built-in `HtmlRspackPlugin` implemented in Rust. + * + * This option is experimental and may affect the available options in `tools.htmlPlugin`, + * since the two implementations are not fully compatible. + * + * @default 'js' + * @experimental + */ + implementation?: HtmlImplementation; } export type NormalizedHtmlConfig = HtmlConfig & { @@ -1601,6 +1616,7 @@ export type NormalizedHtmlConfig = HtmlConfig & { crossorigin: boolean | CrossOrigin; outputStructure: OutputStructure; scriptLoading: ScriptLoading; + implementation: HtmlImplementation; }; export type ProgressBarConfig = { diff --git a/packages/core/tests/__snapshots__/builder.test.ts.snap b/packages/core/tests/__snapshots__/builder.test.ts.snap index e76e4d14be..ff892751bd 100644 --- a/packages/core/tests/__snapshots__/builder.test.ts.snap +++ b/packages/core/tests/__snapshots__/builder.test.ts.snap @@ -466,6 +466,7 @@ exports[`should use Rspack as the default bundler > apply Rspack correctly 1`] = }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, DefinePlugin { diff --git a/packages/core/tests/__snapshots__/default.test.ts.snap b/packages/core/tests/__snapshots__/default.test.ts.snap index 1e66e39cff..66de0dc538 100644 --- a/packages/core/tests/__snapshots__/default.test.ts.snap +++ b/packages/core/tests/__snapshots__/default.test.ts.snap @@ -466,6 +466,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, DefinePlugin { @@ -1019,6 +1020,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when prod }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, DefinePlugin { diff --git a/packages/core/tests/__snapshots__/environments.test.ts.snap b/packages/core/tests/__snapshots__/environments.test.ts.snap index a9937a186f..cc9eac8e3a 100644 --- a/packages/core/tests/__snapshots__/environments.test.ts.snap +++ b/packages/core/tests/__snapshots__/environments.test.ts.snap @@ -23,6 +23,7 @@ exports[`environment config > should normalize environment config correctly 1`] }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -179,6 +180,7 @@ exports[`environment config > should normalize environment config correctly 2`] }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -339,6 +341,7 @@ exports[`environment config > should print environment config when inspect confi }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -495,6 +498,7 @@ exports[`environment config > should print environment config when inspect confi }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -671,6 +675,7 @@ exports[`environment config > should support modify environment config by api.mo }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -827,6 +832,7 @@ exports[`environment config > should support modify environment config by api.mo }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -984,6 +990,7 @@ exports[`environment config > should support modify environment config by api.mo }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -1144,6 +1151,7 @@ exports[`environment config > should support modify single environment config by }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { @@ -1300,6 +1308,7 @@ exports[`environment config > should support modify single environment config by }, "html": { "crossorigin": false, + "implementation": "js", "inject": "head", "meta": { "charset": { diff --git a/packages/core/tests/__snapshots__/html.test.ts.snap b/packages/core/tests/__snapshots__/html.test.ts.snap index 625511517c..5e6d51fc5e 100644 --- a/packages/core/tests/__snapshots__/html.test.ts.snap +++ b/packages/core/tests/__snapshots__/html.test.ts.snap @@ -82,6 +82,7 @@ exports[`plugin-html > should allow to configure html.tags 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ], @@ -164,6 +165,7 @@ exports[`plugin-html > should enable minify in production 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ], @@ -216,6 +218,7 @@ exports[`plugin-html > should register html plugin correctly 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ], @@ -289,6 +292,7 @@ exports[`plugin-html > should support environment html config 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ], @@ -338,6 +342,7 @@ exports[`plugin-html > should support environment html config 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ], @@ -427,6 +432,7 @@ exports[`plugin-html > should support multi entry 1`] = ` }, RsbuildHtmlPlugin { "getExtraData": [Function], + "getHTMLPlugin": [Function], "name": "RsbuildHtmlPlugin", }, ],