Skip to content

Migrate from webpack to Vite 8#36896

Open
silverwind wants to merge 57 commits intogo-gitea:mainfrom
silverwind:vite
Open

Migrate from webpack to Vite 8#36896
silverwind wants to merge 57 commits intogo-gitea:mainfrom
silverwind:vite

Conversation

@silverwind
Copy link
Member

@silverwind silverwind commented Mar 13, 2026

Replace webpack with Vite 8 as the frontend bundler. Frontend build is around 3-4 times faster than before. Will work on all platforms including riscv64 (via wasm).

JS loading order is exactly like before, index.js remains a classic script that is render-blocking. All other JS chunks are now module scripts (supported in all browsers since 2018).

Entry filenames are content-hashed (e.g. index.C6Z2MRVQ.js) and resolved at runtime via the Vite manifest, eliminating the ?v= cache busting (which was unreliable in some scenarios like vscode dev build).

Fixes: #17793
Docs: https://gitea.com/gitea/docs/pulls/364

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Mar 13, 2026
Replace webpack with Vite 8 (Rolldown) as the frontend bundler.

Key changes:
- Replace webpack.config.ts with vite.config.ts using Rolldown
- Use native ES modules with import maps for cache busting
- Build web components as a separate blocking IIFE bundle to prevent
  flash of unstyled content (same behavior as webpack's blocking script)
- Update all dynamic imports from webpack chunk comments to standard
  dynamic import() syntax
- Replace process.env.TEST with import.meta.env.MODE
- Add vite/client types to tsconfig.json
- Update Monaco error suppression regex for new chunk names
- Rename WEBPACK_* Makefile variables to FRONTEND_*

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@silverwind silverwind marked this pull request as draft March 13, 2026 16:33
Replace query-string cache busting (?v=) with content-hashed entry
filenames for JS and CSS assets. Vite now generates a manifest
(.vite/manifest.json) mapping unhashed to hashed paths. A new Go-side
AssetPath function reads the manifest and resolves paths in templates.

This eliminates the need for the importmap workaround that was required
because chunks imported the entry module without the ?v= query parameter.

- Add modules/public/manifest.go with mtime-based cache invalidation
  so dev mode auto-detects frontend rebuilds
- Add AssetPath template function for hashed path resolution
- Update all templates to use AssetPath instead of ?v=AssetVersion
- Pass sharedWorkerPath via window.config for the SharedWorker URL
- Rename eventsource.sharedworker to sharedworker
- Fix markup_external_test.go for type="module" and hashed paths

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
silverwind and others added 2 commits March 13, 2026 18:07
- Add content hash to webcomponents.js IIFE build and append its
  entry to the Vite manifest
- Use AssetPath for swagger asset paths in openapi.go renderer
- Strip content hash from theme CSS filenames in webtheme.go to
  correctly extract internal theme names
- Remove AssetVersion variable and template function, now fully
  replaced by content-hashed filenames via manifest
- Rename AssetPath to GetAssetPath

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@silverwind silverwind marked this pull request as ready for review March 13, 2026 17:10
silverwind and others added 4 commits March 13, 2026 18:25
- Rename AssetPath to GetAssetPath in template helper and all templates
- Clean up stale webcomponents files before IIFE rebuild
- Add comment clarifying TypeError catch for navigation-during-import

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
`build()` returns an array for IIFE lib builds, so the
`'output' in result` check silently skipped the manifest append.
Handle both array and single-object return types.

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@lafriks
Copy link
Member

lafriks commented Mar 13, 2026

But this will break Firefox support?

@silverwind
Copy link
Member Author

silverwind commented Mar 13, 2026

But this will break Firefox support?

Why should it? I've already tested everything in Firefox.

The only new browser requirement is module scripts which are supported in Firefox since 2018.

The webcomponents directory had both index.ts and
webcomponents-blocking.ts with identical content.
Remove the duplicate and use index.ts as the entry.

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates Gitea’s frontend build pipeline from Webpack to Vite 8, switching to content-hashed asset filenames resolved at runtime via a manifest to improve build performance and cache correctness.

Changes:

  • Replace Webpack configuration/build flow with a new Vite 8 build (including manifest generation and hashed outputs).
  • Update backend + templates to resolve asset filenames via public.GetAssetPath / GetAssetPath instead of ?v={{AssetVersion}}.
  • Adjust frontend entrypoints and dynamic imports for Vite, including a dedicated blocking webcomponents bundle and updated Monaco worker wiring.

Reviewed changes

Copilot reviewed 54 out of 60 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
webpack.config.ts Removed legacy Webpack build configuration.
vite.config.ts Added Vite 8 build config, manifest output, webcomponents IIFE build, and license generation.
package.json Replaced Webpack deps with Vite/Vite Vue plugin and related tooling.
pnpm-lock.yaml Lockfile updates reflecting bundler/tooling migration.
types.d.ts Removed webpack-specific module typing.
tsconfig.json Switched TS global types from webpack/module to vite/client.
eslint.config.ts Dropped globals.webpack for web sources.
Makefile Replaced webpack build/watch targets with Vite equivalents and new outputs.
.gitignore Ignored Vite manifest output directory under public/assets/.vite.
web_src/js/webcomponents/webcomponents-blocking.ts New blocking webcomponents entry used for FOUC avoidance.
web_src/js/vitest.setup.ts Updated unit test window config to remove webpack globals and add shared worker path field.
web_src/js/utils/testhelper.ts Updated “unit test mode” detection for Vite/Vitest.
web_src/js/utils/dom.test.ts Adjusted Vitest expectation API usage.
web_src/js/utils.test.ts Adjusted Vitest expectation API usage.
web_src/js/standalone/swagger.ts Ensured standalone CSS is imported from the entrypoint for Vite bundling.
web_src/js/standalone/external-render-iframe.ts Ensured standalone CSS is imported from the entrypoint for Vite bundling.
web_src/js/standalone/devtest.ts Ensured standalone CSS is imported from the entrypoint for Vite bundling.
web_src/js/render/plugins/pdf-viewer.ts Removed webpack chunk annotations; rely on Vite dynamic import.
web_src/js/render/plugins/3d-viewer.ts Removed webpack chunk annotations; rely on Vite dynamic import.
web_src/js/modules/sortable.ts Removed webpack chunk annotation; keep dynamic import for code-splitting.
web_src/js/modules/monaco.ts New Monaco module that wires Vite ?worker imports and exports Monaco API.
web_src/js/markup/refissue.ts Removed webpack chunk annotation; use Vite dynamic import for Vue component.
web_src/js/markup/mermaid.ts Removed webpack chunk annotations; use Vite dynamic import.
web_src/js/markup/math.ts Switched KaTeX + CSS loading to plain dynamic imports for Vite.
web_src/js/markup/asciicast.ts Switched asciinema player + CSS loading to plain dynamic imports for Vite.
web_src/js/index.ts Moved CSS imports into the JS entry; updated dynamic import error handling.
web_src/js/globals.d.ts Updated window.config shape; added MonacoEnvironment typing and *?worker module declaration.
web_src/js/features/tribute.ts Removed webpack chunk annotation; rely on Vite dynamic import.
web_src/js/features/stopwatch.ts Switched SharedWorker URL construction to use manifest-resolved worker path.
web_src/js/features/notification.ts Switched SharedWorker URL construction to use manifest-resolved worker path.
web_src/js/features/sharedworker.ts Added new SharedWorker implementation for event streaming.
web_src/js/features/repo-issue-pull.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/repo-findfile.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/recent-commits.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/heatmap.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/dropzone.ts Switched Dropzone + CSS loading to plain dynamic imports for Vite.
web_src/js/features/contributors.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/comp/Cropper.ts Removed webpack chunk annotation; rely on Vite dynamic import.
web_src/js/features/comp/ComboMarkdownEditor.ts Switched EasyMDE + CSS loading to plain dynamic imports for Vite.
web_src/js/features/colorpicker.ts Switched color picker + CSS loading to plain dynamic imports for Vite.
web_src/js/features/codeeditor.ts Updated Monaco import to use the new modules/monaco.ts wrapper.
web_src/js/features/code-frequency.ts Removed webpack chunk annotation for Vue component dynamic import.
web_src/js/features/citation.ts Removed webpack chunk annotations; rely on Vite dynamic imports.
web_src/js/features/captcha.ts Removed webpack chunk annotation; rely on Vite dynamic import.
web_src/js/bootstrap.ts Removed webpack public path setup; adjusted ignore patterns and asset base URL logic.
templates/swagger/ui.tmpl Updated Swagger assets to use GetAssetPath and module script.
templates/status/500.tmpl Updated “minimal template functions” comment to reference GetAssetPath.
templates/shared/combomarkdowneditor.tmpl Converted inline script to module script.
templates/repo/diff/box.tmpl Converted inline script to module script and adjusted file-tree visibility init.
templates/devtest/devtest-header.tmpl Updated devtest CSS link to GetAssetPath.
templates/devtest/devtest-footer.tmpl Updated devtest JS to module script + GetAssetPath.
templates/base/head_style.tmpl Updated core CSS and theme CSS to use GetAssetPath.
templates/base/head_script.tmpl Removed AssetVersion usage; added sharedWorkerPath config; added blocking webcomponents script and module index.js script via GetAssetPath.
modules/templates/helper.go Exposed GetAssetPath to templates (replacing AssetVersion).
modules/setting/server.go Removed AssetVersion from server settings initialization.
modules/public/manifest.go Added Vite manifest loader/cache + GetAssetPath resolver.
modules/public/manifest_test.go Added unit tests for manifest parsing and GetAssetPath fallback behavior.
modules/markup/render.go Updated external render iframe asset URLs to resolve via GetAssetPath and module script.
modules/markup/external/openapi.go Updated OpenAPI renderer to use GetAssetPath-resolved swagger assets and module script.
tests/integration/markup_external_test.go Updated expected HTML to match module script + manifest-resolved asset paths.
services/webtheme/webtheme.go Stripped Vite content hash from theme internal names when loading theme metadata.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

silverwind and others added 3 commits March 13, 2026 19:06
- Add comment explaining ENABLE_SOURCEMAP=reduced backward compatibility
- Add dev-licenses-stub plugin so licenses.txt exists in dev builds

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@silverwind
Copy link
Member Author

silverwind commented Mar 15, 2026

Actually the mermaid-core group caused a regression in the domready chunk because it contains a shared js-yaml dependency which causes rolldown to pull mermaid-parser into index-domready. As demonstrated by that, manual chunking is tricky to get right, and imho barely worth it for the chance at regressions it can cause. Best to go with the default chunking, even if it results in more files.

@wxiaoguang
Copy link
Contributor

wxiaoguang commented Mar 15, 2026

But why home page also loads "mermaid"?

I don't remember that webpack's output will load the unrelated chunks on home page.

Details image

@silverwind
Copy link
Member Author

But why home page also loads "mermaid"?

because of the bug mentioned in #36896 (comment) caused by js-yaml shared dependency.

@wxiaoguang
Copy link
Contributor

Best to go with the default chunking, even if it results in more files.

OK, if you can clearly document the behavior and most people agree to increase most page's loading time.

@silverwind
Copy link
Member Author

silverwind commented Mar 15, 2026

OK, if you can clearly document the behavior and most people agree to increase most page's loading time.

Async chunks do not increase page load times, those scripts are deferred, so all they cause is the deferred JS to become active maybe a few milliseconds later, dependant on how many waterfall effects there are.

@wxiaoguang
Copy link
Contributor

OK, if you can clearly document the behavior and most people agree to increase most page's loading time.

Async chunks do not increase page load times, those scripts are deferred, so all they cause is the deferred JS to become active maybe a few milliseconds later, dependant on how many waterfall effects there are.

It increases "network time" during the page loading. Don't you see that in your screenshots?

If the "network request" doesn't finish, how can the JS scripts get executed? If the JS scripts don't execute, how can the page JS-related functions work?

@silverwind
Copy link
Member Author

It increases "network time" during the page loading. Don't you see that in your screenshots?

The page is interactive at the same time regardless of how many async chunks, the loading times in the footer are variable, not representative. Only the IIFE chunk is render-blocking and therefor involved in the actual page load time.

silverwind and others added 2 commits March 15, 2026 14:38
Use includeDependenciesRecursively: false to prevent the mermaid
codeSplitting group from absorbing shared dependencies like the
preload-helper, which would make the mermaid chunk a static
dependency of every page.

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@silverwind
Copy link
Member Author

svg shouldn't be chunked, utils shouldn't be chunked, fetch shouldn't be chunked.

Let me quote Claude on this:

svg.ts is already inlined into the entry. The remaining two (utils 8.9 kB, fetch 0.5 kB) exist because they're shared between the index-domready and swagger entry points. Rolldown automatically splits shared code across entries.

So the only way to un-chunk them is remove them from the swagger entry point.

silverwind and others added 2 commits March 15, 2026 14:41
Use native fetch in swagger instead of the shared fetch wrapper to
eliminate utils and fetch shared chunks. Broaden vue group to include
all @VUE packages and rename to just "vue".

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@wxiaoguang
Copy link
Contributor

Although you act as a "frontend expert", you knows nothing about page loading.

Just like you didn't understand what is "async": #17386 (comment)

@silverwind
Copy link
Member Author

svg shouldn't be chunked, utils shouldn't be chunked, fetch shouldn't be chunked.

All fixed except svg which must remain a chunk because 6 Vue components import it which themselves are lazy-loaded.

@wxiaoguang wxiaoguang dismissed their stale review March 15, 2026 13:49

dismiss

@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged labels Mar 15, 2026
@silverwind
Copy link
Member Author

Although you act as a "frontend expert", you knows nothing about page loading.

Just like you didn't understand what is "async": #17386 (comment)

Async and module scripts are non-parser-blocking, meaning the browser will render the page and then execute the scripts after page load when DOMContentReady fires.

Signed-off-by: silverwind <me@silverwind.io>
@silverwind silverwind marked this pull request as draft March 15, 2026 15:07
@silverwind
Copy link
Member Author

Will do a few more refactors in vite config and maybe try to get the chunk number down a bit more.

silverwind and others added 2 commits March 15, 2026 21:30
- Remove unused fileURLToPath, use join(import.meta.dirname, ...) instead
- Remove unnecessary process.env.NODE_ENV define from IIFE build
- Remove dead webcomponents cleanup glob
- Remove unnecessary try/catch around manifest parsing
- Simplify enableSourcemap ternary
- Simplify commonViteOpts generic signature
- Simplify assetFileNames function
- Use names instead of deprecated name in assetFileNames

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
@silverwind
Copy link
Member Author

Down to 197 files wil latest tweaks. Nothing more is feasible in terms of de-chunking and the code is already rather hacky. Ultimatlely rolldown optimizes for smallest possible total size, which is ideal for binary size, and given caching and HTTP 2, a non-issue in practice.

@silverwind silverwind marked this pull request as ready for review March 15, 2026 21:01
silverwind and others added 3 commits March 15, 2026 22:01
…s group

Tippy.js checks process.env.NODE_ENV and is pulled into the IIFE
bundle via overflow-menu.ts -> modules/tippy.ts. The citation-js
codeSplitting group breaks plugin registration because
includeDependenciesRecursively: false leaves the Cite class in a
separate chunk.

Co-Authored-By: Claude (Opus 4.6) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 1 This PR needs approval from one additional maintainer to be merged. modifies/dependencies modifies/docs modifies/frontend modifies/go Pull requests that update Go code modifies/internal modifies/templates This PR modifies the template files topic/code-linting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider replacing Webpack with Vite

7 participants