Skip to content

feat: move from webpack to vite#672

Draft
Filipoliko wants to merge 29 commits into
masterfrom
vite
Draft

feat: move from webpack to vite#672
Filipoliko wants to merge 29 commits into
masterfrom
vite

Conversation

@Filipoliko
Copy link
Copy Markdown
Contributor

@Filipoliko Filipoliko commented Feb 20, 2026

The core IMA.js build tooling in @ima/cli has been migrated from Webpack to Vite.

Context for reviewers: This is a large, phased migration. This PR is intentionally scoped to the foundational work — replacing Webpack with Vite as the build and dev-server engine. The TODO list in the description is a living tracker of known follow-up work for subsequent PRs.


How to test locally

  1. Run npm run build (@ima/storybook-integration will fail as it is not yet migrated to Vite and it is using old imports from @ima/cli, but this does not affect basic create-ima-app)
  2. Run npm pack -w @ima/cli -w @ima/core -w @ima/server -w @ima/dev-utils -w @ima/error-overlay -w @ima/helpers -w @ima/hmr-client -w @ima/react-page-renderer -w @ima/storybook-integration -w @ima/testing-library
  3. Run the following from the repo root to point the template's @ima/* deps to the packed tarballs:
node -e '
  const fs = require("fs");
  const path = require("path");
  const root = process.cwd();
  const pkgJsonPath = "packages/create-ima-app/template/common/package.json";
  const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
  for (const f of fs.readdirSync(".")) {
    const m = f.match(/^ima-([a-z-]+)-\d+\.\d+\.\d+\.tgz$/);
    if (!m) continue;
    const name = "@ima/" + m[1];
    const ref = "file:" + path.join(root, f);
    if (pkg.dependencies?.[name] !== undefined) pkg.dependencies[name] = ref;
    if (pkg.devDependencies?.[name] !== undefined) pkg.devDependencies[name] = ref;
  }
  fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + "\n");
'
  1. Run npm link -w create-ima-app
  2. Run npx create-ima-app <dir> [--typescript] to create a fresh create-ima-app project with Vite

Impact on IMA.js application developers

Breaking changes in ima.config.js

Before After Notes
webpack(config, ctx, imaConfig) hook vite(config, ctx, imaConfig) hook The config object is now a ViteConfigWithEnvironments instead of a Webpack Configuration
swc(config, ctx) hook (removed) Vite handles transpilation natively; JSX and TS work out of the box
swcVendor(config, ctx) hook (removed) Same as above; legacy polyfill injection is handled by @rollup/plugin-babel in the legacy environment
webpackAliases option viteAliases option Same semantics, renamed
sourceMaps: 'cheap-module-source-map' (and other Webpack devtool strings) sourcemap: true | 'inline' | 'hidden' Simplified — pick true for normal source maps
transformVendorPaths option (removed) Hopefully not needed anymore
experiments.css option (removed) It was a Webpack specific configuration
watchOptions (Webpack WatcherOptions) watchOptions (Chokidar/Rollup WatchOptions) Same field name, different underlying type
prepareConfigurations(contexts[]) receives an array of contexts (one per compiler) prepareConfigurations(context) receives a single context Reflects that Vite uses a single unified config with multiple environments instead of separate compiler configs

Breaking changes for ImaCliPlugin authors

Before After
plugin.webpack(config, ctx, imaConfig) plugin.vite(config, ctx, imaConfig)
plugin.prepareConfigurations(contexts[]) plugin.prepareConfigurations(context) — single context, not an array

Removed ima dev CLI flags

  • --legacy / --forceLegacy — Vite always serves modern bundles for dev command without an option to force legacy build; if you need this, use ima build && ima start
  • --writeToDisk — no longer applicable; Vite always serves from memory during dev

Key Changes

New Vite configuration (packages/cli/src/vite/)

This is the heart of the PR. The Webpack config has been deleted and replaced by an equivalent Vite-with-Environments setup.

