Skip to content

Migrate to Vite 8#12411

Open
tordans wants to merge 1 commit into
openstreetmap:developfrom
tordans:vite8b
Open

Migrate to Vite 8#12411
tordans wants to merge 1 commit into
openstreetmap:developfrom
tordans:vite8b

Conversation

@tordans

@tordans tordans commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

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.

  • 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

Possible follow ups

This comment was marked as low quality.

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
Comment thread config/env.ts
* 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;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Note: We override the Vite default envPrefix for public env.* to ID_* which is why this works.

Comment thread config/envs.js

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Comment thread config/id.ts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nearly each line was touched, so I migrated this to TS as a byproduct.

Comment thread css/entry.css

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Vite and lightning handles the CSS now.

Comment thread css/entry.css
Comment on lines +1 to +3
/**
* Single CSS entry for Vite. Order matches previous build_css.js (sorted by filename).
*/

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
/**
* Single CSS entry for Vite. Order matches previous build_css.js (sorted by filename).
*/

We don't need this.

Comment thread scripts/build_css.ts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is one of the more interesting changes…
Here is some explanation summary from Cursor:

With Vite, scripts/build_css.ts is 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 (via css/entry.css@import chain in main.ts), the plugin runs PostCSS with two plugins:

1. Scope all styles under .ideditor

Most 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 in context.js). The plugin uses postcss-prefix-selector to turn .foo into .ideditor .foo so styles do not leak onto the host page when iD is embedded. Selectors that already start with .ideditor are 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-dark classes on the container). The custom duplicate-from-media plugin 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 OS

Without 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.css Vite
Autoprefixer / browser targets Lightning CSS (css.transformer: 'lightningcss')
.ideditor prefix + theme duplication scripts/build_css.ts

The pre-Vite setup had a standalone scripts/build_css.js that 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 browserslist in vite.config.ts.


In short: iD’s CSS is authored as if it were page-global (short selectors + @media (prefers-color-scheme: dark)). build_css.ts adapts that legacy style to iD’s embedding model and theme API. Until someone rewrites all css/*.css with explicit .ideditor prefixes and .theme-dark rules 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.

Comment thread package.json
Comment on lines -20 to -25
"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",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is simplified. Vite handles the minification (and source map) and JS build.

Comment thread package.json
Comment on lines -42 to +41
"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",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is simplified as well. Vite does a lot of the reload and watch internally.

Comment thread tsconfig.json
"types": ["vitest/globals", "vitest/jsdom"]
},
"include": ["modules", "test"]
"include": ["**/*.ts"]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We can simplify this.

@k-yle k-yle Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we need to include JS files too (since definitions can be imported between both files).

could we just keep this line as-is ?

Comment thread vite.config.ts

export default defineConfig(() => ({
base: './',
envPrefix: 'ID_',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

See config/env.ts.

Comment thread vitest.config.ts

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Now inline in vite.config.ts as test prop.

@tordans tordans requested a review from k-yle June 6, 2026 20:41
@tyrasd tyrasd added the chore-build Improvements to the iD build scripts / CI environment label Jun 9, 2026

@k-yle k-yle left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is just a very quick review, following the meeting today. i haven't tried it out yet

Comment thread vite-env.d.ts

declare global {
interface Window {
context?: iD.Context;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if this is whitelisted as a global, i would mark this as @deprecated or something, so that we don't accidentally use it.

Suggested change
context?: iD.Context;
/** @deprecated */
context?: iD.Context;

or alternatively, mark this as a banned global variable in eslint.config.js (see #12210)

Comment thread types/ambient.d.ts
@@ -0,0 +1 @@
declare module 'postcss-prefix-selector';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it would be better to import @types/postcss-prefix-selector rather than silencing the warning

Comment thread main.ts
* 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';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Comment thread config/env.ts
@@ -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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment thread package.json
"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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Comment thread tsconfig.json
"types": ["vitest/globals", "vitest/jsdom"]
},
"include": ["modules", "test"]
"include": ["**/*.ts"]

@k-yle k-yle Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we need to include JS files too (since definitions can be imported between both files).

could we just keep this line as-is ?

Comment thread vite.config.ts
css: {
transformer: 'lightningcss',
lightningcss: {
targets: browserslistToTargets(browserslist()),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment thread index.html
container.style.padding = '20px';

} else {
var context = iD.coreContext()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

Comment thread main.ts
if (!context) return;

window.context = context;
window.id = context;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

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

Labels

chore-build Improvements to the iD build scripts / CI environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants