From 6009601b7375a2c384213cda8fa269db48534b33 Mon Sep 17 00:00:00 2001 From: gaoyuan Date: Fri, 21 Jun 2024 10:46:43 +0800 Subject: [PATCH] feat: support apply environment html-related config (#2647) --- .../tests/__snapshots__/default.test.ts.snap | 2 + .../__snapshots__/webpackConfig.test.ts.snap | 1 + packages/core/src/config.ts | 6 +- packages/core/src/createContext.ts | 20 +++ packages/core/src/initPlugins.ts | 16 +- packages/core/src/plugins/html.ts | 43 +++-- packages/core/src/plugins/manifest.ts | 4 +- packages/core/src/plugins/minimize.ts | 3 +- packages/core/src/provider/initConfigs.ts | 36 ++++- packages/core/src/rspack/HtmlBasicPlugin.ts | 5 + packages/core/src/server/devServer.ts | 5 +- packages/core/src/server/prodServer.ts | 5 +- .../tests/__snapshots__/builder.test.ts.snap | 1 + .../tests/__snapshots__/default.test.ts.snap | 3 + .../tests/__snapshots__/html.test.ts.snap | 152 ++++++++++++++++++ .../__snapshots__/inlineChunk.test.ts.snap | 2 + packages/core/tests/html.test.ts | 40 +++++ .../tests/__snapshots__/index.test.ts.snap | 1 + packages/shared/src/types/context.ts | 1 + packages/shared/src/types/hooks.ts | 2 + packages/shared/src/types/plugin.ts | 2 +- packages/shared/src/utils.ts | 8 +- scripts/test-helper/src/rsbuild.ts | 2 +- website/docs/en/plugins/dev/hooks.mdx | 2 + website/docs/zh/plugins/dev/hooks.mdx | 4 + 25 files changed, 320 insertions(+), 46 deletions(-) diff --git a/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap b/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap index 1b9e6d3594..d869ecf176 100644 --- a/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap +++ b/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap @@ -330,6 +330,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { @@ -760,6 +761,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when produ "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { diff --git a/packages/compat/webpack/tests/__snapshots__/webpackConfig.test.ts.snap b/packages/compat/webpack/tests/__snapshots__/webpackConfig.test.ts.snap index 2315a7b152..31262db977 100644 --- a/packages/compat/webpack/tests/__snapshots__/webpackConfig.test.ts.snap +++ b/packages/compat/webpack/tests/__snapshots__/webpackConfig.test.ts.snap @@ -78,6 +78,7 @@ exports[`webpackConfig > should allow to append and prepend plugins 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 573867a69f..d1b93d0899 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -184,7 +184,7 @@ const createDefaultConfig = (): RsbuildConfig => ({ environments: {}, }); -function getDefaultEntry(root: string): RsbuildEntry { +export function getDefaultEntry(root: string): RsbuildEntry { const files = [ // Most projects are using typescript now. // So we put `.ts` as the first one to improve performance. @@ -215,10 +215,6 @@ export const withDefaultConfig = async ( merged.source ||= {}; - if (!merged.source.entry) { - merged.source.entry = getDefaultEntry(rootPath); - } - if (!merged.source.tsconfigPath) { const tsconfigPath = join(rootPath, TS_CONFIG_FILE); diff --git a/packages/core/src/createContext.ts b/packages/core/src/createContext.ts index 2de6f8a298..bdd2c31e4c 100644 --- a/packages/core/src/createContext.ts +++ b/packages/core/src/createContext.ts @@ -4,11 +4,14 @@ import { DEFAULT_BROWSERSLIST, type NormalizedEnvironmentConfig, type RsbuildContext, + type RsbuildEntry, getBrowserslist, + isHtmlDisabled, } from '@rsbuild/shared'; import { withDefaultConfig } from './config'; import { ROOT_DIST_DIR } from './constants'; import { initHooks } from './initHooks'; +import { getHTMLPathByEntry } from './initPlugins'; import { logger } from './logger'; import { getEntryObject } from './plugins/entry'; import type { @@ -80,6 +83,19 @@ export async function getBrowserslistByEnvironment( return DEFAULT_BROWSERSLIST[target]; } +const getEnvironmentHTMLPaths = ( + entry: RsbuildEntry, + config: NormalizedEnvironmentConfig, +) => { + if (isHtmlDisabled(config, config.output.target)) { + return {}; + } + return Object.keys(entry).reduce>((prev, key) => { + prev[key] = getHTMLPathByEntry(key, config); + return prev; + }, {}); +}; + export async function updateEnvironmentContext( context: RsbuildContext, configs: Record, @@ -90,17 +106,21 @@ export async function updateEnvironmentContext( const tsconfigPath = config.source.tsconfigPath ? getAbsolutePath(context.rootPath, config.source.tsconfigPath) : undefined; + const entry = getEntryObject(config, config.output.target); const browserslist = await getBrowserslistByEnvironment( context.rootPath, config, ); + const htmlPaths = getEnvironmentHTMLPaths(entry, config); + context.environments[name] = { target: config.output.target, distPath: getAbsoluteDistPath(context.rootPath, config), entry: getEntryObject(config, config.output.target), browserslist, + htmlPaths, tsconfigPath, }; } diff --git a/packages/core/src/initPlugins.ts b/packages/core/src/initPlugins.ts index fceb0978b4..6bb030b8eb 100644 --- a/packages/core/src/initPlugins.ts +++ b/packages/core/src/initPlugins.ts @@ -16,7 +16,7 @@ import type { InternalContext, NormalizedConfig } from './types'; export function getHTMLPathByEntry( entryName: string, - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, ) { const filename = config.html.outputStructure === 'flat' @@ -97,13 +97,13 @@ export function getPluginAPI({ throw new Error('`getRsbuildConfig` get an invalid type param.'); }) as GetRsbuildConfig; - const getHTMLPaths = () => { - return Object.keys(context.entry).reduce>( - (prev, key) => { - prev[key] = getHTMLPathByEntry(key, getNormalizedConfig()); - return prev; - }, - {}, + const getHTMLPaths = (options?: { environment: string }) => { + if (options?.environment) { + return context.environments[options.environment].htmlPaths; + } + return Object.values(context.environments).reduce( + (prev, context) => Object.assign(prev, context.htmlPaths), + {} as Record, ); }; diff --git a/packages/core/src/plugins/html.ts b/packages/core/src/plugins/html.ts index 31b120b96b..2902d04e7a 100644 --- a/packages/core/src/plugins/html.ts +++ b/packages/core/src/plugins/html.ts @@ -12,7 +12,7 @@ import type { HTMLPluginOptions, HtmlConfig, ModifyHTMLTagsFn, - NormalizedConfig, + NormalizedEnvironmentConfig, RsbuildPluginAPI, } from '@rsbuild/shared'; import type { EntryDescription } from '@rspack/core'; @@ -28,7 +28,7 @@ import { parseMinifyOptions } from './minimize'; function applyRemoveConsole( options: MinifyJSOptions, - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, ) { const { removeConsole } = config.performance; const compressOptions = @@ -50,7 +50,7 @@ function applyRemoveConsole( return options; } -function getTerserMinifyOptions(config: NormalizedConfig) { +function getTerserMinifyOptions(config: NormalizedEnvironmentConfig) { const options: MinifyJSOptions = { mangle: { safari10: true, @@ -70,7 +70,7 @@ function getTerserMinifyOptions(config: NormalizedConfig) { export async function getHtmlMinifyOptions( isProd: boolean, - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, ) { if ( !isProd || @@ -102,7 +102,10 @@ export async function getHtmlMinifyOptions( : htmlMinifyDefaultOptions; } -export function getTitle(entryName: string, config: NormalizedConfig) { +export function getTitle( + entryName: string, + config: NormalizedEnvironmentConfig, +) { return reduceConfigsMergeContext({ initial: '', config: config.html.title, @@ -110,7 +113,10 @@ export function getTitle(entryName: string, config: NormalizedConfig) { }); } -export function getInject(entryName: string, config: NormalizedConfig) { +export function getInject( + entryName: string, + config: NormalizedEnvironmentConfig, +) { return reduceConfigsMergeContext({ initial: 'head', config: config.html.inject, @@ -122,7 +128,7 @@ const existTemplatePath: string[] = []; export async function getTemplate( entryName: string, - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, rootPath: string, ): Promise<{ templatePath: string; templateContent?: string }> { const DEFAULT_TEMPLATE = path.resolve(STATIC_PATH, 'template.html'); @@ -201,7 +207,7 @@ export function getMetaTags( function getTemplateParameters( entryName: string, - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, assetPrefix: string, ): HTMLPluginOptions['templateParameters'] { return (compilation, assets, assetTags, pluginOptions) => { @@ -252,8 +258,11 @@ function getChunks( return chunks; } -const getTagConfig = (api: RsbuildPluginAPI): TagConfig | undefined => { - const config = api.getNormalizedConfig(); +const getTagConfig = ( + api: RsbuildPluginAPI, + environment: string, +): TagConfig | undefined => { + const config = api.getNormalizedConfig({ environment }); const tags = castArray(config.html.tags).filter(Boolean); // skip if options is empty. @@ -274,8 +283,8 @@ export const pluginHtml = (modifyTagsFn?: ModifyHTMLTagsFn): RsbuildPlugin => ({ setup(api) { api.modifyBundlerChain( - async (chain, { HtmlPlugin, isProd, CHAIN_ID, target }) => { - const config = api.getNormalizedConfig(); + async (chain, { HtmlPlugin, isProd, CHAIN_ID, target, environment }) => { + const config = api.getNormalizedConfig({ environment }); // if html is disabled or target is server, skip html plugin if (isHtmlDisabled(config, target)) { @@ -286,7 +295,7 @@ export const pluginHtml = (modifyTagsFn?: ModifyHTMLTagsFn): RsbuildPlugin => ({ const assetPrefix = getPublicPathFromChain(chain, false); const entries = chain.entryPoints.entries() || {}; const entryNames = Object.keys(entries); - const htmlPaths = api.getHTMLPaths(); + const htmlPaths = api.getHTMLPaths({ environment }); const htmlInfoMap: Record = {}; const finalOptions = await Promise.all( @@ -338,7 +347,7 @@ export const pluginHtml = (modifyTagsFn?: ModifyHTMLTagsFn): RsbuildPlugin => ({ htmlInfo.templateContent = templateContent; } - const tagConfig = getTagConfig(api); + const tagConfig = getTagConfig(api, environment); if (tagConfig) { htmlInfo.tagConfig = tagConfig; } @@ -379,7 +388,7 @@ export const pluginHtml = (modifyTagsFn?: ModifyHTMLTagsFn): RsbuildPlugin => ({ chain .plugin(CHAIN_ID.PLUGIN.HTML_BASIC) - .use(HtmlBasicPlugin, [htmlInfoMap, modifyTagsFn]); + .use(HtmlBasicPlugin, [htmlInfoMap, environment, modifyTagsFn]); if (config.html) { const { appIcon, crossorigin } = config.html; @@ -411,8 +420,8 @@ export const pluginHtml = (modifyTagsFn?: ModifyHTMLTagsFn): RsbuildPlugin => ({ api.modifyHTMLTags({ // ensure `crossorigin` and `nonce` can be applied to all tags order: 'post', - handler: ({ headTags, bodyTags }) => { - const config = api.getNormalizedConfig(); + handler: ({ headTags, bodyTags }, { environment }) => { + const config = api.getNormalizedConfig({ environment }); const { crossorigin } = config.html; const allTags = [...headTags, ...bodyTags]; diff --git a/packages/core/src/plugins/manifest.ts b/packages/core/src/plugins/manifest.ts index 76286859cd..d0fe2517e3 100644 --- a/packages/core/src/plugins/manifest.ts +++ b/packages/core/src/plugins/manifest.ts @@ -143,7 +143,7 @@ export const pluginManifest = (): RsbuildPlugin => ({ name: 'rsbuild:manifest', setup(api) { - api.modifyBundlerChain(async (chain, { CHAIN_ID }) => { + api.modifyBundlerChain(async (chain, { CHAIN_ID, environment }) => { const { output: { manifest }, } = api.getNormalizedConfig(); @@ -156,7 +156,7 @@ export const pluginManifest = (): RsbuildPlugin => ({ typeof manifest === 'string' ? manifest : 'manifest.json'; const { RspackManifestPlugin } = await import('rspack-manifest-plugin'); - const htmlPaths = api.getHTMLPaths(); + const htmlPaths = api.getHTMLPaths({ environment }); chain.plugin(CHAIN_ID.PLUGIN.MANIFEST).use(RspackManifestPlugin, [ { diff --git a/packages/core/src/plugins/minimize.ts b/packages/core/src/plugins/minimize.ts index 62c9956c2b..563bbe7718 100644 --- a/packages/core/src/plugins/minimize.ts +++ b/packages/core/src/plugins/minimize.ts @@ -2,6 +2,7 @@ import { CHAIN_ID, type HTMLPluginOptions, type NormalizedConfig, + type NormalizedEnvironmentConfig, deepmerge, isObject, } from '@rsbuild/shared'; @@ -58,7 +59,7 @@ export const getSwcMinimizerOptions = ( }; export const parseMinifyOptions = ( - config: NormalizedConfig, + config: NormalizedEnvironmentConfig, isProd = true, ): { minifyJs: boolean; diff --git a/packages/core/src/provider/initConfigs.ts b/packages/core/src/provider/initConfigs.ts index f48bac3d4f..2719a28f27 100644 --- a/packages/core/src/provider/initConfigs.ts +++ b/packages/core/src/provider/initConfigs.ts @@ -2,9 +2,10 @@ import type { InspectConfigOptions, NormalizedEnvironmentConfig, PluginManager, + RsbuildEntry, RspackConfig, } from '@rsbuild/shared'; -import { normalizeConfig } from '../config'; +import { getDefaultEntry, normalizeConfig } from '../config'; import { updateContextByNormalizedConfig, updateEnvironmentContext, @@ -40,28 +41,46 @@ export type InitConfigsOptions = { const normalizeEnvironmentsConfigs = ( normalizedConfig: NormalizedConfig, + rootPath: string, ): Record => { + let defaultEntry: RsbuildEntry; + const getDefaultEntryWithMemo = () => { + if (!defaultEntry) { + defaultEntry = getDefaultEntry(rootPath); + } + return defaultEntry; + }; const { environments, dev, server, provider, ...rsbuildSharedConfig } = normalizedConfig; if (environments && Object.keys(environments).length) { return Object.fromEntries( - Object.entries(environments).map(([name, config]) => [ - name, - { + Object.entries(environments).map(([name, config]) => { + const environmentConfig = { ...mergeRsbuildConfig( rsbuildSharedConfig, config as unknown as NormalizedEnvironmentConfig, ), dev, server, - }, - ]), + }; + + if (!environmentConfig.source.entry) { + // @ts-expect-error + environmentConfig.source.entry = getDefaultEntryWithMemo(); + } + + return [name, environmentConfig]; + }), ); } return { [camelCase(rsbuildSharedConfig.output.target)]: { ...rsbuildSharedConfig, + source: { + ...rsbuildSharedConfig.source, + entry: rsbuildSharedConfig.source.entry ?? getDefaultEntryWithMemo(), + }, dev, server, }, @@ -88,7 +107,10 @@ export async function initRsbuildConfig({ await modifyRsbuildConfig(context); const normalizeBaseConfig = normalizeConfig(context.config); - const environments = normalizeEnvironmentsConfigs(normalizeBaseConfig); + const environments = normalizeEnvironmentsConfigs( + normalizeBaseConfig, + context.rootPath, + ); context.normalizedConfig = { ...normalizeBaseConfig, diff --git a/packages/core/src/rspack/HtmlBasicPlugin.ts b/packages/core/src/rspack/HtmlBasicPlugin.ts index c478053398..52e0cd4d78 100644 --- a/packages/core/src/rspack/HtmlBasicPlugin.ts +++ b/packages/core/src/rspack/HtmlBasicPlugin.ts @@ -236,15 +236,19 @@ const addFavicon = (headTags: HtmlTagObject[], favicon?: string) => { export class HtmlBasicPlugin { readonly name: string; + readonly environment: string; + readonly options: HtmlBasicPluginOptions; readonly modifyTagsFn?: ModifyHTMLTagsFn; constructor( options: HtmlBasicPluginOptions, + environment: string, modifyTagsFn?: ModifyHTMLTagsFn, ) { this.name = 'HtmlBasicPlugin'; + this.environment = environment; this.options = options; this.modifyTagsFn = modifyTagsFn; } @@ -280,6 +284,7 @@ export class HtmlBasicPlugin { compilation, assetPrefix: data.publicPath, filename: data.outputName, + environment: this.environment, }) : tags; diff --git a/packages/core/src/server/devServer.ts b/packages/core/src/server/devServer.ts index 4121720ee8..29ae62cee5 100644 --- a/packages/core/src/server/devServer.ts +++ b/packages/core/src/server/devServer.ts @@ -114,7 +114,10 @@ export async function createDevServer< const devConfig = formatDevConfig(config.dev, port); const routes = formatRoutes( - options.context.entry, + Object.values(options.context.environments).reduce( + (prev, context) => Object.assign(prev, context.htmlPaths), + {}, + ), config.output.distPath.html, config.html.outputStructure, ); diff --git a/packages/core/src/server/prodServer.ts b/packages/core/src/server/prodServer.ts index f00484f56c..021793714d 100644 --- a/packages/core/src/server/prodServer.ts +++ b/packages/core/src/server/prodServer.ts @@ -190,7 +190,10 @@ export async function startProdServer( }, async () => { const routes = formatRoutes( - context.entry, + Object.values(context.environments).reduce( + (prev, context) => Object.assign(prev, context.htmlPaths), + {}, + ), config.output.distPath.html, config.html.outputStructure, ); diff --git a/packages/core/tests/__snapshots__/builder.test.ts.snap b/packages/core/tests/__snapshots__/builder.test.ts.snap index 096fcf7f6b..9f9bcfb5b7 100644 --- a/packages/core/tests/__snapshots__/builder.test.ts.snap +++ b/packages/core/tests/__snapshots__/builder.test.ts.snap @@ -410,6 +410,7 @@ exports[`should use rspack as default bundler > apply rspack correctly 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { diff --git a/packages/core/tests/__snapshots__/default.test.ts.snap b/packages/core/tests/__snapshots__/default.test.ts.snap index ad95796ba5..b78448f6e7 100644 --- a/packages/core/tests/__snapshots__/default.test.ts.snap +++ b/packages/core/tests/__snapshots__/default.test.ts.snap @@ -410,6 +410,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { @@ -913,6 +914,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when prod "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { @@ -1696,6 +1698,7 @@ exports[`tools.rspack > should match snapshot 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": [Function], "name": "HtmlBasicPlugin", "options": { diff --git a/packages/core/tests/__snapshots__/html.test.ts.snap b/packages/core/tests/__snapshots__/html.test.ts.snap index cc47ef9981..585406ea13 100644 --- a/packages/core/tests/__snapshots__/html.test.ts.snap +++ b/packages/core/tests/__snapshots__/html.test.ts.snap @@ -116,6 +116,7 @@ exports[`plugin-html > should allow to configure html.tags 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -192,6 +193,7 @@ exports[`plugin-html > should allow to modify plugin options by tools.htmlPlugin "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -264,6 +266,7 @@ exports[`plugin-html > should allow to set favicon by html.favicon option 1`] = "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -335,6 +338,7 @@ exports[`plugin-html > should allow to set inject by html.inject option 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -444,6 +448,7 @@ exports[`plugin-html > should enable minify in production 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -515,6 +520,7 @@ exports[`plugin-html > should register html plugin correctly 1`] = ` "version": 5, }, HtmlBasicPlugin { + "environment": "web", "modifyTagsFn": undefined, "name": "HtmlBasicPlugin", "options": { @@ -586,6 +592,7 @@ exports[`plugin-html > should stop injecting