Develop and build your client-server projects, powered by typescript and webpack.
npm install --save-dev @gravity-ui/app-builder@gravity-ui/app-builder provides CLI (npx app-builder). You can view available commands with the --help flag.
npx app-builder dev # to view supported options add the --help flag.npx app-builder build # to view supported options add the --help flag.You can use any of these files:
- app-builder.config.ts
- app-builder.config.js
- app-builder.config.json
- app-builder property in your package.json
You can also specify a custom filename using the --config flag
import {defineConfig} from '@gravity-ui/app-builder';
export default defineConfig({
client: {
// client settings
},
server: {
// server settings
},
});If the config needs to be conditionally determined, it can export a function instead:
import {defineConfig} from '@gravity-ui/app-builder';
export default defineConfig(
async function (
/** @type dev | build */
command,
/**
* values specified with --env flag
*
* @type {[k in string]: string}
*
* @example
* With follow command:
* app-build dev --env=path.to.member1=value1 --env=path.to.member2=value2
* you get:
* env = {path: {to: {member1: 'value1', member2: 'value2'}}}
*/
env,
) {
return {
verbose: command === 'dev',
client: {
// client settings
},
server: {
// server settings
},
};
},
);
export default config;{
"app-builder": {
"client": {
// client settings
},
"server": {
// server settings
}
},
"scripts": {
"dev": "app-builder dev",
"build": "app-builder build"
}
}target(client | server) — select compilation unit.verbose(boolean) - turn on verbose output.
app-builder automatically injects environment variables during the build process that are available in your application code:
-
process.env.PUBLIC_PATH— automatically set to the resolved public path value (including CDN URLs if configured). This allows your application code to dynamically access the correct resource URLs at runtime.// In your application code, you can access: const publicPath = process.env.PUBLIC_PATH; // e.g., "https://cdn.example.com/build/" or "/build/" // Useful for dynamically loading assets or configuring Module Federation const assetUrl = `${process.env.PUBLIC_PATH}images/logo.png`;
Note: On the server side,
process.env.PUBLIC_PATHis only available when using SWC compiler (compiler: 'swc'). With TypeScript compiler, this variable is not injected. -
process.env.NODE_ENV— current environment ('development'|'production') -
process.env.IS_SSR— boolean flag indicating if code is running in SSR context
app-builder compiles server with typescript.
Default folder for server code is src/server. There is must be file tsconfig.json
{
"compilerOptions": {
"outDir": "../../dist/server"
}
}and index.ts - server entrypoint.
outDir - must be configured to place compiled files to {rootDir}/dist/server.
The server is started with the command node {rootDir}/dist/server/index.js.
All server settings are used only in dev mode:
port(number | true) — specify port that server listens. The port will be used to pass through requests from the client to the server. If set totrue, the port will be selected automatically. The server is started with the commandAPP_PORT=${port} node dist/server/index.js --port ${port}.watch(string[]) — by defaultapp-buildermonitors onlysrc/serverdirectory. If you need to watch other directories, specify them here.watchThrottle(number) — use to add an extra throttle, or delay restarting.inspect/inspectBrk(number | true) — listen for a debugging client on specified port. If specifiedtrue, try to listen on9229.compiler('typescript' | 'swc') — choose TypeScript compiler for server code compilation. Default is'typescript'. Set to'swc'for faster compilation with SWC.
app-builder bundles client with webpack. Client code must be in src/ui folder.
src/ui/entries - each file in this folder is used as entrypoint. dist/public/build is output directory for bundles.
All paths must be specified relative rootDir of the project.
modules(string[]) — Tell webpack what directories should be searched when resolving modules.modulesautomatically populates withbaseUrlfromsrc/ui/tsconfig.json.alias(Record<string, string>) — Create aliases to import or require certain modules more easily, more
With this {rootDir}/src/ui/tsconfig.json:
{
"compilerOptions": {
"baseDir": ".",
"paths": {
"~units": ["units/*"]
}
}
}modules will contain ["{rootDir}/src"] and aliases - {"~units": ["{rootDir}/src/units"]};
includes(string[]) — additional compilation paths. Example:includes: ['node_modules/my-lib', 'src/shared']images(string[]) — Additional paths for images. Example:images: ['node_modules/my-lib/img']icons(string[]) — Additional paths for svg icons. By default, all svgs with paths includingicons/will be processed. Example:icons: [node_modules/@fortawesome/fontawesome-pro/svgs]publicPathPrefix(string) — publicPath prefix, will be added to/build/publicPath(string) — publicPath for bundler, this option has higher priority than publicPathPrefixoutputPath(string) — Build directory for output, default:dist/public/buildanddist/ssr- for SSRassetsManifestFile(string) — File name for assets manifest, default:assets-manifest.jsonsymlinks(boolean) — Follow symbolic links while looking for a file. moreexternals— specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. morenode— include polyfills or mocks for various node stuff. morefallback— Redirect module requests when normal resolving fails. morepolyfill— allow enable Node.jsprocessobject polyfill.hiddenSourceMap(boolean=true) - iffalse- source maps will be generated for prod buildsdisableSourceMapGeneration(boolean) — disable sourcemap generation;definitions— add additional options to DefinePlugin. morenewJsxTransform(boolean=true) — use new JSX Transform.svgr(SvgrConfig) — svgr plugin options. moreentry(string | string[] | Record<string, string | string[]>) — entry for bundler, overrides entry which is generated from entries directoryentryFilter(string[]) — filter used entrypoints.excludeFromClean(string[]) — do not clean provided paths before build.forkTsCheker(false | ForkTsCheckerWebpackPluginOptions) - config for ForkTsCheckerWebpackPlugin more. Iffalse, ForkTsCheckerWebpackPlugin will be disabled.cache(boolean | FileCacheOptions | MemoryCacheOptions) — Cache the generated webpack modules and chunks to improve build speed. morebundler('webpack' | 'rspack') - Option to choose a bundler.javaScriptLoader('babel' | 'swc') - Option to choose a JavaScript loader.babel((config: babel.TransformOptions, options: {configType: 'development' | 'production'; isSsr: boolean}) => babel.TransformOptions | Promise<babel.TransformOptions>) - Allow override the default babel transform options.babelCacheDirectory(boolean | string) — Set directory for babel-loader cache (`default: node_modules/.cache/babel-loader``)swc((config: SwcConfig, options: {configType: 'development' | 'production'; isSsr: boolean}) => SwcConfig | Promise<SwcConfig>) - Allow override the default swc configuration.webpack((config: webpack.Configuration, options: {configType: 'development' | 'production'; isSsr: boolean}) => webpack.Configuration | Promise<webpack.Configuration>) - Allow override the default webpack configuration.rspack((config: rspack.Configuration, options: {configType: 'development' | 'production'; isSsr: boolean}) => rspack.Configuration | Promise<rspack.Configuration>) - Allow override the default rspack configuration.ssr- build SSR bundle. The SSR entries should be insidesrc/ui/ssrdirectory and match the client entries.noExternal(string | RegExp | (string | RegExp)[] | true) - prevent listed dependencies from being externalized for SSR. By default, all dependencies are externalized.moduleType: ('commonjs' | 'esm') - library type for the SSR bundle, by defaultcommonjs.
devServer(Object) — webpack dev server options.ipc(string) — the Unix socket to listen to. Ifipcandportare not defined, then the socket{rootDir}/dist/run/client.sockis used.port(number | true) — specify a port number to listen for requests on. Iftrue, the free port will be selected automatically.webSocketPath(string) — tells clients connected to devServer to use the provided path to connect. Default is${publicPathPrefix}/build/sockjs-node.type('https') — allow to serve over HTTPS.options(import('https').ServerOptions) — allow to provide your own certificate.
watchOptions— a set of options used to customize watch mode, morewatchPackages(boolean) - watch all changes innode_modules.
reactRefresh(false | (options: ReactRefreshPluginOptions) => ReactRefreshPluginOptions) — disable or configurereact-refreshin dev mode, moredetectCircularDependencies(true | CircularDependenciesOptions) - detect modules with circular dependencies, morelazyCompilation(true | LazyCompilationConfig) — enable experimental lazy compilation featuretrue— enable featureLazyCompilationConfigport(number) — port where to listen to from the serverentries(boolean=true) — iffalse- disables lazy compilation forsrc/ui/entriesfolder content
analyzeBundle(true | statoscope) — tools to analyze bundle.true— enable webpack-bundle-analyzer plugin. Report generated todist/public/build/stats.htmlstatoscope— enable statoscope plugin. Reports generated todist/public/build/stats.jsonanddist/public/build/report.json
reactProfiling(boolean) — use react profiler API in production, this option also disable minimization. The API is required by React developers tools for profile.statoscopeConfig(Options) —@statoscope/webpack-pluginconfiguration options. Might be used to override the defaults. RequiresanalyzeBundle: statoscope.cdn(CdnUploadConfig | CdnUploadConfig[]) - upload bundled client files to CDN.bucket(string) — bucket nameprefix(string) — path to files inside the bucketregion(string) — AWS region or any stringendpoint(string) - cdn host to upload filespublicPath(string) - public path to access files from the browsercompress(boolean) - upload also gzip and brotli compressed versions of filesadditionalPattern(string[]) — patterns for uploading additional files. By default, only files generated by webpack are loaded.
sentryConfig(Options) —@sentry/webpack-pluginconfiguration options.
vendors(string[] | (defaultVendors: string[]) => string[]) — additional libraries or a function returning libraries for a vendor chunk;momentTz— settings for moment-timezone (by default data is truncated);contextReplacement(object)highlight.js(string[]) — list of language names to include, e.g.['javascript', 'python', 'bash'];locale: (string[]=['ru']) — list ofmoment.jsorday.jslocales to include, e.g.['de', 'es']. LocaleEnis always present.
safari10(boolean) — Enablessafari10terser's option. Terser optionstransformCssWithLightningCss(boolean) — use Lighting CSS to transform and minimize css instead of PostCSS and cssnanolightningCssMinimizerOptions((options: LightningCssMinimizerRspackPluginOptions) => LightningCssMinimizerRspackPluginOptions) - modify or return a custom LightningCssMinimizerRspackPluginterser((options: TerserOptions) => TerserOptions) - modify or return a custom Terser options.
-
monaco(object) — use monaco-editor-webpack-pluginfileName(string) — custom filename template for worker scripts.languages(string[]) - include only a subset of the languages supported. If you don't need support for all languages, set needed languages explicitly, since it may significantly affect build time.features(string[]) - include only a subset of the editor features.customLanguages(IFeatureDefinition[]) - include custom languages (outside of the ones shipped with themonaco-editor).
Web workers allow you to run JavaScript code in a separate thread from the main UI thread. This can improve the performance and responsiveness of your web application by offloading intensive tasks to the background.
To create a web worker, you need to write a script file that defines the logic of the worker. For example, this file (my.worker.ts) implements a simple function that adds two numbers and sends the result back to the main thread:
// my.worker.ts
self.onmessage = async (ev) => {
const {a = 0, b = 0} = ev.data || {};
const result = a + b;
self.postMessage({
result,
});
};app-builder provides built-in support for web workers for files with the .worker.[jt]s suffix. You can choose
between two variants of getting web workers by setting the newWebWorkerSyntax option:
newWebWorkerSyntax: false(default) - use theworker-loaderto import web workers. Content of worker file will be included in main bundle as blob. This variant does not support dynamic imports inside worker. For example:
// main.ts
import MyWorker from './my.worker.ts';
const worker = new MyWorker();In this variant, you need to add some type declarations for the worker files::
// worker.d.ts
declare module '*.worker.ts' {
class WebpackWorker extends Worker {}
export default WebpackWorker;
}newWebWorkerSyntax: true- use the webpack 5 web workers syntax to import web workers. This variant allows to use dynamic imports inside worker and load worker bundle from CDN. For example:
import {Worker} from '@gravity-ui/app-builder/worker';
const MyWorker = new Worker(new URL('./my.worker', import.meta.url));To use the web worker in your main script, you need to communicate with it using the postMessage and onmessage methods. For example:
// main.ts
const worker = '...'; // Worker creation, first or second variant
worker.onmessage = ({data: {result}}) => {
console.log(result);
};
worker.postMessage({a: 1, b: 2});Module Federation is a Webpack 5 feature that enables micro-frontend architecture, where JavaScript applications can dynamically load code from each other at runtime.
app-builder uses @module-federation/enhanced for advanced Module Federation support.
-
moduleFederation(object) — Module Federation configuration-
name(string) — unique name of the application in the Module Federation ecosystem. Required parameter. -
version(string) — application version. When specified, the entry file will be namedentry-{version}.jsinstead ofentry.js. -
disableManifest(boolean) — disable manifest file generation. Whentrue, uses regular.jsfiles for remote entry instead of manifest files. Default isfalse. -
remotes(string[]) — list of remote application names that this application can load. This is a simplified alternative tooriginalRemotesthat automatically generates remote URLs based on your public path configuration.How it works:
- In development mode: Remote URLs are automatically generated using the pattern
{commonPublicPath}{remoteName}/entry.js(or manifest files if enabled) - In production mode: You need to ensure remote applications are deployed and accessible at the expected URLs
- Remote entry files are loaded at runtime to provide federated modules from other micro-frontends
File naming patterns (depends on configuration):
- With
disableManifest: false(default):mf-manifest.jsonormf-manifest-[version].json(with versioning) - With
disableManifest: true:entry.jsorentry-[version].js(with versioning)
Example:
// Simple configuration - URLs auto-generated in development remotes: ['header', 'navigation', 'footer']; // Results in loading from (in development): // - https://localhost:3000/header/mf-manifest.json // - https://localhost:3000/navigation/mf-manifest.json // - https://localhost:3000/footer/mf-manifest.json
Development vs Production:
- Development: App-builder automatically starts all remote applications and generates their URLs
- Production: Remote applications must be independently deployed and accessible at the generated URLs
Integration with other options:
- Works with
enabledRemotesto selectively enable remotes in development - Affected by
remotesRuntimeVersioningfor versioned file names - File format controlled by
disableManifestoption
For more complex scenarios requiring custom URLs or cross-environment configurations, use
originalRemotesinstead. - In development mode: Remote URLs are automatically generated using the pattern
-
enabledRemotes(string[]) — list of enabled remotes for module federation. Development mode only. If not specified, all remotes from theremotesarray will be enabled by default.Purpose:
- Allows selective enabling/disabling of specific remotes during development
- Useful for debugging, testing individual micro-frontends, or working with partial system setups
- Helps reduce development startup time by loading only needed remotes
- Can be overridden via CLI flag:
--mf-remotes=header,footer
Loading behavior:
- Enabled remotes: Loaded from local development server (auto-started by app-builder)
- Disabled remotes: Loaded from CDN (if
publicPathPrefixor CDN configuration is available), allowing you to use stable production versions while developing specific parts locally
Example:
// Load all available remotes (header, navigation, footer) remotes: ['header', 'navigation', 'footer']; // Enable only specific remotes in development enabledRemotes: ['header', 'footer']; // navigation will be skipped
CLI Override:
# Override enabledRemotes from command line npx app-builder dev --mf-remotes=header,navigationNote: This option has no effect in production builds - all configured remotes will be included in the production bundle configuration.
-
originalRemotes(RemotesObject) — full configuration of remote applications in Module Federation Plugin format. Use this when you need explicit control over remote URLs or for cross-environment deployments.When to use:
- Custom remote URLs (different domains, ports, paths)
- Cross-environment loading (staging, production CDN URLs)
- Complex deployment scenarios
- When
remotesauto-generation doesn't meet your needs
Format:
originalRemotes: { remoteName: 'remoteName@remoteUrl', }
Examples:
originalRemotes: { header: 'header@https://cdn.example.com/header/entry.js', footer: 'footer@https://cdn.example.com/footer/entry.js', }
Note: When
originalRemotesis specified, theremotesoption is ignored. Use eitherremotesORoriginalRemotes, not both. -
remotesRuntimeVersioning(boolean) — enables runtime versioning for remote applications. When enabled, remote entry files include version information in their filenames, allowing for cache busting and version-specific loading.How it affects file names:
- With
disableManifest: false:mf-manifest-[version].jsoninstead ofmf-manifest.json - With
disableManifest: true:entry-[version].jsinstead ofentry.js - Version is taken from the remote application's
versionconfiguration
Benefits:
- Cache busting: Different versions get different URLs, preventing browser caching issues
- Rollback capability: Can load specific versions of remotes
- Deployment safety: Gradual rollouts with version-specific remote loading
Example with versioning enabled:
// Host application { moduleFederation: { name: 'shell', remotes: ['header', 'navigation'], remotesRuntimeVersioning: true, // Enable versioning } } // Remote application (header) { moduleFederation: { name: 'header', version: '2.1.0', // This version appears in filename exposes: { './Header': './src/Header' } } } // Results in loading: header/mf-manifest-2.1.0.json
Runtime behavior:
- The version is resolved at runtime from the remote's manifest or entry file
- Enables loading different versions of the same remote in different environments
- Works with both
remotesandoriginalRemotesconfigurations
- With
-
isolateStyles(object) — CSS style isolation settings to prevent conflicts between micro-frontends.getPrefix((entryName: string) => string) — function to generate CSS class prefix.prefixSelector((prefix: string, selector: string, prefixedSelector: string, filePath: string) => string) — function to add prefix to CSS selectors.
-
Also supports all standard options from @module-federation/enhanced, except
nameandremotes, such as:filename— entry file name (defaultentry.js)exposes— modules that this application exportsshared— shared dependencies between applicationsruntimePlugins— plugins for Module Federation runtime
-
Host Application Configuration Example:
Host applications consume remote modules from other micro-frontends:
export default defineConfig({
client: {
moduleFederation: {
name: 'shell',
// Simple remotes configuration
remotes: ['header', 'footer', 'sidebar'],
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
lodash: {singleton: true},
},
},
},
});Advanced Host Configuration:
export default defineConfig({
client: {
moduleFederation: {
name: 'main-shell',
version: '2.1.0',
// Detailed remotes configuration
originalRemotes: {
header: 'header@https://cdn.example.com/header/entry.js',
footer: 'footer@https://cdn.example.com/footer/entry.js',
userProfile: 'userProfile@https://cdn.example.com/user-profile/entry.js',
},
remotesRuntimeVersioning: true,
isolateStyles: {
getPrefix: (entryName) => `.app-${entryName}`,
prefixSelector: (prefix, selector, prefixedSelector, filePath) => {
if (
[prefix, ':root', 'html', 'body', '.g-root', '.remote-app'].some((item) =>
selector.startsWith(item),
) ||
filePath.includes('@gravity-ui/chartkit')
) {
return selector;
}
return prefixedSelector;
},
},
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
lodash: {singleton: true},
},
},
},
});Remote Application Configuration Example:
Remote applications expose their modules for consumption by host applications:
export default defineConfig({
client: {
moduleFederation: {
name: 'header',
// Expose modules for other applications
exposes: {
'./Header': './src/components/Header',
'./Navigation': './src/components/Navigation',
'./UserMenu': './src/components/UserMenu',
},
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
lodash: {singleton: true},
},
},
},
});Bidirectional Configuration Example:
Applications can be both host and remote simultaneously:
export default defineConfig({
client: {
moduleFederation: {
name: 'dashboard',
version: '1.5.0',
// Consume remote modules
remotes: ['charts', 'notifications'],
// Expose own modules
exposes: {
'./DashboardLayout': './src/layouts/DashboardLayout',
'./DataTable': './src/components/DataTable',
},
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
lodash: {singleton: true},
},
},
},
});Advanced Remotes Configuration Examples:
export default defineConfig({
client: {
moduleFederation: {
name: 'advanced-shell',
version: '3.0.0',
// Example 1: Simple remotes for development
remotes: ['header', 'sidebar', 'footer', 'analytics'],
// Example 2: Enable only specific remotes in development
enabledRemotes: ['header', 'sidebar'], // Only these will load in dev mode
// Example 3: Runtime versioning with manifests (production-ready)
remotesRuntimeVersioning: true, // Enables version-specific loading
disableManifest: false, // Use mf-manifest-[version].json files
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
},
},
},
});
// Alternative: Explicit URLs for production
export default defineConfig({
client: {
moduleFederation: {
name: 'production-shell',
// Use originalRemotes for explicit control
originalRemotes: {
// Static CDN URLs
header: 'header@https://cdn.company.com/header/mf-manifest-2.1.0.json',
sidebar: 'sidebar@https://cdn.company.com/sidebar/entry-1.5.0.js',
// Dynamic remote loading
analytics: `promise new Promise((resolve) => {
const remoteUrl = process.env.NODE_ENV === 'production'
? 'https://analytics.cdn.com/entry.js'
: 'http://localhost:3003/entry.js';
resolve(\`analytics@\${remoteUrl}\`);
})`,
},
shared: {
react: {singleton: true, requiredVersion: '^18.0.0'},
'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
},
},
},
});Development Workflow with Remotes:
# Start host application with all remotes
npx app-builder dev
# Start host with only specific remotes (faster development)
npx app-builder dev --mf-remotes=header,sidebar
# The above is equivalent to setting enabledRemotes in config:
# enabledRemotes: ['header', 'sidebar']