Commit 7208885a authored by Evan You's avatar Evan You

fix: support test-less oneOf rules

parent a37d5ba5
// a noop loader that matches custom blocks with no
// matching rules.
module.exports = function noop () {
return ``
}
const qs = require('querystring') const qs = require('querystring')
const loaderUtils = require('loader-utils') const loaderUtils = require('loader-utils')
const selfPath = require.resolve('vue-loader')
const templateLoaderPath = require.resolve('./templateLoader') const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader') const stylePostLoaderPath = require.resolve('./stylePostLoader')
...@@ -31,7 +32,7 @@ module.exports.pitch = function (remainingRequest) { ...@@ -31,7 +32,7 @@ module.exports.pitch = function (remainingRequest) {
// Inject style-post-loader before css-loader for scoped CSS and trimming // Inject style-post-loader before css-loader for scoped CSS and trimming
if (query.type === `style`) { if (query.type === `style`) {
const cssLoaderIndex = loaders.findIndex(l => /(\/|\\)css-loader/.test(l.request)) const cssLoaderIndex = loaders.findIndex(l => /(\/|\\)css-loader/.test(l.path))
if (cssLoaderIndex) { if (cssLoaderIndex) {
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1).map(toLoaderString) const afterLoaders = loaders.slice(0, cssLoaderIndex + 1).map(toLoaderString)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1).map(toLoaderString) const beforeLoaders = loaders.slice(cssLoaderIndex + 1).map(toLoaderString)
...@@ -40,6 +41,7 @@ module.exports.pitch = function (remainingRequest) { ...@@ -40,6 +41,7 @@ module.exports.pitch = function (remainingRequest) {
stylePostLoaderPath, stylePostLoaderPath,
...beforeLoaders ...beforeLoaders
]) ])
// console.log(request)
// use cjs to ensure exports from (vue-)style-loader/css-loader are intact // use cjs to ensure exports from (vue-)style-loader/css-loader are intact
return `module.exports = require(${request})` return `module.exports = require(${request})`
} }
...@@ -52,13 +54,22 @@ module.exports.pitch = function (remainingRequest) { ...@@ -52,13 +54,22 @@ module.exports.pitch = function (remainingRequest) {
templateLoaderPath + `??vue-loader-options`, templateLoaderPath + `??vue-loader-options`,
...beforeLoaders ...beforeLoaders
]) ])
// console.log(request)
// the template compiler uses esm exports // the template compiler uses esm exports
return `export * from ${request}` return `export * from ${request}`
} }
// if a custom block has no other matching loader other than vue-loader itself,
// we should ignore it
if (query.type === `custom` &&
loaders.length === 1 &&
loaders[0].path === selfPath) {
return ``
}
// When the user defines a rule that has only resourceQuery but no test, // When the user defines a rule that has only resourceQuery but no test,
// both that rule and the cloned rule will match, resulting in duplicated // both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here. // loaders. Therefore it is necessary to perform a dedupe here.
const dedupedRequest = genRequest(loaders.map(toLoaderString)) const request = genRequest(loaders.map(toLoaderString))
return `module.exports = require(${dedupedRequest})` return `module.exports = require(${request})`
} }
...@@ -15,19 +15,20 @@ module.exports = class VueLoaderPlugin { ...@@ -15,19 +15,20 @@ module.exports = class VueLoaderPlugin {
const clone = Object.assign({}, rule) const clone = Object.assign({}, rule)
delete clone.include delete clone.include
const normalized = RuleSet.normalizeRule(clone) const normalized = RuleSet.normalizeRule(clone)
return !rule.enforce && normalized.resource(`foo.vue`) return !rule.enforce && normalized.resource && normalized.resource(`foo.vue`)
}) })
const vueRule = rawRules[vueRuleIndex] const vueRule = rawRules[vueRuleIndex]
if (!vueRule) { if (!vueRule) {
throw new Error( throw new Error(
`VueLoaderPlugin Error: no matching rule for vue files are found.` `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
`Make sure there is at least one root-level rule that matches .vue files.`
) )
} }
if (vueRule.oneOf) { if (vueRule.oneOf) {
throw new Error( throw new Error(
`vue-loader 15 currently does not support vue rules with oneOf.` `[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
) )
} }
...@@ -42,7 +43,8 @@ module.exports = class VueLoaderPlugin { ...@@ -42,7 +43,8 @@ module.exports = class VueLoaderPlugin {
if (vueLoaderUseIndex < 0) { if (vueLoaderUseIndex < 0) {
throw new Error( throw new Error(
`VueLoaderPlugin Error: no matching use for vue-loader is found.` `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
`Make sure the rule matching .vue files include vue-loader in its use.`
) )
} }
...@@ -78,31 +80,19 @@ module.exports = class VueLoaderPlugin { ...@@ -78,31 +80,19 @@ module.exports = class VueLoaderPlugin {
const baseRules = rawRules.filter(r => r !== vueRule) const baseRules = rawRules.filter(r => r !== vueRule)
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule) const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)
const customFallbackRule = { // for each user rule, inject a cloned rule by checking if the rule
loader: require.resolve('./loaders/noop'), // matches the lang specified in the resourceQuery.
resourceQuery: /type=custom/ rawRules.unshift.apply(rawRules, baseRules.map((rule, i) => {
} return cloneRule(rule, normalizedRules[i], normalizedVueUse)
}))
// construct a new rule for vue file, with oneOf containing
// multiple rules with dynamic resourceQuery functions that applies to
// different language blocks in a raw vue file.
const constructedRule = {
test: /\.vue$/,
oneOf: baseRules.map((rule, i) => {
// for each user rule, create a cloned rule by checking if the rule
// matches the lang specified in the resourceQuery.
return cloneRule(rule, normalizedRules[i], normalizedVueUse)
}).concat(customFallbackRule, vueRule)
}
// replace the original vue rule with our new constructed rule.
rawRules.splice(vueRuleIndex, 1, constructedRule)
// inject global pitcher (responsible for injecting template compiler // inject global pitcher (responsible for injecting template compiler
// loader & CSS post loader) // loader & CSS post loader)
rawRules.unshift({ rawRules.unshift({
loader: require.resolve('./loaders/pitch') loader: require.resolve('./loaders/pitch')
}) })
// console.log(rawRules)
} }
} }
...@@ -116,7 +106,7 @@ function cloneRule (rule, normalizedRule, vueUse) { ...@@ -116,7 +106,7 @@ function cloneRule (rule, normalizedRule, vueUse) {
const res = Object.assign({}, rule, { const res = Object.assign({}, rule, {
test: resource => { test: resource => {
currentResource = resource currentResource = resource
return true return /\.vue$/.test(resource)
}, },
resourceQuery: query => { resourceQuery: query => {
const parsed = qs.parse(query.slice(1)) const parsed = qs.parse(query.slice(1))
...@@ -133,12 +123,13 @@ function cloneRule (rule, normalizedRule, vueUse) { ...@@ -133,12 +123,13 @@ function cloneRule (rule, normalizedRule, vueUse) {
} }
return true return true
}, },
use: [ use: (normalizedRule.use || []).map(cleanUse)
...(normalizedRule.use || []).map(cleanUse),
...rule.oneOf ? [] : vueUse
]
}) })
if (!res.use.length) {
delete res.use
}
// delete shorthand since we have normalized use // delete shorthand since we have normalized use
delete res.loader delete res.loader
delete res.options delete res.options
......
...@@ -5,6 +5,23 @@ const { ...@@ -5,6 +5,23 @@ const {
const normalizeNewline = require('normalize-newline') const normalizeNewline = require('normalize-newline')
const assertComponent = ({ window, module }, done) => {
const vnode = mockRender(module, {
msg: 'hi'
})
// <h2 class="red">{{msg}}</h2>
expect(vnode.tag).toBe('h2')
expect(vnode.data.staticClass).toBe('red')
expect(vnode.children[0].text).toBe('hi')
expect(module.data().msg).toContain('Hello from Component A!')
let style = window.document.querySelector('style').textContent
style = normalizeNewline(style)
expect(style).toContain('comp-a h2 {\n color: #f00;\n}')
done()
}
test('vue rule with include', done => { test('vue rule with include', done => {
mockBundleAndRun({ mockBundleAndRun({
entry: 'basic.vue', entry: 'basic.vue',
...@@ -15,20 +32,27 @@ test('vue rule with include', done => { ...@@ -15,20 +32,27 @@ test('vue rule with include', done => {
loader: 'vue-loader' loader: 'vue-loader'
} }
} }
}, ({ window, module, rawModule }) => { }, res => assertComponent(res, done))
const vnode = mockRender(module, { })
msg: 'hi'
})
// <h2 class="red">{{msg}}</h2>
expect(vnode.tag).toBe('h2')
expect(vnode.data.staticClass).toBe('red')
expect(vnode.children[0].text).toBe('hi')
expect(module.data().msg).toContain('Hello from Component A!') test('test-less oneOf rules', done => {
let style = window.document.querySelector('style').textContent mockBundleAndRun({
style = normalizeNewline(style) entry: 'basic.vue',
expect(style).toContain('comp-a h2 {\n color: #f00;\n}') modify: config => {
done() config.module.rules = [
}) {
test: /\.vue$/,
loader: 'vue-loader'
},
{
oneOf: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
}
]
}
]
}
}, res => assertComponent(res, done))
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment