feat: upgrade to vue-loader 15

BREAKING CHANGE: the "vueLoader" option has been removed. To modify vue-loader
options, use chainWebpack then `config.module.rule(vue).use(vue-loader).tap()`.
vue-loader has been upgraded to v15 and expects different options from v14.
This commit is contained in:
Evan You
2018-05-04 18:57:58 -04:00
parent 8dbe262174
commit f5c0f58673
12 changed files with 126 additions and 333 deletions
-22
View File
@@ -42,27 +42,5 @@ module.exports = (api, {
jsRule
.use('babel-loader')
.loader('babel-loader')
webpackConfig.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.loaders = options.loaders || {}
options.loaders.js = [
{
loader: 'cache-loader',
options: { cacheDirectory }
}
]
if (useThreads) {
options.loaders.js.push({
loader: 'thread-loader'
})
}
options.loaders.js.push({
loader: 'babel-loader'
})
return options
})
})
}
+1
View File
@@ -7,6 +7,7 @@ module.exports = (api, { lintOnSave }) => {
.pre()
.exclude
.add(/node_modules/)
.add(require('path').dirname(require.resolve('@vue/cli-service')))
.end()
.test(/\.(vue|(j|t)sx?)$/)
.use('eslint-loader')
+11 -34
View File
@@ -16,42 +16,13 @@ module.exports = (api, {
.extensions
.merge(['.ts', '.tsx'])
const tsRule = config.module
.rule('ts')
.test(/\.tsx?$/)
const vueLoader = config.module
.rule('vue')
.use('vue-loader')
const tsRule = config.module.rule('ts').test(/\.ts$/)
const tsxRule = config.module.rule('tsx').test(/\.tsx$/)
// add a loader to both *.ts & vue<lang="ts">
const addLoader = loader => {
const use = tsRule
.use(loader.loader)
.loader(loader.loader)
if (loader.options) {
use.options(loader.options)
}
vueLoader.tap(options => {
options.loaders = options.loaders || {}
options.loaders.ts = options.loaders.ts || []
options.loaders.ts.push(loader)
options.loaders.tsx = options.loaders.tsx || []
if (loader.loader === 'ts-loader') {
// for TSX need to append tsx suffix
options.loaders.tsx.push({
loader: 'ts-loader',
options: {
transpileOnly: true,
appendTsxSuffixTo: [/\.vue$/],
happyPackMode: useThreads
}
})
} else {
options.loaders.tsx.push(loader)
}
return options
})
const addLoader = ({ loader, options }) => {
tsRule.use(loader).loader(loader).options(options)
tsxRule.use(loader).loader(loader).options(options)
}
addLoader({
@@ -79,6 +50,12 @@ module.exports = (api, {
happyPackMode: useThreads
}
})
// make sure to append TSX suffix
tsxRule.use('ts-loader').loader('ts-loader').tap(options => {
delete options.appendTsSuffixTo
options.appendTsxSuffixTo = [/\.vue$/]
return options
})
} else {
// Experimental: compile TS with babel so that it can leverage
// preset-env for auto-detected polyfills based on browserslists config.
@@ -60,15 +60,6 @@ module.exports = function createConfigPlugin (context, entry, asLib) {
presets: [require.resolve('@vue/babel-preset-app')]
}
// set inline vue-loader options
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.loaders.js[1].options = babelOptions
return options
})
// set inline babel options
config.module
.rule('js')
+15 -40
View File
@@ -22,12 +22,14 @@ const genConfig = (pkg = {}, env) => {
}
const findRule = (config, lang) => config.module.rules.find(rule => {
const test = rule.test.toString().replace(/\\/g, '')
return test.indexOf(`${lang}$`) > -1
return rule.test.test(`.${lang}`)
})
const findLoaders = (config, lang) => {
const rule = findRule(config, lang)
if (!rule) {
throw new Error(`rule not found for ${lang}`)
}
return rule.use.map(({ loader }) => loader.replace(/-loader$/, ''))
}
@@ -37,49 +39,21 @@ const findOptions = (config, lang, _loader) => {
return use.options
}
const findUsesForVue = (config, lang) => {
const vueOptions = findOptions(config, 'vue', 'vue')
return vueOptions.loaders[lang]
}
const findLoadersForVue = (config, lang) => {
return findUsesForVue(config, lang).map(({ loader }) => loader.replace(/-loader$/, ''))
}
const findOptionsForVue = (config, lang, _loader) => {
const uses = findUsesForVue(config, lang)
const use = uses.find(({ loader }) => `${_loader}-loader` === loader)
return use.options
}
const expectedCssLoaderModulesOptions = {
importLoaders: 1,
localIdentName: `[name]_[local]__[hash:base64:5]`,
minimize: false,
sourceMap: false,
modules: true
}
test('default loaders', () => {
const config = genConfig({ postcss: {}})
LANGS.forEach(lang => {
const loader = lang === 'css' ? [] : LOADERS[lang]
expect(findLoaders(config, lang)).toEqual(['vue-style', 'css', 'postcss'].concat(loader))
// vue-loader loaders should not include postcss because it's built-in
expect(findLoadersForVue(config, lang)).toEqual(['vue-style', 'css'].concat(loader))
// assert css-loader options
expect(findOptions(config, lang, 'css')).toEqual({
minimize: false,
sourceMap: false
sourceMap: false,
importLoaders: lang === 'css' ? 1 : 2
})
// files ending in .module.lang
expect(findOptions(config, `module.${lang}`, 'css')).toEqual(expectedCssLoaderModulesOptions)
})
// sass indented syntax
expect(findOptions(config, 'sass', 'sass')).toEqual({ indentedSyntax: true, sourceMap: false })
expect(findOptionsForVue(config, 'sass', 'sass')).toEqual({ indentedSyntax: true, sourceMap: false })
})
test('production defaults', () => {
@@ -88,10 +62,10 @@ test('production defaults', () => {
LANGS.forEach(lang => {
const loader = lang === 'css' ? [] : LOADERS[lang]
expect(findLoaders(config, lang)).toEqual([extractLoaderPath, 'vue-style', 'css', 'postcss'].concat(loader))
expect(findLoadersForVue(config, lang)).toEqual([extractLoaderPath, 'vue-style', 'css'].concat(loader))
expect(findOptions(config, lang, 'css')).toEqual({
minimize: true,
sourceMap: false
sourceMap: false,
importLoaders: lang === 'css' ? 1 : 2
})
})
})
@@ -105,7 +79,13 @@ test('css.modules', () => {
}
})
LANGS.forEach(lang => {
expect(findOptions(config, lang, 'css')).toEqual(expectedCssLoaderModulesOptions)
expect(findOptions(config, lang, 'css')).toEqual({
importLoaders: lang === 'css' ? 0 : 1, // no postcss-loader
localIdentName: `[name]_[local]_[hash:base64:5]`,
minimize: false,
sourceMap: false,
modules: true
})
})
})
@@ -120,7 +100,6 @@ test('css.extract', () => {
const extractLoaderPath = require.resolve('extract-text-webpack-plugin/dist/loader')
LANGS.forEach(lang => {
expect(findLoaders(config, lang)).not.toContain(extractLoaderPath)
expect(findLoadersForVue(config, lang)).not.toContain(extractLoaderPath)
})
})
@@ -137,8 +116,6 @@ test('css.sourceMap', () => {
expect(findOptions(config, lang, 'css').sourceMap).toBe(true)
expect(findOptions(config, lang, 'postcss').sourceMap).toBe(true)
expect(findOptions(config, lang, LOADERS[lang]).sourceMap).toBe(true)
expect(findOptionsForVue(config, lang, 'css').sourceMap).toBe(true)
expect(findOptionsForVue(config, lang, LOADERS[lang]).sourceMap).toBe(true)
})
})
@@ -172,9 +149,7 @@ test('css.loaderOptions', () => {
})
expect(findOptions(config, 'scss', 'sass')).toEqual({ data, sourceMap: false })
expect(findOptionsForVue(config, 'scss', 'sass')).toEqual({ data, sourceMap: false })
expect(findOptions(config, 'sass', 'sass')).toEqual({ data, indentedSyntax: true, sourceMap: false })
expect(findOptionsForVue(config, 'sass', 'sass')).toEqual({ data, indentedSyntax: true, sourceMap: false })
})
test('skip postcss-loader if no postcss config found', () => {
@@ -41,12 +41,13 @@ module.exports = (api, options) => {
} = require('@vue/cli-shared-utils')
log()
const mode = api.service.mode
if (args.target === 'app') {
logWithSpinner(`Building for ${args.mode}...`)
logWithSpinner(`Building for ${mode}...`)
} else {
const buildMode = buildModes[args.target]
if (buildMode) {
logWithSpinner(`Building for ${args.mode} as ${buildMode}...`)
logWithSpinner(`Building for ${mode} as ${buildMode}...`)
} else {
throw new Error(`Unknown build target: ${args.target}`)
}
@@ -22,8 +22,8 @@ const createElement = (prefix, component, file, async) => {
const { camelName, kebabName } = exports.fileToComponentName(prefix, component)
return async
? `window.customElements.define('${kebabName}', wrap(Vue, () => import('~root/${file}')))\n`
: `import ${camelName} from '~root/${file}'\n` +
? `window.customElements.define('${kebabName}', wrap(Vue, () => import('~root/${file}?shadow')))\n`
: `import ${camelName} from '~root/${file}?shadow'\n` +
`window.customElements.define('${kebabName}', wrap(Vue, ${camelName}))\n`
}
@@ -52,7 +52,7 @@ import wrap from '@vue/web-component-wrapper'
// runtime shared by every component chunk
import 'css-loader/lib/css-base'
import 'vue-style-loader/lib/addStylesShadow'
import 'vue-loader/lib/runtime/component-normalizer'
import 'vue-loader/lib/runtime/componentNormalizer'
;(() => {
let i
+25 -2
View File
@@ -37,14 +37,26 @@ module.exports = (api, options) => {
webpackConfig.module
.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
// js is handled by cli-plugin-bable
// js is handled by cli-plugin-bable ---------------------------------------
// vue-loader --------------------------------------------------------------
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('vue-loader')
.loader('vue-loader')
.options(Object.assign({}, options.vueLoader))
.options({
compilerOpitons: {
preserveWhitespace: false
}
})
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader/lib/plugin'))
// static assets -----------------------------------------------------------
webpackConfig.module
.rule('images')
@@ -87,6 +99,17 @@ module.exports = (api, options) => {
name: `fonts/[name].[hash:8].[ext]`
})
// Other common pre-processors ---------------------------------------------
webpackConfig.module
.rule('pug')
.test(/\.pug$/)
.use('pug-plain-loader')
.loader('pug-plain-loader')
.end()
// shims
webpackConfig.node
.merge({
// prevent webpack from injecting useless setImmediate polyfill because Vue
+67 -75
View File
@@ -11,19 +11,21 @@ const findExisting = (context, files) => {
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
const CSSLoaderResolver = require('../webpack/CSSLoaderResolver')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const {
extract = true,
modules = false,
sourceMap = false,
localIdentName = '[name]_[local]_[hash:base64:5]',
loaderOptions = {}
} = options.css || {}
const shadowMode = !!process.env.VUE_CLI_CSS_SHADOW_MODE
const isProd = process.env.NODE_ENV === 'production'
const defaultOptions = {
extract: true,
modules: false,
sourceMap: false,
loaderOptions: {},
localIdentName: '[name]_[local]__[hash:base64:5]'
}
const userOptions = Object.assign(defaultOptions, options.css || {})
const extract = isProd && userOptions.extract !== false
const shouldExtract = isProd && extract !== false && !shadowMode
const extractOptions = Object.assign({
filename: `css/[name].[contenthash:8].css`,
allChunks: true
}, extract && typeof extract === 'object' ? extract : {})
// check if the project has a valid postcss config
// if it doesn't, don't use postcss-loader for direct style imports
@@ -36,82 +38,72 @@ module.exports = (api, options) => {
'.postcssrc.json'
]))
const baseOptions = Object.assign({}, userOptions, {
extract,
minimize: isProd,
postcss: hasPostCSSConfig
})
function createCSSRule (lang, test, loader, options) {
const normalRule = webpackConfig.module.rule(lang).test(test).resourceQuery(q => !/module/.test(q))
applyLoaders(normalRule, modules)
const resolver = new CSSLoaderResolver(baseOptions)
// rules for <style lang="module">
const modulesRule = webpackConfig.module.rule(lang + '-modules').test(test).resourceQuery(/module/)
applyLoaders(modulesRule, true)
// apply css loaders for vue-loader
webpackConfig.module
.rule('vue')
.use('vue-loader')
.tap(options => {
// ensure user injected vueLoader options take higher priority
options.loaders = Object.assign(resolver.vue(), options.loaders)
options.cssSourceMap = !!userOptions.cssSourceMap
options.cssModules = Object.assign({
localIdentName: baseOptions.localIdentName
}, options.cssModules)
return options
function applyLoaders (rule, modules) {
if (shouldExtract) {
rule
.use('extract-css-loader')
.loader(require.resolve('extract-text-webpack-plugin/dist/loader'))
.options({ omit: 1, remove: true })
}
rule.use('vue-style-loader').loader('vue-style-loader').options({
sourceMap,
shadowMode
})
// apply css loaders for standalone style files outside vue-loader
const langs = ['css', 'stylus', 'styl', 'sass', 'scss', 'less']
for (const lang of langs) {
const rule = resolver[lang]()
const context = webpackConfig.module
.rule(lang)
.test(rule.test)
.include
.add(filepath => {
// Not ends with `.module.xxx`
return !/\.module\.[a-z]+$/.test(filepath)
const cssLoaderOptions = {
minimize: isProd,
sourceMap,
importLoaders: hasPostCSSConfig + !!loader // boolean + boolean
}
if (modules) {
Object.assign(cssLoaderOptions, {
modules,
localIdentName
})
.end()
}
rule.use('css-loader')
.loader('css-loader')
.options(cssLoaderOptions)
rule.use.forEach(use => {
context
.use(use.loader)
.loader(use.loader)
.options(use.options)
})
if (hasPostCSSConfig) {
rule.use('postcss-loader').loader('postcss-loader').options({
// TODO: use config value after https://github.com/postcss/postcss-loader/pull/361 is merged
sourceMap: true
})
}
if (loader) {
rule.use(loader).loader(loader).options(Object.assign({
sourceMap
}, options))
}
}
}
// handle cssModules for *.module.js
const cssModulesResolver = new CSSLoaderResolver(Object.assign({}, baseOptions, {
modules: true
}))
const cssModulesLangs = langs.map(lang => [lang, new RegExp(`\\.module\\.${lang}$`)])
for (const cssModulesLang of cssModulesLangs) {
const [lang, test] = cssModulesLang
const rule = cssModulesResolver[lang](test)
const context = webpackConfig.module
.rule(`${lang}-module`)
.test(rule.test)
rule.use.forEach(use => {
context
.use(use.loader)
.loader(use.loader)
.options(use.options)
})
}
createCSSRule('css', /\.css$/)
createCSSRule('scss', /\.scss$/, 'sass-loader', loaderOptions.sass)
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign({
indentedSyntax: true
}, loaderOptions.sass))
createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
preferPathResolver: 'webpack'
}, loaderOptions.stylus))
// inject CSS extraction plugin
if (extract) {
const extractOptions = userOptions.extract && typeof userOptions.extract === 'object'
? userOptions.extract
: {}
if (shouldExtract) {
webpackConfig
.plugin('extract-css')
.use(ExtractTextPlugin, [Object.assign({
filename: `css/[name].[contenthash:8].css`,
allChunks: true
}, extractOptions)])
.use(require('extract-text-webpack-plugin'), [extractOptions])
}
})
}
-10
View File
@@ -6,7 +6,6 @@ const schema = createSchema(joi => joi.object({
compiler: joi.boolean(),
transpileDependencies: joi.array(),
productionSourceMap: joi.boolean(),
vueLoader: joi.object(),
parallel: joi.boolean(),
devServer: joi.object(),
dll: joi.alternatives().try(
@@ -59,15 +58,6 @@ exports.defaults = () => ({
// deps to transpile
transpileDependencies: [/* string or regex */],
// vue-loader options
vueLoader: {
preserveWhitespace: false,
template: {
// for pug
doctype: 'html'
}
},
// sourceMap for production build?
productionSourceMap: true,
@@ -1,135 +0,0 @@
/**
* https://github.com/egoist/webpack-handle-css-loader
* The MIT License (MIT)
* Copyright (c) EGOIST <0x142857@gmail.com> (github.com/egoist)
*
* Modified by Yuxi Evan You
*/
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = class CSSLoaderResolver {
/**
* @param {Object} options
* @param {boolean} [options.sourceMap=undefined] Enable sourcemaps.
* @param {boolean} [options.modules=undefined] Enable CSS modules.
* @param {string} [options.localIdentName='[name]_[local]__[hash:base64:5]'] Customizes CSS modules localIdentName.
* @param {boolean} [options.extract=undefined] Extract CSS.
* @param {boolean} [options.minimize=undefined] Minimize CSS.
* @param {boolean} [options.postcss=undefined] Enable postcss-loader.
* @param {Object} [options.loaderOptions={}] Options to pass on to loaders.
*/
constructor ({
sourceMap,
modules,
localIdentName,
extract,
minimize,
postcss,
loaderOptions
} = {}) {
this.cssLoader = 'css-loader'
this.fallbackLoader = 'vue-style-loader'
this.sourceMap = sourceMap
this.extract = extract && !process.env.VUE_CLI_CSS_SHADOW_MODE
this.minimize = minimize
this.modules = modules
this.localIdentName = localIdentName
this.postcss = postcss
this.loaderOptions = loaderOptions || {}
}
getLoader (test, loader, options = {}) {
const cssLoaderOptions = {
sourceMap: this.sourceMap,
minimize: this.minimize
}
if (this.modules) {
cssLoaderOptions.modules = true
cssLoaderOptions.importLoaders = 1
cssLoaderOptions.localIdentName = this.localIdentName
}
if (loader === 'css') {
Object.assign(cssLoaderOptions, options)
}
const use = [{
loader: this.cssLoader,
options: cssLoaderOptions
}]
if (loader !== 'postcss' && this.postcss !== false) {
use.push({
loader: 'postcss-loader',
options: {
sourceMap: this.sourceMap
}
})
}
if (loader && loader !== 'css') {
use.push({
loader: loader + '-loader',
options: Object.assign({}, this.loaderOptions[loader] || {}, options, {
sourceMap: this.sourceMap
})
})
}
return {
test,
use: this.extract ? ExtractTextPlugin.extract({
use,
fallback: this.fallbackLoader
}) : [{
loader: this.fallbackLoader,
options: {
shadowMode: !!process.env.VUE_CLI_CSS_SHADOW_MODE,
sourceMap: this.sourceMap
}
}, ...use]
}
}
css (test = /\.css$/) {
return this.getLoader(test, 'css')
}
sass (test = /\.sass$/) {
return this.getLoader(test, 'sass', {
indentedSyntax: true
})
}
scss (test = /\.scss$/) {
return this.getLoader(test, 'sass')
}
less (test = /\.less$/) {
return this.getLoader(test, 'less')
}
styl (test = /\.styl$/) {
return this.getLoader(test, 'stylus')
}
stylus (test = /\.stylus$/) {
return this.getLoader(test, 'stylus')
}
vue () {
const originalPostcss = this.postcss
const originalModules = this.modules
this.postcss = false
this.modules = false
const loaders = {}
for (const lang of ['css', 'sass', 'scss', 'less', 'stylus', 'styl']) {
loaders[lang] = this[lang]().use
}
this.postcss = originalPostcss
this.modules = originalModules
return loaders
}
}
+1 -1
View File
@@ -57,7 +57,7 @@
"thread-loader": "^1.1.5",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-loader": "^1.0.1",
"vue-loader": "^14.2.1",
"vue-loader": "^15.0.8",
"vue-template-compiler": "^2.5.16",
"webpack": "^3.10.0",
"webpack-chain": "^4.6.0",