@gravity-ui/app-builder 0.31.1 → 0.31.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,6 +113,25 @@ export default config;
113
113
  - `target` (`client | server`) — select compilation unit.
114
114
  - `verbose` (`boolean`) - turn on verbose output.
115
115
 
116
+ #### Environment Variables
117
+
118
+ `app-builder` automatically injects environment variables during the build process that are available in your application code:
119
+
120
+ - `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.
121
+
122
+ ```ts
123
+ // In your application code, you can access:
124
+ const publicPath = process.env.PUBLIC_PATH; // e.g., "https://cdn.example.com/build/" or "/build/"
125
+
126
+ // Useful for dynamically loading assets or configuring Module Federation
127
+ const assetUrl = `${process.env.PUBLIC_PATH}images/logo.png`;
128
+ ```
129
+
130
+ **Note**: On the server side, `process.env.PUBLIC_PATH` is only available when using SWC compiler (`compiler: 'swc'`). With TypeScript compiler, this variable is not injected.
131
+
132
+ - `process.env.NODE_ENV` — current environment (`'development'` | `'production'`)
133
+ - `process.env.IS_SSR` — boolean flag indicating if code is running in SSR context
134
+
116
135
  ### Server
117
136
 
118
137
  `app-builder` compiles server with typescript.
@@ -238,6 +257,7 @@ With this `{rootDir}/src/ui/tsconfig.json`:
238
257
  - `prefix` (`string`) — path to files inside the bucket
239
258
  - `region` (`string`) — AWS region or any string
240
259
  - `endpoint` (`string`) - cdn host to upload files
260
+ - `publicPath` (`string`) - public path to access files from the browser
241
261
  - `compress` (`boolean`) - upload also gzip and brotli compressed versions of files
242
262
  - `additionalPattern` (`string[]`) — patterns for uploading additional files. By default, only files generated by webpack are loaded.
243
263
  - `sentryConfig` (`Options`) — `@sentry/webpack-plugin` [configuration options](https://www.npmjs.com/package/@sentry/webpack-plugin/v/2.7.1).
@@ -338,18 +358,158 @@ Module Federation is a Webpack 5 feature that enables micro-frontend architectur
338
358
  `app-builder` uses `@module-federation/enhanced` for advanced Module Federation support.
339
359
 
340
360
  - `moduleFederation` (`object`) — Module Federation configuration
361
+
341
362
  - `name` (`string`) — unique name of the application in the Module Federation ecosystem. Required parameter.
342
363
  - `version` (`string`) — application version. When specified, the entry file will be named `entry-{version}.js` instead of `entry.js`.
343
364
  - `disableManifest` (`boolean`) — disable manifest file generation. When `true`, uses regular `.js` files for remote entry instead of manifest files. Default is `false`.
344
- - `publicPath` (`string`) — base URL for loading resources of this micro-frontend. Required parameter.
345
- - `remotes` (`string[]`) — list of remote application names that this application can load. Simplified alternative to `originalRemotes`.
346
- - `originalRemotes` (`RemotesObject`) — full configuration of remote applications in Module Federation Plugin format.
347
- - `remotesRuntimeVersioning` (`boolean`) — enables runtime versioning for remote applications.
365
+ - `remotes` (`string[]`) — list of remote application names that this application can load. This is a simplified alternative to `originalRemotes` that automatically generates remote URLs based on your public path configuration.
366
+
367
+ **How it works:**
368
+
369
+ - In **development mode**: Remote URLs are automatically generated using the pattern `{commonPublicPath}{remoteName}/entry.js` (or manifest files if enabled)
370
+ - In **production mode**: You need to ensure remote applications are deployed and accessible at the expected URLs
371
+ - Remote entry files are loaded at runtime to provide federated modules from other micro-frontends
372
+
373
+ **File naming patterns** (depends on configuration):
374
+
375
+ - With `disableManifest: false` (default): `mf-manifest.json` or `mf-manifest-[version].json` (with versioning)
376
+ - With `disableManifest: true`: `entry.js` or `entry-[version].js` (with versioning)
377
+
378
+ **Example:**
379
+
380
+ ```ts
381
+ // Simple configuration - URLs auto-generated in development
382
+ remotes: ['header', 'navigation', 'footer'];
383
+
384
+ // Results in loading from (in development):
385
+ // - https://localhost:3000/header/mf-manifest.json
386
+ // - https://localhost:3000/navigation/mf-manifest.json
387
+ // - https://localhost:3000/footer/mf-manifest.json
388
+ ```
389
+
390
+ **Development vs Production:**
391
+
392
+ - **Development**: App-builder automatically starts all remote applications and generates their URLs
393
+ - **Production**: Remote applications must be independently deployed and accessible at the generated URLs
394
+
395
+ **Integration with other options:**
396
+
397
+ - Works with `enabledRemotes` to selectively enable remotes in development
398
+ - Affected by `remotesRuntimeVersioning` for versioned file names
399
+ - File format controlled by `disableManifest` option
400
+
401
+ For more complex scenarios requiring custom URLs or cross-environment configurations, use `originalRemotes` instead.
402
+
403
+ - `enabledRemotes` (`string[]`) — list of enabled remotes for module federation. **Development mode only**. If not specified, all remotes from the `remotes` array will be enabled by default.
404
+
405
+ **Purpose:**
406
+
407
+ - Allows selective enabling/disabling of specific remotes during development
408
+ - Useful for debugging, testing individual micro-frontends, or working with partial system setups
409
+ - Helps reduce development startup time by loading only needed remotes
410
+ - Can be overridden via CLI flag: `--mf-remotes=header,footer`
411
+
412
+ **Loading behavior:**
413
+
414
+ - **Enabled remotes**: Loaded from local development server (auto-started by app-builder)
415
+ - **Disabled remotes**: Loaded from CDN (if `publicPathPrefix` or CDN configuration is available), allowing you to use stable production versions while developing specific parts locally
416
+
417
+ **Example:**
418
+
419
+ ```ts
420
+ // Load all available remotes (header, navigation, footer)
421
+ remotes: ['header', 'navigation', 'footer'];
422
+
423
+ // Enable only specific remotes in development
424
+ enabledRemotes: ['header', 'footer']; // navigation will be skipped
425
+ ```
426
+
427
+ **CLI Override:**
428
+
429
+ ```bash
430
+ # Override enabledRemotes from command line
431
+ npx app-builder dev --mf-remotes=header,navigation
432
+ ```
433
+
434
+ **Note:** This option has no effect in production builds - all configured remotes will be included in the production bundle configuration.
435
+
436
+ - `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.
437
+
438
+ **When to use:**
439
+
440
+ - Custom remote URLs (different domains, ports, paths)
441
+ - Cross-environment loading (staging, production CDN URLs)
442
+ - Complex deployment scenarios
443
+ - When `remotes` auto-generation doesn't meet your needs
444
+
445
+ **Format:**
446
+
447
+ ```ts
448
+ originalRemotes: {
449
+ remoteName: 'remoteName@remoteUrl',
450
+ }
451
+ ```
452
+
453
+ **Examples:**
454
+
455
+ ```ts
456
+ originalRemotes: {
457
+ header: 'header@https://cdn.example.com/header/entry.js',
458
+ footer: 'footer@https://cdn.example.com/footer/entry.js',
459
+ }
460
+ ```
461
+
462
+ **Note:** When `originalRemotes` is specified, the `remotes` option is ignored. Use either `remotes` OR `originalRemotes`, not both.
463
+
464
+ - `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.
465
+
466
+ **How it affects file names:**
467
+
468
+ - With `disableManifest: false`: `mf-manifest-[version].json` instead of `mf-manifest.json`
469
+ - With `disableManifest: true`: `entry-[version].js` instead of `entry.js`
470
+ - Version is taken from the remote application's `version` configuration
471
+
472
+ **Benefits:**
473
+
474
+ - **Cache busting**: Different versions get different URLs, preventing browser caching issues
475
+ - **Rollback capability**: Can load specific versions of remotes
476
+ - **Deployment safety**: Gradual rollouts with version-specific remote loading
477
+
478
+ **Example with versioning enabled:**
479
+
480
+ ```ts
481
+ // Host application
482
+ {
483
+ moduleFederation: {
484
+ name: 'shell',
485
+ remotes: ['header', 'navigation'],
486
+ remotesRuntimeVersioning: true, // Enable versioning
487
+ }
488
+ }
489
+
490
+ // Remote application (header)
491
+ {
492
+ moduleFederation: {
493
+ name: 'header',
494
+ version: '2.1.0', // This version appears in filename
495
+ exposes: { './Header': './src/Header' }
496
+ }
497
+ }
498
+
499
+ // Results in loading: header/mf-manifest-2.1.0.json
500
+ ```
501
+
502
+ **Runtime behavior:**
503
+
504
+ - The version is resolved at runtime from the remote's manifest or entry file
505
+ - Enables loading different versions of the same remote in different environments
506
+ - Works with both `remotes` and `originalRemotes` configurations
507
+
348
508
  - `isolateStyles` (`object`) — CSS style isolation settings to prevent conflicts between micro-frontends.
349
509
  - `getPrefix` (`(entryName: string) => string`) — function to generate CSS class prefix.
350
510
  - `prefixSelector` (`(prefix: string, selector: string, prefixedSelector: string, filePath: string) => string`) — function to add prefix to CSS selectors.
351
511
  - Also supports all standard options from [@module-federation/enhanced](https://module-federation.io/), except `name` and `remotes`, such as:
352
- - `filename` — entry file name (default `remoteEntry.js`)
512
+ - `filename` — entry file name (default `entry.js`)
353
513
  - `exposes` — modules that this application exports
354
514
  - `shared` — shared dependencies between applications
355
515
  - `runtimePlugins` — plugins for Module Federation runtime
@@ -363,7 +523,6 @@ export default defineConfig({
363
523
  client: {
364
524
  moduleFederation: {
365
525
  name: 'shell',
366
- publicPath: 'https://cdn.example.com/my-app/',
367
526
  // Simple remotes configuration
368
527
  remotes: ['header', 'footer', 'sidebar'],
369
528
  shared: {
@@ -384,12 +543,11 @@ export default defineConfig({
384
543
  moduleFederation: {
385
544
  name: 'main-shell',
386
545
  version: '2.1.0',
387
- publicPath: 'https://cdn.example.com/my-app/',
388
546
  // Detailed remotes configuration
389
547
  originalRemotes: {
390
- header: 'header@https://cdn.example.com/header/remoteEntry.js',
391
- footer: 'footer@https://cdn.example.com/footer/remoteEntry.js',
392
- userProfile: 'userProfile@https://cdn.example.com/user-profile/remoteEntry.js',
548
+ header: 'header@https://cdn.example.com/header/entry.js',
549
+ footer: 'footer@https://cdn.example.com/footer/entry.js',
550
+ userProfile: 'userProfile@https://cdn.example.com/user-profile/entry.js',
393
551
  },
394
552
  remotesRuntimeVersioning: true,
395
553
  isolateStyles: {
@@ -425,7 +583,6 @@ export default defineConfig({
425
583
  client: {
426
584
  moduleFederation: {
427
585
  name: 'header',
428
- publicPath: 'https://cdn.example.com/my-app/',
429
586
  // Expose modules for other applications
430
587
  exposes: {
431
588
  './Header': './src/components/Header',
@@ -452,7 +609,6 @@ export default defineConfig({
452
609
  moduleFederation: {
453
610
  name: 'dashboard',
454
611
  version: '1.5.0',
455
- publicPath: 'https://cdn.example.com/my-app/',
456
612
  // Consume remote modules
457
613
  remotes: ['charts', 'notifications'],
458
614
  // Expose own modules
@@ -469,3 +625,73 @@ export default defineConfig({
469
625
  },
470
626
  });
471
627
  ```
