Skip to content

Commit

Permalink
feat: compat for vue 2.7, support <script setup>
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 14, 2022
1 parent c1f4660 commit 308715a
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 75 deletions.
50 changes: 50 additions & 0 deletions lib/compiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// resolve compilers to use.

let cached

exports.resolveCompiler = function (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') {
return (cached = {
compiler: loadFromContext('vue/compiler-sfc', ctx),
templateCompiler: undefined
})
}
} catch (e) {}

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

function loadFromContext (path, ctx) {
return require(require.resolve(path, {
paths: [ctx]
}))
}

function loadTemplateCompiler (loaderContext) {
try {
return loadFromContext('vue-template-compiler', loaderContext.rootContext)
} 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.`
)
)
}
}
}
69 changes: 31 additions & 38 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,25 @@ const plugin = require('./plugin')
const selectBlock = require('./select')
const loaderUtils = require('loader-utils')
const { attrsToQuery } = require('./codegen/utils')
const { parse } = require('@vue/component-compiler-utils')
const genStylesCode = require('./codegen/styleInjection')
const { genHotReloadCode } = require('./codegen/hotReload')
const genCustomBlocksCode = require('./codegen/customBlocks')
const componentNormalizerPath = require.resolve('./runtime/componentNormalizer')
const { NS } = require('./plugin')
const { resolveCompiler } = require('./compiler')

let errorEmitted = false

function loadTemplateCompiler (loaderContext) {
try {
return require('vue-template-compiler')
} 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.`
))
}
}
}

