Skip to content

feat(config): Hierarchical site configuration using distributed config files #4660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Apr 15, 2025

Conversation

zhangyx1998
Copy link
Contributor

@zhangyx1998 zhangyx1998 commented Mar 30, 2025

Description

Please see #4659 for more context.

Summary of changes

  1. ‎src/client/app/data.ts:

  2. ‎src/client/app/utils.ts:

    • Added stackView() and supporting utilities to merge multiple layers of objects into a readonly view, without mutating the original objects - this is important because certain layers may be dropped later when user navigates outside its scope.
  3. Configuration Files

    The configuration files are moved from .vitepress/config to their corresponding language subdirectories (e.g. docs/.vitepress/config/en.ts -> docs/en/config.ts) to demonstrate the benefits of the proposed changes. In addition, a language-specific locale is added for 404 pages under /zh/ directory. It is reflected in the rendered preview.

Linked Issues

closes #4659
closes #4654

Additional Context

Please refer to the linked issue for highlights of this PR.


Tip

The author of this PR can publish a preview release by commenting /publish below.

/publish

@brc-dd
Copy link
Member

brc-dd commented Mar 30, 2025

An obvious downside is we can't use node stuff in configs. What if we glob and load all the configs at plugin-level instead of doing it via import.meta.glob in client code? We can create a virtual module that exports extraConfig. Probably resolveUserConfig can be adjusted and reused. That would also avoid hardcoding things in manualChunks.

@brc-dd
Copy link
Member

brc-dd commented Mar 30, 2025

Also, the site appears to be broken. There is probably a circular dependency somewhere in generated code.

@brc-dd
Copy link
Member

brc-dd commented Mar 30, 2025

/publish

Copy link

pkg-pr-new bot commented Mar 30, 2025

npm i https://pkg.pr.new/vitepress@4660

commit: 4995fb9

@zhangyx1998
Copy link
Contributor Author

An obvious downside is we can't use node stuff in configs. What if we glob and load all the configs at plugin-level instead of doing it via import.meta.glob in client code? We can create a virtual module that exports extraConfig. Probably resolveUserConfig can be adjusted and reused. That would also avoid hardcoding things in manualChunks.

This seems like a better solution. I'll try to adapt this approach.

@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Mar 30, 2025

Updates:

1. Removed import.meta.glob from client side. Config file collection is now implemented inside resolveUserConfig().

2. Instead of creating a virtual module @additionalConfig, an optional configuration entry UserConfig.additionalConfig is implemented for better flexibility:

  • When left undefined, resolveUserConfig will automatically collect all eligible config files and organize them into a dictionary as its default value. It utilizes loadConfigFromFile() and hence supports node APIs.

  • When defined as an object, the client code will auto select config layers to apply. For example:

    import en_config from './en'
    import zh_config from './zh'
    export default defineConfig({
        /* ... */
        additionalConfig: {
            '/en/': en_config,
            '/zh/': zh_config,
        }
    })
  • When defined as a function (path: string) => AdditionalConfig[], user can have even higher flexibility defining which configuration layers should be applied, and in what order they will apply:

    export default defineConfig({
        /* ... */
        additionalConfig(path) {
            return [{
                title: `Hello from ${path}`
            }]
        }
    })

    Produces:

    image

@brc-dd could you please review this again? Appreciate it!

@zhangyx1998 zhangyx1998 requested a review from brc-dd March 30, 2025 15:58
@brc-dd
Copy link
Member

brc-dd commented Mar 30, 2025

Thanks a lot! 🙌 Overall looks good. I'll test some stuff locally and merge if nothing pops up.

@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Mar 31, 2025

Summary of update:

Code Cleanup

  • Removed type annotation for injected symbol (VP_SOURCE - internal dev use only).
  • Renamed the injection symbol from VP_SOURCE to [VP_SOURCE]. It is now impossible to be confused with a JS variable nor a legal URL path component. The new symbol is consistent with the one introduced in feat(config, theme): add clientOnly marker #4663 [VP_CLIENT_ONLY]

Typing Helper

  • Added type DeepPartial<...> to reflect the nature of type AdditionalConfig
  • Added type helper defineAdditionaConfig() and defineAdditionalConfigWithTheme()
  • Updated additional config files under /docs/ to reflect the above update.

There is no major change since last review. This PR should be ready to merge.

@brc-dd brc-dd force-pushed the feat/hierarchical-theme-config branch from ee9e572 to 01f600f Compare April 13, 2025 15:41
@brc-dd brc-dd marked this pull request as draft April 13, 2025 18:41
@brc-dd
Copy link
Member

brc-dd commented Apr 13, 2025

Something is wrong with stacking. Try opening http://localhost:5173/zh/guide/what-is-vitepress, then change language to Portuguese from switcher in navbar. It goes to http://localhost:5173/pt/zh/guide/what-is-vitepress but it should go to http://localhost:5173/pt/guide/what-is-vitepress. Also, the debug logs say:

Config Layers for pt/zh/guide/what-is-vitepress.md:
===================================================
1. /pt/config.ts
2. /config.ts
3. locale config (zh)
4. .vitepress/config (root)

Here the third one shouldn't be there? Seems to be broken since the ssr refactor commit - a2a297e

@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Apr 13, 2025

@brc-dd this is fixed.

The fix is very simple: add localeIndex property to localeConfig so it masks the default localeIndex on resolved siteData.


There are actually two issues in the code that makes the problem intertwined:

  1. getLocaleForPath (shared.ts:75) returns the first locale that matches any segment in the path, even if another locale also exist in the same path.

    getLocaleForPath(__VP_SITE_DATA__, 'pt/zh/index.md')
    // "zh"
    getLocaleForPath(__VP_SITE_DATA__, 'zh/pt/index.md')
    // "zh"
    // Search Order
    {
        root: { /* last */ }, // (last)
        zh: { label: "..." }, // 1st
        pt: { label: "..." }, // 2nd
        ru: { label: "..." }, // 3rd
        es: { label: "..." }, // 4th
        ko: { label: "..." }, // 5th
        fa: { label: "..." }, // 6th
    }

    For example, the zh locale will always supersede other locals when /zh/ presents in any part of the path, regardless of where it appears.

  2. According to getLocaleForPath (shared.ts:75), the locale path segment can appear any where in the relativePath. For example, paths zh/foo/bar and foo/zh/bar are both considered zh locale. However, the computed localeLinks simply substitutes the start of a relativePath with the new locale (langes.ts:21).

I think this also needs to be fixed. I can do a separate PR for this later.

@brc-dd brc-dd marked this pull request as ready for review April 15, 2025 15:19
@brc-dd brc-dd marked this pull request as draft April 15, 2025 15:39
@brc-dd brc-dd force-pushed the feat/hierarchical-theme-config branch from 2d31f61 to d5b1563 Compare April 15, 2025 15:40
@brc-dd brc-dd marked this pull request as ready for review April 15, 2025 16:14
@brc-dd brc-dd merged commit c5e2e4d into vuejs:main Apr 15, 2025
10 checks passed
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multi-layer hierarchical configuration overloading
3 participants