Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(plugin-vue): hmr got error by transformSFC to inject code (fix #301) #306

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/plugin-vue/src/handleHotUpdate.ts
Expand Up @@ -31,7 +31,7 @@ export async function handleHotUpdate(
{ file, modules, read }: HmrContext,
options: ResolvedOptions,
): Promise<ModuleNode[] | void> {
const prevDescriptor = getDescriptor(file, options, false, true)
const prevDescriptor = await getDescriptor(file, options, false, true)
if (!prevDescriptor) {
// file hasn't been requested yet (e.g. async component)
return
Expand Down
12 changes: 8 additions & 4 deletions packages/plugin-vue/src/index.ts
Expand Up @@ -16,6 +16,7 @@ import { resolveCompiler } from './compiler'
import { parseVueRequest } from './utils/query'
import {
getDescriptor,
getReadCode,
getSrcDescriptor,
getTempSrcDescriptor,
} from './utils/descriptorCache'
Expand Down Expand Up @@ -81,6 +82,7 @@ export interface ResolvedOptions extends Options {
cssDevSourcemap: boolean
devServer?: ViteDevServer
devToolsEnabled?: boolean
readCode: ReturnType<typeof getReadCode>
}

export interface Api {
Expand All @@ -100,6 +102,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
sourceMap: true,
cssDevSourcemap: false,
devToolsEnabled: process.env.NODE_ENV !== 'production',
readCode: getReadCode([]),
})

const filter = computed(() =>
Expand Down Expand Up @@ -157,6 +160,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
configResolved(config) {
options.value = {
...options.value,
readCode: getReadCode(config.plugins),
root: config.root,
sourceMap: config.command === 'build' ? !!config.build.sourcemap : true,
cssDevSourcemap: config.css?.devSourcemap ?? false,
Expand Down Expand Up @@ -191,7 +195,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
}
},

load(id, opt) {
async load(id, opt) {
const ssr = opt?.ssr === true
if (id === EXPORT_HELPER_ID) {
return helperCode
Expand All @@ -204,7 +208,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
if (query.src) {
return fs.readFileSync(filename, 'utf-8')
}
const descriptor = getDescriptor(filename, options.value)!
const descriptor = (await getDescriptor(filename, options.value))!
let block: SFCBlock | null | undefined
if (query.type === 'script') {
// handle <script> + <script setup> merge via compileScript()
Expand All @@ -225,7 +229,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
}
},

transform(code, id, opt) {
async transform(code, id, opt) {
const ssr = opt?.ssr === true
const { filename, query } = parseVueRequest(id)

Expand All @@ -252,7 +256,7 @@ export default function vuePlugin(rawOptions: Options = {}): Plugin<Api> {
const descriptor = query.src
? getSrcDescriptor(filename, query) ||
getTempSrcDescriptor(filename, query)
: getDescriptor(filename, options.value)!
: (await getDescriptor(filename, options.value))!

if (query.type === 'template') {
return transformTemplateAsModule(
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-vue/src/main.ts
Expand Up @@ -42,7 +42,7 @@ export async function transformMain(

if (fs.existsSync(filename)) {
// populate descriptor cache for HMR if it's not set yet
getDescriptor(
await getDescriptor(
filename,
options,
true,
Expand Down
26 changes: 23 additions & 3 deletions packages/plugin-vue/src/utils/descriptorCache.ts
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { createHash } from 'node:crypto'
import slash from 'slash'
import type { Plugin } from 'vite'
import type { CompilerError, SFCDescriptor } from 'vue/compiler-sfc'
import type { ResolvedOptions, VueQuery } from '..'

Expand Down Expand Up @@ -51,21 +52,21 @@ export function invalidateDescriptor(filename: string, hmr = false): void {
}
}

export function getDescriptor(
export async function getDescriptor(
filename: string,
options: ResolvedOptions,
createIfNotFound = true,
hmr = false,
code?: string,
): SFCDescriptor | undefined {
): Promise<SFCDescriptor | undefined> {
const _cache = hmr ? hmrCache : cache
if (_cache.has(filename)) {
return _cache.get(filename)!
}
if (createIfNotFound) {
const { descriptor, errors } = createDescriptor(
filename,
code ?? fs.readFileSync(filename, 'utf-8'),
code ?? await options.readCode(filename),
options,
hmr,
)
Expand Down Expand Up @@ -119,6 +120,25 @@ export function setSrcDescriptor(
cache.set(filename, entry)
}

type TransformCode = (filename: string, code: string) => Promise<string | null>

export function getReadCode(plugins: readonly Plugin<{ transformCode?: TransformCode }>[]): (filename: string) => Promise<string> {
const transformCodeFns = (plugins || []).reduce<TransformCode[]>((fns, plugin) => {
if (plugin.name.endsWith(':api') && typeof plugin.api?.transformCode === 'function') {
fns.push(plugin.api.transformCode)
}
return fns
}, [])

return async (filename: string) => {
let code = fs.readFileSync(filename, 'utf-8')
for (const transformCode of transformCodeFns) {
code = (await transformCode(filename, code)) || code
}
return code
}
}

function getHash(text: string): string {
return createHash('sha256').update(text).digest('hex').substring(0, 8)
}