From a086af9692f1010ecae32df4e35fa96e93822c7f Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 19 Apr 2023 22:02:52 +0100 Subject: [PATCH] perf: allow using `@parcel/watcher` for dev watcher (#20179) --- packages/nuxt/package.json | 7 ++++ packages/nuxt/src/core/builder.ts | 43 +++++++++++++++++++++- packages/nuxt/src/core/schema.ts | 28 +++++++++++--- packages/schema/src/config/common.ts | 1 + packages/schema/src/config/experimental.ts | 14 +++++++ pnpm-lock.yaml | 18 +++++++++ 6 files changed, 104 insertions(+), 7 deletions(-) diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 6a2ba60e03..00c01c0934 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -99,6 +99,7 @@ "vue-router": "^4.1.6" }, "devDependencies": { + "@parcel/watcher": "^2.1.0", "@types/estree": "^1.0.1", "@types/fs-extra": "^11.0.1", "@types/prompts": "^2.4.4", @@ -109,8 +110,14 @@ "vitest": "^0.30.1" }, "peerDependencies": { + "@parcel/watcher": "^2.1.0", "@types/node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, + "peerDependenciesMeta": { + "@parcel/watcher": { + "optional": true + } + }, "engines": { "node": "^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } diff --git a/packages/nuxt/src/core/builder.ts b/packages/nuxt/src/core/builder.ts index 76707abc9d..548b5ce9b8 100644 --- a/packages/nuxt/src/core/builder.ts +++ b/packages/nuxt/src/core/builder.ts @@ -1,6 +1,8 @@ import { pathToFileURL } from 'node:url' +import type { EventType } from '@parcel/watcher' import chokidar from 'chokidar' import { isIgnored, tryResolveModule } from '@nuxt/kit' +import { interopDefault } from 'mlly' import { debounce } from 'perfect-debounce' import { normalize } from 'pathe' import type { Nuxt } from 'nuxt/schema' @@ -43,7 +45,45 @@ export async function build (nuxt: Nuxt) { } } -function watch (nuxt: Nuxt) { +const watchEvents: Record = { + create: 'add', + delete: 'unlink', + update: 'change' +} + +async function watch (nuxt: Nuxt) { + if (nuxt.options.experimental.watcher === 'parcel') { + if (nuxt.options.debug) { + console.time('[nuxt] builder:parcel:watch') + } + const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir]) + if (watcherPath) { + const { subscribe } = await import(pathToFileURL(watcherPath).href).then(interopDefault) as typeof import('@parcel/watcher') + for (const layer of nuxt.options._layers) { + if (!layer.config.srcDir) { continue } + const watcher = subscribe(layer.config.srcDir, (err, events) => { + if (err) { return } + for (const event of events) { + if (isIgnored(event.path)) { continue } + nuxt.callHook('builder:watch', watchEvents[event.type], normalize(event.path)) + } + }, { + ignore: [ + ...nuxt.options.ignore, + '.nuxt', + 'node_modules' + ] + }) + watcher.then((subscription) => { + console.timeEnd('[nuxt] builder:parcel:watch') + nuxt.hook('close', () => subscription.unsubscribe()) + }) + } + return + } + console.warn('[nuxt] falling back to `chokidar` as `@parcel/watcher` cannot be resolved in your project.') + } + if (nuxt.options.debug) { console.time('[nuxt] builder:chokidar:watch') } @@ -65,7 +105,6 @@ function watch (nuxt: Nuxt) { watcher.on('all', (event, path) => nuxt.callHook('builder:watch', event, normalize(path))) nuxt.hook('close', () => watcher.close()) - return watcher } async function bundle (nuxt: Nuxt) { diff --git a/packages/nuxt/src/core/schema.ts b/packages/nuxt/src/core/schema.ts index 1d77e1e73e..e2f4d3b3cf 100644 --- a/packages/nuxt/src/core/schema.ts +++ b/packages/nuxt/src/core/schema.ts @@ -1,10 +1,12 @@ import { existsSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' +import { pathToFileURL } from 'node:url' import { dirname, resolve } from 'pathe' import chokidar from 'chokidar' +import { interopDefault } from 'mlly' import { defu } from 'defu' import { debounce } from 'perfect-debounce' -import { createResolver, defineNuxtModule } from '@nuxt/kit' +import { createResolver, defineNuxtModule, tryResolveModule } from '@nuxt/kit' import { generateTypes, resolveSchema as resolveUntypedSchema @@ -57,6 +59,26 @@ export default defineNuxtModule({ // Watch for schema changes in development mode if (nuxt.options.dev) { + const onChange = debounce(async () => { + schema = await resolveSchema() + await writeSchema(schema) + }) + + if (nuxt.options.experimental.watcher === 'parcel') { + const watcherPath = await tryResolveModule('@parcel/watcher', [nuxt.options.rootDir, ...nuxt.options.modulesDir]) + if (watcherPath) { + const { subscribe } = await import(pathToFileURL(watcherPath).href).then(interopDefault) as typeof import('@parcel/watcher') + for (const layer of nuxt.options._layers) { + const subscription = await subscribe(layer.config.rootDir, onChange, { + ignore: ['!nuxt.schema.*'] + }) + nuxt.hook('close', () => subscription.unsubscribe()) + } + return + } + console.warn('[nuxt] falling back to `chokidar` as `@parcel/watcher` cannot be resolved in your project.') + } + const filesToWatch = await Promise.all(nuxt.options._layers.map(layer => resolver.resolve(layer.config.rootDir, 'nuxt.schema.*') )) @@ -64,10 +86,6 @@ export default defineNuxtModule({ ...nuxt.options.watchers.chokidar, ignoreInitial: true }) - const onChange = debounce(async () => { - schema = await resolveSchema() - await writeSchema(schema) - }) watcher.on('all', onChange) nuxt.hook('close', () => watcher.close()) } diff --git a/packages/schema/src/config/common.ts b/packages/schema/src/config/common.ts index bce40ea5ad..bc0437e454 100644 --- a/packages/schema/src/config/common.ts +++ b/packages/schema/src/config/common.ts @@ -345,6 +345,7 @@ export default defineUntypedSchema({ '**/*.{spec,test}.{js,ts,jsx,tsx}', // ignore tests '**/*.d.ts', // ignore type declarations '.output', + '.git', await get('ignorePrefix') && `**/${await get('ignorePrefix')}*.*` ].concat(val).filter(Boolean) }, diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 005110a0b4..ab0023d520 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -161,5 +161,19 @@ export default defineUntypedSchema({ /** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */ localLayerAliases: true, + + /** + * Set an alternative watcher that will be used as the watching service for Nuxt. + * + * Nuxt uses 'chokidar' by default, but by setting this to `parcel` it will use + * `@parcel/watcher` instead. This may improve performance in large projects or + * on Windows platforms. + * + * @see https://github.com/paulmillr/chokidar + * @see https://github.com/parcel-bundler/watcher + * @default chokidar + * @type {'chokidar' | 'parcel'} + */ + watcher: 'chokidar' } }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39ea6aafd2..531e5f4242 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -687,6 +687,9 @@ importers: specifier: ^4.1.6 version: 4.1.6(vue@3.2.47) devDependencies: + '@parcel/watcher': + specifier: ^2.1.0 + version: 2.1.0 '@types/estree': specifier: ^1.0.1 version: 1.0.1 @@ -2133,6 +2136,17 @@ packages: - supports-color dev: true + /@parcel/watcher@2.1.0: + resolution: {integrity: sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + is-glob: 4.0.3 + micromatch: 4.0.5 + node-addon-api: 3.2.1 + node-gyp-build: 4.6.0 + dev: true + /@pkgr/utils@2.3.1: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -7238,6 +7252,10 @@ packages: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: false + /node-addon-api@3.2.1: + resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + dev: true + /node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'}