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