File What it does
vite/config.ts (renamed from webpack/config.ts) Single Vite config replacing the old multi-compiler Webpack config. Declares three environments: modern (client ES2024), legacy (client ES2018 + Babel polyfills), server (Node 18 SSR).
vite/utils/utils.ts (renamed from webpack/utils/utils.ts) Core config factory — createViteConfig() replaces createWebpackConfig(). createContexts() is simplified: instead of an array of per-compiler contexts it returns a single unified context.
vite/languages.ts Helpers moved from webpack/languages.ts.
vite/plugins/ Original Webpack loaders and plugins are now consolidated into Vite plugins or supported by Vite natively

Dev server architecture (packages/cli/src/dev-server/)

The dev server has been redesigned. The old model ran Webpack dev server and the user server in separate processes. The new model is using the same process for both environments since we need to pass ViteDevServer instance to @ima/server.

  1. dev.tsima dev now starts nodemon watching server/ and executes devServer.js as the nodemon entry point.
  2. devServer.ts (rewritten) — standalone nodemon-supervised entry. Creates the Vite HMR server, attaches it to global.$IMA_SERVER.viteDevServer, then import()s the app's server/server.js. On changes in server/, nodemon restarts the whole process (including Vite HMR server and IMA server)
  3. createViteDevServer.ts (new) — creates the Vite dev server, most of the code moved here from devServer.ts

TODOs

This is an initial PR and there will be more to follow, which should address these bullet-points.

  • Do we even need hash in the built filenames (and by extension, manifest file)?
    • Still needed for production environment, where we need to invalidate cached static resources when a new version is released. But url parameters with version (or hash) could be an alternative solution, that would allow us to remove manifest-related code.
  • npm run dev is glitching a bit during the load (especially for bigger apps)
    • We could add a loader screen (link)
  • Rename environments - old styled client.es is not possible to use as environment name in vite (due to the dot), maybe we should rename these environments completely to server, legacy, modern?
  • CSS sourcemaps are missing - not supported for build command
  • How to replace window.__IMA_HMR?
    • Globally available @ima/hmr-client, it implements basic HMR, but also triggers error overlay. The HMR logic is now replaced by Vite HMR client, but error overlay is not (even though vite has its own version of error overlay).
  • There is no guarantee of scripts execution order I think I misunderstood how this works
    • Language files are loading after main.js and it can lead to $IMA.i18n not being initialized when the app needs it
    • Styles have no guaranteed order (I think), this needs to be reviewed
  • Do we need to dynamically configure dev server hostname/port?
    • It can be confusing to configure devServer port as the devServer is now the only server and we could use the port from environment, but hostname, publicUrl, or origin could be still configurable.
    • Maybe we could keep the devServer running on a separate port, but within the same Node.js process? This makes it mandatory for the IMA server to run in the same process as the devServer otherwise we cannot pass the ViteDevServer instance.
  • Server error stack traces are off by few lines
    • Partially solved, but the main issue is @ima/plugin-cli, that generates sourcemaps incorrectly. Webpack was ignoring sourcemaps (as it was referencing to the bundled code, but vite references the source code), but the issue is visible in integration tests, where line references in stack traces are also incorrect
  • Dev command injects vite into @ima/server and we need to call ssrLoadModule/ssrFixStacktrace functions all around, maybe using ModuleRunner could make things simpler?
  • Do we still need custom postcss config with postcss-flexbugs-fixes and postcss-preset-env?
  • By moving runner.ejs to @ima/server, it is no longer being minified in production build
  • Logic in @ima/server related to devServer can be now moved to @ima/cli (or the other way around)
  • Update @ima/storybook-integration to Vite
  • What to do with devtools?
  • Is it safe to load favicon from app/public/favicon.ico (source code instead of build)?
  • Update @ima/cli plugins to work with vite build
  • Add support for an alternative to ima dev --forceLegacy (maybe ima start --forceLegacy?)
  • What is createCacheKey function in https://github.com/seznam/ima/blob/master/packages/cli/src/webpack/utils/utils.ts#L100-L142 - it is not being used anywhere, or is it?
  • Consider adding support for Vite DevTools
  • Add tests to new features in @ima/server
  • Remove or make use of cli-progress (and @types/cli-progress) in @ima/cli
  • Fix unit and integration tests in create-ima-app (Jest does not like pure ESM modules)
  • Add support for npm run dev with https - maybe review how we startup the dev server, should we use users server.js to give more flexibility how its run? (Just the way it used to be)
  • Update documentation
  • Rewrite @ima/server to ESM

