diff --git a/docs/guide/basic/build.md b/docs/guide/basic/build.md index 77e6036486..d5521ea039 100644 --- a/docs/guide/basic/build.md +++ b/docs/guide/basic/build.md @@ -579,6 +579,78 @@ dll // dll 构建产物文件夹 └── dll-pkg.json // build.json 中所配置的 dllEntry 信息 ```` +### webpackPlugins + +- 类型:`object` +- 默认值:无 + +通过 `webpackPlugins` 可以方便地新增或者修改工程上的 webpack 插件配置。 + +配置方式: + +```json +{ + "webpackPlugins": { + "webpack.ProvidePlugin": { + "options": { + "identifier": "module1" + } + }, + "HtmlWebpackPlugin": { + "before": "webpack.ProvidePlugin" + } + } +} +``` + +配置规则如下: +- 对于 webpack 内置的 plugins,可以通过 webpack.PluginName 的形式作为 key 值进行配置 +- 对于其他 webpack 插件,需要将插件的 npm 包名作为 key 值进行配置,package.json 中需要添加并安装该插件依赖 +- 每一项插件配置支持 before/after 用来调整 webpack 插件执行顺序 +- 如果配置设置的插件已被添加,则修改插件配置 + +### webpackLoaders + +- 类型:`object` +- 默认值:无 + +通过 `webpackLoaders` 可以方便地新增或者修改工程上的 webpack loader 配置。 + +配置方式: + +```json +{ + "webpackLoaders": { + "css": { + "test": ".css$", + "loaders": { + "style-loader": { + "options": { + "loaderoption": true + }, + "before": "less-loader" + } + } + } + } +} +``` + +配置规则如下: +- webpackLoaders 配置下每一项为具体的 webpack loader 规则,支持参数 + - test:配置类型 `string|string[]`,同 [Rule.test](https://webpack.js.org/configuration/module/#ruletest) + - oneOf:配置类型 `[oneOfName: string]: { resourceQuery: string; loaders: Loaders }`,同[Rule.oneOf](https://webpack.js.org/configuration/module/#ruleoneof) + - includeClear:清除默认 include 配置 + - include:配置类型 `string|string[]`,同 [Rule.include](https://webpack.js.org/configuration/module/#ruleinclude) + - excludeClear:清除默认 exclude 配置 + - exclude:配置类型 `string|string[]`,同 [Rule.exclude](https://webpack.js.org/configuration/module/#ruleexclude) + - pre:配置类型 `boolean`,配置 rule 的 enforce 值为 pre + - post:配置类型 `boolean`,配置 rule 的 enforce 值为 post + - before:配置类型 `string`,用于配置定义顺序,前置指定 + - after:配置类型 `string`,用于配置定义顺序,后置指定 + - loaders:配置具体的 webpack loader +- loaders 参数用来指定具体 webpack loader 的参数;每一项 loader 参数支持 before/after 用来调整 webpack loader 的执行顺序;如果 loader 名已被添加,则修改插件配置 + ## 根据环境区分工程配置 参考 [区分不同环境](/docs/guide/basic/config.md)。 diff --git a/packages/build-user-config/src/config/user.config.js b/packages/build-user-config/src/config/user.config.js index 1996e2909f..3bce23e809 100644 --- a/packages/build-user-config/src/config/user.config.js +++ b/packages/build-user-config/src/config/user.config.js @@ -209,8 +209,16 @@ module.exports = [ name: 'targets', validation: 'array', }, + { + name: 'webpackLoaders', + validation: 'object', + }, + { + name: 'webpackPlugins', + validation: 'object', + }, { name: 'postcssOptions', validation: 'object', - } + }, ]; diff --git a/packages/build-user-config/src/userConfig/webpackLoaders.js b/packages/build-user-config/src/userConfig/webpackLoaders.js new file mode 100644 index 0000000000..e1c14fe6e4 --- /dev/null +++ b/packages/build-user-config/src/userConfig/webpackLoaders.js @@ -0,0 +1,105 @@ +const genRegExpRule = (value) => new RegExp(Array.isArray(value) ? value.join('|') : value); +const ensureArray = (value) => Array.isArray(value) ? value : [value]; + +const optionAPIs = { + test: (rule, value) => { + rule.test(genRegExpRule(value)); + }, + oneOf: (rule, value) => { + /** + * config.module + * .rule('css') + * .oneOf('inline') + * .resourceQuery(/inline/) + * .use('url') + * .loader('url-loader') + * .end() + * .end() + * .oneOf('external') + * .resourceQuery(/external/) + * .use('file') + * .loader('file-loader') + */ + Object.keys(value).forEach((oneOfName) => { + const { resourceQuery, loaders } = value[oneOfName]; + const loaderRule = rule.oneOf(oneOfName); + if (resourceQuery) { + loaderRule.resourceQuery(genRegExpRule(resourceQuery)); + } + configRuleLoaders(loaderRule, loaders || {}); + }); + }, + // clear include rules + includeClear: (rule) => { + rule.include.clear(); + }, + include: (rule, value) => { + ensureArray(value).forEach((includeValue) => { + rule.include.add(includeValue); + }); + }, + // clear exclude rules + excludeClear: (rule) => { + rule.exclude.clear(); + }, + exclude: (rule, value) => { + ensureArray(value).forEach((excludeValue) => { + rule.exclude.add(excludeValue); + }); + }, + pre: (rule) => { + rule.pre(); + }, + post: (rule) => { + rule.post(); + }, + enforce: (rule) => { + rule.enforce(); + }, + before: (rule, value) => { + rule.before(value); + }, + after: (rule, value) => { + rule.after(value); + }, +}; +const validRuleOption = Object.keys(optionAPIs); + +function configModuleRule(rule, options) { + // loop validRuleOption to make sure optionAPIs excute in order + validRuleOption.forEach((optionsKey) => { + const optionValue = options[optionsKey]; + if (optionValue) { + optionAPIs[optionsKey](rule, optionValue); + } + }); +} + +function configRuleLoaders(rule, loaders) { + const loaderNames = Object.keys(loaders); + loaderNames.forEach((loaderName) => { + const { options, before, after } = loaders[loaderName]; + // check if loader is exsits + let loaderRule = null; + if (rule.uses.has(loaderName)) { + loaderRule = rule.use(loaderName).tap(opts => ({ ...opts, ...options})); + } else { + loaderRule = rule.use(loaderName).loader(loaderName).options(options); + } + if (before) loaderRule.before(before); + if (after) loaderRule.after(after); + }); +} + +module.exports = (config, webpackLoaders) => { + if (webpackLoaders) { + const ruleNames = Object.keys(webpackLoaders); + ruleNames.forEach((ruleName) => { + // create new rule if module rule is not exists + const rule = config.module.rule(ruleName); + const ruleOptions = webpackLoaders[ruleName]; + configModuleRule(rule, ruleOptions); + configRuleLoaders(rule, ruleOptions.loaders || {}); + }); + } +}; diff --git a/packages/build-user-config/src/userConfig/webpackPlugins.js b/packages/build-user-config/src/userConfig/webpackPlugins.js new file mode 100644 index 0000000000..0ba2ad576e --- /dev/null +++ b/packages/build-user-config/src/userConfig/webpackPlugins.js @@ -0,0 +1,28 @@ +module.exports = (config, webpackPlugins, context) => { + if (webpackPlugins) { + const pluginNames = Object.keys(webpackPlugins); + pluginNames.forEach((pluginName) => { + const { options, after, before } = webpackPlugins[pluginName]; + let pluginRule = null; + // check if plugin has been already registed + if (config.plugins.has(pluginName)) { + // modify plugin options + pluginRule = config.plugin(pluginName).tap(([args]) => [{...args, ...options}]); + } else { + // add new plugin + let plugin = null; + if (pluginName.match(/^webpack\./)) { + // webpack builtin plugins + const { webpack } = context; + plugin = webpack[pluginName]; + } else { + // eslint-disable-next-line + plugin = require(pluginName); + } + pluginRule = config.plugin(pluginName).use(plugin, [options]); + } + if (before) pluginRule.before(before); + if (after) pluginRule.after(after); + }); + } +};