Skip to content

Commit

Permalink
feat!: replace minimatch with fast-glob
Browse files Browse the repository at this point in the history
`fast-glob` is indeed fast. When running Helia's add-dir benchmark:

Before:

```
┌─────────┬───────────────────────────┬──────────┬──────────┬──────┬──────────┬──────────────────────────────────────────────────┐
│ (index) │ Implementation            │ ops/s    │ ms/op    │ runs │ p99      │ CID                                              │
├─────────┼───────────────────────────┼──────────┼──────────┼──────┼──────────┼──────────────────────────────────────────────────┤
│ 0       │ 'helia-fs - src'          │ '56.95'  │ '17.56'  │ 5    │ '53.93'  │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 1       │ 'helia-fs - dist'         │ '51.74'  │ '19.33'  │ 5    │ '81.24'  │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 2       │ 'helia-fs - ../gc/src'    │ '190.84' │ '5.24'   │ 5    │ '21.17'  │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 3       │ 'helia-mem - src'         │ '657.37' │ '1.52'   │ 5    │ '2.33'   │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 4       │ 'helia-mem - dist'        │ '287.28' │ '3.48'   │ 5    │ '4.09'   │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 5       │ 'helia-mem - ../gc/src'   │ '860.15' │ '1.16'   │ 5    │ '1.32'   │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 6       │ 'kubo - src'              │ '16.50'  │ '60.60'  │ 5    │ '123.53' │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 7       │ 'kubo - dist'             │ '7.01'   │ '142.75' │ 5    │ '477.01' │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 8       │ 'kubo - ../gc/src'        │ '13.77'  │ '72.61'  │ 5    │ '143.97' │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 9       │ 'kubo-direct - src'       │ '17.17'  │ '58.23'  │ 5    │ '120.34' │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 10      │ 'kubo-direct - dist'      │ '9.41'   │ '106.29' │ 5    │ '343.66' │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 11      │ 'kubo-direct - ../gc/src' │ '19.64'  │ '50.92'  │ 5    │ '108.67' │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
└─────────┴───────────────────────────┴──────────┴──────────┴──────┴──────────┴──────────────────────────────────────────────────┘
```

After:

```
┌─────────┬───────────────────────────┬──────────┬──────────┬──────┬──────────┬──────────────────────────────────────────────────┐
│ (index) │ Implementation            │ ops/s    │ ms/op    │ runs │ p99      │ CID                                              │
├─────────┼───────────────────────────┼──────────┼──────────┼──────┼──────────┼──────────────────────────────────────────────────┤
│ 0       │ 'helia-fs - src'          │ '110.42' │ '9.06'   │ 5    │ '35.54'  │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 1       │ 'helia-fs - dist'         │ '44.90'  │ '22.27'  │ 5    │ '96.31'  │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 2       │ 'helia-fs - ../gc/src'    │ '121.47' │ '8.23'   │ 5    │ '35.53'  │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 3       │ 'helia-mem - src'         │ '697.10' │ '1.43'   │ 5    │ '1.99'   │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 4       │ 'helia-mem - dist'        │ '332.42' │ '3.01'   │ 5    │ '3.38'   │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 5       │ 'helia-mem - ../gc/src'   │ '852.31' │ '1.17'   │ 5    │ '2.06'   │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 6       │ 'kubo - src'              │ '15.02'  │ '66.60'  │ 5    │ '143.21' │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 7       │ 'kubo - dist'             │ '8.02'   │ '124.68' │ 5    │ '450.13' │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 8       │ 'kubo - ../gc/src'        │ '16.75'  │ '59.71'  │ 5    │ '126.64' │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
│ 9       │ 'kubo-direct - src'       │ '18.70'  │ '53.49'  │ 5    │ '116.70' │ 'QmY7YuAvtqk9AaMR8mivfyCdSVFT4sJJwLCLYtKYfwAEKf' │
│ 10      │ 'kubo-direct - dist'      │ '9.92'   │ '100.76' │ 5    │ '343.77' │ 'QmdHWaHWXugHHvP2y4SueC1tWUgQBrrzUHUwEMrdNEd7Cn' │
│ 11      │ 'kubo-direct - ../gc/src' │ '18.20'  │ '54.95'  │ 5    │ '116.82' │ 'QmQvJeSxJdCVi8qWwujznXq8Zs2g3gQVNqJUtr9SDUAK9X' │
└─────────┴───────────────────────────┴──────────┴──────────┴──────┴──────────┴──────────────────────────────────────────────────┘
```
  • Loading branch information
achingbrain committed Apr 25, 2024
1 parent f6f7040 commit 87fe460
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 81 deletions.
26 changes: 18 additions & 8 deletions packages/it-glob/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,37 @@
# About

<!--
!IMPORTANT!
Everything in this README between "# About" and "# Install" is automatically
generated and will be overwritten the next time the doc generator is run.
To make changes to this section, please update the @packageDocumentation section
of src/index.js or src/index.ts
To experiment with formatting, please run "npm run docs" from the root of this
repo and examine the changes made.
-->