Old bugs that came to haunt us:

  • The ESM builds of our plugins (and also core ima packages) are not valid ESM. Imports are missing extensions and only default import is exposed, not named ones. It is the same bug, that we ran into with static-content and added workarounds. Now it is everywhere as Vite is using ESM on server-side. As a workaround, we are defining ssr.noExternal: [/@ima/.*/] to bundle the @ima packages and make them work.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 20, 2026

⚠️ No Changeset found

Latest commit: c9f4dc4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Copy Markdown

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 the build/dev toolchain from Webpack to Vite across @ima/cli and updates @ima/server to integrate with the Vite dev server (SSR module loading, HTML transforms, stacktrace fixing) while keeping the existing manifest.json-driven resource loading model.

Changes:

  • Replaces Webpack config/compiler/dev-server flow in @ima/cli with a Vite multi-environment setup (server, legacy, modern) and new manifest/stats generation.
  • Updates @ima/server factories/templates to work in Vite watch mode (Vite HTML transforms, ssrLoadModule, ssrFixStacktrace) and adjusts runner/polyfill handling.
  • Removes Webpack-specific loaders/plugins/utilities and updates create-ima-app template defaults to match the Vite-based workflow.

Reviewed changes

Copilot reviewed 61 out of 62 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
utils/scripts/utils/utils.js Updates watcher ignore patterns used by local dev scripts.
packages/server/polyfill/runner.js Runner now only loads scripts and triggers hooks (no inlined runtime execution).
packages/server/lib/template/devErrorView.ejs Switches dev error page client script to ESM + Vite HMR event.
packages/server/lib/middlewares/memStaticProxyMiddlewareFactory.js Adjusts when the in-memory static proxy is enabled in watch mode.
packages/server/lib/metric/TimingTracker.js Minor formatting refactor for log marker selection.
packages/server/lib/factory/utils/resourcesUtils.js Adds type="module" to generated script resources.
packages/server/lib/factory/utils/tests/snapshots/resourcesUtilsSpec.js.snap Snapshot updates for script attributes.
packages/server/lib/factory/staticPageFactory.js Makes static render helpers async to await boot config creation.
packages/server/lib/factory/serverAppFactory.js Adds Vite stacktrace fixing and awaits static error page rendering.
packages/server/lib/factory/responseUtilsFactory.js Loads runner polyfill from @ima/server package and transforms HTML via Vite in watch mode.
packages/server/lib/factory/hooksFactory.js Adds Vite stacktrace fixing and awaits async helpers during response lifecycle.
packages/server/lib/factory/devUtilsFactory.js Converts manifest-based server module loading to ssrLoadModule() in watch mode.
packages/server/lib/factory/tests/staticPageFactorySpec.js Updates tests for async static page helpers.
packages/server/lib/factory/tests/serverAppFactorySpec.js Adds watch-mode expectations for stacktrace fixing.
packages/server/lib/factory/tests/responseUtilsFactorySpec.js Updates tests for async processContent.
packages/server/lib/factory/tests/snapshots/responseUtilsFactorySpec.js.snap Snapshot updates for script attributes.
packages/server/lib/factory/IMAInternalFactory.js Makes boot config creation async (awaits language loader) and adapts appFactory usage.
packages/server/index.js Makes appFactory/languageLoader async to support Vite SSR module loading.
packages/server/.npmignore Ensures polyfill/** is published with @ima/server.
packages/create-ima-app/template/common/server/app.js Updates template server middleware (favicon path change).
packages/create-ima-app/template/common/package.json Adds extensionless dependency to the app template.
packages/create-ima-app/template/common/app/main.js Removes Webpack HMR error hook (module.hot).
packages/core/src/event/Dispatcher.ts Formatting-only change in interface extends layout.
packages/cli/tsconfig.json Removes Webpack-related TS type dependencies.
packages/cli/src/webpack/utils/findRules.ts Removes Webpack rule/loader lookup utilities.
packages/cli/src/webpack/utils/tests/findRulesSpec.ts Removes tests for deleted Webpack utilities.
packages/cli/src/webpack/plugins/ProgressPlugin.ts Removes Webpack progress tracking implementation.
packages/cli/src/webpack/plugins/ManifestPlugin/options.json Removes schema for deleted Webpack manifest plugin.
packages/cli/src/webpack/plugins/ManifestPlugin/index.ts Removes Webpack manifest plugin implementation.
packages/cli/src/webpack/plugins/GenerateRunnerPlugin/options.json Removes schema for deleted Webpack runner generator.
packages/cli/src/webpack/plugins/GenerateRunnerPlugin/index.ts Removes Webpack runner generator implementation.
packages/cli/src/webpack/loaders/use-server-loader/types.ts Removes Webpack use server loader types.
packages/cli/src/webpack/loaders/use-server-loader/index.ts Removes Webpack use server loader implementation.
packages/cli/src/webpack/loaders/preprocess-loader.ts Removes Webpack preprocess loader.
packages/cli/src/webpack/loaders/null-loader.ts Removes Webpack null-loader.
packages/cli/src/webpack/loaders/js-string-loader.ts Removes Webpack JS-to-string loader.
packages/cli/src/webpack/languages.ts Removes Webpack language compilation pipeline (replaced by Vite plugin/virtual modules).
packages/cli/src/webpack/entries/publicPathEntry.ts Removes Webpack public path entry.
packages/cli/src/webpack/config.ts Removes the full Webpack configuration generator.
packages/cli/src/vite/utils/utils.ts Introduces Vite-oriented context/config creation and updated defaults.
packages/cli/src/vite/plugins/useServerProcessors/stubProcessor.ts Updates import path for shared UseServerProcessor type.
packages/cli/src/vite/plugins/useServerProcessors/serverControllerProcessor.ts Updates import path for shared UseServerProcessor type.
packages/cli/src/vite/plugins/imaUseServerPlugin.ts Adds Vite plugin replacement for the old Webpack use server loader.
packages/cli/src/vite/plugins/imaSkipCssPlugin.ts Adds Vite plugin to prevent CSS emission in selected environments.
packages/cli/src/vite/plugins/imaLanguagesPlugin.ts Adds virtual-module-based language compilation + HMR for Vite.
packages/cli/src/vite/plugins/imaHmrPlugin.ts Adds custom HMR event emission for dev error view reload behavior.
packages/cli/src/vite/languages.ts Adds shared helpers for generating dictionary TS declarations.
packages/cli/src/vite/config.ts Adds the main Vite config with modern/legacy/server environments and build output structure.
packages/cli/src/types.ts Replaces Webpack types with Vite equivalents and updates CLI/plugin extension points.
packages/cli/src/lib/manifest.ts Adds Vite-based manifest.json generation for dev and build outputs.
packages/cli/src/lib/formatStats.ts Replaces Webpack stats formatting with Vite build output summary formatting.
packages/cli/src/lib/compiler.ts Removes Webpack compiler wrapper utilities.
packages/cli/src/lib/cli.ts Updates config requiring to use Vite utils.
packages/cli/src/index.ts Exposes Vite utilities instead of Webpack utilities.
packages/cli/src/dev-server/devServer.ts Replaces Webpack dev server with a nodemon-restarted Vite dev-server entry.
packages/cli/src/dev-server/createViteDevServer.ts Adds factory to start Vite in middleware mode + error overlay/static endpoints.
packages/cli/src/commands/prerender.ts Updates environment resolution import to Vite utils.
packages/cli/src/commands/dev.ts Replaces Webpack watch + dev server with nodemon-based Vite dev server startup.
packages/cli/src/commands/build.ts Replaces Webpack build with Vite builder + manifest generation + Vite stats output.
packages/cli/package.json Swaps Webpack deps for Vite/Rollup/Babel polyfill tooling.
package.json Adds a vite override pin (beta) for the repo.

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

Comment thread packages/create-ima-app/template/common/package.json
Comment thread packages/server/lib/factory/responseUtilsFactory.js
Comment thread packages/server/lib/factory/devUtilsFactory.js
Comment thread utils/scripts/utils/utils.js Outdated
Comment on lines 124 to 137
@@ -132,6 +133,7 @@ function _prepareSources(manifest, language) {
esScripts: buildResources('client.es', jsFilter, {
async: '',
crossorigin: 'anonymous',
type: 'module',
}),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already listed as a TODO and will be addressed in followup PRs.

Comment thread packages/server/lib/factory/serverAppFactory.js
Comment thread packages/server/lib/factory/hooksFactory.js
path.resolve(path.join(__dirname, '../build/static/public/favicon.ico'))
)
)
.use(favicon(path.resolve(path.join(__dirname, '../app/public/favicon.ico'))))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already listed as a TODO and will be addressed in followup PRs.

Comment thread packages/cli/package.json
Comment on lines 47 to 70
"chalk": "4.1.2",
"chokidar": "^4.0.3",
"cli-progress": "^3.12.0",
"compression-webpack-plugin": "^11.1.0",
"copy-webpack-plugin": "^13.0.1",
"core-js": "^3.46.0",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"ejs": "^3.1.10",
"core-js-pure": "^3.48.0",
"express-static-gzip": "^3.0.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"extensionless": "^2.0.6",
"globby": "11.1.0",
"kill-port": "^1.6.1",
"less": "^4.4.2",
"less-loader": "^12.3.0",
"less-plugin-glob": "^3.0.0",
"mini-css-extract-plugin": "^2.9.4",
"nodemon": "^3.1.11",
"open-editor": "5.1.0",
"postcss": "^8.5.6",
"postcss-flexbugs-fixes": "5.0.2",
"postcss-loader": "^8.2.0",
"postcss-preset-env": "^10.4.0",
"preprocess": "^3.2.0",
"pretty-bytes": "^5.6.0",
"pretty-ms": "^7.0.1",
"react-refresh": "^0.18.0",
"schema-utils": "4.3.3",
"source-map-loader": "^5.0.0",
"swc-loader": "0.2.6",
"webpack": "^5.102.1",
"webpack-dev-middleware": "^7.4.5",
"webpack-hot-middleware": "^2.26.1",
"vite": "^8.0.0",
"vite-plugin-compression2": "^2.5.1",
"vite-plugin-static-copy": "^3.3.0",
"yargs": "^18.0.0"
},
"devDependencies": {
"@types/cli-progress": "^3.11.6",
"@types/ejs": "^3.1.5",
"@types/express": "^5.0.5"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already listed as a TODO and will be addressed in followup PRs.

// compression plugin customizes the applyToEnvironment method internally
ctx.command === 'build' && imaConfig.compress
? compression({
exclude: /^server\/(?!runner\.js$)/,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do we still need to exclude this?

Filipoliko and others added 3 commits April 14, 2026 10:54
fix: remove broken sourcemaps, fix esm build, remove extensionless hacks
feat: migrate from @swc/core to rolldown and go pure ESM with all pac…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants