From 10b1a38762a0843f7f7543a6e4ae8b2d11e5f8ac Mon Sep 17 00:00:00 2001 From: endiliey Date: Thu, 30 Aug 2018 03:30:44 +0800 Subject: [PATCH] feat: allow user to modify generated webpack config --- lib/commands/build.js | 12 ++++- lib/commands/start.js | 8 +++ lib/load/config.js | 4 +- lib/webpack/client.js | 5 ++ lib/webpack/server.js | 4 ++ lib/webpack/utils.js | 28 ++++++++++ package.json | 1 + test/webpack/utils.test.js | 107 +++++++++++++++++++++++++++++++++++++ yarn.lock | 6 +++ 9 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 lib/webpack/utils.js create mode 100644 test/webpack/utils.test.js diff --git a/lib/commands/build.js b/lib/commands/build.js index 551fa5be06..a2da9b3dda 100644 --- a/lib/commands/build.js +++ b/lib/commands/build.js @@ -6,6 +6,7 @@ const globby = require('globby'); const load = require('../load'); const createServerConfig = require('../webpack/server'); const createClientConfig = require('../webpack/client'); +const {applyConfigureWebpack} = require('../webpack/utils'); function compile(config) { return new Promise((resolve, reject) => { @@ -36,8 +37,15 @@ module.exports = async function build(siteDir, cliOptions = {}) { const props = await load(siteDir); - const serverConfig = createServerConfig(props).toConfig(); - const clientConfig = createClientConfig(props).toConfig(); + let serverConfig = createServerConfig(props).toConfig(); + let clientConfig = createClientConfig(props).toConfig(); + + // apply user webpack config + const { + siteConfig: {configureWebpack} + } = props; + clientConfig = applyConfigureWebpack(configureWebpack, clientConfig, false); + serverConfig = applyConfigureWebpack(configureWebpack, serverConfig, true); // Build the client bundles first. // We cannot run them in parallel because the server need to pickup the correct client bundle name diff --git a/lib/commands/start.js b/lib/commands/start.js index 7c477511e5..a95d15d050 100644 --- a/lib/commands/start.js +++ b/lib/commands/start.js @@ -13,6 +13,7 @@ const serve = require('webpack-serve'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const load = require('../load'); const createClientConfig = require('../webpack/client'); +const {applyConfigureWebpack} = require('../webpack/utils'); function getHost(reqHost) { return reqHost || 'localhost'; @@ -72,6 +73,13 @@ module.exports = async function start(siteDir, cliOptions = {}) { } ]); config = config.toConfig(); + + // apply user webpack config + const { + siteConfig: {configureWebpack} + } = props; + config = applyConfigureWebpack(configureWebpack, config, false); + const compiler = webpack(config); // webpack-serve diff --git a/lib/load/config.js b/lib/load/config.js index c106e9e0ff..fd325ce08f 100644 --- a/lib/load/config.js +++ b/lib/load/config.js @@ -21,7 +21,9 @@ module.exports = function loadConfig(siteDir, deleteCache = true) { const optionalFields = [ 'customDocsPath', 'highlight', - 'markdownPlugins' + 'markdownPlugins', + 'configureWebpack', + 'chainWebpack' ]; const missingFields = requiredFields.filter(field => !config[field]); if (missingFields && missingFields.length > 0) { diff --git a/lib/webpack/client.js b/lib/webpack/client.js index 8c438ebc71..c544300df9 100644 --- a/lib/webpack/client.js +++ b/lib/webpack/client.js @@ -3,6 +3,7 @@ const webpackNiceLog = require('webpack-nicelog'); const {StatsWriterPlugin} = require('webpack-stats-plugin'); const cleanWebpackPlugin = require('clean-webpack-plugin'); const createBaseConfig = require('./base'); +const {applyChainWebpack} = require('./utils'); module.exports = function createClientConfig(props) { const config = createBaseConfig(props); @@ -21,5 +22,9 @@ module.exports = function createClientConfig(props) { // show compilation progress bar and build time config.plugin('niceLog').use(webpackNiceLog, [{name: 'Client'}]); + + // user extended webpack-chain config + applyChainWebpack(props.siteConfig.chainWebpack, config, false); + return config; }; diff --git a/lib/webpack/server.js b/lib/webpack/server.js index bc236de34d..d55481ffd0 100644 --- a/lib/webpack/server.js +++ b/lib/webpack/server.js @@ -2,6 +2,7 @@ const path = require('path'); const staticSiteGenerator = require('static-site-generator-webpack-plugin'); const webpackNiceLog = require('webpack-nicelog'); const createBaseConfig = require('./base'); +const {applyChainWebpack} = require('./utils'); module.exports = function createServerConfig(props) { const config = createBaseConfig(props, true); @@ -32,5 +33,8 @@ module.exports = function createServerConfig(props) { .plugin('niceLog') .use(webpackNiceLog, [{name: 'Server', color: 'yellow'}]); + // user extended webpack-chain config + applyChainWebpack(props.siteConfig.chainWebpack, config, true); + return config; }; diff --git a/lib/webpack/utils.js b/lib/webpack/utils.js new file mode 100644 index 0000000000..9ce3d29a2c --- /dev/null +++ b/lib/webpack/utils.js @@ -0,0 +1,28 @@ +const merge = require('webpack-merge'); + +// Modify the generated webpack config with normal webpack config +function applyConfigureWebpack(userConfig, config, isServer) { + if (typeof userConfig === 'object') { + return merge(config, userConfig); + } + if (typeof userConfig === 'function') { + const res = userConfig(config, isServer); + if (res && typeof res === 'object') { + return merge(config, res); + } + } + return config; +} + +// Modify the generated webpack config with webpack-chain API +function applyChainWebpack(userChainWebpack, config, isServer) { + if (userChainWebpack) { + userChainWebpack(config, isServer); + } + +} + +module.exports = { + applyConfigureWebpack, + applyChainWebpack +}; diff --git a/package.json b/package.json index 01d9b0898d..5502c30878 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "style-loader": "^0.22.1", "webpack": "^4.16.3", "webpack-chain": "^4.8.0", + "webpack-merge": "^4.1.4", "webpack-nicelog": "^2.2.1", "webpack-serve": "^2.0.2", "webpack-stats-plugin": "^0.2.1" diff --git a/test/webpack/utils.test.js b/test/webpack/utils.test.js new file mode 100644 index 0000000000..8ffd610fbe --- /dev/null +++ b/test/webpack/utils.test.js @@ -0,0 +1,107 @@ +import {validate} from 'webpack'; +import path from 'path'; +import Config from 'webpack-chain'; + +import {applyConfigureWebpack, applyChainWebpack} from '@lib/webpack/utils'; + +describe('extending generated webpack config', () => { + test('direct mutation on generated webpack config object', async () => { + // fake generated webpack config + let config = { + output: { + path: __dirname, + filename: 'bundle.js' + } + }; + + /* eslint-disable */ + const configureWebpack = (generatedConfig, isServer) => { + if (!isServer) { + generatedConfig.entry = 'entry.js'; + generatedConfig.output = { + path: path.join(__dirname, 'dist'), + filename: 'new.bundle.js' + }; + } + }; + /* eslint-enable */ + + config = applyConfigureWebpack(configureWebpack, config, false); + expect(config).toEqual({ + entry: 'entry.js', + output: { + path: path.join(__dirname, 'dist'), + filename: 'new.bundle.js' + } + }); + const errors = validate(config); + + console.log(errors); + expect(errors.length).toBe(0); + }); + + test('webpack-merge with user webpack config object', async () => { + // fake generated webpack config + let config = { + output: { + path: __dirname, + filename: 'bundle.js' + } + }; + + /* eslint-disable */ + const configureWebpack = { + entry: 'entry.js', + output: { + path: path.join(__dirname, 'dist'), + filename: 'new.bundle.js' + } + }; + /* eslint-enable */ + + config = applyConfigureWebpack(configureWebpack, config, false); + expect(config).toEqual({ + entry: 'entry.js', + output: { + path: path.join(__dirname, 'dist'), + filename: 'new.bundle.js' + } + }); + const errors = validate(config); + expect(errors.length).toBe(0); + }); + + test('use webpack-chain API', async () => { + // fake generated webpack config in webpack-chain format + let config = new Config(); + config.output.path(__dirname).filename('bundle.js'); + + // user chainWebpack + /* eslint-disable */ + const chainWebpack = (oldConfig, isServer) => { + if (!isServer) { + oldConfig.entry('main').add('./entry.js'); + oldConfig.output + .path(path.join(__dirname, 'dist')) + .filename('new.bundle.js'); + } + }; + /* eslint-enable */ + + applyChainWebpack(chainWebpack, config, false); + + // transform to webpack configuration object format + config = config.toConfig(); + expect(config).toEqual({ + output: { + path: path.join(__dirname, 'dist'), + filename: 'new.bundle.js' + }, + entry: { + main: ['./entry.js'] + } + }); + const errors = validate(config); + expect(errors.length).toBe(0); + }); +}); diff --git a/yarn.lock b/yarn.lock index 1b464c375f..4897e32bd3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6975,6 +6975,12 @@ webpack-log@^1.0.1, webpack-log@^1.1.1, webpack-log@^1.1.2, webpack-log@^1.2.0: loglevelnext "^1.0.1" uuid "^3.1.0" +webpack-merge@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.4.tgz#0fde38eabf2d5fd85251c24a5a8c48f8a3f4eb7b" + dependencies: + lodash "^4.17.5" + webpack-nicelog@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/webpack-nicelog/-/webpack-nicelog-2.2.1.tgz#e3458003ce0d98966e930657176fb4eb6f536b85"