Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

build(deps): bump ini from 1.3.5 to 1.3.8 #425

Open
wants to merge 8 commits into
base: next
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -18,9 +18,10 @@
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 2"
},
"dependencies": {
"@rollup/pluginutils": "^4.1.0",
"debug": "^4.1.1",
"hash-sum": "^2.0.0",
"rollup-pluginutils": "^2.8.2"
"source-map": "^0.6.1"
},
"peerDependencies": {
"@vue/compiler-sfc": "*"
@@ -38,6 +39,7 @@
"lint-staged": "^10.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"pug": "^3.0.0",
"rollup": "^2.7.2",
"rollup-plugin-postcss": "^3.1.8",
"ts-jest": "^26.0.0",
@@ -55,7 +57,6 @@
},
"prettier": {
"printWidth": 80,
"trailingComma": "es5",
"semi": false,
"singleQuote": true
}
192 changes: 192 additions & 0 deletions src/handleHotUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import fs from 'fs'
import _debug from 'debug'
import { parse, SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
import {
getDescriptor,
setDescriptor,
setPrevDescriptor,
} from './utils/descriptorCache'
import { getResolvedScript, setResolvedScript } from './script'

const debug = _debug('vite:hmr')

/**
* Vite-specific HMR handling
*/
export async function handleHotUpdate(file: string, modules: any[]) {
if (!file.endsWith('.vue')) {
return
}

const prevDescriptor = getDescriptor(file)
if (!prevDescriptor) {
// file hasn't been requested yet (e.g. async component)
return
}

let content = fs.readFileSync(file, 'utf-8')
if (!content) {
await untilModified(file)
content = fs.readFileSync(file, 'utf-8')
}

const { descriptor } = parse(content, {
filename: file,
sourceMap: true,
})
setDescriptor(file, descriptor)
setPrevDescriptor(file, prevDescriptor)

let needRerender = false
const filteredModules = new Set()
const mainModule = modules.find(
(m) => !/type=/.test(m.id) || /type=script/.test(m.id)
)
const templateModule = modules.find((m) => /type=template/.test(m.id))

if (
!isEqualBlock(descriptor.script, prevDescriptor.script) ||
!isEqualBlock(descriptor.scriptSetup, prevDescriptor.scriptSetup)
) {
filteredModules.add(mainModule)
}

if (!isEqualBlock(descriptor.template, prevDescriptor.template)) {
// when a <script setup> component's template changes, it will need correct
// binding metadata. However, when reloading the template alone the binding
// metadata will not be available since the script part isn't loaded.
// in this case, reuse the compiled script from previous descriptor.
if (!filteredModules.has(mainModule)) {
setResolvedScript(descriptor, getResolvedScript(prevDescriptor)!)
}
filteredModules.add(templateModule)
needRerender = true
}

let didUpdateStyle = false
const prevStyles = prevDescriptor.styles || []
const nextStyles = descriptor.styles || []

// force reload if CSS vars injection changed
if (descriptor.cssVars) {
if (prevDescriptor.cssVars.join('') !== descriptor.cssVars.join('')) {
filteredModules.add(mainModule)
}
}

// force reload if scoped status has changed
if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {
// template needs to be invalidated as well
filteredModules.add(templateModule)
filteredModules.add(mainModule)
}

// only need to update styles if not reloading, since reload forces
// style updates as well.
for (let i = 0; i < nextStyles.length; i++) {
const prev = prevStyles[i]
const next = nextStyles[i]
if (!prev || !isEqualBlock(prev, next)) {
didUpdateStyle = true
const mod = modules.find((m) => m.id.includes(`type=style&index=${i}`))
if (mod) {
filteredModules.add(mod)
} else {
// new style block - force reload
filteredModules.add(mainModule)
}
}
}
if (prevStyles.length > nextStyles.length) {
// style block removed - force reload
filteredModules.add(mainModule)
}

const prevCustoms = prevDescriptor.customBlocks || []
const nextCustoms = descriptor.customBlocks || []

// custom blocks update causes a reload
// because the custom block contents is changed and it may be used in JS.
for (let i = 0; i < nextCustoms.length; i++) {
const prev = prevCustoms[i]
const next = nextCustoms[i]
if (!prev || !isEqualBlock(prev, next)) {
const mod = modules.find((m) =>
m.id.includes(`type=${prev.type}&index=${i}`)
)
if (mod) {
filteredModules.add(mod)
} else {
filteredModules.add(mainModule)
}
}
}
if (prevCustoms.length > nextCustoms.length) {
// block rmeoved, force reload
filteredModules.add(mainModule)
}

let updateType = []
if (needRerender) {
updateType.push(`template`)
// template is inlined into main, add main module instead
if (!templateModule) {
filteredModules.add(mainModule)
}
}
if (didUpdateStyle) {
updateType.push(`style`)
}
if (updateType.length) {
debug(`[vue:update(${updateType.join('&')})] ${file}`)
}
return [...filteredModules].filter(Boolean)
}

// vitejs/vite#610 when hot-reloading Vue files, we read immediately on file
// change event and sometimes this can be too early and get an empty buffer.
// Poll until the file's modified time has changed before reading again.
async function untilModified(file: string) {
const mtime = fs.statSync(file).mtimeMs
return new Promise((r) => {
let n = 0
const poll = async () => {
n++
const newMtime = fs.statSync(file).mtimeMs
if (newMtime !== mtime || n > 10) {
r(0)
} else {
setTimeout(poll, 10)
}
}
setTimeout(poll, 10)
})
}

function isEqualBlock(a: SFCBlock | null, b: SFCBlock | null) {
if (!a && !b) return true
if (!a || !b) return false
// src imports will trigger their own updates
if (a.src && b.src && a.src === b.src) return true
if (a.content !== b.content) return false
const keysA = Object.keys(a.attrs)
const keysB = Object.keys(b.attrs)
if (keysA.length !== keysB.length) {
return false
}
return keysA.every((key) => a.attrs[key] === b.attrs[key])
}

export function isOnlyTemplateChanged(
prev: SFCDescriptor,
next: SFCDescriptor
) {
return (
isEqualBlock(prev.script, next.script) &&
isEqualBlock(prev.scriptSetup, next.scriptSetup) &&
prev.styles.length === next.styles.length &&
prev.styles.every((s, i) => isEqualBlock(s, next.styles[i])) &&
prev.customBlocks.length === next.customBlocks.length &&
prev.customBlocks.every((s, i) => isEqualBlock(s, next.customBlocks[i]))
)
}
58 changes: 33 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -14,24 +14,25 @@ import {
import fs from 'fs'
import createDebugger from 'debug'
import { Plugin } from 'rollup'
import { createFilter } from 'rollup-pluginutils'
import { transformSFCEntry } from './sfc'
import { transformTemplate } from './template'
import { createFilter } from '@rollup/pluginutils'
import { genSfcFacade } from './sfcFacade'
import { transformTemplateAsModule } from './template'
import { transformStyle } from './style'
import { createCustomBlockFilter } from './utils/customBlockFilter'
import { getDescriptor, setDescriptor } from './utils/descriptorCache'
import { parseVuePartRequest } from './utils/query'
import { normalizeSourceMap } from './utils/sourceMap'
import { getResolvedScript } from './script'
import { handleHotUpdate } from './handleHotUpdate'

const debug = createDebugger('rollup-plugin-vue')

export interface Options {
include: string | RegExp | (string | RegExp)[]
exclude: string | RegExp | (string | RegExp)[]
target: 'node' | 'browser'
vite: boolean
hmr: boolean
exposeFilename: boolean

customBlocks?: string[]

// if true, handle preprocessors directly instead of delegating to other
@@ -58,6 +59,8 @@ export interface Options {
const defaultOptions: Options = {
include: /\.vue$/,
exclude: [],
vite: false,
hmr: false,
target: 'browser',
exposeFilename: false,
customBlocks: [],
@@ -69,6 +72,10 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
...userOptions,
}

if (options.vite) {
options.preprocessStyles = false
}

const isServer = options.target === 'node'
const isProduction =
process.env.NODE_ENV === 'production' || process.env.BUILD === 'production'
@@ -109,23 +116,21 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
return fs.readFileSync(query.filename, 'utf-8')
}
const descriptor = getDescriptor(query.filename)
if (descriptor) {
const block =
query.type === 'template'
? descriptor.template!
: query.type === 'script'
? getResolvedScript(descriptor, isServer)
: query.type === 'style'
? descriptor.styles[query.index]
: typeof query.index === 'number'
? descriptor.customBlocks[query.index]
: null

if (block) {
return {
code: block.content,
map: normalizeSourceMap(block.map, id),
}
const block =
query.type === 'template'
? descriptor.template!
: query.type === 'script'
? getResolvedScript(descriptor, isServer)
: query.type === 'style'
? descriptor.styles[query.index]
: typeof query.index === 'number'
? descriptor.customBlocks[query.index]
: null

if (block) {
return {
code: block.content,
map: block.map as any,
}
}
}
@@ -139,7 +144,7 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
// generate an entry module that imports the actual blocks of the SFC
if (!query.vue && filter(id)) {
debug(`transform SFC entry (${id})`)
const output = transformSFCEntry(
const output = await genSfcFacade(
code,
id,
options,
@@ -165,14 +170,17 @@ export default function PluginVue(userOptions: Partial<Options> = {}): Plugin {
}
if (query.type === 'template') {
debug(`transform template (${id})`)
return transformTemplate(code, id, options, query, this)
return transformTemplateAsModule(code, options, query, this)
} else if (query.type === 'style') {
debug(`transform style (${id})`)
return transformStyle(code, id, options, query, isProduction, this)
return transformStyle(code, options, query, isProduction, this)
}
}
return null
},

// @ts-ignore
handleHotUpdate,
}
}

12 changes: 10 additions & 2 deletions src/script.ts
Original file line number Diff line number Diff line change
@@ -10,11 +10,19 @@ const serverCache = new WeakMap<SFCDescriptor, SFCScriptBlock | null>()

export function getResolvedScript(
descriptor: SFCDescriptor,
isServer: boolean
isServer = false
): SFCScriptBlock | null | undefined {
return (isServer ? serverCache : clientCache).get(descriptor)
}

export function setResolvedScript(
descriptor: SFCDescriptor,
script: SFCScriptBlock,
isServer = false
) {
;(isServer ? serverCache : clientCache).set(descriptor, script)
}

export function resolveScript(
descriptor: SFCDescriptor,
scopeId: string,
@@ -40,7 +48,7 @@ export function resolveScript(
resolved = compileScript(descriptor, {
id: scopeId,
isProd,
inlineTemplate: true,
inlineTemplate: !options.hmr,
templateOptions: getTemplateCompilerOptions(
options,
descriptor,
256 changes: 0 additions & 256 deletions src/sfc.ts

This file was deleted.

387 changes: 387 additions & 0 deletions src/sfcFacade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
import hash from 'hash-sum'
import path from 'path'
import qs from 'querystring'
import {
parse,
rewriteDefault,
SFCBlock,
SFCDescriptor,
} from '@vue/compiler-sfc'
import { Options } from '.'
import { getPrevDescriptor, setDescriptor } from './utils/descriptorCache'
import { PluginContext, TransformPluginContext } from 'rollup'
import { createRollupError } from './utils/error'
import { resolveScript } from './script'
import { transformTemplateInMain } from './template'
import { isOnlyTemplateChanged } from './handleHotUpdate'
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'

export async function genSfcFacade(
code: string,
filename: string,
options: Options,
sourceRoot: string,
isProduction: boolean,
isServer: boolean,
filterCustomBlock: (type: string) => boolean,
pluginContext: TransformPluginContext
) {
// prev descriptor is only set and used for hmr
const prevDescriptor = getPrevDescriptor(filename)
const { descriptor, errors } = parse(code, {
sourceMap: true,
filename,
})
setDescriptor(filename, descriptor)

if (errors.length) {
errors.forEach((error) =>
pluginContext.error(createRollupError(filename, error))
)
return null
}

const shortFilePath = path
.relative(sourceRoot, filename)
.replace(/^(\.\.[\/\\])+/, '')
.replace(/\\/g, '/')
const scopeId = hash(
isProduction ? shortFilePath + '\n' + code : shortFilePath
)
// feature information
const hasScoped = descriptor.styles.some((s) => s.scoped)

// script
const { code: scriptCode, map } = await genScriptCode(
descriptor,
scopeId,
isProduction,
isServer,
options,
pluginContext
)

// template
const useInlineTemplate =
!options.hmr &&
descriptor.scriptSetup &&
!(descriptor.template && descriptor.template.src)
const hasTemplateImport = descriptor.template && !useInlineTemplate

let templateCode = ''
let templateMap
if (hasTemplateImport) {
;({ code: templateCode, map: templateMap } = genTemplateCode(
descriptor,
scopeId,
options,
isServer,
pluginContext
))
}

const renderReplace = hasTemplateImport
? isServer
? `_sfc_main.ssrRender = _sfc_ssrRender`
: `_sfc_main.render = _sfc_render`
: ''

// styles
const stylesCode = genStyleCode(
descriptor,
scopeId,
options.preprocessStyles,
options.vite
)

// custom blocks
const customBlocksCode = getCustomBlock(descriptor, filterCustomBlock)

const output: string[] = [
scriptCode,
templateCode,
stylesCode,
customBlocksCode,
renderReplace,
]
if (hasScoped) {
output.push(`_sfc_main.__scopeId = ${JSON.stringify(`data-v-${scopeId}`)}`)
}
if (!isProduction) {
output.push(`_sfc_main.__file = ${JSON.stringify(shortFilePath)}`)
} else if (options.exposeFilename) {
output.push(
`_sfc_main.__file = ${JSON.stringify(path.basename(shortFilePath))}`
)
}
output.push('export default _sfc_main')

if (options.hmr) {
// check if the template is the only thing that changed
if (prevDescriptor && isOnlyTemplateChanged(prevDescriptor, descriptor)) {
output.push(`export const _rerender_only = true`)
}
output.push(`_sfc_main.__hmrId = ${JSON.stringify(scopeId)}`)
output.push(
`__VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)`
)
output.push(
`import.meta.hot.accept(({ default: updated, _rerender_only }) => {`,
` if (_rerender_only) {`,
` __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)`,
` } else {`,
` __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)`,
` }`,
`})`
)
}

// if the template is inlined into the main module (indicated by the presence
// of templateMap, we need to concatenate the two source maps.
let resolvedMap = map
if (map && templateMap) {
const generator = SourceMapGenerator.fromSourceMap(
new SourceMapConsumer(map)
)
const offset = scriptCode.match(/\r?\n/g)?.length || 1
const templateMapConsumer = new SourceMapConsumer(templateMap)
templateMapConsumer.eachMapping((m) => {
generator.addMapping({
source: m.source,
original: { line: m.originalLine, column: m.originalColumn },
generated: {
line: m.generatedLine + offset,
column: m.generatedColumn,
},
})
})
resolvedMap = (generator as any).toJSON()
// if this is a template only update, we will be reusing a cached version
// of the main module compile result, which has outdated sourcesContent.
resolvedMap.sourcesContent = templateMap.sourcesContent
}

return {
code: output.join('\n'),
map: resolvedMap || {
mappings: '',
},
}
}

function genTemplateCode(
descriptor: SFCDescriptor,
id: string,
options: Options,
isServer: boolean,
pluginContext: PluginContext
) {
const renderFnName = isServer ? 'ssrRender' : 'render'
const template = descriptor.template!

// If the template is not using pre-processor AND is not using external src,
// compile and inline it directly in the main module. When served in vite this
// saves an extra request per SFC which can improve load performance.
if (!template.lang && !template.src) {
return transformTemplateInMain(
template.content,
descriptor,
id,
options,
pluginContext
)
} else {
const src = template.src || descriptor.filename
const idQuery = `&id=${id}`
const srcQuery = template.src ? `&src` : ``
const attrsQuery = attrsToQuery(template.attrs, 'js', true)
const query = `?vue&type=template${idQuery}${srcQuery}${attrsQuery}`
return {
code: `import { ${renderFnName} as _sfc_${renderFnName} } from ${JSON.stringify(
src + query
)}`,
map: undefined,
}
}
}

async function genScriptCode(
descriptor: SFCDescriptor,
scopeId: string,
isProd: boolean,
isServer: boolean,
options: Options,
pluginContext: TransformPluginContext
): Promise<{
code: string
map: RawSourceMap
}> {
let scriptCode = `const _sfc_main = {}`
let map
const script = resolveScript(
descriptor,
scopeId,
isProd,
isServer,
options,
pluginContext
)
if (script) {
// If the script is js/ts and has no external src, it can be directly placed
// in the main module.
if (
(!script.lang ||
(script.lang === 'ts' && (pluginContext as any).server)) &&
!script.src
) {
scriptCode = rewriteDefault(script.content, `_sfc_main`)
map = script.map
if (script.lang === 'ts') {
const result = await (pluginContext as any).server.transformWithEsbuild(
scriptCode,
descriptor.filename,
{ loader: 'ts' },
map
)
scriptCode = result.code
map = result.map
}
} else {
const src = script.src || descriptor.filename
const attrsQuery = attrsToQuery(script.attrs, 'js')
const srcQuery = script.src ? `&src` : ``
const query = `?vue&type=script${srcQuery}${attrsQuery}`
const scriptRequest = JSON.stringify(src + query)
scriptCode =
`import _sfc_main from ${scriptRequest}\n` +
`export * from ${scriptRequest}` // support named exports
}
}
return {
code: scriptCode,
map,
}
}

function genStyleCode(
descriptor: SFCDescriptor,
scopeId: string,
preprocessStyles?: boolean,
isVite?: boolean
) {
let stylesCode = ``
let hasCSSModules = false
if (descriptor.styles.length) {
descriptor.styles.forEach((style, i) => {
const src = style.src || descriptor.filename
// do not include module in default query, since we use it to indicate
// that the module needs to export the modules json
const attrsQuery = attrsToQuery(style.attrs, 'css', preprocessStyles)
const attrsQueryWithoutModule = attrsQuery.replace(
/&module(=true|=[^&]+)?/,
''
)
// make sure to only pass id when necessary so that we don't inject
// duplicate tags when multiple components import the same css file
const idQuery = `&id=${scopeId}`
const srcQuery = style.src ? `&src` : ``
const query = `?vue&type=style&index=${i}${srcQuery}${idQuery}`
const styleRequest = src + query + attrsQuery
const styleRequestWithoutModule = src + query + attrsQueryWithoutModule
if (style.module) {
if (!hasCSSModules) {
stylesCode += `\nconst cssModules = _sfc_main.__cssModules = {}`
hasCSSModules = true
}
stylesCode += genCSSModulesCode(
i,
styleRequest,
styleRequestWithoutModule,
style.module,
isVite
)
} else {
stylesCode += `\nimport ${JSON.stringify(styleRequest)}`
}
// TODO SSR critical CSS collection
})
}
return stylesCode
}

function getCustomBlock(
descriptor: SFCDescriptor,
filter: (type: string) => boolean
) {
let code = ''

descriptor.customBlocks.forEach((block, index) => {
if (filter(block.type)) {
const src = block.src || descriptor.filename
const attrsQuery = attrsToQuery(block.attrs, block.type)
const srcQuery = block.src ? `&src` : ``
const query = `?vue&type=${block.type}&index=${index}${srcQuery}${attrsQuery}`
const request = JSON.stringify(src + query)
code += `import block${index} from ${request}\n`
code += `if (typeof block${index} === 'function') block${index}(_sfc_main)\n`
}
})

return code
}

function genCSSModulesCode(
index: number,
request: string,
requestWithoutModule: string,
moduleName: string | boolean,
isVite?: boolean
): string {
const styleVar = `style${index}`
let code
if (!isVite) {
code =
// first import the CSS for extraction
`\nimport ${JSON.stringify(requestWithoutModule)}` +
// then import the json file to expose to component...
`\nimport ${styleVar} from ${JSON.stringify(request + '.js')}`
} else {
// vite handles module.ext in a single import
request = request.replace(/\.(\w+)$/, '.module.$1.js')
code = `\n import ${styleVar} from ${JSON.stringify(request)}`
}

// inject variable
const name = typeof moduleName === 'string' ? moduleName : '$style'
code += `\ncssModules["${name}"] = ${styleVar}`
return code
}

// these are built-in query parameters so should be ignored
// if the user happen to add them as attrs
const ignoreList = ['id', 'index', 'src', 'type', 'lang']

function attrsToQuery(
attrs: SFCBlock['attrs'],
langFallback?: string,
forceLangFallback = false
): string {
let query = ``
for (const name in attrs) {
const value = attrs[name]
if (!ignoreList.includes(name)) {
query += `&${qs.escape(name)}${
value ? `=${qs.escape(String(value))}` : ``
}`
}
}
if (langFallback || attrs.lang) {
query +=
`lang` in attrs
? forceLangFallback
? `&lang.${langFallback}`
: `&lang.${attrs.lang}`
: `&lang.${langFallback}`
}
return query
}
28 changes: 16 additions & 12 deletions src/style.ts
Original file line number Diff line number Diff line change
@@ -6,11 +6,9 @@ import { TransformPluginContext } from 'rollup'
import { Options } from '.'
import { getDescriptor } from './utils/descriptorCache'
import { StyleBlockQuery } from './utils/query'
import { normalizeSourceMap } from './utils/sourceMap'

export async function transformStyle(
code: string,
request: string,
options: Options,
query: StyleBlockQuery,
isProduction: boolean,
@@ -20,7 +18,7 @@ export async function transformStyle(
const block = descriptor.styles[query.index]!

let preprocessOptions = options.preprocessOptions || {}
const preprocessLang = (options.preprocessStyles
const preprocessLang = (options.preprocessStyles && !options.vite
? block.lang
: undefined) as SFCAsyncStyleCompileOptions['preprocessLang']

@@ -52,7 +50,8 @@ export async function transformStyle(
isProd: isProduction,
source: code,
scoped: block.scoped,
modules: !!block.module,
// vite handle CSS modules
modules: !!block.module && !options.vite,
postcssOptions: options.postcssOptions,
postcssPlugins: options.postcssPlugins,
modulesOptions: options.cssModulesOptions,
@@ -62,24 +61,29 @@ export async function transformStyle(
})

if (result.errors.length) {
result.errors.forEach((error) =>
pluginContext.error({
id: query.filename,
message: error.message,
})
)
result.errors.forEach((error: any) => {
if (error.line && error.column) {
error.loc = {
file: query.filename,
line: error.line + block.loc.start.line,
column: error.column,
}
}
pluginContext.error(error)
})
return null
}

if (query.module) {
if (query.module && !options.vite) {
// vite handles css modules code generation down the stream
return {
code: `export default ${JSON.stringify(result.modules)}`,
map: null,
}
} else {
return {
code: result.code,
map: normalizeSourceMap(result.map!, request),
map: result.map as any,
}
}
}
79 changes: 62 additions & 17 deletions src/template.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,100 @@
import {
compileTemplate,
compileTemplate as compile,
SFCDescriptor,
SFCTemplateCompileOptions,
} from '@vue/compiler-sfc'
import { TransformPluginContext } from 'rollup'
import { PluginContext, TransformPluginContext } from 'rollup'
import { Options } from '.'
import { getResolvedScript } from './script'
import { getDescriptor } from './utils/descriptorCache'
import { createRollupError } from './utils/error'
import { TemplateBlockQuery } from './utils/query'
import { normalizeSourceMap } from './utils/sourceMap'

export function transformTemplate(
export function transformTemplateAsModule(
code: string,
request: string,
options: Options,
query: TemplateBlockQuery,
pluginContext: TransformPluginContext
) {
const descriptor = getDescriptor(query.filename)
const result = compileTemplate({
...getTemplateCompilerOptions(options, descriptor, query.id),
id: query.id,

const result = compileTemplate(
code,
descriptor,
query.id,
options,
pluginContext
)

let returnCode = result.code
if (options.hmr) {
returnCode += `\nimport.meta.hot.accept(({ render }) => {
__VUE_HMR_RUNTIME__.rerender(${JSON.stringify(query.id)}, render)
})`
}

return {
code: returnCode,
map: result.map as any,
}
}

/**
* transform the template directly in the main SFC module
*/
export function transformTemplateInMain(
code: string,
descriptor: SFCDescriptor,
id: string,
options: Options,
pluginContext: PluginContext
) {
const result = compileTemplate(code, descriptor, id, options, pluginContext)
return {
...result,
code: result.code.replace(
/\nexport (function|const) (render|ssrRender)/,
'\n$1 _sfc_$2'
),
}
}

export function compileTemplate(
code: string,
descriptor: SFCDescriptor,
id: string,
options: Options,
pluginContext: PluginContext
) {
const filename = descriptor.filename
const compileOptions = getTemplateCompilerOptions(options, descriptor, id)
const result = compile({
...compileOptions,
id,
source: code,
filename: query.filename,
filename,
})

if (result.errors.length) {
result.errors.forEach((error) =>
pluginContext.error(
typeof error === 'string'
? { id: query.filename, message: error }
: createRollupError(query.filename, error)
? { id: filename, message: error }
: createRollupError(filename, error)
)
)
return null
}

if (result.tips.length) {
result.tips.forEach((tip) =>
pluginContext.warn({
id: query.filename,
id: filename,
message: tip,
})
)
}

return {
code: result.code,
map: normalizeSourceMap(result.map!, request),
}
return result
}

export function getTemplateCompilerOptions(
10 changes: 9 additions & 1 deletion src/utils/descriptorCache.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { SFCDescriptor } from '@vue/compiler-sfc'

const cache = new Map<string, SFCDescriptor>()
const prevCache = new Map<string, SFCDescriptor | undefined>()

export function setDescriptor(id: string, entry: SFCDescriptor) {
cache.set(id, entry)
}

export function getPrevDescriptor(id: string) {
return prevCache.get(id)
}

export function setPrevDescriptor(id: string, entry: SFCDescriptor) {
prevCache.set(id, entry)
}

export function getDescriptor(id: string) {
if (cache.has(id)) {
return cache.get(id)!
}

throw new Error(`${id} is not parsed yet`)
}
239 changes: 227 additions & 12 deletions yarn.lock

Large diffs are not rendered by default.