From 0492df61dd750cb580c740dbfdb41ba694e2f3ce Mon Sep 17 00:00:00 2001 From: XXXMrG Date: Fri, 17 Jan 2025 17:22:27 +0800 Subject: [PATCH] feat: support environments api --- packages/ice/src/bundler/rspack/getConfig.ts | 51 ++++++++++++------- .../src/bundler/webpack/getWebpackConfig.ts | 20 ++++++-- packages/ice/src/config.ts | 4 ++ packages/ice/src/createService.ts | 14 +++++ .../ice/src/service/onGetBundlerConfig.ts | 41 +++++++++++++++ .../ice/src/service/onGetEnvironmentConfig.ts | 36 +++++++++++++ packages/ice/src/types/environment.ts | 23 +++++++++ packages/ice/src/types/index.ts | 1 + packages/ice/src/types/plugin.ts | 4 ++ packages/ice/src/types/userConfig.ts | 5 ++ 10 files changed, 177 insertions(+), 22 deletions(-) create mode 100644 packages/ice/src/service/onGetBundlerConfig.ts create mode 100644 packages/ice/src/service/onGetEnvironmentConfig.ts create mode 100644 packages/ice/src/types/environment.ts diff --git a/packages/ice/src/bundler/rspack/getConfig.ts b/packages/ice/src/bundler/rspack/getConfig.ts index a724b49595..a0d922409e 100644 --- a/packages/ice/src/bundler/rspack/getConfig.ts +++ b/packages/ice/src/bundler/rspack/getConfig.ts @@ -15,6 +15,7 @@ import { getFallbackEntry, getReCompilePlugin, getServerPlugin, getSpinnerPlugin import { getExpandedEnvs } from '../../utils/runtimeEnv.js'; import type { BundlerOptions, Context } from '../types.js'; import type { PluginData } from '../../types/plugin.js'; +import { bundlerConfigContext } from '../../service/onGetBundlerConfig.js'; type GetConfig = ( context: Context, @@ -71,25 +72,37 @@ const getConfig: GetConfig = async (context, options, rspack) => { getReCompilePlugin(reCompile, routeManifest), ].filter(Boolean) as Config['plugins']; }; - return await Promise.all(taskConfigs.map(async ({ config }) => { - const plugins = getPlugins(config); - return await getRspackConfig({ - rootDir, - rspack, - runtimeTmpDir: RUNTIME_TMP_DIR, - runtimeDefineVars: { - [IMPORT_META_TARGET]: JSON.stringify(config.target), - [IMPORT_META_RENDERER]: JSON.stringify('client'), - }, - getRoutesFile, - getExpandedEnvs, - localIdentName: config.cssModules?.localIdentName || (config.mode === 'development' ? CSS_MODULES_LOCAL_IDENT_NAME_DEV : CSS_MODULES_LOCAL_IDENT_NAME), - taskConfig: { - ...config, - plugins: (config.plugins || []).concat(plugins), - }, - }); - })); + return await Promise.all( + taskConfigs.map(async ({ config, name }) => { + const plugins = getPlugins(config); + const rspackConfig = await getRspackConfig({ + rootDir, + rspack, + runtimeTmpDir: RUNTIME_TMP_DIR, + runtimeDefineVars: { + [IMPORT_META_TARGET]: JSON.stringify(config.target), + [IMPORT_META_RENDERER]: JSON.stringify('client'), + }, + getRoutesFile, + getExpandedEnvs, + localIdentName: + config.cssModules?.localIdentName || + (config.mode === 'development' ? CSS_MODULES_LOCAL_IDENT_NAME_DEV : CSS_MODULES_LOCAL_IDENT_NAME), + taskConfig: { + ...config, + plugins: (config.plugins || []).concat(plugins), + }, + }); + + // run onGetBundlerConfig hooks + const finalConfig = await bundlerConfigContext.runOnGetBundlerConfig(rspackConfig, { + environment: { name }, + type: 'rspack', + }); + + return finalConfig as Configuration; + }), + ); }; type GetDataLoaderRspackConfig = ( diff --git a/packages/ice/src/bundler/webpack/getWebpackConfig.ts b/packages/ice/src/bundler/webpack/getWebpackConfig.ts index 4c57414eb3..b0e2893d5a 100644 --- a/packages/ice/src/bundler/webpack/getWebpackConfig.ts +++ b/packages/ice/src/bundler/webpack/getWebpackConfig.ts @@ -12,6 +12,7 @@ import type RouteManifest from '../../utils/routeManifest.js'; import type ServerRunnerPlugin from '../../webpack/ServerRunnerPlugin.js'; import type ServerCompilerPlugin from '../../webpack/ServerCompilerPlugin.js'; import type { BundlerOptions, Context } from '../types.js'; +import { bundlerConfigContext } from '../../service/onGetBundlerConfig.js'; const { debounce } = lodash; @@ -67,7 +68,7 @@ const getWebpackConfig: GetWebpackConfig = async (context, options) => { const { target = WEB } = commandArgs; const userConfigHash = await getFileHash(configFilePath); - const webpackConfigs = taskConfigs.map(({ config }) => { + const webpackConfigs = taskConfigs.map(({ config, name }) => { const { useDevServer, useDataLoader, server } = config; // If the target in the task config doesn't exit, use the target from cli command option. config.target ||= target; @@ -125,10 +126,23 @@ const getWebpackConfig: GetWebpackConfig = async (context, options) => { // Add spinner for webpack task. webpackConfig.plugins.push(getSpinnerPlugin(spinner)); - return webpackConfig; + return { + webpackConfig, + name, + }; }); - return webpackConfigs; + const finalConfigs = await Promise.all( + webpackConfigs.map(async ({ webpackConfig, name }) => { + const result = await bundlerConfigContext.runOnGetBundlerConfig(webpackConfig, { + environment: { name }, + type: 'webpack', + }); + return result; + }), + ); + + return finalConfigs as Configuration[]; }; export default getWebpackConfig; diff --git a/packages/ice/src/config.ts b/packages/ice/src/config.ts index 0dd14ca72b..c380e27d1f 100644 --- a/packages/ice/src/config.ts +++ b/packages/ice/src/config.ts @@ -511,6 +511,10 @@ const userConfig = [ validation: 'boolean', defaultValue: true, }, + { + name: 'environments', + validation: 'object', + }, ]; const cliOption = [ diff --git a/packages/ice/src/createService.ts b/packages/ice/src/createService.ts index 4a97092a1d..e85e36ddb2 100644 --- a/packages/ice/src/createService.ts +++ b/packages/ice/src/createService.ts @@ -40,6 +40,8 @@ import rspackBundler from './bundler/rspack/index.js'; import getDefaultTaskConfig from './plugins/task.js'; import { multipleServerEntry, renderMultiEntry } from './utils/multipleEntry.js'; import hasDocument from './utils/hasDocument.js'; +import { onGetBundlerConfig } from './service/onGetBundlerConfig.js'; +import { onGetEnvironmentConfig, environmentConfigContext } from './service/onGetEnvironmentConfig.js'; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -186,6 +188,8 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt const defaultTaskConfig = getDefaultTaskConfig({ rootDir, command }); return ctx.registerTask(target, mergeConfig(defaultTaskConfig, config)); }, + onGetBundlerConfig, + onGetEnvironmentConfig, }, }); // Load .env before resolve user config, so we can access env variables defined in .env files. @@ -384,6 +388,16 @@ async function createService({ rootDir, command, commandArgs }: CreateServiceOpt dataLoader: command !== 'build' || loaderExports, }); + const { environments } = userConfig; + if (environments) { + for (const [envName, envConfig] of Object.entries(environments)) { + const envTaskConfig = mergeConfig(Object.assign({}, platformTaskConfig.config), envConfig as Config); + ctx.registerTask(envName, envTaskConfig); + } + + environmentConfigContext.runOnGetEnvironmentConfig(taskConfigs); + } + return { run: async () => { const bundlerConfig = { diff --git a/packages/ice/src/service/onGetBundlerConfig.ts b/packages/ice/src/service/onGetBundlerConfig.ts new file mode 100644 index 0000000000..f45596d815 --- /dev/null +++ b/packages/ice/src/service/onGetBundlerConfig.ts @@ -0,0 +1,41 @@ +import type { Configuration as RspackConfiguration } from '@rspack/core'; +import type { Configuration as WebpackConfiguration } from 'webpack'; +import type { EnvironmentContext } from '../types'; + +type BundlerType = 'rspack' | 'webpack'; +type BundlerConfig = RspackConfiguration | WebpackConfiguration; + +export interface ModifyBundlerConfigOption { + environment: EnvironmentContext; + type: BundlerType; +} + +type ModifyConfigFn = ( + config: BundlerConfig, + options: ModifyBundlerConfigOption, +) => Promise | void | BundlerConfig; + +export type OnGetBundlerConfig = (cb: ModifyConfigFn) => void; + +class BundlerConfigContext { + private modifyConfigFns: ModifyConfigFn[] = []; + + onGetBundlerConfig(cb: ModifyConfigFn) { + this.modifyConfigFns.push(cb); + } + + async runOnGetBundlerConfig(config: BundlerConfig, options: ModifyBundlerConfigOption) { + for (const fn of this.modifyConfigFns) { + const result = await fn(config, options); + if (result) { + config = result; + } + } + return config; + } +} + +export const bundlerConfigContext = new BundlerConfigContext(); + +export const onGetBundlerConfig: OnGetBundlerConfig = + bundlerConfigContext.onGetBundlerConfig.bind(bundlerConfigContext); diff --git a/packages/ice/src/service/onGetEnvironmentConfig.ts b/packages/ice/src/service/onGetEnvironmentConfig.ts new file mode 100644 index 0000000000..ca853544bc --- /dev/null +++ b/packages/ice/src/service/onGetEnvironmentConfig.ts @@ -0,0 +1,36 @@ +import type { TaskConfig } from 'build-scripts'; +import type { Config, EnvironmentContext } from '../types'; + +interface ModifyEnvironmentConfigOption { + environment: EnvironmentContext; +} + +export type OnGetEnvironmentConfig = (cb: ModifyEnvironmentConfigFn) => void; + +type ModifyEnvironmentConfigFn = ( + config: Config, + options: ModifyEnvironmentConfigOption, +) => Promise | void | Config; + +class EnvironmentConfigContext { + private modifyConfigFns: ModifyEnvironmentConfigFn[] = []; + + onGetEnvironmentConfig(cb: ModifyEnvironmentConfigFn) { + this.modifyConfigFns.push(cb); + } + + async runOnGetEnvironmentConfig(taskConfigs: TaskConfig[]) { + for (const fn of this.modifyConfigFns) { + taskConfigs.forEach(async (taskConfig) => { + const result = await fn(taskConfig.config, { environment: { name: taskConfig.name } }); + if (result) { + taskConfig.config = result; + } + }); + } + } +} + +export const environmentConfigContext = new EnvironmentConfigContext(); +export const onGetEnvironmentConfig: OnGetEnvironmentConfig = + environmentConfigContext.onGetEnvironmentConfig.bind(environmentConfigContext); diff --git a/packages/ice/src/types/environment.ts b/packages/ice/src/types/environment.ts new file mode 100644 index 0000000000..e0087b44e7 --- /dev/null +++ b/packages/ice/src/types/environment.ts @@ -0,0 +1,23 @@ +import type { UserConfig } from './userConfig'; + +type EnvironmentNotSupportConfig = Omit< + UserConfig, + | 'server' + | 'configureWebpack' + | 'webpack' + | 'routes' + | 'eslint' + | 'tsChecker' + | 'ssr' + | 'ssg' + | 'optimization' + | 'mock' + | 'plugins' +>; + +export type EnvironmentUserConfig = Record; + +export interface EnvironmentContext { + name: string; + // dependencies: Set; +} diff --git a/packages/ice/src/types/index.ts b/packages/ice/src/types/index.ts index 9531652587..7ba50b540d 100644 --- a/packages/ice/src/types/index.ts +++ b/packages/ice/src/types/index.ts @@ -3,3 +3,4 @@ export * from './plugin.js'; export * from './userConfig.js'; // Export type webpack for same instance of webpack. export type { Config, webpack } from '@ice/shared-config/types'; +export * from './environment.js'; diff --git a/packages/ice/src/types/plugin.ts b/packages/ice/src/types/plugin.ts index 174e1cbc86..209347df12 100644 --- a/packages/ice/src/types/plugin.ts +++ b/packages/ice/src/types/plugin.ts @@ -7,6 +7,8 @@ import type { Config } from '@ice/shared-config/types'; import type { AppConfig, AssetsManifest } from '@ice/runtime/types'; import type ServerCompileTask from '../utils/ServerCompileTask.js'; import type { CreateLogger } from '../utils/logger.js'; +import type { OnGetEnvironmentConfig } from '../service/onGetEnvironmentConfig.js'; +import type { OnGetBundlerConfig } from '../service/onGetBundlerConfig.js'; import type { DeclarationData, AddRenderFile, AddTemplateFiles, ModifyRenderData, AddDataLoaderImport, Render } from './generator.js'; export type { CreateLoggerReturnType } from '../utils/logger.js'; @@ -167,6 +169,8 @@ export interface ExtendsPluginAPI { addRoutesDefinition: (defineRoutes: DefineExtraRoutes) => void; dataCache: Map; createLogger: CreateLogger; + onGetBundlerConfig: OnGetBundlerConfig; + onGetEnvironmentConfig: OnGetEnvironmentConfig; } export interface OverwritePluginAPI extends ExtendsPluginAPI { diff --git a/packages/ice/src/types/userConfig.ts b/packages/ice/src/types/userConfig.ts index ae252c00a9..2974844ab9 100644 --- a/packages/ice/src/types/userConfig.ts +++ b/packages/ice/src/types/userConfig.ts @@ -4,6 +4,7 @@ import type { UnpluginOptions } from '@ice/bundles/compiled/unplugin/index.js'; import type { ProcessOptions } from '@ice/bundles'; import type { Config, ModifyWebpackConfig, MinimizerOptions } from '@ice/shared-config/types'; import type { OverwritePluginAPI } from './plugin'; +import type { EnvironmentUserConfig } from './environment'; interface SyntaxFeatures { // syntax exportDefaultFrom and functionBind is not supported by esbuild @@ -281,4 +282,8 @@ export interface UserConfig { * @see https://v3.ice.work/docs/guide/basic/config#crossoriginloading */ crossOriginLoading?: Config['output']['crossOriginLoading']; + /** + * Setup environments + */ + environments?: EnvironmentUserConfig; }