Like [`glob`](https://npmjs.com/package/glob) but async iterable.

## Example

```javascript
import glob from 'it-glob'

const options = {
cwd // defaults to process.cwd
absolute // return absolute paths, defaults to false
nodir // only yield file paths, skip directories

// all other options are passed to minimatch
}
// All options are passed through to fast-glob
const options = {}

for await (const path of glob('/path/to/file', '**/*', options)) {
console.info(path)
}
```

See the [minimatch docs](https://www.npmjs.com/package/minimatch#options) for the full list of options.
See the [fast-glob docs](https://github.com/mrmlnc/fast-glob#options-3) for the full list of options.

# Install

Expand Down
8 changes: 2 additions & 6 deletions packages/it-glob/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,17 @@
}
},
"scripts": {
"build": "aegir build",
"build": "aegir build --bundle false",
"lint": "aegir lint",
"dep-check": "aegir dep-check",
"test": "aegir test -t node",
"test:node": "aegir test -t node --cov"
},
"dependencies": {
"minimatch": "^9.0.4"
"fast-glob": "^3.3.2"
},
"devDependencies": {
"aegir": "^42.2.5",
"it-all": "^3.0.0"
},
"browser": {
"fs/promises": false,
"path": false
}
}
78 changes: 13 additions & 65 deletions packages/it-glob/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,33 @@
* ```javascript
* import glob from 'it-glob'
*
* const options = {
* cwd // defaults to process.cwd
* absolute // return absolute paths, defaults to false
* nodir // only yield file paths, skip directories
*
* // all other options are passed to minimatch
* }
* // All options are passed through to fast-glob
* const options = {}
*
* for await (const path of glob('/path/to/file', '**\/*', options)) {
* console.info(path)
* }
* ```
*
* See the [minimatch docs](https://www.npmjs.com/package/minimatch#options) for the full list of options.
* See the [fast-glob docs](https://github.com/mrmlnc/fast-glob#options-3) for the full list of options.
*/

import fs from 'fs/promises'
import path from 'path'
import { minimatch } from 'minimatch'
import type { MinimatchOptions } from 'minimatch'

export interface GlobOptions extends MinimatchOptions {
/**
* The current working directory
*/
cwd?: string

/**
* If true produces absolute paths (default: false)
*/
absolute?: boolean

/**
* If true yields file paths and skip directories (default: false)
*/
nodir?: boolean
}
import fs from 'node:fs/promises'
import path from 'node:path'
import fastGlob from 'fast-glob'
import type { Options } from 'fast-glob'

/**
* Async iterable filename pattern matcher
*/
export default async function * glob (dir: string, pattern: string, options: GlobOptions = {}): AsyncGenerator<string, void, undefined> {
export default async function * glob (dir: string, pattern: string, options: Options = {}): AsyncGenerator<string, void, undefined> {
const absoluteDir = path.resolve(dir)
const relativeDir = path.relative(options.cwd ?? process.cwd(), dir)

const stats = await fs.stat(absoluteDir)

if (stats.isDirectory()) {
for await (const entry of _glob(absoluteDir, '', pattern, options)) {
yield entry
}

return
}

if (minimatch(relativeDir, pattern, options)) {
yield options.absolute === true ? absoluteDir : relativeDir
}
}

async function * _glob (base: string, dir: string, pattern: string, options: GlobOptions): AsyncGenerator<string, void, undefined> {
for await (const entry of await fs.opendir(path.join(base, dir))) {
const relativeEntryPath = path.join(dir, entry.name)
const absoluteEntryPath = path.join(base, dir, entry.name)

let match = minimatch(relativeEntryPath, pattern, options)

const isDirectory = entry.isDirectory()

if (isDirectory && options.nodir === true) {
match = false
}

if (match) {
yield options.absolute === true ? absoluteEntryPath : relativeEntryPath
}

if (isDirectory) {
yield * _glob(base, relativeEntryPath, pattern, options)
}
for await (const entry of fastGlob.stream(pattern, {
...options,
cwd: stats.isDirectory() ? dir : process.cwd()
})) {
yield entry.toString()
}
}
6 changes: 4 additions & 2 deletions packages/it-glob/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,16 @@ describe('it-glob', () => {
})

it('should match directories', async () => {
const files = await all(glob(path.resolve(dir, '..', '..'), 'dist/*'))
const files = await all(glob(path.resolve(dir, '..', '..'), 'dist/*', {
onlyFiles: false
}))

expect(files.includes(path.join('dist', 'src'))).to.be.true()
})

it('should skip directories', async () => {
const files = await all(glob(path.resolve(dir, '..', '..'), 'dist/**/*', {
nodir: true,
onlyFiles: true,
dot: true
}))

Expand Down

0 comments on commit 87fe460

Please sign in to comment.