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: 5.74.0_3cawqt5e67dzz3yqkvwzyd4tq4 | ||||||
|       webpack-dev-server: 4.10.0_webpack@5.74.0 |       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: |   website: | ||||||
|     specifiers: |     specifiers: | ||||||
|       '@algolia/client-search': ^4.9.1 |       '@algolia/client-search': ^4.9.1 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue