Skip to content

Commit

Permalink
fix: support test-less oneOf rules
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Mar 23, 2018
1 parent a37d5ba commit 7208885
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 51 deletions.
5 changes: 0 additions & 5 deletions lib/loaders/noop.js

This file was deleted.

17 changes: 14 additions & 3 deletions lib/loaders/pitch.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const selfPath = require.resolve('vue-loader')
const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader')

Expand Down Expand Up @@ -31,7 +32,7 @@ module.exports.pitch = function (remainingRequest) {

// Inject style-post-loader before css-loader for scoped CSS and trimming
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) {
const afterLoaders = loaders.slice(0, cssLoaderIndex + 1).map(toLoaderString)
const beforeLoaders = loaders.slice(cssLoaderIndex + 1).map(toLoaderString)
Expand All @@ -40,6 +41,7 @@ module.exports.pitch = function (remainingRequest) {
stylePostLoaderPath,
...beforeLoaders
])
// console.log(request)
// use cjs to ensure exports from (vue-)style-loader/css-loader are intact
return `module.exports = require(${request})`
}
Expand All @@ -52,13 +54,22 @@ module.exports.pitch = function (remainingRequest) {
templateLoaderPath + `??vue-loader-options`,
...beforeLoaders
])
// console.log(request)
// the template compiler uses esm exports
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,
// both that rule and the cloned rule will match, resulting in duplicated
// loaders. Therefore it is necessary to perform a dedupe here.
const dedupedRequest = genRequest(loaders.map(toLoaderString))
return `module.exports = require(${dedupedRequest})`
const request = genRequest(loaders.map(toLoaderString))
return `module.exports = require(${request})`
}
47 changes: 19 additions & 28 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ module.exports = class VueLoaderPlugin {
const clone = Object.assign({}, rule)
delete clone.include
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]

if (!vueRule) {
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) {
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.`
)
}

Expand All @@ -42,7 +43,8 @@ module.exports = class VueLoaderPlugin {

if (vueLoaderUseIndex < 0) {
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.`
)
}

Expand Down Expand Up @@ -78,31 +80,19 @@ module.exports = class VueLoaderPlugin {
const baseRules = rawRules.filter(r => r !== vueRule)
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)

const customFallbackRule = {
loader: require.resolve('./loaders/noop'),
resourceQuery: /type=custom/
}

// 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)
// for each user rule, inject a cloned rule by checking if the rule
// matches the lang specified in the resourceQuery.
rawRules.unshift.apply(rawRules, baseRules.map((rule, i) => {
return cloneRule(rule, normalizedRules[i], normalizedVueUse)
}))

// inject global pitcher (responsible for injecting template compiler
// loader & CSS post loader)
rawRules.unshift({
loader: require.resolve('./loaders/pitch')
})

// console.log(rawRules)
}
}

Expand All @@ -116,7 +106,7 @@ function cloneRule (rule, normalizedRule, vueUse) {
const res = Object.assign({}, rule, {
test: resource => {
currentResource = resource
return true
return /\.vue$/.test(resource)
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
Expand All @@ -133,12 +123,13 @@ function cloneRule (rule, normalizedRule, vueUse) {
}
return true
},
use: [
...(normalizedRule.use || []).map(cleanUse),
...rule.oneOf ? [] : vueUse
]
use: (normalizedRule.use || []).map(cleanUse)
})

if (!res.use.length) {
delete res.use
}

// delete shorthand since we have normalized use
delete res.loader
delete res.options
Expand Down
54 changes: 39 additions & 15 deletions test/edgeCases.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ const {

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 => {
mockBundleAndRun({
entry: 'basic.vue',
Expand All @@ -15,20 +32,27 @@ test('vue rule with include', done => {
loader: 'vue-loader'
}
}
}, ({ window, module, rawModule }) => {
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')
}, res => assertComponent(res, done))
})

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('test-less oneOf rules', done => {
mockBundleAndRun({
entry: 'basic.vue',
modify: config => {
config.module.rules = [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
oneOf: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
}
]
}
]
}
}, res => assertComponent(res, done))
})

0 comments on commit 7208885

Please sign in to comment.