diff --git a/lib/builder/webpack/base.mjs b/lib/builder/webpack/base.mjs index 583ba2b8f8..d8221094ea 100644 --- a/lib/builder/webpack/base.mjs +++ b/lib/builder/webpack/base.mjs @@ -9,7 +9,7 @@ import VueLoader from 'vue-loader' import { isUrl, urlJoin } from '../../common/utils' import customLoaders from './loaders' -import createStyleLoader from './utils/style-loader' +import StyleLoader from './utils/style-loader' import WarnFixPlugin from './plugins/warnfix' import ProgressPlugin from './plugins/progress' import StatsPlugin from './plugins/stats' @@ -128,7 +128,11 @@ export default class WebpackBaseConfig { } rules() { - const styleLoader = createStyleLoader({ isServer: this.isServer }) + const styleLoader = new StyleLoader( + this.options, + this.builder.nuxt, + { isServer: this.isServer } + ) return [ { @@ -146,26 +150,26 @@ export default class WebpackBaseConfig { }, { test: /\.css$/, - use: styleLoader.call(this.builder, 'css') + use: styleLoader.apply('css') }, { test: /\.less$/, - use: styleLoader.call(this.builder, 'less', 'less-loader') + use: styleLoader.apply('less', 'less-loader') }, { test: /\.sass$/, - use: styleLoader.call(this.builder, 'sass', { + use: styleLoader.apply('sass', { loader: 'sass-loader', options: { indentedSyntax: true } }) }, { test: /\.scss$/, - use: styleLoader.call(this.builder, 'scss', 'sass-loader') + use: styleLoader.apply('scss', 'sass-loader') }, { test: /\.styl(us)?$/, - use: styleLoader.call(this.builder, 'stylus', 'stylus-loader') + use: styleLoader.apply('stylus', 'stylus-loader') }, { test: /\.(png|jpe?g|gif|svg)$/, diff --git a/lib/builder/webpack/utils/postcss.mjs b/lib/builder/webpack/utils/postcss.mjs index 1ad9a6a17d..39311bb67c 100644 --- a/lib/builder/webpack/utils/postcss.mjs +++ b/lib/builder/webpack/utils/postcss.mjs @@ -6,88 +6,114 @@ import createResolver from 'postcss-import-resolver' import { isPureObject } from '../../../common/utils' -export default function postcssConfig() { - let config = ـ.cloneDeep(this.options.build.postcss) - - /* istanbul ignore if */ - if (!config) { - return false +export default class PostcssConfig { + constructor(options, nuxt) { + this.nuxt = nuxt + this.postcss = options.build.postcss + this.srcDir = options.srcDir + this.rootDir = options.rootDir + this.cssSourceMap = options.build.cssSourceMap + this.modulesDir = options.modulesDir } - // Search for postCSS config file and use it if exists - // https://github.com/michael-ciniawsky/postcss-load-config - for (let dir of [this.options.srcDir, this.options.rootDir]) { - for (let file of [ - 'postcss.config.js', - '.postcssrc.js', - '.postcssrc', - '.postcssrc.json', - '.postcssrc.yaml' - ]) { - if (fs.existsSync(path.resolve(dir, file))) { - const postcssConfigPath = path.resolve(dir, file) - return { - sourceMap: this.options.build.cssSourceMap, - config: { - path: postcssConfigPath + get defaultConfig() { + return { + useConfigFile: false, + sourceMap: this.cssSourceMap, + plugins: { + // https://github.com/postcss/postcss-import + 'postcss-import': { + resolve: createResolver({ + alias: { + '~': path.join(this.srcDir), + '~~': path.join(this.rootDir), + '@': path.join(this.srcDir), + '@@': path.join(this.rootDir) + }, + modules: [ + this.srcDir, + this.rootDir, + ...this.modulesDir + ] + }) + }, + + // https://github.com/postcss/postcss-url + 'postcss-url': {}, + + // http://cssnext.io/postcss + 'postcss-cssnext': {} + } + } + } + + configFromFile() { + // Search for postCSS config file and use it if exists + // https://github.com/michael-ciniawsky/postcss-load-config + for (let dir of [this.srcDir, this.rootDir]) { + for (let file of [ + 'postcss.config.js', + '.postcssrc.js', + '.postcssrc', + '.postcssrc.json', + '.postcssrc.yaml' + ]) { + if (fs.existsSync(path.resolve(dir, file))) { + const postcssConfigPath = path.resolve(dir, file) + return { + sourceMap: this.cssSourceMap, + config: { + path: postcssConfigPath + } } } } } } - // Normalize - if (Array.isArray(config)) { - config = { plugins: config } + normalize(config) { + if (Array.isArray(config)) { + config = { plugins: config } + } + return config } - // Apply default plugins - if (isPureObject(config)) { - config = Object.assign( - { - useConfigFile: false, - sourceMap: this.options.build.cssSourceMap, - plugins: { - // https://github.com/postcss/postcss-import - 'postcss-import': { - resolve: createResolver({ - alias: { - '~': path.join(this.options.srcDir), - '~~': path.join(this.options.rootDir), - '@': path.join(this.options.srcDir), - '@@': path.join(this.options.rootDir) - }, - modules: [ - this.options.srcDir, - this.options.rootDir, - ...this.options.modulesDir - ] - }) - }, - - // https://github.com/postcss/postcss-url - 'postcss-url': {}, - - // http://cssnext.io/postcss - 'postcss-cssnext': {} - } - }, - config - ) + loadPlugins(config) { + const plugins = config.plugins + if (isPureObject(plugins)) { + // Map postcss plugins into instances on object mode once + config.plugins = Object.keys(plugins) + .map(p => { + const plugin = this.nuxt.requireModule(p) + const opts = plugins[p] + if (opts === false) return // Disabled + const instance = plugin(opts) + return instance + }) + .filter(e => e) + } } - // Map postcss plugins into instances on object mode once - if (isPureObject(config) && isPureObject(config.plugins)) { - config.plugins = Object.keys(config.plugins) - .map(p => { - const plugin = this.nuxt.requireModule(p) - const opts = config.plugins[p] - if (opts === false) return // Disabled - const instance = plugin(opts) - return instance - }) - .filter(e => e) - } + config() { + /* istanbul ignore if */ + if (!this.postcss) { + return false + } - return config + let config = this.configFromFile() + if (config) { + return config + } + + config = this.normalize(ـ.cloneDeep(this.postcss)) + + // Apply default plugins + if (isPureObject(config)) { + config = ـ.defaults(config, this.defaultConfig) + + this.loadPlugins(config) + } + + return config + } } diff --git a/lib/builder/webpack/utils/style-loader.js b/lib/builder/webpack/utils/style-loader.js index 5226cc4d95..1d75d9ca94 100644 --- a/lib/builder/webpack/utils/style-loader.js +++ b/lib/builder/webpack/utils/style-loader.js @@ -2,88 +2,119 @@ import path from 'path' import MiniCssExtractPlugin from 'mini-css-extract-plugin' -import postcssConfig from './postcss' +import PostcssConfig from './postcss' -export default ({ isServer }) => { - return function styleLoader(ext, loaders = []) { - const sourceMap = Boolean(this.options.build.cssSourceMap) +export default class StyleLoader { + constructor(options, nuxt, { isServer }) { + this.isServer = isServer + this.dev = options.dev + this.srcDir = options.srcDir + this.assetsDir = options.dir.assets + this.staticDir = options.dir.static + this.extractCSS = options.build.extractCSS + this.resources = options.build.styleResources + this.sourceMap = Boolean(options.build.cssSourceMap) - // Normalize loaders - loaders = (Array.isArray(loaders) ? loaders : [loaders]).map(loader => - Object.assign( - { options: { sourceMap } }, - typeof loader === 'string' ? { loader } : loader - ) - ) + if (options.build.postcss) { + this.postcssConfig = new PostcssConfig(options, nuxt) + } + } - // -- Configure additional loaders -- + normalize(loaders) { + loaders = Array.isArray(loaders) ? loaders : [loaders] + return loaders.map(loader => Object.assign( + { options: { sourceMap: this.sourceMap } }, + typeof loader === 'string' ? { loader } : loader + )) + } + styleResource(ext, loaders) { + const extResource = this.resources[ext] // style-resources-loader // https://github.com/yenshih/style-resources-loader - if (this.options.build.styleResources[ext]) { - const patterns = Array.isArray(this.options.build.styleResources[ext]) - ? this.options.build.styleResources[ext] - : [this.options.build.styleResources[ext]] - const options = Object.assign( - {}, - this.options.build.styleResources.options || {}, - { patterns } - ) + if (extResource) { + const patterns = Array.isArray(extResource) + ? extResource + : [extResource] loaders.push({ loader: 'style-resources-loader', - options + options: Object.assign( + { patterns }, + this.resources.options || {} + ) }) } + } + postcss(loaders) { // postcss-loader // https://github.com/postcss/postcss-loader - const _postcssConfig = postcssConfig.call(this) - - if (_postcssConfig) { - loaders.unshift({ - loader: 'postcss-loader', - options: Object.assign({ sourceMap }, _postcssConfig) - }) + if (this.postcssConfig) { + const config = this.postcssConfig.config() + if (config) { + loaders.unshift({ + loader: 'postcss-loader', + options: Object.assign({ sourceMap: this.sourceMap }, config) + }) + } } + } + css(loaders) { // css-loader // https://github.com/webpack-contrib/css-loader - const cssLoaderAlias = {} - cssLoaderAlias[`/${this.options.dir.assets}`] = path.join(this.options.srcDir, this.options.dir.assets) - cssLoaderAlias[`/${this.options.dir.static}`] = path.join(this.options.srcDir, this.options.dir.static) + const cssLoaderAlias = { + [`/${this.assetsDir}`]: path.join(this.srcDir, this.assetsDir), + [`/${this.staticDir}`]: path.join(this.srcDir, this.staticDir) + } loaders.unshift({ loader: 'css-loader', options: { - sourceMap, - minimize: !this.options.dev, + sourceMap: this.sourceMap, + minimize: !this.dev, importLoaders: loaders.length, // Important! alias: cssLoaderAlias } }) + } - // -- With extractCSS -- - if (this.options.build.extractCSS) { - if (!isServer) { - loaders.unshift(MiniCssExtractPlugin.loader) - if (this.options.dev) { - // css-hot-loader - // https://github.com/shepherdwind/css-hot-loader - loaders.unshift({ - loader: 'css-hot-loader', - options: { sourceMap } - }) - } + extract(loaders) { + if (this.extractCSS && !this.isServer) { + loaders.unshift(MiniCssExtractPlugin.loader) + if (this.dev) { + // css-hot-loader + // https://github.com/shepherdwind/css-hot-loader + loaders.unshift({ + loader: 'css-hot-loader', + options: { sourceMap: this.sourceMap } + }) } - } else { - // Prepare vue-style-loader - // https://github.com/vuejs/vue-style-loader - loaders.unshift({ - loader: 'vue-style-loader', - options: { sourceMap } - }) + return true } + } + + vueStyle(loaders) { + // https://github.com/vuejs/vue-style-loader + loaders.unshift({ + loader: 'vue-style-loader', + options: { sourceMap: this.sourceMap } + }) + } + + apply(ext, loaders = []) { + // Normalize loaders + loaders = this.normalize(loaders) + + // -- Configure additional loaders -- + this.styleResource(ext, loaders) + this.postcss(loaders) + this.css(loaders) + if (!this.extract(loaders)) { + this.vueStyle(loaders) + } + return loaders } }