feat(webpack, builder): allow extending loader options (#3799)

This commit is contained in:
Clark Du 2018-09-10 09:27:01 +01:00 committed by Pooya Parsa
parent 1e0219543a
commit c77fa479f6
7 changed files with 127 additions and 52 deletions

View File

@ -21,6 +21,7 @@ export default class WebpackBaseConfig {
this.isStatic = builder.isStatic
this.options = builder.options
this.spinner = builder.spinner
this.loaders = this.options.build.loaders
}
get nuxtEnv() {
@ -129,25 +130,26 @@ export default class WebpackBaseConfig {
{
test: /\.vue$/,
loader: 'vue-loader',
options: Object.assign({
productionMode: !this.options.dev,
transformAssetUrls: {
video: 'src',
source: 'src',
object: 'src',
embed: 'src'
}
}, this.options.build.vueLoader)
options: this.loaders.vue
},
{
test: /\.pug$/,
oneOf: [
{
resourceQuery: /^\?vue/,
use: ['pug-plain-loader']
use: [{
loader: 'pug-plain-loader',
options: this.loaders.pugPlain
}]
},
{
use: ['raw-loader', 'pug-plain-loader']
use: [
'raw-loader',
{
loader: 'pug-plain-loader',
options: this.loaders.pugPlain
}
]
}
]
},
@ -176,50 +178,60 @@ export default class WebpackBaseConfig {
},
{
test: /\.less$/,
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('less', 'less-loader'))
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('less', {
loader: 'less-loader',
options: this.loaders.less
}))
},
{
test: /\.sass$/,
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('sass', {
loader: 'sass-loader',
options: { indentedSyntax: true }
options: this.loaders.sass
}))
},
{
test: /\.scss$/,
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('scss', 'sass-loader'))
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('scss', {
loader: 'sass-loader',
options: this.loaders.scss
}))
},
{
test: /\.styl(us)?$/,
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('stylus', 'stylus-loader'))
oneOf: perfLoader.poolOneOf('css', styleLoader.apply('stylus', {
loader: 'stylus-loader',
options: this.loaders.stylus
}))
},
{
test: /\.(png|jpe?g|gif|svg|webp)$/,
use: perfLoader.pool('assets', {
loader: 'url-loader',
options: {
limit: 1000, // 1KO
name: this.getFileName('img')
}
options: Object.assign(
this.loaders.imgUrl,
{ name: this.getFileName('img') }
)
})
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: perfLoader.pool('assets', {
loader: 'url-loader',
options: {
limit: 1000, // 1 KO
name: this.getFileName('font')
}
options: Object.assign(
this.loaders.fontUrl,
{ name: this.getFileName('font') }
)
})
},
{
test: /\.(webm|mp4|ogv)$/,
use: perfLoader.pool('assets', {
loader: 'file-loader',
options: {
name: this.getFileName('video')
}
options: Object.assign(
this.loaders.file,
{ name: this.getFileName('video') }
)
})
}
]
@ -274,9 +286,11 @@ export default class WebpackBaseConfig {
return plugins
}
customize(config) {
extendConfig(config) {
if (typeof this.options.build.extend === 'function') {
const extendedConfig = this.options.build.extend.call(this.builder, config, this.nuxtEnv)
const extendedConfig = this.options.build.extend.call(
this.builder, config, { loaders: this.loaders, ...this.nuxtEnv }
)
// Only overwrite config when something is returned for backwards compatibility
if (extendedConfig !== undefined) {
return extendedConfig
@ -312,7 +326,9 @@ export default class WebpackBaseConfig {
plugins: this.plugins()
}
const extendedConfig = this.extendConfig(config)
// Clone deep avoid leaking config between Client and Server
return _.cloneDeep(config)
return _.cloneDeep(extendedConfig)
}
}

View File

@ -96,8 +96,8 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
return plugins
}
customize() {
const config = super.customize(...arguments)
extendConfig() {
const config = super.extendConfig(...arguments)
if (!this.options.dev && !config.optimization.minimizer) {
config.optimization.minimizer = []
@ -158,6 +158,6 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
)
}
return this.customize(config)
return config
}
}

View File

@ -81,6 +81,6 @@ export default class WebpackServerConfig extends BaseConfig {
}
})
return this.customize(config)
return config
}
}

View File

