From 96a082209db33a4e0c5141b2edad22db221ce4e5 Mon Sep 17 00:00:00 2001 From: LingyuCoder Date: Wed, 14 Aug 2024 18:34:12 +0800 Subject: [PATCH 1/3] feat(html): add hash config and refactor code to pass test cases --- Cargo.lock | 1 + crates/node_binding/binding.d.ts | 3 +- .../src/options/raw_builtins/raw_html.rs | 6 +- crates/rspack_plugin_html/Cargo.toml | 1 + crates/rspack_plugin_html/src/config.rs | 6 +- crates/rspack_plugin_html/src/plugin.rs | 52 +- .../rspack_plugin_html/src/visitors/asset.rs | 8 +- crates/rspack_plugin_html/src/visitors/mod.rs | 2 +- .../rspack_plugin_html/src/visitors/utils.rs | 15 +- packages/rspack/etc/api.md | 15 +- .../src/builtin-plugin/HtmlRspackPlugin.ts | 5 +- tests/plugin-test/html-plugin/basic.test.js | 549 +++++++++--------- 12 files changed, 360 insertions(+), 303 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc00bd24bb9..e62f5f6830f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3628,6 +3628,7 @@ dependencies = [ "swc_html", "swc_html_minifier", "tracing", + "urlencoding", ] [[package]] diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 883f67afbaf..602144a9f4f 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1244,12 +1244,13 @@ export interface RawHtmlRspackPluginOptions { scriptLoading: "blocking" | "defer" | "module" /** entry_chunk_name (only entry chunks are supported) */ chunks?: Array - excludedChunks?: Array + excludeChunks?: Array sri?: "sha256" | "sha384" | "sha512" minify?: boolean title?: string favicon?: string meta?: Record> + hash?: boolean } export interface RawHttpExternalsRspackPluginOptions { diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_html.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_html.rs index 51ac3b92dc9..180b932f078 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/raw_html.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_html.rs @@ -33,13 +33,14 @@ pub struct RawHtmlRspackPluginOptions { /// entry_chunk_name (only entry chunks are supported) pub chunks: Option>, - pub excluded_chunks: Option>, + pub exclude_chunks: Option>, #[napi(ts_type = "\"sha256\" | \"sha384\" | \"sha512\"")] pub sri: Option, pub minify: Option, pub title: Option, pub favicon: Option, pub meta: Option>>, + pub hash: Option, } impl From for HtmlRspackPluginOptions { @@ -62,12 +63,13 @@ impl From for HtmlRspackPluginOptions { public_path: value.public_path, script_loading, chunks: value.chunks, - excluded_chunks: value.excluded_chunks, + exclude_chunks: value.exclude_chunks, sri, minify: value.minify.unwrap_or_default(), title: value.title, favicon: value.favicon, meta: value.meta, + hash: value.hash.unwrap_or_default(), } } } diff --git a/crates/rspack_plugin_html/Cargo.toml b/crates/rspack_plugin_html/Cargo.toml index 8f8d942c388..92dd1accc41 100644 --- a/crates/rspack_plugin_html/Cargo.toml +++ b/crates/rspack_plugin_html/Cargo.toml @@ -30,6 +30,7 @@ swc_core = { workspace = true } swc_html = { workspace = true } swc_html_minifier = { workspace = true } tracing = { workspace = true } +urlencoding = { workspace = true } [package.metadata.cargo-shear] ignored = ["tracing"] diff --git a/crates/rspack_plugin_html/src/config.rs b/crates/rspack_plugin_html/src/config.rs index f5892bda760..0fff316e9f3 100644 --- a/crates/rspack_plugin_html/src/config.rs +++ b/crates/rspack_plugin_html/src/config.rs @@ -84,7 +84,7 @@ pub struct HtmlRspackPluginOptions { /// entry_chunk_name (only entry chunks are supported) pub chunks: Option>, - pub excluded_chunks: Option>, + pub exclude_chunks: Option>, /// hash func that used in subsource integrity /// sha384, sha256 or sha512 @@ -94,6 +94,7 @@ pub struct HtmlRspackPluginOptions { pub title: Option, pub favicon: Option, pub meta: Option>>, + pub hash: bool, } fn default_filename() -> String { @@ -119,12 +120,13 @@ impl Default for HtmlRspackPluginOptions { public_path: None, script_loading: default_script_loading(), chunks: None, - excluded_chunks: None, + exclude_chunks: None, sri: None, minify: false, title: None, favicon: None, meta: None, + hash: false, } } } diff --git a/crates/rspack_plugin_html/src/plugin.rs b/crates/rspack_plugin_html/src/plugin.rs index 63baaf380e6..2a7376143c9 100644 --- a/crates/rspack_plugin_html/src/plugin.rs +++ b/crates/rspack_plugin_html/src/plugin.rs @@ -7,6 +7,7 @@ use std::{ use anyhow::Context; use dojang::dojang::Dojang; +use itertools::Itertools; use rayon::prelude::*; use rspack_core::{ parse_to_url, @@ -22,7 +23,10 @@ use crate::{ config::{HtmlInject, HtmlRspackPluginOptions}, parser::HtmlCompiler, sri::{add_sri, create_digest_from_asset}, - visitors::asset::{AssetWriter, HTMLPluginTag}, + visitors::{ + asset::{AssetWriter, HTMLPluginTag}, + utils::append_hash, + }, }; #[plugin] @@ -100,8 +104,8 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { if let Some(included_chunks) = &config.chunks { included = included_chunks.iter().any(|c| c.eq(entry_name)); } - if let Some(excluded_chunks) = &config.excluded_chunks { - included = included && !excluded_chunks.iter().any(|c| c.eq(entry_name)); + if let Some(exclude_chunks) = &config.exclude_chunks { + included = included && !exclude_chunks.iter().any(|c| c.eq(entry_name)); } included }) @@ -121,11 +125,19 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { // if inject is 'false', don't do anything if !matches!(config.inject, HtmlInject::False) { for (asset_name, asset) in included_assets { - if let Some(extension) = Path::new(&asset_name).extension() { - let asset_uri = format!( - "{}{asset_name}", + if let Some(extension) = + Path::new(asset_name.split("?").next().unwrap_or_default()).extension() + { + let mut asset_uri = format!( + "{}{}", config.get_public_path(compilation, &self.config.filename), + url_encode_path(&asset_name) ); + if config.hash { + if let Some(hash) = compilation.get_hash() { + asset_uri = append_hash(&asset_uri, hash); + } + } let mut tag: Option = None; if extension.eq_ignore_ascii_case("css") { tag = Some(HTMLPluginTag::create_style(&asset_uri, HtmlInject::Head)); @@ -160,7 +172,9 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { let mut visitor = AssetWriter::new(config, &tags, compilation); current_ast.visit_mut_with(&mut visitor); - let source = parser.codegen(&mut current_ast)?; + let source = parser + .codegen(&mut current_ast)? + .replace("$$RSPACK_URL_AMP$$", "&"); let hash = hash_for_source(&source); let html_file_name = FilenameTemplate::from(config.filename.clone()); // Use the same filename as template @@ -243,3 +257,27 @@ fn hash_for_source(source: &str) -> String { source.hash(&mut hasher); format!("{:016x}", hasher.finish()) } + +fn url_encode_path(file_path: &str) -> String { + let query_string_start = file_path.find('?'); + let url_path = if let Some(query_string_start) = query_string_start { + file_path[..query_string_start].to_string() + } else { + file_path.to_string() + }; + let query_string = if let Some(query_string_start) = query_string_start { + file_path[query_string_start..].to_string() + } else { + "".to_string() + }; + + format!( + "{}{}", + url_path + .split('/') + .map(|p| { urlencoding::encode(p) }) + .join("/"), + // element.outerHTML will escape '&' so need to add a placeholder here + query_string.replace("&", "$$RSPACK_URL_AMP$$") + ) +} diff --git a/crates/rspack_plugin_html/src/visitors/asset.rs b/crates/rspack_plugin_html/src/visitors/asset.rs index d517f0ac5a8..db313114f9d 100644 --- a/crates/rspack_plugin_html/src/visitors/asset.rs +++ b/crates/rspack_plugin_html/src/visitors/asset.rs @@ -8,7 +8,7 @@ use swc_core::{common::DUMMY_SP, ecma::atoms::Atom}; use swc_html::ast::{Attribute, Child, Element, Namespace, Text}; use swc_html::visit::{VisitMut, VisitMutWith}; -use super::utils::create_element; +use super::utils::{append_hash, create_element}; use crate::config::{HtmlInject, HtmlRspackPluginOptions, HtmlScriptLoading}; // the tag @@ -185,6 +185,12 @@ impl VisitMut for AssetWriter<'_, '_> { favicon_link_path = reg.replace_all(favicon_link_path.as_str(), "/").to_string(); } + if self.config.hash { + if let Some(hash) = self.compilation.get_hash() { + favicon_link_path = append_hash(&favicon_link_path, hash); + } + } + n.children.push(Child::Element(Element { tag_name: Atom::from("link"), children: vec![], diff --git a/crates/rspack_plugin_html/src/visitors/mod.rs b/crates/rspack_plugin_html/src/visitors/mod.rs index 2313b1338e9..18daee55235 100644 --- a/crates/rspack_plugin_html/src/visitors/mod.rs +++ b/crates/rspack_plugin_html/src/visitors/mod.rs @@ -1,2 +1,2 @@ pub mod asset; -mod utils; +pub mod utils; diff --git a/crates/rspack_plugin_html/src/visitors/utils.rs b/crates/rspack_plugin_html/src/visitors/utils.rs index 93274d7ee6f..810fcfca8d7 100644 --- a/crates/rspack_plugin_html/src/visitors/utils.rs +++ b/crates/rspack_plugin_html/src/visitors/utils.rs @@ -11,7 +11,7 @@ pub fn create_attribute(name: &str, value: &Option) -> Attribute { name: name.into(), raw_name: None, value: value.as_ref().map(|str| Atom::from(str.as_str())), - raw_value: None, + raw_value: value.as_ref().map(|str| Atom::from(str.as_str())), } } @@ -33,3 +33,16 @@ pub fn create_element(tag: &HTMLPluginTag) -> Element { span: DUMMY_SP, } } + +pub fn append_hash(url: &str, hash: &str) -> String { + format!( + "{}{}{}", + url, + if url.contains("?") { + "$$RSPACK_URL_AMP$$" + } else { + "?" + }, + hash + ) +} diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index a6895a2cdbc..463a9f827f5 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -4587,13 +4587,14 @@ export const HtmlRspackPlugin: { new (c?: { filename?: string | undefined; publicPath?: string | undefined; + hash?: boolean | undefined; chunks?: string[] | undefined; template?: string | undefined; templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | undefined; - excludedChunks?: string[] | undefined; + excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; title?: string | undefined; @@ -4604,13 +4605,14 @@ export const HtmlRspackPlugin: { _args: [c?: { filename?: string | undefined; publicPath?: string | undefined; + hash?: boolean | undefined; chunks?: string[] | undefined; template?: string | undefined; templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | undefined; - excludedChunks?: string[] | undefined; + excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; title?: string | undefined; @@ -4636,22 +4638,24 @@ const htmlRspackPluginOptions: z.ZodObject<{ publicPath: z.ZodOptional; scriptLoading: z.ZodOptional>; chunks: z.ZodOptional>; - excludedChunks: z.ZodOptional>; + excludeChunks: z.ZodOptional>; sri: z.ZodOptional>; minify: z.ZodOptional; title: z.ZodOptional; favicon: z.ZodOptional; meta: z.ZodOptional]>>>; + hash: z.ZodOptional; }, "strict", z.ZodTypeAny, { filename?: string | undefined; publicPath?: string | undefined; + hash?: boolean | undefined; chunks?: string[] | undefined; template?: string | undefined; templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | undefined; - excludedChunks?: string[] | undefined; + excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; title?: string | undefined; @@ -4660,13 +4664,14 @@ const htmlRspackPluginOptions: z.ZodObject<{ }, { filename?: string | undefined; publicPath?: string | undefined; + hash?: boolean | undefined; chunks?: string[] | undefined; template?: string | undefined; templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | undefined; - excludedChunks?: string[] | undefined; + excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; title?: string | undefined; diff --git a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts index f0b478208fc..f47f748aa84 100644 --- a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts +++ b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts @@ -16,12 +16,13 @@ const htmlRspackPluginOptions = z.strictObject({ publicPath: z.string().optional(), scriptLoading: z.enum(["blocking", "defer", "module"]).optional(), chunks: z.string().array().optional(), - excludedChunks: z.string().array().optional(), + excludeChunks: z.string().array().optional(), sri: z.enum(["sha256", "sha384", "sha512"]).optional(), minify: z.boolean().optional(), title: z.string().optional(), favicon: z.string().optional(), - meta: z.record(z.string().or(z.record(z.string()))).optional() + meta: z.record(z.string().or(z.record(z.string()))).optional(), + hash: z.boolean().optional() }); export type HtmlRspackPluginOptions = z.infer; export const HtmlRspackPlugin = create( diff --git a/tests/plugin-test/html-plugin/basic.test.js b/tests/plugin-test/html-plugin/basic.test.js index df350d369a3..a78523ed26f 100644 --- a/tests/plugin-test/html-plugin/basic.test.js +++ b/tests/plugin-test/html-plugin/basic.test.js @@ -139,49 +139,47 @@ describe("HtmlWebpackPlugin", () => { ); }); - // TODO: url encodes the file name - // it("properly encodes file names in emitted URIs", (done) => { - // testHtmlPlugin( - // { - // mode: "production", - // entry: path.join(__dirname, "fixtures/index.js"), - // output: { - // path: OUTPUT_DIR, - // filename: "foo/very fancy+name.js", - // }, - // plugins: [new HtmlWebpackPlugin()], - // }, - // [ - // / - - - +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/__snapshots__/output.snap.txt index e1ca79897cb..1c58cc70e1e 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/__snapshots__/output.snap.txt @@ -1,14 +1,3 @@ ```html title=index.html - - - - - Rspack App - - - - - - - +Rspack App ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/rspack.config.js b/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/rspack.config.js index 0df7838d354..cab21ce7436 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/rspack.config.js +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/chunks/rspack.config.js @@ -16,7 +16,7 @@ module.exports = { { template: "index.html", chunks: ["chunk1", "chunk2"], - excludedChunks: ["chunk2"] + excludeChunks: ["chunk2"] } ] } diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/favicon/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/favicon/__snapshots__/output.snap.txt index 099e49a6351..9475815f7ed 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/favicon/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/favicon/__snapshots__/output.snap.txt @@ -1,11 +1,3 @@ ```html title=index.html - - - - - rspack - - - - +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/filename/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/filename/__snapshots__/output.snap.txt index f6376ba8553..3d810ef18cd 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/filename/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/filename/__snapshots__/output.snap.txt @@ -1,26 +1,7 @@ ```html title=default.28094d62af6f500a.html - - - - - rspack - - - - +rspack ``` ```html title=index.28094d62af6f500a.html - - - - - Rspack App - - - - - - - +Rspack App ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/inject/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/inject/__snapshots__/output.snap.txt index 87a5eaefebb..5c345196d3a 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/inject/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/inject/__snapshots__/output.snap.txt @@ -1,35 +1,11 @@ ```html title=inject_body.html - - - - - rspack - - - - +rspack ``` ```html title=inject_false.html - - - - - rspack - - - - +rspack ``` ```html title=inject_head.html - - - - - rspack - - - - +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/meta/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/meta/__snapshots__/output.snap.txt index 95cf917d497..10bbe1b0881 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/meta/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/meta/__snapshots__/output.snap.txt @@ -1,11 +1,3 @@ ```html title=index.html - - - - - rspack - - - - +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/minify/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/minify/__snapshots__/output.snap.txt index 6e139cbd7eb..e49a0794e93 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/minify/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/minify/__snapshots__/output.snap.txt @@ -1,3 +1,3 @@ ```html title=index.html -rspack +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/mpa/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/mpa/__snapshots__/output.snap.txt index 3d4eb09c602..db8e70525a6 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/mpa/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/mpa/__snapshots__/output.snap.txt @@ -1,44 +1,11 @@ ```html title=chunk1.html - - - - - Rspack App - - - - - - - +Rspack App ``` ```html title=chunk2.html - - - - - Rspack App - - - - - - - +Rspack App ``` ```html title=chunk3.html - - - - - Rspack App - - - - - - - +Rspack App ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/public-path/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/public-path/__snapshots__/output.snap.txt index 3dda21ac059..c62ab016f55 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/public-path/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/public-path/__snapshots__/output.snap.txt @@ -1,11 +1,3 @@ ```html title=index.html - - - - - rspack - - - - +rspack ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/template+templateParameters/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/template+templateParameters/__snapshots__/output.snap.txt index 27e173188a5..ec4ae71af90 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/template+templateParameters/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/template+templateParameters/__snapshots__/output.snap.txt @@ -1,13 +1,3 @@ ```html title=index.html - - - - - bar - - - - - - +bar ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/templateContent+templateParameters/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/templateContent+templateParameters/__snapshots__/output.snap.txt index 910ed6aae49..f3c66651d4c 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/templateContent+templateParameters/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/templateContent+templateParameters/__snapshots__/output.snap.txt @@ -1,5 +1,3 @@ ```html title=index.html - - -
bar
+
bar
``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/title/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/title/__snapshots__/output.snap.txt index 471e9b071ed..b200b1d9c06 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/title/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/title/__snapshots__/output.snap.txt @@ -1,11 +1,3 @@ ```html title=index.html - - - - - Rspack title - - - - +Rspack title ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-html/variant/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-html/variant/__snapshots__/output.snap.txt index adeefba195a..23cf29d059b 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-html/variant/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-html/variant/__snapshots__/output.snap.txt @@ -1,14 +1,3 @@ ```html title=output.html - - - - - Rspack App - - - - - - - +Rspack App ``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-publicpath/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-publicpath/index.js index b20432fd3df..59d03916a04 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-publicpath/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-publicpath/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path and public path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir-publicpath/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir-publicpath/index.js index b20432fd3df..59d03916a04 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir-publicpath/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir-publicpath/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path and public path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir/index.js index 99cca1a2504..010fa701ed1 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute-subdir/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path in subdirectory", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute/index.js index b1cf204ee59..b7c2416e055 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-absolute/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect(htmlContent.includes('')).toBe( - true - ); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-publicpath/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-publicpath/index.js index b20432fd3df..59d03916a04 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-publicpath/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-publicpath/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path and public path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir-publicpath/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir-publicpath/index.js index b20432fd3df..59d03916a04 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir-publicpath/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir-publicpath/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path and public path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir/index.js index 99cca1a2504..010fa701ed1 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative-subdir/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path in subdirectory", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes('') - ).toBe(true); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative/index.js index b1cf204ee59..b7c2416e055 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-favicon-relative/index.js @@ -7,7 +7,5 @@ it("html favicon with absolute path", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect(htmlContent.includes('')).toBe( - true - ); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/html-meta/index.js b/packages/rspack-test-tools/tests/configCases/builtins/html-meta/index.js index dc1c787de13..8ae6eef5e8c 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/html-meta/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/html-meta/index.js @@ -4,10 +4,6 @@ const path = require("path"); it("html meta", () => { const htmlPath = path.join(__dirname, "./index.html"); const htmlContent = fs.readFileSync(htmlPath, "utf-8"); - expect( - htmlContent.includes( - '' - ) - ).toBe(true); - expect(htmlContent.includes('')).toBe(true); + expect(htmlContent).toContain(''); + expect(htmlContent).toContain(''); }); diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/builtins/html_syntax_error/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/builtins/html_syntax_error/stats.err index 486237994f3..660d87ea161 100644 --- a/packages/rspack-test-tools/tests/diagnosticsCases/builtins/html_syntax_error/stats.err +++ b/packages/rspack-test-tools/tests/diagnosticsCases/builtins/html_syntax_error/stats.err @@ -1,13 +1,6 @@ -ERROR in × HTML parsing error: Start tag seen without seeing a doctype first, expected "" - ╭─[1:0] - 1 │ - · ────── - 2 │ - ╰──── - ERROR in × HTML parsing error: Stray end tag "something" ╭─[2:2] - 1 │ + 1 │ 2 │ · ──────────── ╰──── \ No newline at end of file diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 463a9f827f5..6464b7e4b13 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -4593,7 +4593,11 @@ export const HtmlRspackPlugin: { templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; - scriptLoading?: "module" | "blocking" | "defer" | undefined; + base?: string | { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + } | undefined; + scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; @@ -4611,7 +4615,11 @@ export const HtmlRspackPlugin: { templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; - scriptLoading?: "module" | "blocking" | "defer" | undefined; + base?: string | { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + } | undefined; + scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; @@ -4636,7 +4644,17 @@ const htmlRspackPluginOptions: z.ZodObject<{ templateParameters: z.ZodOptional>; inject: z.ZodOptional, z.ZodBoolean]>>; publicPath: z.ZodOptional; - scriptLoading: z.ZodOptional>; + base: z.ZodOptional; + target: z.ZodOptional>; + }, "strict", z.ZodTypeAny, { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + }, { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + }>]>>; + scriptLoading: z.ZodOptional>; chunks: z.ZodOptional>; excludeChunks: z.ZodOptional>; sri: z.ZodOptional>; @@ -4654,7 +4672,11 @@ const htmlRspackPluginOptions: z.ZodObject<{ templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; - scriptLoading?: "module" | "blocking" | "defer" | undefined; + base?: string | { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + } | undefined; + scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; @@ -4670,7 +4692,11 @@ const htmlRspackPluginOptions: z.ZodObject<{ templateContent?: string | undefined; templateParameters?: Record | undefined; inject?: boolean | "head" | "body" | undefined; - scriptLoading?: "module" | "blocking" | "defer" | undefined; + base?: string | { + target?: "_self" | "_blank" | "_parent" | "_top" | undefined; + href?: string | undefined; + } | undefined; + scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; minify?: boolean | undefined; diff --git a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts index f47f748aa84..c0d253ccb75 100644 --- a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts +++ b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts @@ -14,7 +14,18 @@ const htmlRspackPluginOptions = z.strictObject({ templateParameters: z.record(z.string()).optional(), inject: z.enum(["head", "body"]).or(z.boolean()).optional(), publicPath: z.string().optional(), - scriptLoading: z.enum(["blocking", "defer", "module"]).optional(), + base: z + .string() + .or( + z.strictObject({ + href: z.string().optional(), + target: z.enum(["_self", "_blank", "_parent", "_top"]).optional() + }) + ) + .optional(), + scriptLoading: z + .enum(["blocking", "defer", "module", "systemjs-module"]) + .optional(), chunks: z.string().array().optional(), excludeChunks: z.string().array().optional(), sri: z.enum(["sha256", "sha384", "sha512"]).optional(), @@ -54,11 +65,13 @@ export const HtmlRspackPlugin = create( : configInject === false ? "false" : configInject; + const base = typeof c.base === "string" ? { href: c.base } : c.base; return { ...c, meta, scriptLoading, - inject + inject, + base }; } ); diff --git a/tests/plugin-test/html-plugin/basic.test.js b/tests/plugin-test/html-plugin/basic.test.js index a78523ed26f..3c39ccc2f1a 100644 --- a/tests/plugin-test/html-plugin/basic.test.js +++ b/tests/plugin-test/html-plugin/basic.test.js @@ -594,8 +594,7 @@ describe("HtmlWebpackPlugin", () => { }), ], }, - // DIFF [""], - [/\s*<\/body>/], + [""], null, done, ); @@ -739,7 +738,7 @@ describe("HtmlWebpackPlugin", () => { new MiniCssExtractPlugin({ filename: "styles.css" }), ], }, - [''], + [''], null, done, ); @@ -1392,47 +1391,47 @@ describe("HtmlWebpackPlugin", () => { ); }); - // it("will replace [contenthash] in the filename with a content hash of 32 hex characters", (done) => { - // testHtmlPlugin( - // { - // mode: "production", - // entry: { - // index: path.join(__dirname, "fixtures/index.js"), - // }, - // output: { - // path: OUTPUT_DIR, - // filename: "[name]_bundle.js", - // }, - // plugins: [ - // new HtmlWebpackPlugin({ filename: "index.[contenthash].html" }), - // ], - // }, - // [], - // /index\.[a-f0-9]{20}\.html/, - // done, - // ); - // }); + it("will replace [contenthash] in the filename with a content hash of 32 hex characters", (done) => { + testHtmlPlugin( + { + mode: "production", + entry: { + index: path.join(__dirname, "fixtures/index.js"), + }, + output: { + path: OUTPUT_DIR, + filename: "[name]_bundle.js", + }, + plugins: [ + new HtmlWebpackPlugin({ filename: "index.[contenthash].html" }), + ], + }, + [], + /index\.[a-f0-9]{16}\.html/, + done, + ); + }); - // it("will replace [templatehash] in the filename with a content hash of 32 hex characters", (done) => { - // testHtmlPlugin( - // { - // mode: "production", - // entry: { - // index: path.join(__dirname, "fixtures/index.js"), - // }, - // output: { - // path: OUTPUT_DIR, - // filename: "[name]_bundle.js", - // }, - // plugins: [ - // new HtmlWebpackPlugin({ filename: "index.[templatehash].html" }), - // ], - // }, - // [], - // /index\.[a-f0-9]{20}\.html/, - // done, - // ); - // }); + it("will replace [templatehash] in the filename with a content hash of 32 hex characters", (done) => { + testHtmlPlugin( + { + mode: "production", + entry: { + index: path.join(__dirname, "fixtures/index.js"), + }, + output: { + path: OUTPUT_DIR, + filename: "[name]_bundle.js", + }, + plugins: [ + new HtmlWebpackPlugin({ filename: "index.[templatehash].html" }), + ], + }, + [], + /index\.[a-f0-9]{16}\.html/, + done, + ); + }); it("allows you to use an absolute output filename", (done) => { testHtmlPlugin( @@ -1514,23 +1513,22 @@ describe("HtmlWebpackPlugin", () => { ); }); - // TODO: win32 path sep "..\\../assets/index_bundle.js" - // it('will try to use a relative name if the filename and the script defer are in a subdirectory', (done) => { - // testHtmlPlugin( - // { - // mode: "production", - // entry: path.join(__dirname, "fixtures/index.js"), - // output: { - // path: OUTPUT_DIR, - // filename: "assets/index_bundle.js", - // }, - // plugins: [new HtmlWebpackPlugin({ filename: "assets/demo/test.html" })], - // }, - // ['', + '', ], null, done, @@ -3655,7 +3644,7 @@ describe("HtmlWebpackPlugin", () => { ], }, [ - '', + '', ], null, done, diff --git a/website/docs/en/plugins/rspack/html-rspack-plugin.mdx b/website/docs/en/plugins/rspack/html-rspack-plugin.mdx index 86a27602af2..75e03a607d9 100644 --- a/website/docs/en/plugins/rspack/html-rspack-plugin.mdx +++ b/website/docs/en/plugins/rspack/html-rspack-plugin.mdx @@ -74,7 +74,7 @@ type HtmlRspackPluginOptions = { publicPath?: string; scriptLoading?: 'blocking' | 'defer' | 'module'; chunks?: string[]; - excludedChunks?: string[]; + excludeChunks?: string[]; sri?: 'sha256' | 'sha384' | 'sha512'; minify?: boolean; favicon?: string; @@ -162,7 +162,7 @@ type HtmlRspackPluginOptions = { description: 'Allows you to add only some chunks.', }, { - name: '`excludedChunks`', + name: '`excludeChunks`', type: '`string[]|undefined`', default: 'undefined', description: 'Allows you to skip some chunks.', diff --git a/website/docs/zh/plugins/rspack/html-rspack-plugin.mdx b/website/docs/zh/plugins/rspack/html-rspack-plugin.mdx index 7e2f6d7586c..1f09070bf15 100644 --- a/website/docs/zh/plugins/rspack/html-rspack-plugin.mdx +++ b/website/docs/zh/plugins/rspack/html-rspack-plugin.mdx @@ -74,7 +74,7 @@ type HtmlRspackPluginOptions = { publicPath?: string; scriptLoading?: 'blocking' | 'defer' | 'module'; chunks?: string[]; - excludedChunks?: string[]; + excludeChunks?: string[]; sri?: 'sha256' | 'sha384' | 'sha512'; minify?: boolean; favicon?: string; @@ -160,7 +160,7 @@ type HtmlRspackPluginOptions = { description: '配置需要注入的 chunk,默认注入所有 chunk', }, { - name: '`excludedChunks`', + name: '`excludeChunks`', type: '`string[]|undefined`', default: 'undefined', description: '配置需要跳过注入的 chunk',