mirror of https://github.com/alibaba/ice.git
				
				
				
			feat: support several APIs to modify webpack (#532)
* feat: support several APIs to modify webpack * chore: optimize code * fix: test case * chore: optimize code
This commit is contained in:
		
							parent
							
								
									86b234162d
								
							
						
					
					
						commit
						c38af1ce64
					
				|  | @ -0,0 +1,5 @@ | |||
| # Changelog | ||||
| 
 | ||||
| ## 1.0.0 | ||||
| 
 | ||||
| - [feat] support several APIs to modify webpack. | ||||
|  | @ -0,0 +1,59 @@ | |||
| # @ice/webpack-modify | ||||
| 
 | ||||
| This package providers several APIs to simplify the modification of webpack configurations. | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| ```js | ||||
| import { removeLoader, modifyLoader, addLoader, removePlugin } from '@ice/webpack-modify'; | ||||
| 
 | ||||
| let modifiedConfig = {}; | ||||
| 
 | ||||
| const webpackConfig = { | ||||
|   module: { | ||||
|     rules: [ | ||||
|       { | ||||
|         test: /\.css$/, | ||||
|         use: [{ loader: "style-loader" }, { loader: "sass-loader" }], | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| // remove loader | ||||
| modifiedConfig = removeLoader(webpackConfig, { | ||||
|   rule: 'css', | ||||
|   loader: 'style-loader', | ||||
| }); | ||||
| // modify loader | ||||
| modifiedConfig = modifyLoader(webpackConfig, { | ||||
|   rule: 'css', | ||||
|   loader: 'style-loader', | ||||
|   options: () => ({}), | ||||
| }); | ||||
| // add loader | ||||
| modifiedConfig = addLoader(webpackConfig, { | ||||
|   rule: 'css', | ||||
|   before: 'style-loader' | ||||
| }); | ||||
| modifiedConfig = addLoader(webpackConfig, { | ||||
|   rule: 'css', | ||||
|   after: 'style-loader' | ||||
| }); | ||||
| // modify webpack rule options | ||||
| modifiedConfig = modifyRule(webpackConfig, { | ||||
|   rule: 'css', | ||||
|   options: () => { | ||||
|     return [ | ||||
|       { | ||||
|         loader: 'css-loader', | ||||
|         options: () => ([]), | ||||
|       }, | ||||
|     ] | ||||
|   }, | ||||
| }) | ||||
| // remove plugin | ||||
| modifiedConfig = removePlugin(webpackConfig, { | ||||
|   pluginName: 'AssetsManifestPlugin', | ||||
| }); | ||||
| ``` | ||||
|  | @ -0,0 +1,25 @@ | |||
| { | ||||
|   "name": "@ice/webpack-modify", | ||||
|   "version": "1.0.0", | ||||
|   "repository": "ice-lab/ice-next", | ||||
|   "bugs": "https://github.com/ice-lab/ice-next/issues", | ||||
|   "homepage": "https://next.ice.work", | ||||
|   "type": "module", | ||||
|   "main": "./esm/index.js", | ||||
|   "exports": "./esm/index.js", | ||||
|   "files": [ | ||||
|     "esm", | ||||
|     "!esm/**/*.map" | ||||
|   ], | ||||
|   "dependencies": { | ||||
|     "consola": "^2.15.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@ice/webpack-config": "^1.0.0", | ||||
|     "webpack": "^5.73.0" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "watch": "tsc -w", | ||||
|     "build": "tsc" | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,114 @@ | |||
| import type { Configuration, RuleSetRule, RuleSetUseItem } from 'webpack'; | ||||
| import consola from 'consola'; | ||||
| 
 | ||||
| interface RemoveOptions { | ||||
|   rule: string; | ||||
|   loader: string; | ||||
| } | ||||
| 
 | ||||
| function findLoader(webpackConfig: Configuration, ruleName: string) { | ||||
|   // Find webpack loader by options
 | ||||
|   const targetRule = webpackConfig?.module?.rules?.find((rule) => { | ||||
|     return typeof rule === 'object' && | ||||
|       rule.test instanceof RegExp && | ||||
|       rule.test.source.includes(ruleName); | ||||
|   }); | ||||
|   if (!targetRule) { | ||||
|     consola.warn(`Can not find webpack rule with rule.test of ${ruleName}`); | ||||
|   } | ||||
|   return targetRule; | ||||
| } | ||||
| 
 | ||||
| export function removeLoader(webpackConfig: Configuration, options: RemoveOptions): Configuration { | ||||
|   const { rule: ruleName, loader: loaderName } = options; | ||||
|   // Find webpack loader by options
 | ||||
|   const targetRule = findLoader(webpackConfig, ruleName) as RuleSetRule; | ||||
|   if (targetRule && Array.isArray(targetRule?.use)) { | ||||
|     targetRule.use = targetRule.use.filter((ruleItem) => { | ||||
|       const matched = typeof ruleItem === 'object' && ruleItem.loader?.match(new RegExp(`[@/\\\\]${loaderName}`)); | ||||
|       return !matched; | ||||
|     }); | ||||
|   } | ||||
|   return webpackConfig; | ||||
| } | ||||
| 
 | ||||
| interface ModifyRuleOptions { | ||||
|   rule: string; | ||||
|   options: (rule: RuleSetRule) => RuleSetRule; | ||||
| } | ||||
| 
 | ||||
| export function modifyRule(webpackConfig: Configuration, options: ModifyRuleOptions) { | ||||
|   const { rule: ruleName, options: modifyOptions } = options; | ||||
|   const modifiedRules = webpackConfig?.module?.rules?.map((rule) => { | ||||
|     if (typeof rule === 'object' && rule.test instanceof RegExp && rule.test.source.includes(ruleName)) { | ||||
|       return modifyOptions(rule); | ||||
|     } | ||||
|     return rule; | ||||
|   }); | ||||
|   return { | ||||
|     ...webpackConfig, | ||||
|     module: { | ||||
|       ...(webpackConfig.module || {}), | ||||
|       rules: modifiedRules, | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| type LoaderOptions = string | { [index: string]: any }; | ||||
| interface ModifyLoaderOptions { | ||||
|   rule: string; | ||||
|   loader: string; | ||||
|   options: (loaderOptions?: LoaderOptions) => LoaderOptions; | ||||
| } | ||||
| 
 | ||||
| export function modifyLoader(webpackConfig: Configuration, options: ModifyLoaderOptions) { | ||||
|   const { rule: ruleName, loader: loaderName, options: modifyOptions } = options; | ||||
|   // Find webpack loader by options
 | ||||
|   const targetRule = findLoader(webpackConfig, ruleName) as RuleSetRule; | ||||
|   if (targetRule && Array.isArray(targetRule?.use)) { | ||||
|     targetRule.use = targetRule.use.map((ruleItem) => { | ||||
|       if (typeof ruleItem === 'object' && ruleItem.loader?.match(new RegExp(`[@/\\\\]${loaderName}`))) { | ||||
|         return { | ||||
|           ...ruleItem, | ||||
|           options: modifyOptions(ruleItem.options), | ||||
|         }; | ||||
|       } | ||||
|       return ruleItem; | ||||
|     }); | ||||
|   } | ||||
|   return webpackConfig; | ||||
| } | ||||
| 
 | ||||
| interface AddLoaderOptions { | ||||
|   rule: string; | ||||
|   useItem: RuleSetUseItem; | ||||
|   before?: string; | ||||
|   after?: string; | ||||
| } | ||||
| 
 | ||||
| export function addLoader(webpackConfig: Configuration, options: AddLoaderOptions) { | ||||
|   const { rule: ruleName, after, before, useItem } = options; | ||||
|   const targetRule = findLoader(webpackConfig, ruleName) as RuleSetRule; | ||||
|   if (targetRule && Array.isArray(targetRule?.use)) { | ||||
|     const loaderIndex = targetRule.use.findIndex((ruleItem) => { | ||||
|       const matchLoader = after || before; | ||||
|       return typeof ruleItem === 'object' && matchLoader && ruleItem.loader?.match(new RegExp(`[@/\\\\]${matchLoader}`)); | ||||
|     }); | ||||
|     if (loaderIndex > -1) { | ||||
|       const spliceIndex = before ? loaderIndex : loaderIndex + 1; | ||||
|       targetRule.use.splice(spliceIndex, 0, useItem); | ||||
|     } | ||||
|   } | ||||
|   return webpackConfig; | ||||
| } | ||||
| 
 | ||||
| export function removePlugin(webpackConfig: Configuration, pluginName: string) { | ||||
|   const webpackPlugins = (webpackConfig.plugins || []).filter((plugin) => { | ||||
|     return !(plugin?.constructor?.name === pluginName); | ||||
|   }); | ||||
| 
 | ||||
|   return { | ||||
|     ...webpackConfig, | ||||
|     plugins: webpackPlugins, | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,158 @@ | |||
| import { expect, it, describe } from 'vitest'; | ||||
| import type { Configuration } from 'webpack'; | ||||
| import { removeLoader, addLoader, modifyLoader, modifyRule, removePlugin } from '../src/index'; | ||||
| 
 | ||||
| describe('test webpack config modify', () => { | ||||
|   const getWebpackConfig = () => ({ | ||||
|     module: { | ||||
|       rules: [ | ||||
|         { | ||||
|           test: /.css$/, | ||||
|           use: [ | ||||
|             { | ||||
|               loader: '/absoulte/path/to/postcss-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: '/absoulte/path/to/css-loader', | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   }); | ||||
|   it('remove loader', () => { | ||||
|     const webpackConfig: Configuration = getWebpackConfig(); | ||||
|     expect(removeLoader(webpackConfig, { | ||||
|       rule: '.css', | ||||
|       loader: 'css-loader', | ||||
|     })).toStrictEqual({ | ||||
|       module: { | ||||
|         rules: [{ | ||||
|           test: /.css$/, | ||||
|           use: [ | ||||
|             { | ||||
|               loader: '/absoulte/path/to/postcss-loader', | ||||
|             }, | ||||
|           ], | ||||
|         }], | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
|   it('add loader', () => { | ||||
|     expect(addLoader(getWebpackConfig(), { | ||||
|       rule: '.css', | ||||
|       useItem: { | ||||
|         loader: 'custom-loader', | ||||
|       }, | ||||
|       before: 'css-loader', | ||||
|     })).toStrictEqual({ | ||||
|       module: { | ||||
|         rules: [{ | ||||
|           test: /.css$/, | ||||
|           use: [ | ||||
|             { | ||||
|               loader: '/absoulte/path/to/postcss-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: 'custom-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: '/absoulte/path/to/css-loader', | ||||
|             }, | ||||
|           ], | ||||
|         }], | ||||
|       }, | ||||
|     }); | ||||
|     expect(addLoader(getWebpackConfig(), { | ||||
|       rule: '.css', | ||||
|       useItem: { | ||||
|         loader: 'custom-loader', | ||||
|       }, | ||||
|       after: 'css-loader', | ||||
|     })).toStrictEqual({ | ||||
|       module: { | ||||
|         rules: [{ | ||||
|           test: /.css$/, | ||||
|           use: [ | ||||
|             { | ||||
|               loader: '/absoulte/path/to/postcss-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: '/absoulte/path/to/css-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: 'custom-loader', | ||||
|             }, | ||||
|           ], | ||||
|         }], | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
|   it('modify loader', () => { | ||||
|     expect(modifyLoader(getWebpackConfig(), { | ||||
|       rule: '.css', | ||||
|       loader: 'css-loader', | ||||
|       options: () => ({ module: true }), | ||||
|     })).toStrictEqual({ | ||||
|       module: { | ||||
|         rules: [{ | ||||
|           test: /.css$/, | ||||
|           use: [ | ||||
|             { | ||||
|               loader: '/absoulte/path/to/postcss-loader', | ||||
|             }, | ||||
|             { | ||||
|               loader: '/absoulte/path/to/css-loader', | ||||
|               options: { module: true }, | ||||
|             }, | ||||
|           ], | ||||
|         }], | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
|   it('modify rule', () => { | ||||
|     const webpackConfig = getWebpackConfig(); | ||||
|     webpackConfig.module.rules.push({ | ||||
|       test: /.less/, | ||||
|       use: [], | ||||
|     }); | ||||
|     expect(modifyRule(webpackConfig, { | ||||
|       rule: '.css', | ||||
|       options: () => ({ | ||||
|         test: /.css$/, | ||||
|         use: [], | ||||
|       }), | ||||
|     })).toStrictEqual({ | ||||
|       module: { | ||||
|         rules: [{ | ||||
|           test: /.css$/, | ||||
|           use: [], | ||||
|         }, { | ||||
|           test: /.less/, | ||||
|           use: [], | ||||
|         }], | ||||
|       }, | ||||
|     }); | ||||
|   }); | ||||
|   it('remove plugin', () => { | ||||
|     class TestPlugin {} | ||||
|     expect(removePlugin({ | ||||
|       plugins: [ | ||||
|         // @ts-ignore fake webpack plugin
 | ||||
|         new TestPlugin(), | ||||
|       ], | ||||
|     }, 'TestPlugin')).toStrictEqual({ | ||||
|       plugins: [], | ||||
|     }); | ||||
|   }); | ||||
|   it('miss loader', () => { | ||||
|     expect(removeLoader(getWebpackConfig(), { | ||||
|       rule: '.css', | ||||
|       loader: 'test-loader', | ||||
|     })).toStrictEqual(getWebpackConfig()); | ||||
|     expect(removeLoader(getWebpackConfig(), { | ||||
|       rule: '.test', | ||||
|       loader: 'css-loader', | ||||
|     })).toStrictEqual(getWebpackConfig()); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1066,6 +1066,17 @@ importers: | |||
|       webpack: 5.74.0_3cawqt5e67dzz3yqkvwzyd4tq4 | ||||
|       webpack-dev-server: 4.10.0_webpack@5.74.0 | ||||
| 
 | ||||
|   packages/webpack-modify: | ||||
|     specifiers: | ||||
|       '@ice/webpack-config': ^1.0.0 | ||||
|       consola: ^2.15.3 | ||||
|       webpack: ^5.73.0 | ||||
|     dependencies: | ||||
|       consola: 2.15.3 | ||||
|     devDependencies: | ||||
|       '@ice/webpack-config': link:../webpack-config | ||||
|       webpack: 5.74.0 | ||||
| 
 | ||||
|   website: | ||||
|     specifiers: | ||||
|       '@algolia/client-search': ^4.9.1 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue