Skip to content

Commit

Permalink
feat: apply js loaders to compiled template code when used with 2.7
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 14, 2022
1 parent 308715a commit 30464a8
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 112 deletions.
32 changes: 18 additions & 14 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

let cached

exports.resolveCompiler = function (loaderContext) {
exports.resolveCompiler = function (ctx, loaderContext) {
if (cached) {
return cached
}

const ctx = loaderContext.rootContext
// check 2.7
try {
const pkg = loadFromContext('vue/package.json', ctx)
const [major, minor] = pkg.version.split('.')
if (major === '2' && minor === '7') {
if (major === '2' && Number(minor) >= 7) {
return (cached = {
is27: true,
compiler: loadFromContext('vue/compiler-sfc', ctx),
templateCompiler: undefined
})
Expand All @@ -22,7 +22,7 @@ exports.resolveCompiler = function (loaderContext) {

return (cached = {
compiler: require('@vue/component-compiler-utils'),
templateCompiler: loadTemplateCompiler(loaderContext)
templateCompiler: loadTemplateCompiler(ctx, loaderContext)
})
}

Expand All @@ -32,19 +32,23 @@ function loadFromContext (path, ctx) {
}))
}

function loadTemplateCompiler (loaderContext) {
function loadTemplateCompiler (ctx, loaderContext) {
try {
return loadFromContext('vue-template-compiler', loaderContext.rootContext)
return loadFromContext('vue-template-compiler', ctx)
} catch (e) {
if (/version mismatch/.test(e.toString())) {
loaderContext.emitError(e)
} else {
loaderContext.emitError(
new Error(
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
`or a compatible compiler implementation must be passed via options.`
if (loaderContext) {
if (/version mismatch/.test(e.toString())) {
loaderContext.emitError(e)
} else {
loaderContext.emitError(
new Error(
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
`or a compatible compiler implementation must be passed via options.`
)
)
)
}
} else {
throw e
}
}
}
34 changes: 21 additions & 13 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ module.exports = function (source) {
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))

const { compiler, templateCompiler } = resolveCompiler(loaderContext)
const { compiler, templateCompiler } = resolveCompiler(
rootContext,
loaderContext
)

const descriptor = compiler.parse({
source,
Expand Down Expand Up @@ -98,6 +101,21 @@ module.exports = function (source) {
(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
options.hotReload !== false

// script
let scriptImport = `var script = {}`
// let isTS = false
const { script, scriptSetup } = descriptor
if (script || scriptSetup) {
// const lang = script?.lang || scriptSetup?.lang
// isTS = !!(lang && /tsx?/.test(lang))
const src = (script && !scriptSetup && script.src) || resourcePath
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport =
`import script from ${request}\n` + `export * from ${request}` // support named exports
}

// template
let templateImport = `var render, staticRenderFns`
let templateRequest
Expand All @@ -106,23 +124,13 @@ module.exports = function (source) {
const idQuery = `&id=${id}`
const scopedQuery = hasScoped ? `&scoped=true` : ``
const attrsQuery = attrsToQuery(descriptor.template.attrs)
// const tsQuery =
// options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
const request = (templateRequest = stringifyRequest(src + query))
templateImport = `import { render, staticRenderFns } from ${request}`
}

// script
let scriptImport = `var script = {}`
const { script, scriptSetup } = descriptor
if (script || scriptSetup) {
const src = (script && !scriptSetup && script.src) || resourcePath
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
const request = stringifyRequest(src + query)
scriptImport =
`import script from ${request}\n` + `export * from ${request}` // support named exports
}

// styles
let stylesCode = ``
if (descriptor.styles.length) {
Expand Down
44 changes: 25 additions & 19 deletions lib/loaders/pitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const hash = require('hash-sum')
const selfPath = require.resolve('../index')
const templateLoaderPath = require.resolve('./templateLoader')
const stylePostLoaderPath = require.resolve('./stylePostLoader')
const { resolveCompiler } = require('../compiler')

const isESLintLoader = l => /(\/|\\|@)eslint-loader/.test(l.path)
const isNullLoader = l => /(\/|\\|@)null-loader/.test(l.path)
Expand Down Expand Up @@ -90,9 +91,8 @@ module.exports.pitch = function (remainingRequest) {
const loaderStrings = []

loaders.forEach(loader => {
const identifier = typeof loader === 'string'
? loader
: (loader.path + loader.query)
const identifier =
typeof loader === 'string' ? loader : loader.path + loader.query
const request = typeof loader === 'string' ? loader : loader.request
if (!seen.has(identifier)) {
seen.set(identifier, true)
Expand All @@ -102,10 +102,11 @@ module.exports.pitch = function (remainingRequest) {
}
})

return loaderUtils.stringifyRequest(this, '-!' + [
...loaderStrings,
this.resourcePath + this.resourceQuery
].join('!'))
return loaderUtils.stringifyRequest(
this,
'-!' +
[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')
)
}

// Inject style-post-loader before css-loader for scoped CSS and trimming
Expand All @@ -129,25 +130,30 @@ module.exports.pitch = function (remainingRequest) {
// for templates: inject the template compiler & optional cache
if (query.type === `template`) {
const path = require('path')
const cacheLoader = cacheDirectory && cacheIdentifier
? [`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`]
: []
const cacheLoader =
cacheDirectory && cacheIdentifier
? [
`${require.resolve('cache-loader')}?${JSON.stringify({
// For some reason, webpack fails to generate consistent hash if we
// use absolute paths here, even though the path is only used in a
// comment. For now we have to ensure cacheDirectory is a relative path.
cacheDirectory: (path.isAbsolute(cacheDirectory)
? path.relative(process.cwd(), cacheDirectory)
: cacheDirectory
).replace(/\\/g, '/'),
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
})}`
]
: []

const preLoaders = loaders.filter(isPreLoader)
const postLoaders = loaders.filter(isPostLoader)
const { is27 } = resolveCompiler(this.rootContext, this)

const request = genRequest([
...cacheLoader,
...postLoaders,
templateLoaderPath + `??vue-loader-options`,
...(is27 ? [] : [templateLoaderPath + `??vue-loader-options`]),
...preLoaders
])
// console.log(request)
Expand Down
2 changes: 1 addition & 1 deletion lib/loaders/stylePostLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { resolveCompiler } = require('../compiler')
// for any <style scoped> selection requests initiated from within vue files.
module.exports = function (source, inMap) {
const query = qs.parse(this.resourceQuery.slice(1))
const { compiler } = resolveCompiler(this)
const { compiler } = resolveCompiler(this.rootContext, this)
const { code, map, errors } = compiler.compileStyle({
source,
filename: this.resourcePath,
Expand Down
5 changes: 4 additions & 1 deletion lib/loaders/templateLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ module.exports = function (source) {
}
)

const { compiler, templateCompiler } = resolveCompiler(loaderContext)
const { compiler, templateCompiler } = resolveCompiler(
loaderContext.rootContext,
loaderContext
)

// for vue/compiler-sfc OR @vue/component-compiler-utils
const finalOptions = {
Expand Down
77 changes: 67 additions & 10 deletions lib/plugin-webpack4.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
const { resolveCompiler } = require('./compiler')

const id = 'vue-loader-plugin'
const NS = 'vue-loader'
Expand Down Expand Up @@ -38,7 +39,7 @@ class VueLoaderPlugin {
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.`
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
)
}

Expand All @@ -58,7 +59,7 @@ class VueLoaderPlugin {
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.`
`Make sure the rule matching .vue files include vue-loader in its use.`
)
}

Expand All @@ -71,9 +72,30 @@ class VueLoaderPlugin {

// for each user rule (except 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)
const clonedRules = rules.filter(r => r !== vueRule).map(cloneRule)

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

// for each rule that matches plain .js/.ts 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)
let jsRulesForRenderFn = []
if (resolveCompiler(compiler.options.context).is27) {
const matchesJS = createMatcher(`test.js`)
// const matchesTS = createMatcher(`test.ts`)
jsRulesForRenderFn = rules
.filter(r => r !== vueRule && matchesJS(r))
.map(cloneRuleForRenderFn)
}

// global pitcher (responsible for injecting template compiler loader & CSS
// post loader)
Expand All @@ -92,6 +114,8 @@ class VueLoaderPlugin {
// replace original rules
compiler.options.module.rules = [
pitcher,
...jsRulesForRenderFn,
templateCompilerRule,
...clonedRules,
...rules
]
Expand All @@ -104,11 +128,7 @@ function createMatcher (fakeFile) {
const clone = Object.assign({}, rule)
delete clone.include
const normalized = RuleSet.normalizeRule(clone, {}, '')
return (
!rule.enforce &&
normalized.resource &&
normalized.resource(fakeFile)
)
return !rule.enforce && normalized.resource && normalized.resource(fakeFile)
}
}

Expand Down Expand Up @@ -157,5 +177,42 @@ function cloneRule (rule) {
return res
}

function cloneRuleForRenderFn (rule) {
const resource = rule.resource
const resourceQuery = rule.resourceQuery
let currentResource
const res = {
...rule,
resource: resource => {
currentResource = resource
return true
},
resourceQuery: query => {
const parsed = qs.parse(query.slice(1))
if (parsed.vue == null || parsed.type !== 'template') {
return false
}
const fakeResourcePath = `${currentResource}.${parsed.ts ? `ts` : `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
}

VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin
Loading

0 comments on commit 30464a8

Please sign in to comment.