Migrate to Vite 8#12411
Conversation
Migrate build from esbuild to Vite 8 - Replace esbuild + standalone CSS concat with Vite (`vite.config.ts`, `main.ts` entry) - Dev: `npm start` runs `build:data` then Vite HMR (no separate JS watch/server) - Prod: `dist/iD.js` / `dist/iD.min.js` / `dist/iD.css` with stable names for embedders - Move env config from esbuild `define` to `import.meta.env` (`config/env.ts`, `config/id.ts`) - Keep PostCSS plugin for `.ideditor` scoping + dark-mode duplication; Lightning CSS for autoprefix - Hand-maintain `dist/index.html` (minimal diff: inline loader → `<script type="module">`) - Merge Vitest into `vite.config.ts`; broaden `tsconfig` to `**/*.ts` - PR preview deploys from `dist/`; add `netlify.toml` - Bump Vite ecosystem deps to latest (vite, vitest, postcss, browserslist, rollup-plugin-visualizer) - Suppress Vite warning for test data imports - Simplify build and dev scripts
| * Client env values from import.meta.env (Vite envPrefix: 'ID_'). | ||
| * Used by config/id.ts for CDN URLs, API connection, and feature flags. | ||
| */ | ||
| export const idPresetsCdnUrl: string | null = import.meta.env.ID_PRESETS_CDN_URL ?? null; |
There was a problem hiding this comment.
Note: We override the Vite default envPrefix for public env.* to ID_* which is why this works.
There was a problem hiding this comment.
Same idea, now in config/env.ts. We could preserve the names ENV_* but given that config/id.ts reassigns many of them anyways, this looks nicer to me.
There was a problem hiding this comment.
Nearly each line was touched, so I migrated this to TS as a byproduct.
There was a problem hiding this comment.
Vite and lightning handles the CSS now.
| /** | ||
| * Single CSS entry for Vite. Order matches previous build_css.js (sorted by filename). | ||
| */ |
There was a problem hiding this comment.
| /** | |
| * Single CSS entry for Vite. Order matches previous build_css.js (sorted by filename). | |
| */ |
We don't need this.
There was a problem hiding this comment.
This is one of the more interesting changes…
Here is some explanation summary from Cursor:
With Vite,
scripts/build_css.tsis not a separate build step anymore — it is a Vite plugin (idCssPlugin) that applies two transforms iD still depends on, which Vite/Lightning CSS do not provide on their own.What it does
When Vite processes any file in the
css/tree (viacss/entry.css→@importchain inmain.ts), the plugin runs PostCSS with two plugins:1. Scope all styles under
.ideditorMost source CSS is written with bare selectors:
path.line.shadow.tag-aerialway, path.line.shadow.tag-attraction-summer_toboggan, ...At runtime, iD mounts into a container with class
ideditor(set incontext.js). The plugin usespostcss-prefix-selectorto turn.foointo.ideditor .fooso styles do not leak onto the host page when iD is embedded. Selectors that already start with.ideditorare skipped via the exclude regex.2. Make OS dark mode work with explicit theme choice
Authors write dark styles the natural way:
@media (prefers-color-scheme: dark) { .ideditor {iD also lets users pick light, dark, or system in settings (
theme-light/theme-darkclasses on the container). The customduplicate-from-mediaplugin rewrites those@media (prefers-color-scheme: dark)blocks:
- Original rules get
:not(.theme-light)— explicit light theme ignores OS dark preference- Duplicated rules get
.theme-dark— explicit dark theme applies dark styles even on a light OSWithout this, “use dark theme” in iD settings would not work reliably when the OS preference disagrees.
Why it exists as its own file
Concern Handled by Bundle JS, HMR, output iD.cssVite Autoprefixer / browser targets Lightning CSS ( css.transformer: 'lightningcss').ideditorprefix + theme duplicationscripts/build_css.tsThe pre-Vite setup had a standalone
scripts/build_css.jsthat did the same PostCSS work. After the Vite migration, that logic became a plugin so it runs in dev and production on the same CSS pipeline — no duplicate “build CSS then build JS” step.What it deliberately does not do
The file header says it only handles prefix + dark-mode. Autoprefixer was removed from PostCSS here because Lightning CSS already covers that via
browserslistinvite.config.ts.
In short: iD’s CSS is authored as if it were page-global (short selectors +
@media (prefers-color-scheme: dark)).build_css.tsadapts that legacy style to iD’s embedding model and theme API. Until someone rewrites allcss/*.csswith explicit.ideditorprefixes and.theme-darkrules by hand, this plugin is required.A follow-up from the migration plan was to eventually drop the PostCSS dark-mode duplication once CSS is simplified — that would be a large CSS refactor, not a Vite config tweak.
| "build": "run-s build:css build:data build:js", | ||
| "build:css": "node scripts/build_css.js", | ||
| "build": "run-s build:data build:app", | ||
| "build:app": "vite build", | ||
| "build:data": "shx mkdir -p dist/data && node scripts/build_data.js", | ||
| "build:stats": "node config/esbuild.config.min.js --stats && esbuild-visualizer --metadata dist/esbuild.json --filename docs/statistics.html && shx rm dist/esbuild.json", | ||
| "build:js": "node config/esbuild.config.js", | ||
| "build:js:watch": "node config/esbuild.config.js --watch", |
There was a problem hiding this comment.
This is simplified. Vite handles the minification (and source map) and JS build.
| "start": "run-s start:watch", | ||
| "start:single-build": "run-p build:js start:server", | ||
| "start:watch": "run-p build:js:watch start:server", | ||
| "start:server": "node scripts/server.js", | ||
| "test": "npm-run-all -s lint build test:typecheck test:spec", | ||
| "start": "npm run build:data && vite", | ||
| "test": "run-s lint build test:typecheck test:spec", |
There was a problem hiding this comment.
This is simplified as well. Vite does a lot of the reload and watch internally.
| "types": ["vitest/globals", "vitest/jsdom"] | ||
| }, | ||
| "include": ["modules", "test"] | ||
| "include": ["**/*.ts"] |
There was a problem hiding this comment.
We can simplify this.
There was a problem hiding this comment.
we need to include JS files too (since definitions can be imported between both files).
could we just keep this line as-is ?
|
|
||
| export default defineConfig(() => ({ | ||
| base: './', | ||
| envPrefix: 'ID_', |
There was a problem hiding this comment.
Now inline in vite.config.ts as test prop.
k-yle
left a comment
There was a problem hiding this comment.
this is just a very quick review, following the meeting today. i haven't tried it out yet
|
|
||
| declare global { | ||
| interface Window { | ||
| context?: iD.Context; |
There was a problem hiding this comment.
if this is whitelisted as a global, i would mark this as @deprecated or something, so that we don't accidentally use it.
| context?: iD.Context; | |
| /** @deprecated */ | |
| context?: iD.Context; |
or alternatively, mark this as a banned global variable in eslint.config.js (see #12210)
| @@ -0,0 +1 @@ | |||
| declare module 'postcss-prefix-selector'; | |||
There was a problem hiding this comment.
it would be better to import @types/postcss-prefix-selector rather than silencing the warning
| * Entry point for the iD editor (Vite dev and build). | ||
| * In dev, Vite serves this and transforms on the fly; in build, this is bundled to dist/iD.min.js. | ||
| */ | ||
| import './css/entry.css'; |
There was a problem hiding this comment.
I'm guessing this import magic will be converted into a <link rel="stylesheet" /> tag?
If so, then so then this only applies to the standalone build? because the OSM website will load iD.css and iD.js directly, right?
| @@ -0,0 +1,15 @@ | |||
| /** | |||
| * Client env values from import.meta.env (Vite envPrefix: 'ID_'). | |||
| * Used by config/id.ts for CDN URLs, API connection, and feature flags. | |||
There was a problem hiding this comment.
can we just delete this file? with a modern bundler we don't need a file that reëxports everything.
and import.meta.env.* is type-safe already
| "build:js": "node config/esbuild.config.js", | ||
| "build:js:watch": "node config/esbuild.config.js --watch", | ||
| "clean": "shx rm -f dist/esbuild.json dist/*.js dist/*.map dist/*.css dist/img/*.svg", | ||
| "build:stats": "BUILD_STATS=true vite build", |
There was a problem hiding this comment.
this won't work on windows, can we use cross-env (or whatever the modern replacement is - shx and shelljs are both already installed)
or can we use a CLI argument instead?
| "types": ["vitest/globals", "vitest/jsdom"] | ||
| }, | ||
| "include": ["modules", "test"] | ||
| "include": ["**/*.ts"] |
There was a problem hiding this comment.
we need to include JS files too (since definitions can be imported between both files).
could we just keep this line as-is ?
| css: { | ||
| transformer: 'lightningcss', | ||
| lightningcss: { | ||
| targets: browserslistToTargets(browserslist()), |
There was a problem hiding this comment.
the only place where browserlist is used is here, for CSS.
do we need to link the browserslist to the JS somehow? vite uses build.target instead.
or - should we delete the browserlist config and rewrite it using vite's syntax? that might be easier
| container.style.padding = '20px'; | ||
|
|
||
| } else { | ||
| var context = iD.coreContext() |
There was a problem hiding this comment.
all this code is copy-pasted (with modifications) in the osm-website repo.
so any changes needs to be coordinated with them.
we could solve #11039 at the same time by exposing context differently - I've commented at #11039 (comment)
| if (!context) return; | ||
|
|
||
| window.context = context; | ||
| window.id = context; |
There was a problem hiding this comment.
previously, this was only available in debug mode. now it's available in production, so people might start using it.
I think we need to find a conclusion to #11039 first, before people start relying on this hack
As discussed no Slack, this is a take on migrating to Vite 8.
I tested the app a bit an all looks nice. Changing light<>dark mode on a system level still works.
vite.config.ts,main.tsentry)npm startrunsbuild:datathen Vite HMR (no separate JS watch/server)dist/iD.js/dist/iD.min.js/dist/iD.csswith stable names for embeddersdefinetoimport.meta.env(config/env.ts,config/id.ts).ideditorscoping + dark-mode duplication; Lightning CSS for autoprefixdist/index.html(minimal diff: inline loader →<script type="module">)vite.config.ts; broadentsconfigto**/*.tsdist/; addnetlify.tomlPossible follow ups
dist:mapillary,dist:pannellumwith native Vite lazy loading.