Skip to content

Commit 30464a8

Browse files
committed
feat: apply js loaders to compiled template code when used with 2.7
1 parent 308715a commit 30464a8

9 files changed

+252
-112
lines changed

Diff for: lib/compiler.js

+18-14
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
let cached
44

5-
exports.resolveCompiler = function (loaderContext) {
5+
exports.resolveCompiler = function (ctx, loaderContext) {
66
if (cached) {
77
return cached
88
}
99

10-
const ctx = loaderContext.rootContext
1110
// check 2.7
1211
try {
1312
const pkg = loadFromContext('vue/package.json', ctx)
1413
const [major, minor] = pkg.version.split('.')
15-
if (major === '2' && minor === '7') {
14+
if (major === '2' && Number(minor) >= 7) {
1615
return (cached = {
16+
is27: true,
1717
compiler: loadFromContext('vue/compiler-sfc', ctx),
1818
templateCompiler: undefined
1919
})
@@ -22,7 +22,7 @@ exports.resolveCompiler = function (loaderContext) {
2222

2323
return (cached = {
2424
compiler: require('@vue/component-compiler-utils'),
25-
templateCompiler: loadTemplateCompiler(loaderContext)
25+
templateCompiler: loadTemplateCompiler(ctx, loaderContext)
2626
})
2727
}
2828

@@ -32,19 +32,23 @@ function loadFromContext (path, ctx) {
3232
}))
3333
}
3434

35-
function loadTemplateCompiler (loaderContext) {
35+
function loadTemplateCompiler (ctx, loaderContext) {
3636
try {
37-
return loadFromContext('vue-template-compiler', loaderContext.rootContext)
37+
return loadFromContext('vue-template-compiler', ctx)
3838
} catch (e) {
39-
if (/version mismatch/.test(e.toString())) {
40-
loaderContext.emitError(e)
41-
} else {
42-
loaderContext.emitError(
43-
new Error(
44-
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
45-
`or a compatible compiler implementation must be passed via options.`
39+
if (loaderContext) {
40+
if (/version mismatch/.test(e.toString())) {
41+
loaderContext.emitError(e)
42+
} else {
43+
loaderContext.emitError(
44+
new Error(
45+
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
46+
`or a compatible compiler implementation must be passed via options.`
47+
)
4648
)
47-
)
49+
}
50+
} else {
51+
throw e
4852
}
4953
}
5054
}

Diff for: lib/index.js

+21-13
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ module.exports = function (source) {
5252
const context = rootContext || process.cwd()
5353
const sourceRoot = path.dirname(path.relative(context, resourcePath))
5454

55-
const { compiler, templateCompiler } = resolveCompiler(loaderContext)
55+
const { compiler, templateCompiler } = resolveCompiler(
56+
rootContext,
57+
loaderContext
58+
)
5659

5760
const descriptor = compiler.parse({
5861
source,
@@ -98,6 +101,21 @@ module.exports = function (source) {
98101
(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
99102
options.hotReload !== false
100103

104+
// script
105+
let scriptImport = `var script = {}`
106+
// let isTS = false
107+
const { script, scriptSetup } = descriptor
108+
if (script || scriptSetup) {
109+
// const lang = script?.lang || scriptSetup?.lang
110+
// isTS = !!(lang && /tsx?/.test(lang))
111+
const src = (script && !scriptSetup && script.src) || resourcePath
112+
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
113+
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
114+
const request = stringifyRequest(src + query)
115+
scriptImport =
116+
`import script from ${request}\n` + `export * from ${request}` // support named exports
117+
}
118+
101119
// template
102120
let templateImport = `var render, staticRenderFns`
103121
let templateRequest
@@ -106,23 +124,13 @@ module.exports = function (source) {
106124
const idQuery = `&id=${id}`
107125
const scopedQuery = hasScoped ? `&scoped=true` : ``
108126
const attrsQuery = attrsToQuery(descriptor.template.attrs)
127+
// const tsQuery =
128+
// options.enableTsInTemplate !== false && isTS ? `&ts=true` : ``
109129
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
110130
const request = (templateRequest = stringifyRequest(src + query))
111131
templateImport = `import { render, staticRenderFns } from ${request}`
112132
}
113133

114-
// script
115-
let scriptImport = `var script = {}`
116-
const { script, scriptSetup } = descriptor
117-
if (script || scriptSetup) {
118-
const src = (script && !scriptSetup && script.src) || resourcePath
119-
const attrsQuery = attrsToQuery((scriptSetup || script).attrs, 'js')
120-
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
121-
const request = stringifyRequest(src + query)
122-
scriptImport =
123-
`import script from ${request}\n` + `export * from ${request}` // support named exports
124-
}
125-
126134
// styles
127135
let stylesCode = ``
128136
if (descriptor.styles.length) {

Diff for: lib/loaders/pitcher.js

+25-19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const hash = require('hash-sum')
44
const selfPath = require.resolve('../index')
55
const templateLoaderPath = require.resolve('./templateLoader')
66
const stylePostLoaderPath = require.resolve('./stylePostLoader')
7+
const { resolveCompiler } = require('../compiler')
78

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

9293
loaders.forEach(loader => {
93-
const identifier = typeof loader === 'string'
94-
? loader
95-
: (loader.path + loader.query)
94+
const identifier =
95+
typeof loader === 'string' ? loader : loader.path + loader.query
9696
const request = typeof loader === 'string' ? loader : loader.request
9797
if (!seen.has(identifier)) {
9898
seen.set(identifier, true)
@@ -102,10 +102,11 @@ module.exports.pitch = function (remainingRequest) {
102102
}
103103
})
104104

105-
return loaderUtils.stringifyRequest(this, '-!' + [
106-
...loaderStrings,
107-
this.resourcePath + this.resourceQuery
108-
].join('!'))
105+
return loaderUtils.stringifyRequest(
106+
this,
107+
'-!' +
108+
[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')
109+
)
109110
}
110111

111112
// Inject style-post-loader before css-loader for scoped CSS and trimming
@@ -129,25 +130,30 @@ module.exports.pitch = function (remainingRequest) {
129130
// for templates: inject the template compiler & optional cache
130131
if (query.type === `template`) {
131132
const path = require('path')
132-
const cacheLoader = cacheDirectory && cacheIdentifier
133-
? [`${require.resolve('cache-loader')}?${JSON.stringify({
134-
// For some reason, webpack fails to generate consistent hash if we
135-
// use absolute paths here, even though the path is only used in a
136-
// comment. For now we have to ensure cacheDirectory is a relative path.
137-
cacheDirectory: (path.isAbsolute(cacheDirectory)
138-
? path.relative(process.cwd(), cacheDirectory)
139-
: cacheDirectory).replace(/\\/g, '/'),
140-
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
141-
})}`]
142-
: []
133+
const cacheLoader =
134+
cacheDirectory && cacheIdentifier
135+
? [
136+
`${require.resolve('cache-loader')}?${JSON.stringify({
137+
// For some reason, webpack fails to generate consistent hash if we
138+
// use absolute paths here, even though the path is only used in a
139+
// comment. For now we have to ensure cacheDirectory is a relative path.
140+
cacheDirectory: (path.isAbsolute(cacheDirectory)
141+
? path.relative(process.cwd(), cacheDirectory)
142+
: cacheDirectory
143+
).replace(/\\/g, '/'),
144+
cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
145+
})}`
146+
]
147+
: []
143148

144149
const preLoaders = loaders.filter(isPreLoader)
145150
const postLoaders = loaders.filter(isPostLoader)
151+
const { is27 } = resolveCompiler(this.rootContext, this)
146152

147153
const request = genRequest([
148154
...cacheLoader,
149155
...postLoaders,
150-
templateLoaderPath + `??vue-loader-options`,
156+
...(is27 ? [] : [templateLoaderPath + `??vue-loader-options`]),
151157
...preLoaders
152158
])
153159
// console.log(request)

Diff for: lib/loaders/stylePostLoader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const { resolveCompiler } = require('../compiler')
66
// for any <style scoped> selection requests initiated from within vue files.
77
module.exports = function (source, inMap) {
88
const query = qs.parse(this.resourceQuery.slice(1))
9-
const { compiler } = resolveCompiler(this)
9+
const { compiler } = resolveCompiler(this.rootContext, this)
1010
const { code, map, errors } = compiler.compileStyle({
1111
source,
1212
filename: this.resourcePath,

Diff for: lib/loaders/templateLoader.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ module.exports = function (source) {
3232
}
3333
)
3434

35-
const { compiler, templateCompiler } = resolveCompiler(loaderContext)
35+
const { compiler, templateCompiler } = resolveCompiler(
36+
loaderContext.rootContext,
37+
loaderContext
38+
)
3639

3740
// for vue/compiler-sfc OR @vue/component-compiler-utils
3841
const finalOptions = {

Diff for: lib/plugin-webpack4.js

+67-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const qs = require('querystring')
22
const RuleSet = require('webpack/lib/RuleSet')
3+
const { resolveCompiler } = require('./compiler')
34

45
const id = 'vue-loader-plugin'
56
const NS = 'vue-loader'
@@ -38,7 +39,7 @@ class VueLoaderPlugin {
3839
if (!vueRule) {
3940
throw new Error(
4041
`[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
41-
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
42+
`Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
4243
)
4344
}
4445

@@ -58,7 +59,7 @@ class VueLoaderPlugin {
5859
if (vueLoaderUseIndex < 0) {
5960
throw new Error(
6061
`[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
61-
`Make sure the rule matching .vue files include vue-loader in its use.`
62+
`Make sure the rule matching .vue files include vue-loader in its use.`
6263
)
6364
}
6465

@@ -71,9 +72,30 @@ class VueLoaderPlugin {
7172

7273
// for each user rule (except the vue rule), create a cloned rule
7374
// that targets the corresponding language blocks in *.vue files.
74-
const clonedRules = rules
75-
.filter(r => r !== vueRule)
76-
.map(cloneRule)
75+
const clonedRules = rules.filter(r => r !== vueRule).map(cloneRule)
76+
77+
// rule for template compiler
78+
const templateCompilerRule = {
79+
loader: require.resolve('./loaders/templateLoader'),
80+
resourceQuery: query => {
81+
const parsed = qs.parse(query.slice(1))
82+
return parsed.vue != null && parsed.type === 'template'
83+
},
84+
options: vueLoaderUse.options
85+
}
86+
87+
// for each rule that matches plain .js/.ts files, also create a clone and
88+
// match it against the compiled template code inside *.vue files, so that
89+
// compiled vue render functions receive the same treatment as user code
90+
// (mostly babel)
91+
let jsRulesForRenderFn = []
92+
if (resolveCompiler(compiler.options.context).is27) {
93+
const matchesJS = createMatcher(`test.js`)
94+
// const matchesTS = createMatcher(`test.ts`)
95+
jsRulesForRenderFn = rules
96+
.filter(r => r !== vueRule && matchesJS(r))
97+
.map(cloneRuleForRenderFn)
98+
}
7799

78100
// global pitcher (responsible for injecting template compiler loader & CSS
79101
// post loader)
@@ -92,6 +114,8 @@ class VueLoaderPlugin {
92114
// replace original rules
93115
compiler.options.module.rules = [
94116
pitcher,
117+
...jsRulesForRenderFn,
118+
templateCompilerRule,
95119
...clonedRules,
96120
...rules
97121
]
@@ -104,11 +128,7 @@ function createMatcher (fakeFile) {
104128
const clone = Object.assign({}, rule)
105129
delete clone.include
106130
const normalized = RuleSet.normalizeRule(clone, {}, '')
107-
return (
108-
!rule.enforce &&
109-
normalized.resource &&
110-
normalized.resource(fakeFile)
111-
)
131+
return !rule.enforce && normalized.resource && normalized.resource(fakeFile)
112132
}
113133
}
114134

@@ -157,5 +177,42 @@ function cloneRule (rule) {
157177
return res
158178
}
159179

180+
function cloneRuleForRenderFn (rule) {
181+
const resource = rule.resource
182+
const resourceQuery = rule.resourceQuery
183+
let currentResource
184+
const res = {
185+
...rule,
186+
resource: resource => {
187+
currentResource = resource
188+
return true
189+
},
190+
resourceQuery: query => {
191+
const parsed = qs.parse(query.slice(1))
192+
if (parsed.vue == null || parsed.type !== 'template') {
193+
return false
194+
}
195+
const fakeResourcePath = `${currentResource}.${parsed.ts ? `ts` : `js`}`
196+
if (resource && !resource(fakeResourcePath)) {
197+
return false
198+
}
199+
if (resourceQuery && !resourceQuery(query)) {
200+
return false
201+
}
202+
return true
203+
}
204+
}
205+
206+
if (rule.rules) {
207+
res.rules = rule.rules.map(cloneRuleForRenderFn)
208+
}
209+
210+
if (rule.oneOf) {
211+
res.oneOf = rule.oneOf.map(cloneRuleForRenderFn)
212+
}
213+
214+
return res
215+
}
216+
160217
VueLoaderPlugin.NS = NS
161218
module.exports = VueLoaderPlugin

0 commit comments

Comments
 (0)