628
+
629
+ **Advanced Remotes Configuration Examples:**
630
+
631
+ ```ts
632
+ export default defineConfig({
633
+ client: {
634
+ moduleFederation: {
635
+ name: 'advanced-shell',
636
+ version: '3.0.0',
637
+
638
+ // Example 1: Simple remotes for development
639
+ remotes: ['header', 'sidebar', 'footer', 'analytics'],
640
+
641
+ // Example 2: Enable only specific remotes in development
642
+ enabledRemotes: ['header', 'sidebar'], // Only these will load in dev mode
643
+
644
+ // Example 3: Runtime versioning with manifests (production-ready)
645
+ remotesRuntimeVersioning: true, // Enables version-specific loading
646
+ disableManifest: false, // Use mf-manifest-[version].json files
647
+
648
+ shared: {
649
+ react: {singleton: true, requiredVersion: '^18.0.0'},
650
+ 'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
651
+ },
652
+ },
653
+ },
654
+ });
655
+
656
+ // Alternative: Explicit URLs for production
657
+ export default defineConfig({
658
+ client: {
659
+ moduleFederation: {
660
+ name: 'production-shell',
661
+
662
+ // Use originalRemotes for explicit control
663
+ originalRemotes: {
664
+ // Static CDN URLs
665
+ header: 'header@https://cdn.company.com/header/mf-manifest-2.1.0.json',
666
+ sidebar: 'sidebar@https://cdn.company.com/sidebar/entry-1.5.0.js',
667
+
668
+ // Dynamic remote loading
669
+ analytics: `promise new Promise((resolve) => {
670
+ const remoteUrl = process.env.NODE_ENV === 'production'
671
+ ? 'https://analytics.cdn.com/entry.js'
672
+ : 'http://localhost:3003/entry.js';
673
+ resolve(\`analytics@\${remoteUrl}\`);
674
+ })`,
675
+ },
676
+
677
+ shared: {
678
+ react: {singleton: true, requiredVersion: '^18.0.0'},
679
+ 'react-dom': {singleton: true, requiredVersion: '^18.0.0'},
680
+ },
681
+ },
682
+ },
683
+ });
684
+ ```
685
+
686
+ **Development Workflow with Remotes:**
687
+
688
+ ```bash
689
+ # Start host application with all remotes
690
+ npx app-builder dev
691
+
692
+ # Start host with only specific remotes (faster development)
693
+ npx app-builder dev --mf-remotes=header,sidebar
694
+
695
+ # The above is equivalent to setting enabledRemotes in config:
696
+ # enabledRemotes: ['header', 'sidebar']
697
+ ```
@@ -20,6 +20,7 @@ compile({
20
20
  projectPath: ${JSON.stringify(paths_1.default.appServer)},
21
21
  additionalPaths: ${JSON.stringify(config.server.swcOptions?.additionalPaths)},
22
22
  exclude: ${JSON.stringify(config.server.swcOptions?.exclude)},
23
+ publicPath: ${JSON.stringify(config.client.browserPublicPath)},
23
24
  });`;
