Skip to content

Commit 308715a

Browse files
committed
feat: compat for vue 2.7, support <script setup>
1 parent c1f4660 commit 308715a

File tree

7 files changed

+174
-75
lines changed

7 files changed

+174
-75
lines changed

Diff for: lib/compiler.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// resolve compilers to use.
2+
3+
let cached
4+
5+
exports.resolveCompiler = function (loaderContext) {
6+
if (cached) {
7+
return cached
8+
}
9+
10+
const ctx = loaderContext.rootContext
11+
// check 2.7
12+
try {
13+
const pkg = loadFromContext('vue/package.json', ctx)
14+
const [major, minor] = pkg.version.split('.')
15+
if (major === '2' && minor === '7') {
16+
return (cached = {
17+
compiler: loadFromContext('vue/compiler-sfc', ctx),
18+
templateCompiler: undefined
19+
})
20+
}
21+
} catch (e) {}
22+
23+
return (cached = {
24+
compiler: require('@vue/component-compiler-utils'),
25+
templateCompiler: loadTemplateCompiler(loaderContext)
26+
})
27+
}
28+
29+
function loadFromContext (path, ctx) {
30+
return require(require.resolve(path, {
31+
paths: [ctx]
32+
}))
33+
}
34+
35+
function loadTemplateCompiler (loaderContext) {
36+
try {
37+
return loadFromContext('vue-template-compiler', loaderContext.rootContext)
38+
} 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.`
46+
)
47+
)
48+
}
49+
}
50+
}

Diff for: lib/index.js

+31-38
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,25 @@ const plugin = require('./plugin')
55
const selectBlock = require('./select')
66
const loaderUtils = require('loader-utils')
77
const { attrsToQuery } = require('./codegen/utils')
8-
const { parse } = require('@vue/component-compiler-utils')
98
const genStylesCode = require('./codegen/styleInjection')
109
const { genHotReloadCode } = require('./codegen/hotReload')
1110
const genCustomBlocksCode = require('./codegen/customBlocks')
1211
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
1312
const { NS } = require('./plugin')
13+
const { resolveCompiler } = require('./compiler')
1414

1515
let errorEmitted = false
1616

17-
function loadTemplateCompiler (loaderContext) {
18-
try {
19-
return require('vue-template-compiler')
20-
} catch (e) {
21-
if (/version mismatch/.test(e.toString())) {
22-
loaderContext.emitError(e)
23-
} else {
24-
loaderContext.emitError(new Error(
25-
`[vue-loader] vue-template-compiler must be installed as a peer dependency, ` +
26-
`or a compatible compiler implementation must be passed via options.`
27-
))
28-
}
29-
}
30-
}
31-
3217
module.exports = function (source) {
3318
const loaderContext = this
3419

3520
if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
36-
loaderContext.emitError(new Error(
37-
`vue-loader was used without the corresponding plugin. ` +
38-
`Make sure to include VueLoaderPlugin in your webpack config.`
39-
))
21+
loaderContext.emitError(
22+
new Error(
23+
`vue-loader was used without the corresponding plugin. ` +
24+
`Make sure to include VueLoaderPlugin in your webpack config.`
25+
)
26+
)
4027
errorEmitted = true
4128
}
4229

@@ -59,14 +46,17 @@ module.exports = function (source) {
5946

6047
const isServer = target === 'node'
6148
const isShadow = !!options.shadowMode
62-
const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
49+
const isProduction =
50+
options.productionMode || minimize || process.env.NODE_ENV === 'production'
6351
const filename = path.basename(resourcePath)
6452
const context = rootContext || process.cwd()
6553
const sourceRoot = path.dirname(path.relative(context, resourcePath))
6654

67-
const descriptor = parse({
55+
const { compiler, templateCompiler } = resolveCompiler(loaderContext)
56+
57+
const descriptor = compiler.parse({
6858
source,
69-
compiler: options.compiler || loadTemplateCompiler(loaderContext),
59+
compiler: options.compiler || templateCompiler,
7060
filename,
7161
sourceRoot,
7262
needMap: sourceMap
@@ -78,6 +68,7 @@ module.exports = function (source) {
7868
if (incomingQuery.type) {
7969
return selectBlock(
8070
descriptor,
71+
options,
8172
loaderContext,
8273
incomingQuery,
8374
!!options.appendExtension
@@ -93,19 +84,19 @@ module.exports = function (source) {
9384

9485
const id = hash(
9586
isProduction
96-
? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
87+
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
9788
: shortFilePath
9889
)
9990

10091
// feature information
10192
const hasScoped = descriptor.styles.some(s => s.scoped)
102-
const hasFunctional = descriptor.template && descriptor.template.attrs.functional
103-
const needsHotReload = (
93+
const hasFunctional =
94+
descriptor.template && descriptor.template.attrs.functional
95+
const needsHotReload =
10496
!isServer &&
10597
!isProduction &&
106-
(descriptor.script || descriptor.template) &&
98+
(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
10799
options.hotReload !== false
108-
)
109100

110101
// template
111102
let templateImport = `var render, staticRenderFns`
@@ -116,21 +107,20 @@ module.exports = function (source) {
116107
const scopedQuery = hasScoped ? `&scoped=true` : ``
117108
const attrsQuery = attrsToQuery(descriptor.template.attrs)
118109
const query = `?vue&type=template${idQuery}${scopedQuery}${attrsQuery}${inheritQuery}`
119-
const request = templateRequest = stringifyRequest(src + query)
110+
const request = (templateRequest = stringifyRequest(src + query))
120111
templateImport = `import { render, staticRenderFns } from ${request}`
121112
}
122113

123114
// script
124115
let scriptImport = `var script = {}`
125-
if (descriptor.script) {
126-
const src = descriptor.script.src || resourcePath
127-
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
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')
128120
const query = `?vue&type=script${attrsQuery}${inheritQuery}`
129121
const request = stringifyRequest(src + query)
130-
scriptImport = (
131-
`import script from ${request}\n` +
132-
`export * from ${request}` // support named exports
133-
)
122+
scriptImport =
123+
`import script from ${request}\n` + `export * from ${request}` // support named exports
134124
}
135125

136126
// styles
@@ -147,7 +137,8 @@ module.exports = function (source) {
147137
)
148138
}
149139

150-
let code = `
140+
let code =
141+
`
151142
${templateImport}
152143
${scriptImport}
153144
${stylesCode}
@@ -183,7 +174,9 @@ var component = normalizer(
183174
if (!isProduction) {
184175
// Expose the file's full path in development, so that it can be opened
185176
// from the devtools.
186-
code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`
177+
code += `\ncomponent.options.__file = ${JSON.stringify(
178+
rawShortFilePath.replace(/\\/g, '/')
179+
)}`
187180
} else if (options.exposeFilename) {
188181
// Libraries can opt-in to expose their components' filenames in production builds.
189182
// For security reasons, only expose the file's basename in production.

Diff for: lib/loaders/stylePostLoader.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
const qs = require('querystring')
2-
const { compileStyle } = require('@vue/component-compiler-utils')
2+
const { resolveCompiler } = require('../compiler')
33

44
// This is a post loader that handles scoped CSS transforms.
55
// Injected right before css-loader by the global pitcher (../pitch.js)
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 { code, map, errors } = compileStyle({
9+
const { compiler } = resolveCompiler(this)
10+
const { code, map, errors } = compiler.compileStyle({
1011
source,
1112
filename: this.resourcePath,
1213
id: `data-v-${query.id}`,

Diff for: lib/loaders/templateLoader.js

+30-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const qs = require('querystring')
22
const loaderUtils = require('loader-utils')
3-
const { compileTemplate } = require('@vue/component-compiler-utils')
3+
const { resolveCompiler } = require('../compiler')
44

55
// Loader that compiles raw template into JavaScript functions.
66
// This is injected by the global pitcher (../pitch) for template
@@ -15,24 +15,30 @@ module.exports = function (source) {
1515
const options = loaderUtils.getOptions(loaderContext) || {}
1616
const { id } = query
1717
const isServer = loaderContext.target === 'node'
18-
const isProduction = options.productionMode || loaderContext.minimize || process.env.NODE_ENV === 'production'
18+
const isProduction =
19+
options.productionMode ||
20+
loaderContext.minimize ||
21+
process.env.NODE_ENV === 'production'
1922
const isFunctional = query.functional
2023

21-
// allow using custom compiler via options
22-
const compiler = options.compiler || require('vue-template-compiler')
24+
const compilerOptions = Object.assign(
25+
{
26+
outputSourceRange: true
27+
},
28+
options.compilerOptions,
29+
{
30+
scopeId: query.scoped ? `data-v-${id}` : null,
31+
comments: query.comments
32+
}
33+
)
2334

24-
const compilerOptions = Object.assign({
25-
outputSourceRange: true
26-
}, options.compilerOptions, {
27-
scopeId: query.scoped ? `data-v-${id}` : null,
28-
comments: query.comments
29-
})
35+
const { compiler, templateCompiler } = resolveCompiler(loaderContext)
3036

31-
// for vue-component-compiler
37+
// for vue/compiler-sfc OR @vue/component-compiler-utils
3238
const finalOptions = {
3339
source,
3440
filename: this.resourcePath,
35-
compiler,
41+
compiler: options.compiler || templateCompiler,
3642
compilerOptions,
3743
// allow customizing behavior of vue-template-es2015-compiler
3844
transpileOptions: options.transpileOptions,
@@ -43,7 +49,7 @@ module.exports = function (source) {
4349
prettify: options.prettify
4450
}
4551

46-
const compiled = compileTemplate(finalOptions)
52+
const compiled = compiler.compileTemplate(finalOptions)
4753

4854
// tips
4955
if (compiled.tips && compiled.tips.length) {
@@ -55,16 +61,21 @@ module.exports = function (source) {
5561
// errors
5662
if (compiled.errors && compiled.errors.length) {
5763
// 2.6 compiler outputs errors as objects with range
58-
if (compiler.generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
64+
if (
65+
compiler.generateCodeFrame &&
66+
finalOptions.compilerOptions.outputSourceRange
67+
) {
5968
// TODO account for line offset in case template isn't placed at top
6069
// of the file
6170
loaderContext.emitError(
6271
`\n\n Errors compiling template:\n\n` +
63-
compiled.errors.map(({ msg, start, end }) => {
64-
const frame = compiler.generateCodeFrame(source, start, end)
65-
return ` ${msg}\n\n${pad(frame)}`
66-
}).join(`\n\n`) +
67-
'\n'
72+
compiled.errors
73+
.map(({ msg, start, end }) => {
74+
const frame = compiler.generateCodeFrame(source, start, end)
75+
return ` ${msg}\n\n${pad(frame)}`
76+
})
77+
.join(`\n\n`) +
78+
'\n'
6879
)
6980
} else {
7081
loaderContext.emitError(

Diff for: lib/resolveScript.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const { resolveCompiler } = require('./compiler')
2+
3+
const clientCache = new WeakMap()
4+
const serverCache = new WeakMap()
5+
6+
exports.resolveScript = function resolveScript (
7+
descriptor,
8+
options,
9+
loaderContext
10+
) {
11+
if (!descriptor.script && !descriptor.scriptSetup) {
12+
return null
13+
}
14+
15+
const { compiler } = resolveCompiler(loaderContext)
16+
if (!compiler.compileScript) {
17+
if (descriptor.scriptSetup) {
18+
loaderContext.emitError(
19+
'The version of Vue you are using does not support <script setup>. ' +
20+
'Please upgrade to 2.7 or above.'
21+
)
22+
}
23+
return descriptor.script
24+
}
25+
26+
const isProd =
27+
loaderContext.mode === 'production' || process.env.NODE_ENV === 'production'
28+
const isServer = options.optimizeSSR || loaderContext.target === 'node'
29+
30+
const cacheToUse = isServer ? serverCache : clientCache
31+
const cached = cacheToUse.get(descriptor)
32+
if (cached) {
33+
return cached
34+
}
35+
36+
let resolved = null
37+
38+
try {
39+
resolved = compiler.compileScript(descriptor, {
40+
isProd,
41+
babelParserPlugins: options.babelParserPlugins
42+
})
43+
} catch (e) {
44+
loaderContext.emitError(e)
45+
}
46+
47+
cacheToUse.set(descriptor, resolved)
48+
return resolved
49+
}

0 commit comments

Comments
 (0)