Skip to content

Commit

Permalink
feat: support webpack 5
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Mar 26, 2020
1 parent 36af01c commit 552bcb7
Show file tree
Hide file tree
Showing 3 changed files with 519 additions and 200 deletions.
209 changes: 9 additions & 200 deletions src/plugin.ts
@@ -1,203 +1,12 @@
import qs from 'querystring'
import * as webpack from 'webpack'
import { VueLoaderOptions } from './'
const RuleSet = require('webpack/lib/RuleSet')

const id = 'vue-loader-plugin'
const NS = 'vue-loader'

class VueLoaderPlugin implements webpack.Plugin {
static NS = NS

apply(compiler: webpack.Compiler) {
// inject NS for plugin installation check in the main loader
compiler.hooks.compilation.tap(id, compilation => {
compilation.hooks.normalModuleLoader.tap(id, (loaderContext: any) => {
loaderContext[NS] = true
})
})

const rawRules = compiler.options.module!.rules
// use webpack's RuleSet utility to normalize user rules
const rules = new RuleSet(rawRules).rules as webpack.RuleSetRule[]

// find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
}
const vueRule = rules[vueRuleIndex]

if (!vueRule) {
throw new Error(
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
)
}

if (vueRule.oneOf) {
throw new Error(
`[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
)
}

// get the normlized "use" for vue files
const vueUse = vueRule.use as webpack.RuleSetLoader[]
// get vue-loader options
const vueLoaderUseIndex = vueUse.findIndex(u => {
return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader || '')
})

if (vueLoaderUseIndex < 0) {
throw new Error(
`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
`Make sure the rule matching .vue files include vue-loader in its use.`
)
}

const vueLoaderUse = vueUse[vueLoaderUseIndex]
const vueLoaderOptions = (vueLoaderUse.options =
vueLoaderUse.options || {}) as VueLoaderOptions

// for each user rule (expect the vue rule), create a cloned rule
// that targets the corresponding language blocks in *.vue files.
const clonedRules = rules.filter(r => r !== vueRule).map(cloneRule)

// rule for template compiler
const templateCompilerRule = {
loader: require.resolve('./templateLoader'),
test: /\.vue$/,
resourceQuery: (query: string) => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null && parsed.type === 'template'
},
options: vueLoaderOptions
}

// for each rule that matches plain .js files, also create a clone and
// match it against the compiled template code inside *.vue files, so that
// compiled vue render functions receive the same treatment as user code
// (mostly babel)
const matchesJS = createMatcher(`test.js`)
const jsRulesForRenderFn = rules
.filter(r => r !== vueRule && matchesJS(r))
.map(cloneRuleForRenderFn)

// pitcher for block requests (for injecting stylePostLoader and deduping
// loaders matched for src imports)
const pitcher = {
loader: require.resolve('./pitcher'),
resourceQuery: (query: string) => {
const parsed = qs.parse(query.slice(1))
return parsed.vue != null
}
}

// replace original rules
compiler.options.module!.rules = [
pitcher,
...jsRulesForRenderFn,
templateCompilerRule,
...clonedRules,
...rules
]
}
}

function createMatcher(fakeFile: string) {
return (rule: webpack.RuleSetRule) => {
// #1201 we need to skip the `include` check when locating the vue rule
const clone = Object.assign({}, rule)
delete clone.include
const normalized = RuleSet.normalizeRule(clone, {}, '')
return !rule.enforce && normalized.resource && normalized.resource(fakeFile)
}
}

function cloneRule(rule: webpack.RuleSetRule) {
const resource = rule.resource as Function
const resourceQuery = rule.resourceQuery as Function
// Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access
// it in `resourceQuery`. This ensures when we use the normalized rule's
// resource check, include/exclude are matched correctly.
let currentResource: string
const res = {
...rule,
resource: {
test: (resource: string) => {
currentResource = resource
return true
}
},
resourceQuery: (query: string) => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) {
return false
}
if (resource && parsed.lang == null) {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.lang}`
if (resource && !resource(fakeResourcePath)) {
return false
}
if (resourceQuery && !resourceQuery(query)) {
return false
}
return true
}
}

if (rule.rules) {
res.rules = rule.rules.map(cloneRule)
}

if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRule)
}

return res
}

function cloneRuleForRenderFn(rule: webpack.RuleSetRule) {
const resource = rule.resource as Function
const resourceQuery = rule.resourceQuery as Function
let currentResource: string
const res = {
...rule,
resource: {
test: (resource: string) => {
currentResource = resource
return true
}
},
resourceQuery: (query: string) => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null || parsed.type !== 'template') {
return false
}
const fakeResourcePath = `${currentResource}.js`
if (resource && !resource(fakeResourcePath)) {
return false
}
if (resourceQuery && !resourceQuery(query)) {
return false
}
return true
}
}

if (rule.rules) {
res.rules = rule.rules.map(cloneRuleForRenderFn)
}

if (rule.oneOf) {
res.oneOf = rule.oneOf.map(cloneRuleForRenderFn)
}

return res
const webpack = require('webpack')
let VueLoaderPlugin = null

if (webpack.version && webpack.version[0] > 4) {
// webpack5 and upper
VueLoaderPlugin = require('./pluginWebpack5')
} else {
// webpack4 and lower
VueLoaderPlugin = require('./pluginWebpack4')
}

module.exports = VueLoaderPlugin

0 comments on commit 552bcb7

Please sign in to comment.