@ -1,7 +1,5 @@
import path from 'path'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import PostcssConfig from './postcss'
export default class StyleLoader {
@ -11,6 +9,7 @@ export default class StyleLoader {
this.srcDir = options.srcDir
this.assetsDir = options.dir.assets
this.staticDir = options.dir.static
this.loaders = options.build.loaders
this.extractCSS = options.build.extractCSS
this.resources = options.build.styleResources
this.sourceMap = Boolean(options.build.cssSourceMap)
@ -22,10 +21,7 @@ export default class StyleLoader {
normalize(loaders) {
loaders = Array.isArray(loaders) ? loaders : [loaders]
return loaders.map(loader => Object.assign(
{ options: { sourceMap: this.sourceMap } },
typeof loader === 'string' ? { loader } : loader
))
return loaders.map(loader => (typeof loader === 'string' ? { loader } : loader))
}
styleResource(ext) {
@ -61,7 +57,7 @@ export default class StyleLoader {
}
}
css(importLoaders, options) {
css(options) {
// css-loader
// https://github.com/webpack-contrib/css-loader
const cssLoaderAlias = {
@ -71,14 +67,17 @@ export default class StyleLoader {
return {
loader: (this.isServer && this.extractCSS) ? 'css-loader/locals' : 'css-loader',
options: Object.assign({
sourceMap: this.sourceMap,
importLoaders: importLoaders,
options: Object.assign(options, {
alias: cssLoaderAlias
}, options)
})
}
}
cssModules(options) {
options.modules = true
return this.css(options)
}
extract() {
if (this.extractCSS && !this.isServer) {
return MiniCssExtractPlugin.loader
@ -89,7 +88,7 @@ export default class StyleLoader {
// https://github.com/vuejs/vue-style-loader
return {
loader: 'vue-style-loader',
options: { sourceMap: this.sourceMap }
options: this.loaders.vueStyle
}
}
@ -100,6 +99,9 @@ export default class StyleLoader {
this.styleResource(ext)
).filter(Boolean)
const { css: cssOptions, cssModules: cssModulesOptions } = this.loaders
cssOptions.importLoaders = cssModulesOptions.importLoaders = customLoaders.length
const styleLoader = this.extract() || this.vueStyle()
return [
@ -108,10 +110,7 @@ export default class StyleLoader {
resourceQuery: /module/,
use: [].concat(
styleLoader,
this.css(customLoaders.length, {
modules: true,
localIdentName: '[local]_[hash:base64:5]'
}),
this.cssModules(cssModulesOptions),
customLoaders
)
},
@ -119,7 +118,7 @@ export default class StyleLoader {
{
use: [].concat(
styleLoader,
this.css(customLoaders.length),
this.css(cssOptions),
customLoaders
)
}

View File

@ -60,6 +60,31 @@ export default {
font: ({ isDev }) => isDev ? '[path][name].[ext]' : 'fonts/[hash:7].[ext]',
video: ({ isDev }) => isDev ? '[path][name].[ext]' : 'videos/[hash:7].[ext]'
},
loaders: {
file: {},
fontUrl: { limit: 1000 },
imgUrl: { limit: 1000 },
pugPlain: {},
vue: {
transformAssetUrls: {
video: 'src',
source: 'src',
object: 'src',
embed: 'src'
}
},
css: {},
cssModules: {
localIdentName: '[local]_[hash:base64:5]'
},
less: {},
sass: {
indentedSyntax: true
},
scss: {},
stylus: {},
vueStyle: {}
},
styleResources: {},
plugins: [],
optimization: {
@ -81,7 +106,6 @@ export default {
cacheDirectory: undefined
},
transpile: [], // Name of NPM packages to be transpiled
vueLoader: {},
postcss: {
preset: {
// https://cssdb.org/#staging-process

View File

@ -209,6 +209,22 @@ Options.from = function (_options) {
options.build.extractCSS = false
}
const loaders = options.build.loaders
const vueLoader = loaders.vue
if (vueLoader.productionMode === undefined) {
vueLoader.productionMode = !options.dev
}
const styleLoaders = [
'css', 'cssModules', 'less',
'sass', 'scss', 'stylus', 'vueStyle'
]
for (const name of styleLoaders) {
const loader = loaders[name]
if (loader && loader.sourceMap === undefined) {
loader.sourceMap = Boolean(options.build.cssSourceMap)
}
}
// include SFCs in node_modules
options.build.transpile = [].concat(options.build.transpile || [])
.map(module => module instanceof RegExp ? module : new RegExp(module))

View File

@ -8,6 +8,8 @@ let nuxt = null
let builder = null
let transpile = null
let output = null
let loadersOptions
let vueLoader
describe('basic dev', () => {
beforeAll(async () => {
@ -26,11 +28,18 @@ describe('basic dev', () => {
'vue\\.test\\.js',
/vue-test/
],
extend({ module: { rules }, output: wpOutput }, { isClient }) {
loaders: {
cssModules: {
localIdentName: '[hash:base64:6]'
}
},
extend({ module: { rules }, output: wpOutput }, { isClient, loaders }) {
if (isClient) {
const babelLoader = rules.find(loader => loader.test.test('.jsx'))
transpile = file => !babelLoader.exclude(file)
output = wpOutput
loadersOptions = loaders
vueLoader = rules.find(loader => loader.test.test('.vue'))
}
}
}
@ -62,6 +71,17 @@ describe('basic dev', () => {
)
})
test('Config: build.loaders', () => {
expect(Object.keys(loadersOptions)).toHaveLength(12)
expect(loadersOptions).toHaveProperty(
'file', 'fontUrl', 'imgUrl', 'pugPlain', 'vue',
'css', 'cssModules', 'less', 'sass', 'scss', 'stylus', 'vueStyle'
)
const { cssModules, vue } = loadersOptions
expect(cssModules.localIdentName).toBe('[hash:base64:6]')
expect(vueLoader.options).toBe(vue)
})
test('/stateless', async () => {
const window = await nuxt.renderAndGetWindow(url('/stateless'))
const html = window.document.body.innerHTML