perf(kit,nuxt): use `fdir` to speed up fs scanning

This commit is contained in:
Daniel Roe 2024-06-24 16:39:34 +02:00
parent d9e55546d9
commit d7718f4474
No known key found for this signature in database
GPG Key ID: CBC814C393D93268
7 changed files with 73 additions and 26 deletions

View File

@ -68,7 +68,7 @@
"eslint-plugin-perfectionist": "2.11.0",
"eslint-typegen": "0.2.4",
"execa": "9.2.0",
"globby": "14.0.1",
"fdir": "6.1.1",
"h3": "1.12.0",
"happy-dom": "14.12.3",
"jiti": "1.21.6",
@ -79,6 +79,7 @@
"nuxt-content-twoslash": "0.0.10",
"ofetch": "1.3.4",
"pathe": "1.1.2",
"picomatch": "^4.0.2",
"playwright-core": "1.44.1",
"rimraf": "5.0.7",
"semver": "7.6.2",

View File

@ -31,13 +31,14 @@
"consola": "^3.2.3",
"defu": "^6.1.4",
"destr": "^2.0.3",
"globby": "^14.0.1",
"fdir": "^6.1.1",
"hash-sum": "^2.0.0",
"ignore": "^5.3.1",
"jiti": "^1.21.6",
"klona": "^2.0.6",
"mlly": "^1.7.1",
"pathe": "^1.1.2",
"picomatch": "^4.0.2",
"pkg-types": "^1.1.1",
"scule": "^1.3.0",
"semver": "^7.6.2",
@ -48,6 +49,7 @@
},
"devDependencies": {
"@types/hash-sum": "1.0.2",
"@types/picomatch": "^2.3.3",
"@types/semver": "7.5.8",
"nitropack": "2.9.6",
"unbuild": "latest",

View File

@ -1,7 +1,7 @@
import { existsSync, promises as fsp } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { basename, dirname, isAbsolute, join, normalize, resolve } from 'pathe'
import { globby } from 'globby'
import { fdir } from 'fdir'
import { resolvePath as _resolvePath } from 'mlly'
import { resolveAlias as _resolveAlias } from 'pathe/utils'
import { tryUseNuxt } from './context'
@ -210,7 +210,7 @@ function existsInVFS (path: string, nuxt = tryUseNuxt()) {
export async function resolveFiles (path: string, pattern: string | string[], opts: { followSymbolicLinks?: boolean } = {}) {
const files: string[] = []
for (const file of await globby(pattern, { cwd: path, followSymbolicLinks: opts.followSymbolicLinks ?? true })) {
for (const file of await new fdir().withRelativePaths().globWithOptions(toArray(pattern), { dot: true }).crawl(path).withPromise()) {
const p = resolve(path, file)
if (!isIgnored(p)) {
files.push(p)

View File

@ -79,7 +79,7 @@
"esbuild": "^0.21.5",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"globby": "^14.0.1",
"fdir": "^6.1.1",
"h3": "^1.12.0",
"hookable": "^5.5.3",
"ignore": "^5.3.1",
@ -95,6 +95,7 @@
"ohash": "^1.1.3",
"pathe": "^1.1.2",
"perfect-debounce": "^1.0.0",
"picomatch": "^4.0.2",
"pkg-types": "^1.1.1",
"radix3": "^1.1.2",
"scule": "^1.3.0",
@ -121,6 +122,7 @@
"@nuxt/ui-templates": "1.3.4",
"@parcel/watcher": "2.4.1",
"@types/estree": "1.0.5",
"@types/picomatch": "^2.3.3",
"@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.29",
"unbuild": "latest",

View File

@ -1,12 +1,13 @@
import { readdir } from 'node:fs/promises'
import { basename, dirname, extname, join, relative } from 'pathe'
import { globby } from 'globby'
import { fdir } from 'fdir'
import { kebabCase, pascalCase, splitByCase } from 'scule'
import { isIgnored, logger, useNuxt } from '@nuxt/kit'
import { withTrailingSlash } from 'ufo'
import type { Component, ComponentsDir } from 'nuxt/schema'
import { resolveComponentNameSegments } from '../core/utils'
import { toArray } from '../utils'
/**
* Scan the components inside different components folders
@ -32,7 +33,8 @@ export async function scanComponents (dirs: ComponentsDir[], srcDir: string): Pr
// A map from resolved path to component name (used for making duplicate warning message)
const resolvedNames = new Map<string, string>()
const files = (await globby(dir.pattern!, { cwd: dir.path, ignore: dir.ignore })).sort()
const patterns = toArray(dir.pattern!)
const files = (await new fdir().withRelativePaths().globWithOptions(patterns, { ignore: dir.ignore, dot: true }).crawl(dir.path).withPromise()).sort()
// Check if the directory exists (globby will otherwise read it case insensitively on MacOS)
if (files.length) {

View File

@ -86,9 +86,9 @@ importers:
execa:
specifier: 9.2.0
version: 9.2.0
globby:
specifier: 14.0.1
version: 14.0.1
fdir:
specifier: 6.1.1
version: 6.1.1(picomatch@4.0.2)
h3:
specifier: 1.12.0
version: 1.12.0
@ -119,6 +119,9 @@ importers:
pathe:
specifier: 1.1.2
version: 1.1.2
picomatch:
specifier: ^4.0.2
version: 4.0.2
playwright-core:
specifier: 1.44.1
version: 1.44.1
@ -170,9 +173,9 @@ importers:
destr:
specifier: ^2.0.3
version: 2.0.3
globby:
specifier: ^14.0.1
version: 14.0.1
fdir:
specifier: ^6.1.1
version: 6.1.1(picomatch@4.0.2)
hash-sum:
specifier: ^2.0.0
version: 2.0.0
@ -191,6 +194,9 @@ importers:
pathe:
specifier: ^1.1.2
version: 1.1.2
picomatch:
specifier: ^4.0.2
version: 4.0.2
pkg-types:
specifier: ^1.1.1
version: 1.1.1
@ -216,6 +222,9 @@ importers:
'@types/hash-sum':
specifier: 1.0.2
version: 1.0.2
'@types/picomatch':
specifier: ^2.3.3
version: 2.3.3
'@types/semver':
specifier: 7.5.8
version: 7.5.8
@ -300,9 +309,9 @@ importers:
estree-walker:
specifier: ^3.0.3
version: 3.0.3
globby:
specifier: ^14.0.1
version: 14.0.1
fdir:
specifier: ^6.1.1
version: 6.1.1(picomatch@4.0.2)
h3:
specifier: ^1.12.0
version: 1.12.0
@ -348,6 +357,9 @@ importers:
perfect-debounce:
specifier: ^1.0.0
version: 1.0.0
picomatch:
specifier: ^4.0.2
version: 4.0.2
pkg-types:
specifier: ^1.1.1
version: 1.1.1
@ -421,6 +433,9 @@ importers:
'@types/estree':
specifier: 1.0.5
version: 1.0.5
'@types/picomatch':
specifier: ^2.3.3
version: 2.3.3
'@vitejs/plugin-vue':
specifier: 5.0.4
version: 5.0.4(vite@5.3.1(@types/node@20.14.7)(sass@1.69.4)(terser@5.27.0))(vue@3.4.29(typescript@5.5.2))
@ -572,9 +587,9 @@ importers:
execa:
specifier: 9.2.0
version: 9.2.0
globby:
specifier: 14.0.1
version: 14.0.1
fdir:
specifier: 6.1.1
version: 6.1.1(picomatch@4.0.2)
html-minifier:
specifier: 4.0.0
version: 4.0.0
@ -587,6 +602,9 @@ importers:
pathe:
specifier: 1.1.2
version: 1.1.2
picomatch:
specifier: 4.0.2
version: 4.0.2
prettier:
specifier: 3.3.2
version: 3.3.2
@ -2520,6 +2538,9 @@ packages:
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
'@types/picomatch@2.3.3':
resolution: {integrity: sha512-Yll76ZHikRFCyz/pffKGjrCwe/le2CDwOP5F210KQo27kpRE46U2rDnzikNlVn6/ezH3Mhn46bJMTfeVTtcYMg==}
'@types/pify@5.0.4':
resolution: {integrity: sha512-gxKJ1Aw8LbyCsCQWIsip9bYKJCNsKHMoZoQMAe2IWH7U7hgp/l6TvJpbFvu8ZlGBimjZZNvEx2S1ZQlj02ayNQ==}
@ -4264,6 +4285,14 @@ packages:
fastq@1.15.0:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
fdir@6.1.1:
resolution: {integrity: sha512-QfKBVg453Dyn3mr0Q0O+Tkr1r79lOTAKSi9f/Ot4+qVEwxWhav2Z+SudrG9vQjM2aYRMQQZ2/Q1zdA8ACM1pDg==}
peerDependencies:
picomatch: 3.x
peerDependenciesMeta:
picomatch:
optional: true
figures@3.2.0:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'}
@ -9186,6 +9215,8 @@ snapshots:
'@types/normalize-package-data@2.4.4': {}
'@types/picomatch@2.3.3': {}
'@types/pify@5.0.4': {}
'@types/pug@2.0.10': {}
@ -11518,6 +11549,10 @@ snapshots:
dependencies:
reusify: 1.0.4
fdir@6.1.1(picomatch@4.0.2):
optionalDependencies:
picomatch: 4.0.2
figures@3.2.0:
dependencies:
escape-string-regexp: 1.0.5

View File

@ -2,8 +2,8 @@ import { fileURLToPath } from 'node:url'
import fsp from 'node:fs/promises'
import { beforeAll, describe, expect, it } from 'vitest'
import { execaCommand } from 'execa'
import { globby } from 'globby'
import { join } from 'pathe'
import { fdir } from 'fdir'
import { join, resolve } from 'pathe'
describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM_CI)('minimal nuxt application', () => {
const rootDir = fileURLToPath(new URL('./fixtures/minimal', import.meta.url))
@ -31,7 +31,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
it('default server bundle size', async () => {
const serverDir = join(rootDir, '.output/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
const serverStats = await analyzeSizes('**/*.mjs', serverDir, { excludeNodeModules: true })
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"210k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
@ -71,7 +71,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
it('default server bundle size (inlined vue modules)', async () => {
const serverDir = join(rootDir, '.output-inline/server')
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
const serverStats = await analyzeSizes('**/*.mjs', serverDir, { excludeNodeModules: true })
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"531k"`)
const modules = await analyzeSizes('node_modules/**/*', serverDir)
@ -94,11 +94,16 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
})
})
async function analyzeSizes (pattern: string | string[], rootDir: string) {
const files: string[] = await globby(pattern, { cwd: rootDir })
async function analyzeSizes (pattern: string | string[], rootDir: string, opts: { excludeNodeModules?: boolean } = {}) {
const patterns = Array.isArray(pattern) ? pattern : [pattern]
const files: string[] = new fdir({
resolveSymlinks: true,
exclude: p => opts.excludeNodeModules ? p.includes('node_modules') : false
})
.withRelativePaths().globWithOptions(patterns, { dot: true }).crawl(rootDir).sync()
let totalBytes = 0
for (const file of files) {
const path = join(rootDir, file)
const path = resolve(rootDir, file)
const isSymlink = (await fsp.lstat(path).catch(() => null))?.isSymbolicLink()
if (!isSymlink) {