From 9ec4d8c077b6d5e4bac9317ddb6b226364f154a0 Mon Sep 17 00:00:00 2001 From: hemengke <23536175@qq.com> Date: Wed, 31 Jul 2024 17:01:05 +0800 Subject: [PATCH] fix: duplicate watcher, virtual module hmr --- client.d.ts | 2 +- src/client/manifest.d.ts => manifest.d.ts | 0 package.json | 3 +- pnpm-lock.yaml | 17 ++++++++++ src/node/helper/file-watcher.ts | 17 +++++++--- src/node/index.ts | 38 +++++++++++++++++------ src/node/manifest-cache/index.ts | 2 +- src/node/plugins/inject-script.ts | 12 +++---- src/node/plugins/virtual.ts | 4 +-- 9 files changed, 71 insertions(+), 24 deletions(-) rename src/client/manifest.d.ts => manifest.d.ts (100%) diff --git a/client.d.ts b/client.d.ts index e1695bb..875737f 100644 --- a/client.d.ts +++ b/client.d.ts @@ -1 +1 @@ -export * from './dist/client/index.js' +export * from './dist/client' diff --git a/src/client/manifest.d.ts b/manifest.d.ts similarity index 100% rename from src/client/manifest.d.ts rename to manifest.d.ts diff --git a/package.json b/package.json index 13b6693..d7d92fe 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "bump": "bumpp package.json -c --no-push -t --all -x \"pnpm run changelog\"" }, "peerDependencies": { - "esbuild": ">=0.19.0", + "esbuild": ">=0.21.3", "vite": ">=4.0.0 || >=5.0.0" }, "peerDependenciesMeta": { @@ -71,6 +71,7 @@ "@babel/preset-typescript": "^7.24.1", "browserslist": "^4.23.0", "core-js": "^3.37.1", + "debounce": "^2.1.0", "debug": "^4.3.4", "esbuild": "^0.21.3", "esbuild-plugin-browserslist": "^0.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3658d1..a7a58e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: core-js: specifier: ^3.37.1 version: 3.37.1 + debounce: + specifier: ^2.1.0 + version: 2.1.0 debug: specifier: ^4.3.4 version: 4.3.4 @@ -4094,6 +4097,7 @@ packages: resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} cpu: [arm] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4110,6 +4114,7 @@ packages: resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} cpu: [arm] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -4118,6 +4123,7 @@ packages: resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4135,6 +4141,7 @@ packages: resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -4152,6 +4159,7 @@ packages: resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} cpu: [ppc64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4160,6 +4168,7 @@ packages: resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} cpu: [riscv64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4168,6 +4177,7 @@ packages: resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} cpu: [s390x] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4176,6 +4186,7 @@ packages: resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: true optional: true @@ -4193,6 +4204,7 @@ packages: resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: true optional: true @@ -5735,6 +5747,11 @@ packages: is-data-view: 1.0.1 dev: true + /debounce@2.1.0: + resolution: {integrity: sha512-OkL3+0pPWCqoBc/nhO9u6TIQNTK44fnBnzuVtJAbp13Naxw9R6u21x+8tVTka87AhDZ3htqZ2pSSsZl9fqL2Wg==} + engines: {node: '>=18'} + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: diff --git a/src/node/helper/file-watcher.ts b/src/node/helper/file-watcher.ts index 5d9d25a..d66e76d 100644 --- a/src/node/helper/file-watcher.ts +++ b/src/node/helper/file-watcher.ts @@ -1,3 +1,4 @@ +import debounce from 'debounce' import createDebug from 'debug' import path from 'node:path' import colors from 'picocolors' @@ -38,6 +39,10 @@ async function handleFileChange(filePath: string, cb?: () => void) { handleFileAdded(filePath, cb) } +function debounced(fn: () => void) { + debounce(fn, 200, { immediate: true })() +} + export async function initWatcher(cb: (file: HmrFile) => void) { try { const { default: Watcher } = await import('watcher') @@ -49,16 +54,20 @@ export async function initWatcher(cb: (file: HmrFile) => void) { renameTimeout: 100, }) - watcher.on('unlink', (filePath: string) => handleUnlink(filePath, () => cb({ path: filePath, event: 'deleted' }))) + watcher.on('unlink', (filePath: string) => + debounced(() => handleUnlink(filePath, () => cb({ path: filePath, event: 'deleted' }))), + ) - watcher.on('add', (filePath: string) => handleFileAdded(filePath, () => cb({ path: filePath, event: 'added' }))) + watcher.on('add', (filePath: string) => + debounced(() => handleFileAdded(filePath, () => cb({ path: filePath, event: 'added' }))), + ) watcher.on('rename', async (f: string, fNext: string) => { - handleFileRenamed(f, fNext, () => cb({ path: fNext, event: `renamed` })) + debounced(() => handleFileRenamed(f, fNext, () => cb({ path: fNext, event: `renamed` }))) }) watcher.on('change', (filePath: string) => { - handleFileChange(filePath, () => cb({ path: filePath, event: 'changed' })) + debounced(() => handleFileChange(filePath, () => cb({ path: filePath, event: 'changed' }))) }) return watcher diff --git a/src/node/index.ts b/src/node/index.ts index c0c9b36..bd48ea9 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -1,7 +1,8 @@ +import type Watcher from 'watcher' import createDebug from 'debug' import fs from 'fs-extra' import path from 'node:path' -import { type PluginOption, type ResolvedConfig } from 'vite' +import { type PluginOption, type ResolvedConfig, type ViteDevServer } from 'vite' import { buildAllOnce } from './build' import { globalConfig } from './global-config' import { resolveOptions } from './helper/default-options' @@ -18,17 +19,21 @@ import { setupGlobalConfig, setupManifestCache, } from './helper/utils' +import { resolvedVirtualModuleId } from './helper/virtual' import { type VitePublicTypescriptOptions } from './interface' -import { getManifest, manifestCache } from './manifest-cache' +import { getManifestInNode, manifestCache } from './manifest-cache' import { pluginServer } from './plugins/server' import { pluginVirtual } from './plugins/virtual' const debug = createDebug('vite-plugin-public-typescript:index ===> ') +let wathcer: Watcher | undefined + export default function publicTypescript(options: VitePublicTypescriptOptions = {}) { debug('user options:', options) let viteConfig: ResolvedConfig + let server: ViteDevServer let opts = { ...options, @@ -48,12 +53,17 @@ export default function publicTypescript(options: VitePublicTypescriptOptions = await setupGlobalConfig(viteConfig, opts) await setupManifestCache(viteConfig, opts) }, - async configureServer(server) { + async configureServer(_server) { + server = _server const { ws } = server globalConfig.set('viteDevServer', server) - const wathcer = await initWatcher((file) => reloadPage(ws, file)) + if (wathcer) { + wathcer.close() + } + + wathcer = await initWatcher((file) => reloadPage(ws, file)) server.httpServer?.addListener('close', () => { wathcer?.close() }) @@ -65,7 +75,7 @@ export default function publicTypescript(options: VitePublicTypescriptOptions = fs.ensureFileSync(manifestPath) - const parsedCacheJson = getManifest() + const parsedCacheJson = getManifestInNode() debug('buildStart - parsedCacheJson:', parsedCacheJson) @@ -137,10 +147,15 @@ export default function publicTypescript(options: VitePublicTypescriptOptions = }, async handleHotUpdate(ctx) { const { file } = ctx - + const { moduleGraph } = server + moduleGraph.onFileChange(resolvedVirtualModuleId) + const module = moduleGraph.getModuleById(resolvedVirtualModuleId) + // virtual module hmr + if (module) { + moduleGraph.invalidateModule(module) + } if (_isPublicTypescript(file) || isManifestFile(file)) { debug('hmr disabled:', file) - return [] } }, @@ -153,7 +168,12 @@ export default function publicTypescript(options: VitePublicTypescriptOptions = return plugins as any } -export { getManifest } from './manifest-cache' -export { type Scripts, type ScriptDescriptor, injectScripts, injectScriptsToHtml } from './plugins/inject-script' +export { getManifestInNode } from './manifest-cache' +export { + type ManifestScriptsFn, + type ScriptDescriptor, + injectScripts, + injectScriptsToHtml, +} from './plugins/inject-script' export { publicTypescript } export { type VitePublicTypescriptOptions } diff --git a/src/node/manifest-cache/index.ts b/src/node/manifest-cache/index.ts index 37148c6..ae0ace5 100644 --- a/src/node/manifest-cache/index.ts +++ b/src/node/manifest-cache/index.ts @@ -35,7 +35,7 @@ export function saveManifestPathToDisk(cacheDir: string) { }) } -export function getManifest(root?: string): Record { +export function getManifestInNode(root?: string): Record { if (!isEmptyObject(manifestCache.getManifestJson())) { return manifestCache.getManifestJson() } diff --git a/src/node/plugins/inject-script.ts b/src/node/plugins/inject-script.ts index 36efea0..92de29e 100644 --- a/src/node/plugins/inject-script.ts +++ b/src/node/plugins/inject-script.ts @@ -1,12 +1,12 @@ import { type HtmlTagDescriptor, type PluginOption } from 'vite' import { VPPT_DATA_ATTR, injectTagsToHtml } from '../helper/html' -import { getManifest } from '../manifest-cache' +import { getManifestInNode } from '../manifest-cache' export type ScriptDescriptor = Omit[] -export type Scripts = (manifest: Record) => ScriptDescriptor +export type ManifestScriptsFn = (manifest: Record) => ScriptDescriptor -function generateScriptTags(scripts: Scripts) { - const _scripts = scripts(getManifest()) || [] +function generateScriptTags(scripts: ManifestScriptsFn) { + const _scripts = scripts(getManifestInNode()) || [] const tags: HtmlTagDescriptor[] = _scripts.map((s) => ({ ...s, attrs: { @@ -19,11 +19,11 @@ function generateScriptTags(scripts: Scripts) { return tags } -export function injectScriptsToHtml(html: string, scripts: Scripts) { +export function injectScriptsToHtml(html: string, scripts: ManifestScriptsFn) { return injectTagsToHtml(html, generateScriptTags(scripts)) } -export function injectScripts(scripts: Scripts) { +export function injectScripts(scripts: ManifestScriptsFn) { const plugin: PluginOption = { name: 'vite:public-typescript:inject-script', enforce: 'post', diff --git a/src/node/plugins/virtual.ts b/src/node/plugins/virtual.ts index a50060d..d2e3f3a 100644 --- a/src/node/plugins/virtual.ts +++ b/src/node/plugins/virtual.ts @@ -1,6 +1,6 @@ import { type PluginOption } from 'vite' import { resolvedVirtualModuleId, virtualModuleId } from '../helper/virtual' -import { getManifest } from '../manifest-cache' +import { getManifestInNode } from '../manifest-cache' export function pluginVirtual(): PluginOption { const plugin: PluginOption = { @@ -18,7 +18,7 @@ export function pluginVirtual(): PluginOption { }, async load(id) { if (id === resolvedVirtualModuleId) { - return `export default ${JSON.stringify(getManifest())}` + return `export default ${JSON.stringify(getManifestInNode())}` } }, }