Skip to content

When using tinyglobby get wrong components files order #831

Open

Description

@SoulLyoko

Describe the bug

The latest version uses tinyglobby and gets the wrong order:
Image

v28.0.0 uses fast-glob to get the correct order:
Image

What I want to get is that web/src/components/TheFooter.vue overrides layer-shared/src/components/TheFooter.vue.

Reproduction

no repo

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (12) x64 Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
    Memory: 8.56 GB / 31.87 GB
  Binaries:
    Node: 22.10.0 - C:\Program Files\nodejs\node.EXE     
    Yarn: 1.22.22 - C:\Program Files\nodejs\yarn.CMD     
    npm: 10.9.0 - C:\Program Files\nodejs\npm.CMD        
    pnpm: 9.15.5 - C:\Program Files\nodejs\pnpm.CMD      
  Browsers:
    Edge: Chromium (133.0.3065.69)
    Internet Explorer: 11.0.19041.4355

Used Package Manager

npm

Validations

  • Follow our Code of Conduct
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.
  • The provided reproduction is a minimal reproducible of the bug.

Activity

  1. ilyaliao commented on Jun 20, 2025

    @ilyaliao
    Collaborator

    However, according to the fast-glob docs, results are returned in arbitrary order.

  2. samuelwei commented on Sep 25, 2025

    @samuelwei

    I also need a deterministic method for overwriting, whereby one directory should take precedence over the other.

  3. userquin commented on Sep 25, 2025

    @userquin
    Member

    I added a sort function where you can add your logic to sort your components, the test added in the PR using simple ascending/descending sorts, I guess with this callback applied before generating the output record should be fine for your use case, maybe we can use asc sort by default.

    We can also iterate later using the dirs order to apply 2 sorts, first using the dirs order and then by name for each dirs entry.

  4. userquin commented on Sep 25, 2025

    @userquin
    Member

    However, according to the fast-glob docs, results are returned in arbitrary order.

    This is not fine if the arbitrary order breaks for example the dirs/globs order. Looks like the globs being sorted in ascending order.

    I'm going to check tinyglobby, looks like a bug to me.

  5. userquin commented on Sep 25, 2025

    @userquin
    Member
  6. samuelwei commented on Sep 25, 2025

    @samuelwei

    I added a sort function where you can add your logic to sort your components, the test added in the PR using simple ascending/descending sorts, I guess with this callback applied before generating the output record should be fine for your use case, maybe we can use asc sort by default.

    We can also iterate later using the dirs order to apply 2 sorts, first using the dirs order and then by name for each dirs entry.

    This would be extremely helpful, I was looking for something like this in the source code this evening and tried to setup a custom resolver to replace the glob / dir feature, but it’s not really good, especially for developing where files are added, renamed and removed. Really appreciate your work on a fix

  7. userquin commented on Sep 25, 2025

    @userquin
    Member

    I'll try adding a new option at tinyglobby, unimport and unplugin-auto-import may have the same issue.

    /cc @danielroe @antfu

  8. userquin commented on Sep 25, 2025

    @userquin
    Member
  9. userquin commented on Sep 25, 2025

    @userquin
    Member

    PR sent to tinyglobby 🤞 SuperchupuDev/tinyglobby#167

  10. samuelwei commented on Sep 26, 2025

    @samuelwei

    I'll try adding a new option at tinyglobby, unimport and unplugin-auto-import may have the same issue.

    /cc @danielroe @antfu

    Disadvantage of adding it to tinyglobby, you can not replace this dependency with another glob lib in the future.
    Is there a big performance diff. in running the glob for each dir/glob speratly vs. all at once? If not, running the glob search sequentially could also result in a deterministic output.

  11. userquin commented on Sep 26, 2025

    @userquin
    Member

    Yeah, since we also have a watcher, we need to sort the files at least when adding files, maybe we need some helper function at tiny-globby, it is using picomatch or maybe just add some logic there here: we only need the matched patterns and the picomatch options.

    Will try another approach, running N scans (1 per pattern/glob) instead 1 global. Maybe we can also move sync to async scan, I need to review the code and the perf. We still have the same issue about the watcher when adding new files.

    I also need to check unimport and unplugin-auto-import (not the same problem, since unimport has an order where you can apply some priority => not sure if it is a nuxt thingy) => unimport type => usage https://github.com/elk-zone/elk/blob/main/nuxt.config.ts#L90-L94

  12. userquin commented on Sep 26, 2025

    @userquin
    Member

    @samuelwei SuperchupuDev/tinyglobby#168 (new helper function at tinyglobby abstracting picomatch usage, this way, if tinyglobby changes its internal matcher, this repo should be also fine => don't require to add picomatch or switch to xxxmatch when tinyglob changes it internally)

    I'm working on the PR to include the abstraction using Generator, here some work on my local, the sortFilesByGlobPrecedence should go to src/core/fs/glob.ts, and should use the new compileGlob helper function at SuperchupuDev/tinyglobby#168:

    // src/core/context.ts
    export class Context {
      // other entries
      private* sortFilesByGlobPrecedence(files: Iterable<string>): Generator<string, undefined, void> {
        const processedFiles = new Set<string>()
        const allFiles = Array.from(files)
    
        const { globs, ...rest } = this.options
        for (const glob of globs) {
          const isMatch = picomatch(glob, rest)
    
          for (const file of allFiles) {
            if (!processedFiles.has(file) && isMatch(file)) {
              processedFiles.add(file)
              yield file
            }
          }
        }
      }
    
      private* preparePaths(): Generator<string, undefined, void> {
        if (this.options.sortByGlob) {
          yield* this.sortFilesByGlobPrecedence(this._componentPaths)
        }
        else {
          yield* this._componentPaths
        }
      }
    
      private* applyPathsSorting(files: Iterable<string>): Generator<string, undefined, void> {
        const generator = this.options.sort
        if (generator) {
          const {
            root,
            globs,
            globsExclude,
            resolvedDirs,
          } = this.options
          yield* generator({
            root,
            globs,
            globsExclude,
            resolvedDirs,
          }, Array.from(files))
        }
        else {
          yield* files
        }
      }
    
      private updateComponentNameMap() {
        this._componentNameMap = {}
    
        const { prefix, excludeNames, allowOverrides } = this.options
        for (const path of this.applyPathsSorting(this.preparePaths())) {
          const fileName = getNameFromFilePath(path, this.options)
          const name = prefix
            ? `${pascalCase(prefix)}${pascalCase(fileName)}`
            : pascalCase(fileName)
          if (isExclude(name, excludeNames)) {
            debug.components('exclude', name)
            continue
          }
          if (this._componentNameMap[name] && !allowOverrides) {
            console.warn(`[unplugin-vue-components] component "${name}"(${path}) has naming conflicts with other components, ignored.`)
            continue
          }
    
          this._componentNameMap[name] = {
            as: name,
            from: path,
          }
        }
      }
    }

    and here the new sort and sortByGlob options:

    // src/types.ts
    /**
     * Plugin options.
     */
    export interface Options {
      // other options
      /**
       * Enable `globs` sort for scanned files.
       *
       * `unplugin-vue-components` using `tinyglobby` to scan files, which is non-deterministic.
       */
      sortByGlob?: true
    
      /**
       * Generator to provide files before preparing the components.
       *
       * @param options The resolved options.
       * @param files The scanned files.
       */
      sort?: (options: Pick<ResolvedOptions, 'root' | 'resolvedDirs' | 'globs' | 'globsExclude'>, files: string[]) => Generator<string, undefined, void>
    }
  13. userquin commented on Sep 26, 2025

    @userquin
    Member

    @samuelwei @SoulLyoko my PR here updated using pkg-pr-new from compileGlobs PR at tinyglobby, I'm going to add pkg-pr-new also to unplugin-vue-components.

  14. samuelwei commented on Oct 27, 2025

    @samuelwei

    @userquin Any progress on this?

  15. userquin commented on Oct 27, 2025

    @userquin
    Member

    No, the PR here should be ready (pr moved to draft), awaiting tinyglobby: SuperchupuDev/tinyglobby#168 (comment)

  16. samuelwei commented on Jan 19, 2026

    @samuelwei

    @userquin I guess tinyglobby is not going to add this feature. Can we still get a fix in this project?

  17. userquin commented on Jan 27, 2026

    @userquin
    Member

    @samuelwei I'm preparing a new PR at tinyglobby (a lot of changes on main = feat: add sort support) 🤞 , I tried with a new package but I need to copy almost every module at tinyglobby... 🤦

    SuperchupuDev/tinyglobby#189

  18. userquin commented on Jan 28, 2026

    @userquin
    Member

    can anyone check using "unplugin-vue-components": "https://pkg.pr.new/unplugin-vue-components@905"? the vite-vue3 example is working here

  19. SoulLyoko commented on Jan 28, 2026

    @SoulLyoko
    Author

    can anyone check using "unplugin-vue-components": "https://pkg.pr.new/unplugin-vue-components@905"? the vite-vue3 example is working here

    I will try it in my project later🤞

  20. userquin commented on Jan 28, 2026

    @userquin
    Member

    can anyone check using "unplugin-vue-components": "https://pkg.pr.new/unplugin-vue-components@905"? the vite-vue3 example is working here

    I will try it in my project later🤞

    Read the new sort option 🙏 , you need to configure the correct dirs, check the sort test snapshot and the tests, use sort: 'pattern':

  21. SoulLyoko commented on Jan 28, 2026

    @SoulLyoko
    Author

    @userquin I test my project with sort: 'pattern', it works well👍

  22. samuelwei commented on Jan 28, 2026

    @samuelwei

    @userquin Tested in my project with sort: 'pattern', it worked

    However I do not understand whats the difference between pattern, pattern-asc and pattern-desc

    Using sort: 'pattern' and sort: 'pattern-desc' worked, sort: 'pattern-asc' didn't work in my case where I wanted to overwrite a component.

    Image
  23. userquin commented on Jan 28, 2026

    @userquin
    Member

    There are some edge cases where the tinyglobby consumer may want to sort the files:

    • tinyglobby: sort first applying pattern, inside the resulting string[] for each pattern the consumer may want to sort them asc or desc (pattern-asc and pattern-desc respectivelly) => this is the default behavior at tinyglobby
    • unplugin-vue-components: sort the patterns and the resulting string[] for each pattern => in your screenshot, won't work at tinyglobby since the patterns won't be sorted and then the files will preserve the dirs => first resources/js (sort the files inside in ascending) then resources/custom/js (sort the files inside in ascending)

    Suppose we have this: dirs: ['a', 'b'] where a contains a/a.vue and a/b.vue ad b contains b/a.vue and b/b.vue, the result will be:

    • tinygloby:
      • pattern: [a/a.vue, a/b.vue, b/a.vue, b/b.vue] or any combination of a.vue and b.vue (first ones from a and last ones from b), for example: [a/b.vue, a/a.vue, b/b.vue, b/a.vue] => tinyglobby will create in this case 2 arrays, one per pattern and before returning them will sort the files if using pattern-asc/desc
      • pattern-desc: [a/b.vue, a/a.vue, b/b.vue, b/a.vue]; if you change the dirs (patterns) to be ['b', 'a'] then [b/b.vue, b/a.vue, a/b.vue, a/a.vue]
      • pattern-asc: [a/a.vue, a/b.vue, b/a.vue, b/b.vue]; if you change the dirs (patterns) be ['b', 'a'] then [b/a.vue, b/b.vue, a/b.vue, a/a.vue]
    • unplugin-vue-components: since we need to override components when using pattern, we need to sort also the patterns (patterns built from dirs)
  24. YunYouJun commented on Oct 19, 2026

    @YunYouJun
    Contributor

    Hi! Thanks @userquin for all the work on #905 and SuperchupuDev/tinyglobby#189. Since the tinyglobby PR seems to be stuck on package-size concerns, I tried a smaller fix that doesn't touch the glob layer at all: #922.

    The idea: in Context.updateComponentNameMap(), sort the collected paths by the index of the first dirs/globs entry each path matches (with picomatch, already a dependency) and break ties with localeCompare. Later dirs then override earlier ones under allowOverrides, as they did with fast-glob in v28. Because the sort runs each time the map is rebuilt, files added through HMR follow the same priority. It's about 15 lines plus tests, with no new option and no dependency change.

    I see it as a complement to #905, not a replacement. It fixes the default order, and the sort option from #905 could still be layered on top later for anyone who wants different ordering inside a glob.

    We've been running this as a patch in Valaxy (which layers core → theme → addons → user component dirs), and it would let us drop our vendored copy. Would the maintainers be open to taking it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions