mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
feat(webpack, builder): allow extending loader options (#3799)
This commit is contained in:
parent
1e0219543a
commit
c77fa479f6
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,6 @@ export default class WebpackServerConfig extends BaseConfig {
|
||||
}
|
||||
})
|
||||
|
||||
return this.customize(config)
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user