module.exports = function (source) {
const loaderContext = this

if (!errorEmitted && !loaderContext['thread-loader'] && !loaderContext[NS]) {
loaderContext.emitError(new Error(
`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`
))
loaderContext.emitError(
new Error(
`vue-loader was used without the corresponding plugin. ` +
`Make sure to include VueLoaderPlugin in your webpack config.`
)
)
errorEmitted = true
}

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

const isServer = target === 'node'
const isShadow = !!options.shadowMode
const isProduction = options.productionMode || minimize || process.env.NODE_ENV === 'production'
const isProduction =
options.productionMode || minimize || process.env.NODE_ENV === 'production'
const filename = path.basename(resourcePath)
const context = rootContext || process.cwd()
const sourceRoot = path.dirname(path.relative(context, resourcePath))

const descriptor = parse({
const { compiler, templateCompiler } = resolveCompiler(loaderContext)

const descriptor = compiler.parse({
source,
compiler: options.compiler || loadTemplateCompiler(loaderContext),
compiler: options.compiler || templateCompiler,
filename,
sourceRoot,
needMap: sourceMap
Expand All @@ -78,6 +68,7 @@ module.exports = function (source) {
if (incomingQuery.type) {
return selectBlock(
descriptor,
options,
loaderContext,
incomingQuery,
!!options.appendExtension
Expand All @@ -93,19 +84,19 @@ module.exports = function (source) {

const id = hash(
isProduction
? (shortFilePath + '\n' + source.replace(/\r\n/g, '\n'))
? shortFilePath + '\n' + source.replace(/\r\n/g, '\n')
: shortFilePath
)

// feature information
const hasScoped = descriptor.styles.some(s => s.scoped)
const hasFunctional = descriptor.template && descriptor.template.attrs.functional
const needsHotReload = (
const hasFunctional =
descriptor.template && descriptor.template.attrs.functional
const needsHotReload =
!isServer &&
!isProduction &&
(descriptor.script || descriptor.template) &&
(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
options.hotReload !== false
)

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

// script
let scriptImport = `var script = {}`
if (descriptor.script) {
const src = descriptor.script.src || resourcePath
const attrsQuery = attrsToQuery(descriptor.script.attrs, 'js')
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
)
scriptImport =
`import script from ${request}\n` + `export * from ${request}` // support named exports
}

// styles
Expand All @@ -147,7 +137,8 @@ module.exports = function (source) {
)
}

let code = `
let code =
`
${templateImport}
${scriptImport}
${stylesCode}
Expand Down Expand Up @@ -183,7 +174,9 @@ var component = normalizer(
if (!isProduction) {
// Expose the file's full path in development, so that it can be opened
// from the devtools.
code += `\ncomponent.options.__file = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))}`
code += `\ncomponent.options.__file = ${JSON.stringify(
rawShortFilePath.replace(/\\/g, '/')
)}`
} else if (options.exposeFilename) {
// Libraries can opt-in to expose their components' filenames in production builds.
// For security reasons, only expose the file's basename in production.
Expand Down
5 changes: 3 additions & 2 deletions lib/loaders/stylePostLoader.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const qs = require('querystring')
const { compileStyle } = require('@vue/component-compiler-utils')
const { resolveCompiler } = require('../compiler')

// This is a post loader that handles scoped CSS transforms.
// Injected right before css-loader by the global pitcher (../pitch.js)
// 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 { code, map, errors } = compileStyle({
const { compiler } = resolveCompiler(this)
const { code, map, errors } = compiler.compileStyle({
source,
filename: this.resourcePath,
id: `data-v-${query.id}`,
Expand Down
49 changes: 30 additions & 19 deletions lib/loaders/templateLoader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const qs = require('querystring')
const loaderUtils = require('loader-utils')
const { compileTemplate } = require('@vue/component-compiler-utils')
const { resolveCompiler } = require('../compiler')

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

// allow using custom compiler via options
const compiler = options.compiler || require('vue-template-compiler')
const compilerOptions = Object.assign(
{
outputSourceRange: true
},
options.compilerOptions,
{
scopeId: query.scoped ? `data-v-${id}` : null,
comments: query.comments
}
)

const compilerOptions = Object.assign({
outputSourceRange: true
}, options.compilerOptions, {
scopeId: query.scoped ? `data-v-${id}` : null,
comments: query.comments
})
const { compiler, templateCompiler } = resolveCompiler(loaderContext)

// for vue-component-compiler
// for vue/compiler-sfc OR @vue/component-compiler-utils
const finalOptions = {
source,
filename: this.resourcePath,
compiler,
compiler: options.compiler || templateCompiler,
compilerOptions,
// allow customizing behavior of vue-template-es2015-compiler
transpileOptions: options.transpileOptions,
Expand All @@ -43,7 +49,7 @@ module.exports = function (source) {
prettify: options.prettify
}

const compiled = compileTemplate(finalOptions)
const compiled = compiler.compileTemplate(finalOptions)

// tips
if (compiled.tips && compiled.tips.length) {
Expand All @@ -55,16 +61,21 @@ module.exports = function (source) {
// errors
if (compiled.errors && compiled.errors.length) {
// 2.6 compiler outputs errors as objects with range
if (compiler.generateCodeFrame && finalOptions.compilerOptions.outputSourceRange) {
if (
compiler.generateCodeFrame &&
finalOptions.compilerOptions.outputSourceRange
) {
// TODO account for line offset in case template isn't placed at top
// of the file
loaderContext.emitError(
`\n\n Errors compiling template:\n\n` +
compiled.errors.map(({ msg, start, end }) => {
const frame = compiler.generateCodeFrame(source, start, end)
return ` ${msg}\n\n${pad(frame)}`
}).join(`\n\n`) +
'\n'
compiled.errors
.map(({ msg, start, end }) => {
const frame = compiler.generateCodeFrame(source, start, end)
return ` ${msg}\n\n${pad(frame)}`
})
.join(`\n\n`) +
'\n'
)
} else {
loaderContext.emitError(
Expand Down
49 changes: 49 additions & 0 deletions lib/resolveScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const { resolveCompiler } = require('./compiler')

const clientCache = new WeakMap()
const serverCache = new WeakMap()

exports.resolveScript = function resolveScript (
descriptor,
options,
loaderContext
) {
if (!descriptor.script && !descriptor.scriptSetup) {
return null
}

const { compiler } = resolveCompiler(loaderContext)
if (!compiler.compileScript) {
if (descriptor.scriptSetup) {
loaderContext.emitError(
'The version of Vue you are using does not support <script setup>. ' +
'Please upgrade to 2.7 or above.'
)
}
return descriptor.script
}

const isProd =
loaderContext.mode === 'production' || process.env.NODE_ENV === 'production'
const isServer = options.optimizeSSR || loaderContext.target === 'node'

const cacheToUse = isServer ? serverCache : clientCache
const cached = cacheToUse.get(descriptor)
if (cached) {
return cached
}

let resolved = null

try {
resolved = compiler.compileScript(descriptor, {
isProd,
babelParserPlugins: options.babelParserPlugins
})
} catch (e) {
loaderContext.emitError(e)
}

cacheToUse.set(descriptor, resolved)
return resolved
}
Loading

0 comments on commit 308715a

Please sign in to comment.