From 818e982eca8bb6fd3b229dab1ab77d91614f3ce7 Mon Sep 17 00:00:00 2001 From: Clark Du Date: Thu, 22 Mar 2018 22:06:54 +0800 Subject: [PATCH] refactor: webpack build config --- lib/builder/builder.mjs | 41 +--- lib/builder/webpack/base.config.mjs | 327 ++++++++++++++++---------- lib/builder/webpack/client.config.mjs | 246 ++++++++++--------- lib/builder/webpack/server.config.mjs | 137 +++++------ 4 files changed, 393 insertions(+), 358 deletions(-) diff --git a/lib/builder/builder.mjs b/lib/builder/builder.mjs index d4265907ab..3263889670 100644 --- a/lib/builder/builder.mjs +++ b/lib/builder/builder.mjs @@ -18,8 +18,8 @@ import upath from 'upath' import { r, wp, wChunk, createRoutes, parallel, relativeTo, waitFor, createSpinner } from '../common/utils' import Options from '../common/options' -import clientWebpackConfig from './webpack/client.config' -import serverWebpackConfig from './webpack/server.config' +import ClientWebpackConfig from './webpack/client.config' +import ServerWebpackConfig from './webpack/server.config' const debug = Debug('nuxt:build') debug.color = 2 // Force green color @@ -147,39 +147,6 @@ export default class Builder { return this } - getBabelOptions({ isServer }) { - const options = _.clone(this.options.build.babel) - - if (typeof options.presets === 'function') { - options.presets = options.presets({ isServer }) - } - - if (!options.babelrc && !options.presets) { - options.presets = [ - [ - this.nuxt.resolvePath('babel-preset-vue-app'), - { - targets: isServer ? { node: '8.0.0' } : { ie: 9, uglify: true } - } - ] - ] - } - - return options - } - - getFileName(name) { - let fileName = this.options.build.filenames[name] - - // Don't use hashes when watching - // https://github.com/webpack/webpack/issues/1914#issuecomment-174171709 - if (this.options.dev) { - fileName = fileName.replace(/\[(chunkhash|contenthash|hash)\]\./g, '') - } - - return fileName - } - async generateRoutesAndFiles() { this.spinner.start(`Generating nuxt files...`) @@ -452,13 +419,13 @@ export default class Builder { const compilersOptions = [] // Client - const clientConfig = clientWebpackConfig.call(this) + const clientConfig = new ClientWebpackConfig(this).config() compilersOptions.push(clientConfig) // Server let serverConfig = null if (this.options.build.ssr) { - serverConfig = serverWebpackConfig.call(this) + serverConfig = new ServerWebpackConfig(this).config() compilersOptions.push(serverConfig) } diff --git a/lib/builder/webpack/base.config.mjs b/lib/builder/webpack/base.config.mjs index 81815319c0..6e801eb521 100644 --- a/lib/builder/webpack/base.config.mjs +++ b/lib/builder/webpack/base.config.mjs @@ -9,7 +9,7 @@ import VueLoader from 'vue-loader' import { isUrl, urlJoin } from '../../common/utils' import customLoaders from './loaders' -import styleLoaderWrapper from './style-loader' +import createStyleLoader from './style-loader' import WarnFixPlugin from './plugins/warnfix' import ProgressPlugin from './plugins/progress' import StatsPlugin from './plugins/stats' @@ -22,149 +22,218 @@ import StatsPlugin from './plugins/stats' | webpack config files |-------------------------------------------------------------------------- */ -export default function webpackBaseConfig({ name, isServer }) { - // Prioritize nested node_modules in webpack search path (#2558) - const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir) +export default class WebpackBaseConfig { + constructor(builder, options) { + this.name = options.name + this.isServer = options.isServer + this.builder = builder + this.isStatic = builder.isStatic + this.options = builder.options + this.spinner = builder.spinner + } - const configAlias = {} - const styleLoader = styleLoaderWrapper({ isServer }) + getBabelOptions() { + const options = _.clone(this.options.build.babel) - // Used by vue-loader so we can use in templates - // with - configAlias[this.options.dir.assets] = path.join( - this.options.srcDir, - this.options.dir.assets - ) - configAlias[this.options.dir.static] = path.join( - this.options.srcDir, - this.options.dir.static - ) + if (typeof options.presets === 'function') { + options.presets = options.presets({ isServer: this.isServer }) + } - const config = { - name, - mode: this.options.dev ? 'development' : 'production', - optimization: {}, - output: { + if (!options.babelrc && !options.presets) { + options.presets = [ + [ + this.builder.nuxt.resolvePath('babel-preset-vue-app'), + { + targets: this.isServer ? { node: '8.0.0' } : { ie: 9, uglify: true } + } + ] + ] + } + + return options + } + + getFileName(name) { + let fileName = this.options.build.filenames[name] + + // Don't use hashes when watching + // https://github.com/webpack/webpack/issues/1914#issuecomment-174171709 + if (this.options.dev) { + fileName = fileName.replace(/\[(chunkhash|contenthash|hash)\]\./g, '') + } + + return fileName + } + + env() { + const env = { + 'process.mode': JSON.stringify(this.options.mode), + 'process.static': this.isStatic + } + _.each(this.options.env, (value, key) => { + env['process.env.' + key] = + ['boolean', 'number'].indexOf(typeof value) !== -1 + ? value + : JSON.stringify(value) + }) + return env + } + + output() { + return { path: path.resolve(this.options.buildDir, 'dist'), filename: this.getFileName('app'), chunkFilename: this.getFileName('chunk'), publicPath: isUrl(this.options.build.publicPath) ? this.options.build.publicPath : urlJoin(this.options.router.base, this.options.build.publicPath) - }, - performance: { - maxEntrypointSize: 1000 * 1024, - hints: this.options.dev ? false : 'warning' - }, - resolve: { - extensions: ['.js', '.json', '.vue', '.jsx'], - alias: Object.assign( - { - '~': path.join(this.options.srcDir), - '~~': path.join(this.options.rootDir), - '@': path.join(this.options.srcDir), - '@@': path.join(this.options.rootDir) - }, - configAlias + } + } + + alias() { + return { + '~': path.join(this.options.srcDir), + '~~': path.join(this.options.rootDir), + '@': path.join(this.options.srcDir), + '@@': path.join(this.options.rootDir), + [this.options.dir.assets]: path.join( + this.options.srcDir, + this.options.dir.assets ), - modules: webpackModulesDir - }, - resolveLoader: { - alias: customLoaders, - modules: webpackModulesDir - }, - module: { - noParse: /es6-promise\.js$/, // Avoid webpack shimming process - rules: [ - { - test: /\.vue$/, - loader: 'vue-loader', - options: _.cloneDeep(this.options.build.vue) - }, - { - test: /\.jsx?$/, - loader: 'babel-loader', - exclude: /node_modules/, - options: this.getBabelOptions({ isServer }) - }, - { test: /\.css$/, use: styleLoader.call(this, 'css') }, - { test: /\.less$/, use: styleLoader.call(this, 'less', 'less-loader') }, - { - test: /\.sass$/, - use: styleLoader.call(this, 'sass', { - loader: 'sass-loader', - options: { indentedSyntax: true } - }) - }, - { test: /\.scss$/, use: styleLoader.call(this, 'scss', 'sass-loader') }, - { - test: /\.styl(us)?$/, - use: styleLoader.call(this, 'stylus', 'stylus-loader') - }, - { - test: /\.(png|jpe?g|gif|svg)$/, - loader: 'url-loader', - options: { - limit: 1000, // 1KO - name: 'img/[name].[hash:7].[ext]' - } - }, - { - test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - loader: 'url-loader', - options: { - limit: 1000, // 1 KO - name: 'fonts/[name].[hash:7].[ext]' - } - }, - { - test: /\.(webm|mp4)$/, - loader: 'file-loader', - options: { - name: 'videos/[name].[hash:7].[ext]' - } + [this.options.dir.static]: path.join( + this.options.srcDir, + this.options.dir.static + ) + } + } + + rules() { + const styleLoader = createStyleLoader({ + isServer: this.isServer + }) + return [ + { + test: /\.vue$/, + loader: 'vue-loader', + options: _.cloneDeep(this.options.build.vue) + }, + { + test: /\.jsx?$/, + loader: 'babel-loader', + exclude: /node_modules/, + options: this.getBabelOptions() + }, + { test: /\.css$/, use: styleLoader.call(this.builder, 'css') }, + { test: /\.less$/, use: styleLoader.call(this.builder, 'less', 'less-loader') }, + { + test: /\.sass$/, + use: styleLoader.call(this.builder, 'sass', { + loader: 'sass-loader', + options: { indentedSyntax: true } + }) + }, + { test: /\.scss$/, use: styleLoader.call(this.builder, 'scss', 'sass-loader') }, + { + test: /\.styl(us)?$/, + use: styleLoader.call(this.builder, 'stylus', 'stylus-loader') + }, + { + test: /\.(png|jpe?g|gif|svg)$/, + loader: 'url-loader', + options: { + limit: 1000, // 1KO + name: 'img/[name].[hash:7].[ext]' + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 1000, // 1 KO + name: 'fonts/[name].[hash:7].[ext]' + } + }, + { + test: /\.(webm|mp4)$/, + loader: 'file-loader', + options: { + name: 'videos/[name].[hash:7].[ext]' } - ] - }, - plugins: [ - new VueLoader.VueLoaderPlugin() - ].concat(this.options.build.plugins || []) - } - - // Add timefix-plugin before others plugins - if (this.options.dev) { - config.plugins.unshift(new TimeFixPlugin()) - } - - // Hide warnings about plugins without a default export (#1179) - config.plugins.push(new WarnFixPlugin()) - - if (!this.options.test) { - // Build progress indicator - if (this.options.build.profile) { - config.plugins.push(new webpack.ProgressPlugin({ profile: true })) - } else { - if (!(this.options.minimalCLI)) { - config.plugins.push(new ProgressPlugin({ - spinner: this.spinner, - name: isServer ? 'server' : 'client', - color: isServer ? 'green' : 'darkgreen' - })) } + ] + } + + plugins() { + const plugins = [ new VueLoader.VueLoaderPlugin() ] + + Array.prototype.push.apply(plugins, this.options.build.plugins || []) + + // Add timefix-plugin before others plugins + if (this.options.dev) { + plugins.unshift(new TimeFixPlugin()) } - // Add stats plugin - config.plugins.push(new StatsPlugin(this.options.build.stats)) + // Hide warnings about plugins without a default export (#1179) + plugins.push(new WarnFixPlugin()) - // Add friendly error plugin - config.plugins.push( - new FriendlyErrorsWebpackPlugin({ - clearConsole: true, - logLevel: 'WARNING' - }) - ) + if (!this.options.test) { + // Build progress indicator + if (this.options.build.profile) { + plugins.push(new webpack.ProgressPlugin({ profile: true })) + } else { + if (!(this.options.minimalCLI)) { + plugins.push(new ProgressPlugin({ + spinner: this.spinner, + name: this.isServer ? 'server' : 'client', + color: this.isServer ? 'green' : 'darkgreen' + })) + } + } + + // Add stats plugin + plugins.push(new StatsPlugin(this.options.build.stats)) + + // Add friendly error plugin + plugins.push( + new FriendlyErrorsWebpackPlugin({ + clearConsole: true, + logLevel: 'WARNING' + }) + ) + } + return plugins } - // Clone deep avoid leaking config between Client and Server - return _.cloneDeep(config) + config() { + // Prioritize nested node_modules in webpack search path (#2558) + const webpackModulesDir = ['node_modules'].concat(this.options.modulesDir) + + const config = { + name: this.name, + mode: this.options.dev ? 'development' : 'production', + optimization: {}, + output: this.output(), + performance: { + maxEntrypointSize: 1000 * 1024, + hints: this.options.dev ? false : 'warning' + }, + resolve: { + extensions: ['.js', '.json', '.vue', '.jsx'], + alias: this.alias(), + modules: webpackModulesDir + }, + resolveLoader: { + alias: customLoaders, + modules: webpackModulesDir + }, + module: { + noParse: /es6-promise\.js$/, // Avoid webpack shimming process + rules: this.rules() + }, + plugins: this.plugins() + } + + // Clone deep avoid leaking config between Client and Server + return _.cloneDeep(config) + } } diff --git a/lib/builder/webpack/client.config.mjs b/lib/builder/webpack/client.config.mjs index 9d7a30fc76..9f088edb78 100644 --- a/lib/builder/webpack/client.config.mjs +++ b/lib/builder/webpack/client.config.mjs @@ -1,6 +1,5 @@ import path from 'path' -import _ from 'lodash' import webpack from 'webpack' import HTMLPlugin from 'html-webpack-plugin' @@ -8,7 +7,7 @@ import BundleAnalyzer from 'webpack-bundle-analyzer' import MiniCssExtractPlugin from 'mini-css-extract-plugin' import Debug from 'debug' -import base from './base.config' +import BaseConfig from './base.config' // import VueSSRClientPlugin from 'vue-server-renderer/client-plugin' import VueSSRClientPlugin from './plugins/vue/client' @@ -21,141 +20,140 @@ debug.color = 2 // Force green color | Webpack Client Config |-------------------------------------------------------------------------- */ -export default function webpackClientConfig() { - let config = base.call(this, { name: 'client', isServer: false }) +export default class WebpackClientConfig extends BaseConfig { + constructor(builder) { + super(builder, { name: 'client', isServer: false }) + } - // Entry points - config.entry = path.resolve(this.options.buildDir, 'client.js') - - // Env object defined in nuxt.config.js - let env = {} - _.each(this.options.env, (value, key) => { - env['process.env.' + key] = - ['boolean', 'number'].indexOf(typeof value) !== -1 - ? value - : JSON.stringify(value) - }) - - // Generate output HTML for SPA - config.plugins.push( - new HTMLPlugin({ - filename: 'index.spa.html', - template: 'lodash!' + this.options.appTemplatePath, - inject: true, - chunksSortMode: 'dependency' + env() { + return Object.assign(super.env(), { + 'process.env.VUE_ENV': JSON.stringify('client'), + 'process.browser': true, + 'process.client': true, + 'process.server': false }) - ) - - // Generate output HTML for SSR - if (this.options.build.ssr) { - config.plugins.push( - new HTMLPlugin({ - filename: 'index.ssr.html', - template: 'lodash!' + this.options.appTemplatePath, - inject: false // Resources will be injected using bundleRenderer - }) - ) } - // Generate vue-ssr-client-manifest - config.plugins.push( - new VueSSRClientPlugin({ - filename: 'vue-ssr-client-manifest.json' - }) - ) + plugins() { + const plugins = super.plugins() - // Define Env - config.plugins.push( - new webpack.DefinePlugin( - Object.assign(env, { - 'process.env.VUE_ENV': JSON.stringify('client'), - 'process.mode': JSON.stringify(this.options.mode), - 'process.browser': true, - 'process.client': true, - 'process.server': false, - 'process.static': this.isStatic - }) - ) - ) - - // -- Optimization -- - config.optimization = this.options.build.optimization - - // Small, known and common modules which are usually used project-wise - // Sum of them may not be more than 244 KiB - if ( - this.options.build.splitChunks.commons === true && - config.optimization.splitChunks.cacheGroups.commons === undefined - ) { - config.optimization.splitChunks.cacheGroups.commons = { - test: /node_modules\/(vue|vue-loader|vue-router|vuex|vue-meta|core-js|babel-runtime|es6-promise|axios|webpack|setimediate|timers-browserify|process|regenerator-runtime|cookie|js-cookie|is-buffer|dotprop|nuxt\.js)\//, - chunks: 'all', - priority: 10, - name: 'commons' - } - } - - // -------------------------------------- - // Dev specific config - // -------------------------------------- - if (this.options.dev) { - // Add HMR support - config.entry = [ - // https://github.com/glenjamin/webpack-hot-middleware#config - `webpack-hot-middleware/client?name=client&reload=true&timeout=30000&path=${ - this.options.router.base - }/__webpack_hmr`.replace(/\/\//g, '/'), - config.entry - ] - - // HMR - config.plugins.push(new webpack.HotModuleReplacementPlugin()) - } - - // -------------------------------------- - // Production specific config - // -------------------------------------- - if (!this.options.dev) { - // Chunks size limit - // https://webpack.js.org/plugins/aggressive-splitting-plugin/ - if (this.options.build.maxChunkSize) { - config.plugins.push( - new webpack.optimize.AggressiveSplittingPlugin({ - minSize: this.options.build.maxChunkSize, - maxSize: this.options.build.maxChunkSize + // Generate output HTML for SSR + if (this.options.build.ssr) { + plugins.push( + new HTMLPlugin({ + filename: 'index.ssr.html', + template: 'lodash!' + this.options.appTemplatePath, + inject: false // Resources will be injected using bundleRenderer }) ) } - // Webpack Bundle Analyzer - if (this.options.build.analyze) { - config.plugins.push( - new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({}, this.options.build.analyze)) - ) + plugins.push( + new HTMLPlugin({ + filename: 'index.spa.html', + template: 'lodash!' + this.options.appTemplatePath, + inject: true, + chunksSortMode: 'dependency' + }), + new VueSSRClientPlugin({ + filename: 'vue-ssr-client-manifest.json' + }), + new webpack.DefinePlugin(this.env()) + ) + + if (this.options.dev) { + // -------------------------------------- + // Dev specific config + // -------------------------------------- + + // HMR + plugins.push(new webpack.HotModuleReplacementPlugin()) + } else { + // -------------------------------------- + // Production specific config + // -------------------------------------- + + // Chunks size limit + // https://webpack.js.org/plugins/aggressive-splitting-plugin/ + if (this.options.build.maxChunkSize) { + plugins.push( + new webpack.optimize.AggressiveSplittingPlugin({ + minSize: this.options.build.maxChunkSize, + maxSize: this.options.build.maxChunkSize + }) + ) + } + + // Webpack Bundle Analyzer + if (this.options.build.analyze) { + plugins.push( + new BundleAnalyzer.BundleAnalyzerPlugin(Object.assign({}, this.options.build.analyze)) + ) + } } - } - // Extend config - if (typeof this.options.build.extend === 'function') { - const isDev = this.options.dev - const extendedConfig = this.options.build.extend.call(this, config, { - isDev, - isClient: true - }) - - // Only overwrite config when something is returned for backwards compatibility - if (extendedConfig !== undefined) { - config = extendedConfig + // CSS extraction + const extractCSS = this.options.build.extractCSS + if (extractCSS) { + plugins.push(new MiniCssExtractPlugin(Object.assign({ + filename: this.getFileName('css') + }, typeof extractCSS === 'object' ? extractCSS : {}))) } + + return plugins } - // CSS extraction - const extractCSS = this.options.build.extractCSS - if (extractCSS) { - config.plugins.push(new MiniCssExtractPlugin(Object.assign({ - filename: this.getFileName('css') - }, typeof extractCSS === 'object' ? extractCSS : {}))) - } + config() { + let config = super.config() - return config + // Entry points + config.entry = path.resolve(this.options.buildDir, 'client.js') + + // -- Optimization -- + config.optimization = this.options.build.optimization + + // Small, known and common modules which are usually used project-wise + // Sum of them may not be more than 244 KiB + if ( + this.options.build.splitChunks.commons === true && + config.optimization.splitChunks.cacheGroups.commons === undefined + ) { + config.optimization.splitChunks.cacheGroups.commons = { + test: /node_modules\/(vue|vue-loader|vue-router|vuex|vue-meta|core-js|babel-runtime|es6-promise|axios|webpack|setimediate|timers-browserify|process|regenerator-runtime|cookie|js-cookie|is-buffer|dotprop|nuxt\.js)\//, + chunks: 'all', + priority: 10, + name: 'commons' + } + } + + // -------------------------------------- + // Dev specific config + // -------------------------------------- + if (this.options.dev) { + // Add HMR support + config.entry = [ + // https://github.com/glenjamin/webpack-hot-middleware#config + `webpack-hot-middleware/client?name=client&reload=true&timeout=30000&path=${ + this.options.router.base + }/__webpack_hmr`.replace(/\/\//g, '/'), + config.entry + ] + } + + // Extend config + if (typeof this.options.build.extend === 'function') { + const isDev = this.options.dev + const extendedConfig = this.options.build.extend.call(this.builder, config, { + isDev, + isClient: true + }) + + // Only overwrite config when something is returned for backwards compatibility + if (extendedConfig !== undefined) { + config = extendedConfig + } + } + + return config + } } diff --git a/lib/builder/webpack/server.config.mjs b/lib/builder/webpack/server.config.mjs index 4cc202c866..9dfc061256 100644 --- a/lib/builder/webpack/server.config.mjs +++ b/lib/builder/webpack/server.config.mjs @@ -3,9 +3,8 @@ import fs from 'fs' import webpack from 'webpack' import nodeExternals from 'webpack-node-externals' -import _ from 'lodash' -import base from './base.config' +import BaseConfig from './base.config' // import VueSSRServerPlugin from 'vue-server-renderer/server-plugin' import VueSSRServerPlugin from './plugins/vue/server' @@ -15,77 +14,79 @@ import VueSSRServerPlugin from './plugins/vue/server' | Webpack Server Config |-------------------------------------------------------------------------- */ -export default function webpackServerConfig() { - let config = base.call(this, { name: 'server', isServer: true }) +export default class WebpackServerConfig extends BaseConfig { + constructor(builder) { + super(builder, { name: 'server', isServer: true }) + } - // Env object defined in nuxt.config.js - let env = {} - _.each(this.options.env, (value, key) => { - env['process.env.' + key] = - ['boolean', 'number'].indexOf(typeof value) !== -1 - ? value - : JSON.stringify(value) - }) + env() { + return Object.assign(super.env(), { + 'process.env.VUE_ENV': JSON.stringify('server'), + 'process.browser': false, + 'process.client': false, + 'process.server': true + }) + } - // Config devtool - config.devtool = this.options.dev ? 'cheap-source-map' : false - - config = Object.assign(config, { - target: 'node', - node: false, - entry: path.resolve(this.options.buildDir, 'server.js'), - output: Object.assign({}, config.output, { - filename: 'server-bundle.js', - libraryTarget: 'commonjs2' - }), - performance: { - hints: false, - maxAssetSize: Infinity - }, - externals: [], - plugins: (config.plugins || []).concat([ + plugins() { + const plugins = super.plugins() + plugins.push( new VueSSRServerPlugin({ filename: 'server-bundle.json' }), - new webpack.DefinePlugin( - Object.assign(env, { - 'process.env.VUE_ENV': JSON.stringify('server'), - 'process.mode': JSON.stringify(this.options.mode), - 'process.browser': false, - 'process.client': false, - 'process.server': true, - 'process.static': this.isStatic - }) - ) - ]) - }) - - // https://webpack.js.org/configuration/externals/#externals - // https://github.com/liady/webpack-node-externals - this.options.modulesDir.forEach(dir => { - if (fs.existsSync(dir)) { - config.externals.push( - nodeExternals({ - // load non-javascript files with extensions, presumably via loaders - whitelist: [/es6-promise|\.(?!(?:js|json)$).{1,5}$/i], - modulesDir: dir - }) - ) - } - }) - - // Extend config - if (typeof this.options.build.extend === 'function') { - const isDev = this.options.dev - const extendedConfig = this.options.build.extend.call(this, config, { - isDev, - isServer: true - }) - // Only overwrite config when something is returned for backwards compatibility - if (extendedConfig !== undefined) { - config = extendedConfig - } + new webpack.DefinePlugin(this.env()) + ) + return plugins } - return config + config() { + let config = super.config() + + // Config devtool + config.devtool = this.options.dev ? 'cheap-source-map' : false + + Object.assign(config, { + target: 'node', + node: false, + entry: path.resolve(this.options.buildDir, 'server.js'), + output: Object.assign({}, config.output, { + filename: 'server-bundle.js', + libraryTarget: 'commonjs2' + }), + performance: { + hints: false, + maxAssetSize: Infinity + }, + externals: [] + }) + + // https://webpack.js.org/configuration/externals/#externals + // https://github.com/liady/webpack-node-externals + this.options.modulesDir.forEach(dir => { + if (fs.existsSync(dir)) { + config.externals.push( + nodeExternals({ + // load non-javascript files with extensions, presumably via loaders + whitelist: [/es6-promise|\.(?!(?:js|json)$).{1,5}$/i], + modulesDir: dir + }) + ) + } + }) + + // Extend config + if (typeof this.options.build.extend === 'function') { + const isDev = this.options.dev + const extendedConfig = this.options.build.extend.call(this.builder, config, { + isDev, + isServer: true + }) + // Only overwrite config when something is returned for backwards compatibility + if (extendedConfig !== undefined) { + config = extendedConfig + } + } + + return config + } }