24
25
  }
25
26
  function createTypescriptBuildScript(config) {
@@ -74,6 +74,7 @@ watch(
74
74
  },
75
75
  additionalPaths: ${JSON.stringify(config.server.swcOptions?.additionalPaths)},
76
76
  exclude: ${JSON.stringify(config.server.swcOptions?.exclude)},
77
+ publicPath: ${JSON.stringify(config.client.browserPublicPath)},
77
78
  }
78
79
  );`;
79
80
  }
@@ -144,6 +144,9 @@ async function getProjectConfig(command, { env, storybook, ...argv }) {
144
144
  ...omitUndefined(server),
145
145
  },
146
146
  };
147
+ if (projectConfig.client?.moduleFederation && argv.mfRemotes) {
148
+ projectConfig.client.moduleFederation.enabledRemotes = argv.mfRemotes;
149
+ }
147
150
  return normalizeConfig(projectConfig, command);
148
151
  }
149
152
  async function normalizeConfig(userConfig, mode) {
@@ -187,10 +190,14 @@ async function normalizeConfig(userConfig, mode) {
187
190
  config.lib.newJsxTransform = config.lib.newJsxTransform ?? true;
188
191
  return config;
189
192
  }
193
+ // TODO(DakEnviy): Make mode type strict
190
194
  async function normalizeClientConfig(client, mode) {
195
+ const cdnConfig = Array.isArray(client.cdn) ? client.cdn[0] : client.cdn;
191
196
  let publicPath = client.publicPath || path.normalize(`${client.publicPathPrefix || ''}/build/`);
197
+ let browserPublicPath = (mode !== 'dev' && cdnConfig?.publicPath) || publicPath;
192
198
  if (client.moduleFederation) {
193
199
  publicPath = path.normalize(`${publicPath}${client.moduleFederation.name}/`);
200
+ browserPublicPath = path.normalize(`${browserPublicPath}${client.moduleFederation.name}/`);
194
201
  }
195
202
  let transformCssWithLightningCss = Boolean(client.transformCssWithLightningCss);
196
203
  if (client.moduleFederation?.isolateStyles && transformCssWithLightningCss) {
@@ -208,6 +215,8 @@ async function normalizeClientConfig(client, mode) {
208
215
  : (client.reactRefresh ?? ((options) => options)),
209
216
  newJsxTransform: client.newJsxTransform ?? true,
210
217
  publicPath,
218
+ cdnPublicPath: cdnConfig?.publicPath,
219
+ browserPublicPath,
211
220
  assetsManifestFile: client.assetsManifestFile ||
212
221
  (client.moduleFederation?.version
213
222
  ? `assets-manifest-${client.moduleFederation.version}.json`
@@ -77,22 +77,23 @@ export type ModuleFederationConfig = Omit<moduleFederationPlugin.ModuleFederatio
77
77
  * @default false
78
78
  */
79
79
  disableManifest?: boolean;
80
- /**
81
- * Base URL for loading resources of this micro-frontend
82
- * Should point to a publicly accessible URL where the files will be hosted
83
- * @example 'https://cdn.example.com/my-app/'
84
- */
85
- publicPath: string;
86
80
  /**
87
81
  * List of remote application names that this application can load
88
82
  * Simplified alternative to originalRemotes - only names are specified
89
83
  * @example ['header', 'footer', 'navigation']
90
84
  */
91
85
  remotes?: string[];
86
+ /**
87
+ * List of enabled remotes for module federation
88
+ * If not specified, all remotes will be enabled by default
89
+ * It used only for development mode
90
+ * @example ['header', 'navigation']
91
+ */
92
+ enabledRemotes?: string[];
92
93
  /**
93
94
  * Full configuration of remote applications in Module Federation format
94
95
  * Allows more detailed configuration of each remote application
95
- * @example { header: 'header@https://header.example.com/remoteEntry.js' }
96
+ * @example { header: 'header@https://header.example.com/entry.js' }
96
97
  */
97
98
  originalRemotes?: moduleFederationPlugin.ModuleFederationPluginOptions['remotes'];
98
99
  /**
@@ -323,6 +324,7 @@ export interface CdnUploadConfig {
323
324
  prefix?: string;
324
325
  region?: string;
325
326
  endpoint?: string;
327
+ publicPath?: string;
326
328
  compress?: boolean;
327
329
  cacheControl?: UploadOptions['cacheControl'];
328
330
  /**
@@ -361,7 +363,22 @@ export interface ServiceConfig {
361
363
  export type NormalizedClientConfig = Omit<ClientConfig, 'publicPathPrefix' | 'publicPath' | 'assetsManifestFile' | 'hiddenSourceMap' | 'svgr' | 'lazyCompilation' | 'devServer' | 'disableForkTsChecker' | 'disableReactRefresh' | 'transformCssWithLightningCss'> & {
362
364
  bundler: Bundler;
363
365
  javaScriptLoader: JavaScriptLoader;
366
+ /**
367
+ * Build public path
368
+ * (concatenated with micro-frontend name if module federation is configured).
369
+ */
364
370
  publicPath: string;
371
+ /**
372
+ * Public path for CDN,
373
+ * it presents even if CDN is disabled.
374
+ */
375
+ cdnPublicPath?: string;
376
+ /**
377
+ * Final public path for browser,
378
+ * it is based on cdnPublicPath if CDN is enabled or publicPath otherwise
379
+ * (concatenated with micro-frontend name if module federation is configured).
380
+ */
381
+ browserPublicPath: string;
365
382
  assetsManifestFile: string;
366
383
  hiddenSourceMap: boolean;
367
384
  svgr: NonNullable<ClientConfig['svgr']>;
@@ -378,6 +395,7 @@ export type NormalizedClientConfig = Omit<ClientConfig, 'publicPathPrefix' | 'pu
378
395
  }) => Configuration | Promise<Configuration>;
379
396
  rspack: (config: RspackConfiguration, options: {
380
397
  configType: `${WebpackMode}`;
398
+ isSsr: boolean;
381
399
  }) => RspackConfiguration | Promise<RspackConfiguration>;
382
400
  debugWebpack?: boolean;
383
401
  babel: (config: Babel.TransformOptions, options: {
@@ -1,9 +1,9 @@
1
1
  import type { Logger } from '../logger';
2
- import type { GetSwcOptionsFromTsconfigOptions } from './utils';
3
- type SwcCompileOptions = Pick<GetSwcOptionsFromTsconfigOptions, 'additionalPaths' | 'exclude'> & {
2
+ import type { GetSwcOptionsParams } from './utils';
3
+ type SwcCompileOptions = Pick<GetSwcOptionsParams, 'additionalPaths' | 'exclude' | 'publicPath'> & {
4
4
  projectPath: string;
5
5
  outputPath: string;
6
6
  logger: Logger;
7
7
  };
8
- export declare function compile({ projectPath, outputPath, logger, additionalPaths, exclude, }: SwcCompileOptions): Promise<void>;
8
+ export declare function compile({ projectPath, outputPath, logger, additionalPaths, exclude, publicPath, }: SwcCompileOptions): Promise<void>;
9
9
  export {};
@@ -5,13 +5,14 @@ const pretty_time_1 = require("../logger/pretty-time");
5
5
  // @ts-ignore @swc/cli is not typed
6
6
  const cli_1 = require("@swc/cli");
7
7
  const utils_1 = require("./utils");
8
- async function compile({ projectPath, outputPath, logger, additionalPaths, exclude, }) {
8
+ async function compile({ projectPath, outputPath, logger, additionalPaths, exclude, publicPath, }) {
9
9
  const start = process.hrtime.bigint();
10
10
  logger.message('Start compilation');
11
- const { swcOptions, directoriesToCompile } = (0, utils_1.getSwcOptionsFromTsconfig)({
11
+ const { swcOptions, directoriesToCompile } = (0, utils_1.getSwcOptions)({
12
12
  projectPath,
13
13
  additionalPaths,
14
14
  exclude,
15
+ publicPath,
15
16
  });
16
17
  const cliOptions = {
17
18
  filenames: directoriesToCompile,
@@ -1,11 +1,12 @@
1
1
  export declare const EXTENSIONS_TO_COMPILE: string[];
2
- export type GetSwcOptionsFromTsconfigOptions = {
2
+ export interface GetSwcOptionsParams {
3
3
  projectPath: string;
4
4
  filename?: string;
5
5
  additionalPaths?: string[];
6
6
  exclude?: string | string[];
7
- };
8
- export declare function getSwcOptionsFromTsconfig({ projectPath, filename, additionalPaths, exclude, }: GetSwcOptionsFromTsconfigOptions): {
7
+ publicPath: string;
8
+ }
9
+ export declare function getSwcOptions({ projectPath, filename, additionalPaths, exclude, publicPath, }: GetSwcOptionsParams): {
9
10
  swcOptions: import("@swc/types").Options;
10
11
  directoriesToCompile: string[];
11
12
  };
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.EXTENSIONS_TO_COMPILE = void 0;
7
- exports.getSwcOptionsFromTsconfig = getSwcOptionsFromTsconfig;
7
+ exports.getSwcOptions = getSwcOptions;
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const tsconfig_to_swcconfig_1 = require("tsconfig-to-swcconfig");
10
10
  const DEFAULT_EXCLUDE = ['node_modules'];
@@ -19,13 +19,26 @@ function resolvePaths(paths, baseUrl) {
19
19
  }
20
20
  return entries;
21
21
  }
22
- function getSwcOptionsFromTsconfig({ projectPath, filename = 'tsconfig.json', additionalPaths, exclude, }) {
22
+ function getSwcOptions({ projectPath, filename = 'tsconfig.json', additionalPaths, exclude, publicPath, }) {
23
23
  const swcOptions = (0, tsconfig_to_swcconfig_1.convert)(filename, projectPath);
24
24
  swcOptions.exclude = swcOptions.exclude || [];
25
25
  swcOptions.jsc = {
26
26
  ...swcOptions.jsc,
27
27
  // SWC requires absolute path as baseUrl
28
28
  baseUrl: projectPath,
29
+ transform: {
30
+ ...swcOptions.jsc?.transform,
31
+ optimizer: {
32
+ ...swcOptions.jsc?.transform?.optimizer,
33
+ globals: {
34
+ ...swcOptions.jsc?.transform?.optimizer?.globals,
35
+ vars: {
36
+ 'process.env.PUBLIC_PATH': JSON.stringify(publicPath),
37
+ ...swcOptions.jsc?.transform?.optimizer?.globals?.vars,
38
+ },
39
+ },
40
+ },
41
+ },
29
42
  };
30
43
  let customExclude = [];
31
44
  if (Array.isArray(exclude)) {
@@ -1,9 +1,9 @@
1
1
  import type { Logger } from '../logger';
2
- import type { GetSwcOptionsFromTsconfigOptions } from './utils';
3
- type SwcWatchOptions = Pick<GetSwcOptionsFromTsconfigOptions, 'additionalPaths' | 'exclude'> & {
2
+ import type { GetSwcOptionsParams } from './utils';
3
+ type SwcWatchOptions = Pick<GetSwcOptionsParams, 'additionalPaths' | 'exclude' | 'publicPath'> & {
4
4
  outputPath: string;
5
5
  logger: Logger;
6
6
  onAfterFilesEmitted?: () => void;
7
7
  };
8
- export declare function watch(projectPath: string, { outputPath, logger, onAfterFilesEmitted, additionalPaths, exclude }: SwcWatchOptions): Promise<void>;
8
+ export declare function watch(projectPath: string, { outputPath, logger, onAfterFilesEmitted, additionalPaths, exclude, publicPath, }: SwcWatchOptions): Promise<void>;
9
9
  export {};
@@ -4,12 +4,13 @@ exports.watch = watch;
4
4
  // @ts-ignore @swc/cli is not typed
5
5
  const cli_1 = require("@swc/cli");
6
6
  const utils_1 = require("./utils");
7
- async function watch(projectPath, { outputPath, logger, onAfterFilesEmitted, additionalPaths, exclude }) {
7
+ async function watch(projectPath, { outputPath, logger, onAfterFilesEmitted, additionalPaths, exclude, publicPath, }) {
8
8
  logger.message('Start compilation in watch mode');
9
- const { swcOptions, directoriesToCompile } = (0, utils_1.getSwcOptionsFromTsconfig)({
9
+ const { swcOptions, directoriesToCompile } = (0, utils_1.getSwcOptions)({
10
10
  projectPath,
11
11
  additionalPaths,
12
12
  exclude,
13
+ publicPath,
13
14
  });
14
15
  const cliOptions = {
15
16
  filenames: directoriesToCompile,
@@ -353,10 +353,6 @@ function configureResolve({ isEnvProduction, config }) {
353
353
  fallback: config.fallback,
354
354
  };
355
355
  }
356
- function isModuleFederationEntry(entryName, fileName) {
357
- // Ignore bootstrap file for module federation entries
358
- return entryName === fileName || `${entryName}-bootstrap` === fileName;
359
- }
360
356
  function createEntryArray(entry) {
361
357
  if (typeof entry === 'string') {
362
358
  return [require.resolve('./public-path'), entry];
@@ -380,7 +376,6 @@ function configureEntry({ config, entriesDirectory }) {
380
376
  }), {});
381
377
  }
382
378
  let entryFiles = fs.readdirSync(entriesDirectory).filter((file) => /\.[jt]sx?$/.test(file));
383
- let result = {};
384
379
  if (config.moduleFederation) {
385
380
  const { name, remotes, originalRemotes } = config.moduleFederation;
386
381
  const entryFile = entryFiles.find((item) => path.parse(item).name === name);
@@ -394,12 +389,13 @@ function configureEntry({ config, entriesDirectory }) {
394
389
  }
395
390
  entryFiles = entryFiles.filter((file) => {
396
391
  const fileName = path.parse(file).name;
397
- return (!isModuleFederationEntry(name, fileName) &&
398
- remoteNames.every((remote) => !isModuleFederationEntry(remote, fileName)));
392
+ return (
393
+ // Ignore bootstrap file for module federation host
394
+ fileName !== `${name}-bootstrap` &&
395
+ remoteNames.every(
396
+ // Ignore bootstrap and entry files for module federation remotes
397
+ (remote) => remote !== fileName && `${remote}-bootstrap` !== fileName));
399
398
  });
400
- result = {
401
- main: createEntryArray(path.resolve(entriesDirectory, entryFile)),
402
- };
403
399
  }
404
400
  if (Array.isArray(config.entryFilter) && config.entryFilter.length) {
405
401
  entryFiles = entryFiles.filter((file) => config.entryFilter?.includes(path.parse(file).name));
@@ -407,7 +403,7 @@ function configureEntry({ config, entriesDirectory }) {
407
403
  if (!entryFiles.length) {
408
404
  throw new Error('No entries were found after applying entry filter');
409
405
  }
410
- return entryFiles.reduce((acc, file) => addEntry(acc, path.resolve(entriesDirectory, file)), result);
406
+ return entryFiles.reduce((acc, file) => addEntry(acc, path.resolve(entriesDirectory, file)), {});
411
407
  }
412
408
  function getFileNames({ isEnvProduction, isSsr, config }) {
413
409
  let ext = 'js';
@@ -859,6 +855,7 @@ function getDefinitions({ config, isSsr }) {
859
855
  return {
860
856
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
861
857
  'process.env.IS_SSR': JSON.stringify(isSsr),
858
+ 'process.env.PUBLIC_PATH': JSON.stringify(config.browserPublicPath),
862
859
  ...config.definitions,
863
860
  };
864
861
  }
@@ -966,9 +963,12 @@ function configureCommonPlugins(options, bundlerPlugins) {
966
963
  }
967
964
  plugins.push(createMomentTimezoneDataPlugin(config.momentTz));
968
965
  if (config.moduleFederation) {
969
- const { name, version, disableManifest, publicPath, remotes, originalRemotes, remotesRuntimeVersioning, runtimePlugins, ...restOptions } = config.moduleFederation;
966
+ const { name, version, disableManifest, remotes, enabledRemotes = remotes, originalRemotes, remotesRuntimeVersioning, isolateStyles: _isolateStyles, // Omit isolateStyles from restOptions
967
+ runtimePlugins, ...restOptions } = config.moduleFederation;
970
968
  let actualRemotes = originalRemotes;
971
- if (remotes) {
969
+ if (!actualRemotes && remotes && enabledRemotes) {
970
+ // Remove micro-frontend name from public path
971
+ const commonPublicPath = config.browserPublicPath.replace(`${name}/`, '');
972
972
  let remoteFile;
973
973
  if (disableManifest) {
974
974
  if (remotesRuntimeVersioning) {
@@ -984,13 +984,26 @@ function configureCommonPlugins(options, bundlerPlugins) {
984
984
  else {
985
985
  remoteFile = 'mf-manifest.json';
986
986
  }
987
- actualRemotes = remotes.reduce((acc, remoteName) => {
987
+ actualRemotes = remotes.reduce((acc, remote) => {
988
+ let remotePath;
989
+ if (isEnvDevelopment) {
990
+ if (enabledRemotes.includes(remote)) {
991
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
992
+ remotePath = `${commonPublicPath}${remote}/${remoteFile.replace('[version]', version)}`;
993
+ }
994
+ else if (config.cdnPublicPath) {
995
+ remotePath = `${config.cdnPublicPath}${remote}/${remoteFile}`;
996
+ }
997
+ }
998
+ if (!remotePath) {
999
+ remotePath = `${commonPublicPath}${remote}/${remoteFile}`;
1000
+ }
988
1001
  // eslint-disable-next-line no-param-reassign
989
- acc[remoteName] = `${remoteName}@${publicPath}${remoteName}/${remoteFile}`;
1002
+ acc[remote] = `${remote}@${remotePath}`;
990
1003
  return acc;
991
1004
  }, {});
992
1005
  }
993
- const actualRuntimePlugins = runtimePlugins || [];
1006
+ const actualRuntimePlugins = runtimePlugins.slice() || [];
994
1007
  if (remotesRuntimeVersioning) {
995
1008
  actualRuntimePlugins.push(require.resolve('./runtime-versioning-plugin'));
996
1009
  }
@@ -17,6 +17,8 @@ export declare function createCli(argv: string[]): {
17
17
  "disable-react-refresh": boolean | undefined;
18
18
  lazyCompilation: boolean | undefined;
19
19
  "lazy-compilation": boolean | undefined;
20
+ "mf-remotes": string[] | undefined;
21
+ mfRemotes: string[] | undefined;
20
22
  reactProfiling: boolean | undefined;
21
23
  "react-profiling": boolean | undefined;
22
24
  analyzeBundle: "true" | "statoscope" | "rsdoctor" | undefined;
@@ -45,6 +47,8 @@ export declare function createCli(argv: string[]): {
45
47
  "disable-react-refresh": boolean | undefined;
46
48
  lazyCompilation: boolean | undefined;
47
49
  "lazy-compilation": boolean | undefined;
50
+ "mf-remotes": string[] | undefined;
51
+ mfRemotes: string[] | undefined;
48
52
  reactProfiling: boolean | undefined;
49
53
  "react-profiling": boolean | undefined;
50
54
  analyzeBundle: "true" | "statoscope" | "rsdoctor" | undefined;
@@ -142,6 +142,12 @@ function createCli(argv) {
142
142
  group: 'Client',
143
143
  type: 'boolean',
144
144
  describe: 'Display final webpack configurations for debugging purposes',
145
+ })
146
+ .option('mf-remotes', {
147
+ group: 'Client',
148
+ type: 'string',
149
+ describe: 'Enabled remotes for module federation (all remotes by default)',
150
+ array: true,
145
151
  }),
146
152
  handler: handlerP(getCommandHandler('dev', (args, cmd) => {
147
153
  if ((0, models_1.isLibraryConfig)(args)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/app-builder",
3
- "version": "0.31.1",
3
+ "version": "0.31.2-beta.0",
4
4
  "description": "Develop and build your React client-server projects, powered by typescript and webpack",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",