Skip to content

Migrate from webpack to vite#37002

Merged
silverwind merged 119 commits intogo-gitea:mainfrom
silverwind:vite
Mar 29, 2026
Merged

Migrate from webpack to vite#37002
silverwind merged 119 commits intogo-gitea:mainfrom
silverwind:vite

Conversation

@silverwind
Copy link
Copy Markdown
Member

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).

iife.js is a classic render-blocking script in <head> (handles web components/early DOM setup). index.js is loaded as a type="module" script in the footer. All other JS chunks are also 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).

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

silverwind and others added 30 commits March 13, 2026 16:59
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) <[email protected]>
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) <[email protected]>
- 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) <[email protected]>
- 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) <[email protected]>
`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) <[email protected]>
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) <[email protected]>
- 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) <[email protected]>
Co-Authored-By: Claude (Opus 4.6) <[email protected]>
In reduced mode (the production default), only sourcemaps for the
index chunks are kept. All other map files including worker maps
are deleted after the build.

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
Signed-off-by: silverwind <[email protected]>
In reduced mode (the production default), node_modules are excluded
from sourcemaps via a transform hook that empties their mappings.
A closeBundle hook then deletes map files with no own code and strips
node_modules sourcesContent from mixed maps, reducing total map size
from 68 MB to 1.5 MB.

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
Replace the separate webcomponents IIFE + deferred module index.js with a single
blocking IIFE index.js (containing bootstrap, jQuery, webcomponents, user-settings,
htmx) and a deferred module index-domready.js loaded in the footer.

This matches webpack's behavior: index.js is a blocking script that sets up globals
before body parsing, while heavy features load asynchronously via index-domready.

- Build index.ts as IIFE via lib mode in closeBundle, CSS extracted by main build
- Add index-domready as separate entry in main build
- Redirect jquery imports in main build to window.jQuery (single instance)
- Export initGlobalErrorHandler to prevent double-init from shared chunks
- Revert inline script type=module changes (blocking index.js makes them unnecessary)
- Add index-domready.css to head_style.tmpl for Vue component styles

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
Replace `import $ from 'jquery'` in fomantic wrappers with global $
(set by globals.ts in the IIFE). This eliminates the need for the
jquery-global Vite plugin since no code in the main build imports jquery.

Move CSS imports from index.ts to index-domready.ts so index.ts is only
in the IIFE build. Remove index from main build entries — no duplicate
build, no strip-css plugin needed. Single CSS file (index-domready.css).

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
Replace os.Stat/os.ReadFile with AssetFS().Open() which handles both
disk files and embedded bindata, respecting StaticRootPath.

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
Use sync.Once + cached AssetFS for manifest loading. In production the
manifest is loaded once with zero per-request overhead. In dev mode,
mod time is checked for watch-frontend hot reload.

Remove htmx import from index.ts (IIFE) — it was being loaded twice
(IIFE + index-domready), causing htmx to process hx-* attributes twice
and breaking pages with htmx content. htmx in index-domready (deferred
module) executes before DOMContentLoaded, so the init timing is correct.

IIFE size: 204KB -> 134KB.

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
@delvh
Copy link
Copy Markdown
Member

delvh commented Mar 29, 2026

Gitpod is actually used by some maintainers.
That's the reason why it was added in the first place.

@TheFox0x7
Copy link
Copy Markdown
Contributor

Well egg on my face then. :)

@wxiaoguang
Copy link
Copy Markdown
Contributor

@silverwind
Copy link
Copy Markdown
Member Author

silverwind commented Mar 29, 2026

Why don't we serve the vite assets via its port directly

That'd require CORS and CORS is a pain in the ass that I strongly prefer to avoid by using reverse proxies. There's two valid setups imho:

  1. Have the reverse proxy in go. A bit complicated because of the needed URL filtering logic.
  2. Have the reverse proxy in vite. No filtering logic needed, but imho not right because your client will not be talking directly to the go backend.

So, I think the current approach with the go proxy is the best way that avoid CORS. Also I think we should not test primarly with CORS. It's pre-flight requests are not representative on how the server is commonly ran.

@wxiaoguang
Copy link
Copy Markdown
Contributor

wxiaoguang commented Mar 29, 2026

Why don't we serve the vite assets via its port directly

So, I think the current approach with the go proxy is the best way that avoid CORS.

It should be good to keep it now. I have refactored the vite proxy middleware. It is 99% clear to me now, and the accesses are properly limited (unless vite dev server has security problems in its websocket handler)

@yardenshoham
Copy link
Copy Markdown
Member

https://github.com/go-gitea/gitea/commits/main/.gitpod.yml

@yp05327 @yardenshoham @anbraten @6543 @techknowlogick are you still using gitpod?

No they killed that product. Now they are some AI product

@TheFox0x7
Copy link
Copy Markdown
Contributor

TheFox0x7 commented Mar 29, 2026

huh. I would've kept the branding but their pick.
https://ona.com/stories/gitpod-is-now-ona

Anyway the gitpod.yaml is dead - it uses devcontainers now

@silverwind
Copy link
Copy Markdown
Member Author

Let's remove gitpod then.

Gitpod has shut down, remove the configuration file, badge links
from all READMEs, labeler entry, and devcontainer comment reference.

Co-Authored-By: Claude (Opus 4.6) <[email protected]>
@silverwind
Copy link
Copy Markdown
Member Author

Gitpod removed, lets merge it.

@silverwind silverwind added the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Mar 29, 2026
@silverwind silverwind enabled auto-merge (squash) March 29, 2026 08:58
@silverwind silverwind merged commit 0ec66b5 into go-gitea:main Mar 29, 2026
26 checks passed
@silverwind silverwind deleted the vite branch March 29, 2026 10:24
@GiteaBot GiteaBot added this to the 1.26.0 milestone Mar 29, 2026
@GiteaBot GiteaBot removed the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Mar 29, 2026
@wxiaoguang
Copy link
Copy Markdown
Contributor

When running "make watch-frontend", it doesn't generate "manifest.json" sometimes

image image

@silverwind
Copy link
Copy Markdown
Member Author

silverwind commented Mar 29, 2026

dev server does not generate a manifest, it does not need one. manifest is for prod build only.

@wxiaoguang
Copy link
Copy Markdown
Contributor

dev server does not generate a manifest, it does not need one.

But, you made the "theme" functions depend on manifest.

You will see no theme from fresh start.

@wxiaoguang
Copy link
Copy Markdown
Contributor

image

@silverwind
Copy link
Copy Markdown
Member Author

silverwind commented Mar 29, 2026

Hmm yes that's an issue, we have to make dev mode themes work without a previous build. And they should be HMR-able. I can check a bit later.

zjjhot added a commit to zjjhot/gitea that referenced this pull request Mar 30, 2026
* main: (35 commits)
  Correct swagger annotations for enums, status codes, and notification state (go-gitea#37030)
  Update Nix flake (go-gitea#37024)
  Bump go and python versions in nix flake (go-gitea#37031)
  Make task list checkboxes clickable in the preview tab (go-gitea#37010)
  Add support for in_progress event in workflow_run webhook (go-gitea#36979)
  Fix various problems (go-gitea#37029)
  Update AI Contribution Policy (go-gitea#37022)
  Migrate from webpack to vite (go-gitea#37002)
  Upgrade yaml (go-gitea#37015)
  Fix issue label deletion with Actions tokens (go-gitea#37013)
  Hide delete branch or tag buttons in mirror or archived repositories. (go-gitea#37006)
  Update AGENTS.md with additional guidelines (go-gitea#37018)
  Optimize 'refreshAccesses' to perform update without removing then adding (go-gitea#35702)
  Fix relative-time RangeError (go-gitea#37021)
  Restyle Workflow Graph (go-gitea#36912)
  Update message severity colors, fix navbar double border (go-gitea#37019)
  Clean up checkbox cursor styles (go-gitea#37016)
  add missing cron tasks to example ini (go-gitea#37012)
  Add e2e tests for server push events (go-gitea#36879)
  Update JS dependencies (go-gitea#37001)
  ...
silverwind added a commit to silverwind/gitea that referenced this pull request Mar 30, 2026
* origin/main: (69 commits)
  Correct swagger annotations for enums, status codes, and notification state (go-gitea#37030)
  Update Nix flake (go-gitea#37024)
  Bump go and python versions in nix flake (go-gitea#37031)
  Make task list checkboxes clickable in the preview tab (go-gitea#37010)
  Add support for in_progress event in workflow_run webhook (go-gitea#36979)
  Fix various problems (go-gitea#37029)
  Update AI Contribution Policy (go-gitea#37022)
  Migrate from webpack to vite (go-gitea#37002)
  Upgrade yaml (go-gitea#37015)
  Fix issue label deletion with Actions tokens (go-gitea#37013)
  Hide delete branch or tag buttons in mirror or archived repositories. (go-gitea#37006)
  Update AGENTS.md with additional guidelines (go-gitea#37018)
  Optimize 'refreshAccesses' to perform update without removing then adding (go-gitea#35702)
  Fix relative-time RangeError (go-gitea#37021)
  Restyle Workflow Graph (go-gitea#36912)
  Update message severity colors, fix navbar double border (go-gitea#37019)
  Clean up checkbox cursor styles (go-gitea#37016)
  add missing cron tasks to example ini (go-gitea#37012)
  Add e2e tests for server push events (go-gitea#36879)
  Update JS dependencies (go-gitea#37001)
  ...

# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	tests/e2e/utils.ts
#	web_src/css/themes/theme-gitea-dark.css
#	web_src/css/themes/theme-gitea-light.css
#	web_src/js/bootstrap.ts
#	web_src/js/features/codeeditor.ts
#	web_src/js/modules/errors.test.ts
#	webpack.config.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. 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

8 participants