@gravity-ui/app-builder 0.13.1 → 0.14.0-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.
@@ -40,9 +40,6 @@ const config_1 = require("../../common/webpack/config");
40
40
  async function watchClientCompilation(config, onManifestReady) {
41
41
  const clientCompilation = await buildWebpackServer(config);
42
42
  const compiler = clientCompilation.compiler;
43
- if ('compilers' in compiler) {
44
- throw new Error('Unexpected multi compiler');
45
- }
46
43
  subscribeToManifestReadyEvent(compiler, onManifestReady);
47
44
  return clientCompilation;
48
45
  }
@@ -50,7 +47,14 @@ async function buildWebpackServer(config) {
50
47
  const logger = new logger_1.Logger('webpack', config.verbose);
51
48
  const { webSocketPath = path.normalize(`/${config.client.publicPathPrefix}/build/sockjs-node`), writeToDisk, ...devServer } = config.client.devServer || {};
52
49
  const normalizedConfig = { ...config.client, devServer: { ...devServer, webSocketPath } };
53
- const webpackConfig = await (0, config_1.webpackConfigFactory)("development" /* WebpackMode.Dev */, normalizedConfig, { logger });
50
+ const webpackConfigs = [
51
+ await (0, config_1.webpackConfigFactory)("development" /* WebpackMode.Dev */, normalizedConfig, { logger }),
52
+ ];
53
+ const isSsr = Boolean(normalizedConfig.ssr);
54
+ if (isSsr) {
55
+ const logger = new logger_1.Logger('webpack(SSR)', config.verbose);
56
+ webpackConfigs.push(await (0, config_1.webpackConfigFactory)("development" /* WebpackMode.Dev */, normalizedConfig, { logger, isSsr }));
57
+ }
54
58
  const publicPath = path.normalize(config.client.publicPathPrefix + '/build/');
55
59
  const staticFolder = path.resolve(paths_1.default.appDist, 'public');
56
60
  const options = {
@@ -58,7 +62,18 @@ async function buildWebpackServer(config) {
58
62
  devMiddleware: {
59
63
  publicPath,
60
64
  stats: 'errors-warnings',
61
- writeToDisk,
65
+ writeToDisk: (target) => {
66
+ if (writeToDisk === true) {
67
+ return true;
68
+ }
69
+ if (isSsr && target.startsWith(paths_1.default.appSsrBuild)) {
70
+ return true;
71
+ }
72
+ if (typeof writeToDisk === 'function') {
73
+ return writeToDisk(target);
74
+ }
75
+ return false;
76
+ },
62
77
  },
63
78
  liveReload: false,
64
79
  hot: true,
@@ -116,7 +131,7 @@ async function buildWebpackServer(config) {
116
131
  });
117
132
  }
118
133
  options.proxy = proxy;
119
- const compiler = (0, webpack_1.default)(webpackConfig);
134
+ const compiler = (0, webpack_1.default)(webpackConfigs);
120
135
  const server = new webpack_dev_server_1.default(options, compiler);
121
136
  try {
122
137
  await server.start();
@@ -129,17 +144,28 @@ async function buildWebpackServer(config) {
129
144
  }
130
145
  return server;
131
146
  }
132
- function subscribeToManifestReadyEvent(compiler, onManifestReady) {
147
+ function subscribeToManifestReadyEvent(webpackCompiler, onManifestReady) {
133
148
  const promises = [];
134
- const assetsManifestPlugin = compiler.options.plugins.find((plugin) => plugin instanceof webpack_assets_manifest_1.default);
135
- if (assetsManifestPlugin) {
136
- const assetsManifestReady = (0, utils_1.deferredPromise)();
137
- promises.push(assetsManifestReady.promise);
138
- assetsManifestPlugin.hooks.done.tap('app-builder', assetsManifestReady.resolve);
149
+ const options = Array.isArray(webpackCompiler.options)
150
+ ? webpackCompiler.options
151
+ : [webpackCompiler.options];
152
+ const compilers = 'compilers' in webpackCompiler ? webpackCompiler.compilers : [webpackCompiler];
153
+ for (let i = 0; i < options.length; i++) {
154
+ const config = options[i];
155
+ const compiler = compilers[i];
156
+ if (!config || !compiler) {
157
+ throw new Error('Something goes wrong!');
158
+ }
159
+ const assetsManifestPlugin = config.plugins.find((plugin) => plugin instanceof webpack_assets_manifest_1.default);
160
+ if (assetsManifestPlugin) {
161
+ const assetsManifestReady = (0, utils_1.deferredPromise)();
162
+ promises.push(assetsManifestReady.promise);
163
+ assetsManifestPlugin.hooks.done.tap('app-builder', assetsManifestReady.resolve);
164
+ }
165
+ const manifestReady = (0, utils_1.deferredPromise)();
166
+ promises.push(manifestReady.promise);
167
+ const { afterEmit } = (0, webpack_manifest_plugin_1.getCompilerHooks)(compiler);
168
+ afterEmit.tap('app-builder', manifestReady.resolve);
139
169
  }
140
- const manifestReady = (0, utils_1.deferredPromise)();
141
- promises.push(manifestReady.promise);
142
- const { afterEmit } = (0, webpack_manifest_plugin_1.getCompilerHooks)(compiler);
143
- afterEmit.tap('app-builder', manifestReady.resolve);
144
170
  Promise.all(promises).then(() => onManifestReady());
145
171
  }
@@ -56,7 +56,7 @@ async function default_1(config) {
56
56
  script: `${serverPath}/index.js`,
57
57
  args: ['--dev', config.server.port ? `--port=${config.server.port}` : ''],
58
58
  env: {
59
- ...(config.server.port ? { APP_PORT: config.server.port } : undefined),
59
+ ...(config.server.port ? { APP_PORT: `${config.server.port}` } : undefined),
60
60
  },
61
61
  nodeArgs: inspect || inspectBrk
62
62
  ? [`--${inspect ? 'inspect' : 'inspect-brk'}=:::${inspect || inspectBrk}`]
@@ -191,6 +191,10 @@ export interface ClientConfig {
191
191
  * Modify or return a custom [Terser options](https://github.com/terser/terser#minify-options).
192
192
  */
193
193
  terser?: (options: TerserOptions) => TerserOptions;
194
+ ssr?: {
195
+ noExternal?: string | RegExp | (string | RegExp)[] | true;
196
+ moduleType?: 'commonjs' | 'esm';
197
+ };
194
198
  }
195
199
  export interface CdnUploadConfig {
196
200
  bucket: string;
@@ -7,6 +7,8 @@ declare const _default: {
7
7
  appDist: string;
8
8
  appRun: string;
9
9
  appBuild: string;
10
+ appSsrEntry: string;
11
+ appSsrBuild: string;
10
12
  src: string;
11
13
  libBuild: string;
12
14
  libBuildEsm: string;
@@ -36,6 +36,8 @@ exports.default = {
36
36
  appDist: resolveApp('dist'),
37
37
  appRun: resolveApp('dist/run'),
38
38
  appBuild: resolveApp('dist/public/build'),
39
+ appSsrEntry: resolveApp('src/ui/ssr'),
40
+ appSsrBuild: resolveApp('dist/ssr'),
39
41
  src: resolveApp('src'),
40
42
  libBuild: resolveApp('build'),
41
43
  libBuildEsm: resolveApp('build/esm'),
@@ -10,10 +10,15 @@ const config_1 = require("./config");
10
10
  const utils_1 = require("./utils");
11
11
  async function webpackCompile(config) {
12
12
  const logger = new logger_1.Logger('webpack', config.verbose);
13
- const webpackConfig = await (0, config_1.webpackConfigFactory)("production" /* WebpackMode.Prod */, config, { logger });
13
+ const webpackConfigs = [await (0, config_1.webpackConfigFactory)("production" /* WebpackMode.Prod */, config, { logger })];
14
+ const isSsr = Boolean(config.ssr);
15
+ if (isSsr) {
16
+ const logger = new logger_1.Logger('webpack(SSR)', config.verbose);
17
+ webpackConfigs.push(await (0, config_1.webpackConfigFactory)("production" /* WebpackMode.Prod */, config, { logger, isSsr }));
18
+ }
14
19
  logger.verbose('Config created');
15
20
  return new Promise((resolve) => {
16
- const compiler = (0, webpack_1.default)(webpackConfig, (0, utils_1.webpackCompilerHandlerFactory)(logger, async () => {
21
+ const compiler = (0, webpack_1.default)(webpackConfigs, (0, utils_1.webpackCompilerHandlerFactory)(logger, async () => {
17
22
  resolve();
18
23
  }));
19
24
  process.on('SIGINT', async () => {
@@ -7,16 +7,20 @@ export interface HelperOptions {
7
7
  isEnvDevelopment: boolean;
8
8
  isEnvProduction: boolean;
9
9
  configType: `${WebpackMode}`;
10
+ buildDirectory: string;
11
+ entriesDirectory: string;
12
+ isSsr: boolean;
10
13
  }
11
14
  export declare const enum WebpackMode {
12
15
  Prod = "production",
13
16
  Dev = "development"
14
17
  }
15
- export declare function webpackConfigFactory(webpackMode: WebpackMode, config: NormalizedClientConfig, { logger }?: {
18
+ export declare function webpackConfigFactory(webpackMode: WebpackMode, config: NormalizedClientConfig, { logger, isSsr }?: {
16
19
  logger?: Logger;
20
+ isSsr?: boolean;
17
21
  }): Promise<webpack.Configuration>;
18
22
  export declare function configureModuleRules(helperOptions: HelperOptions, additionalRules?: NonNullable<webpack.RuleSetRule['oneOf']>): webpack.RuleSetRule[];
19
23
  export declare function configureResolve({ isEnvProduction, config }: HelperOptions): webpack.ResolveOptions;
20
24
  type Optimization = NonNullable<webpack.Configuration['optimization']>;
21
- export declare function configureOptimization({ config }: HelperOptions): Optimization;
25
+ export declare function configureOptimization({ config, isSsr }: HelperOptions): Optimization;
22
26
  export {};
@@ -44,6 +44,7 @@ const react_refresh_webpack_plugin_1 = __importDefault(require("@pmmmwh/react-re
44
44
  const moment_timezone_data_webpack_plugin_1 = __importDefault(require("moment-timezone-data-webpack-plugin"));
45
45
  const webpack_plugin_1 = __importDefault(require("@statoscope/webpack-plugin"));
46
46
  const circular_dependency_plugin_1 = __importDefault(require("circular-dependency-plugin"));
47
+ const webpack_node_externals_1 = __importDefault(require("webpack-node-externals"));
47
48
  const paths_1 = __importDefault(require("../paths"));
48
49
  const babel_1 = require("../babel");
49
50
  const progress_plugin_1 = require("./progress-plugin");
@@ -53,7 +54,7 @@ const log_config_1 = require("../logger/log-config");
53
54
  const utils_2 = require("../typescript/utils");
54
55
  const imagesSizeLimit = 2048;
55
56
  const fontSizeLimit = 8192;
56
- async function webpackConfigFactory(webpackMode, config, { logger } = {}) {
57
+ async function webpackConfigFactory(webpackMode, config, { logger, isSsr = false } = {}) {
57
58
  const isEnvDevelopment = webpackMode === "development" /* WebpackMode.Dev */;
58
59
  const isEnvProduction = webpackMode === "production" /* WebpackMode.Prod */;
59
60
  const helperOptions = {
@@ -62,11 +63,25 @@ async function webpackConfigFactory(webpackMode, config, { logger } = {}) {
62
63
  isEnvDevelopment,
63
64
  isEnvProduction,
64
65
  configType: webpackMode,
66
+ buildDirectory: isSsr ? paths_1.default.appSsrBuild : paths_1.default.appBuild,
67
+ entriesDirectory: isSsr ? paths_1.default.appSsrEntry : paths_1.default.appEntry,
68
+ isSsr,
65
69
  };
70
+ let externals = config.externals;
71
+ if (isSsr) {
72
+ externals =
73
+ config.ssr?.noExternal === true
74
+ ? undefined
75
+ : (0, webpack_node_externals_1.default)({
76
+ allowlist: config.ssr?.noExternal,
77
+ importType: config.ssr?.moduleType === 'esm' ? 'module' : 'commonjs',
78
+ });
79
+ }
66
80
  let webpackConfig = {
67
81
  mode: webpackMode,
68
82
  context: paths_1.default.app,
69
83
  bail: isEnvProduction,
84
+ target: isSsr ? 'node' : undefined,
70
85
  devtool: configureDevTool(helperOptions),
71
86
  entry: configureEntry(helperOptions),
72
87
  output: configureOutput(helperOptions),
@@ -76,7 +91,7 @@ async function webpackConfigFactory(webpackMode, config, { logger } = {}) {
76
91
  },
77
92
  plugins: configurePlugins(helperOptions),
78
93
  optimization: configureOptimization(helperOptions),
79
- externals: config.externals,
94
+ externals,
80
95
  node: config.node,
81
96
  watchOptions: configureWatchOptions(helperOptions),
82
97
  ignoreWarnings: [/Failed to parse source map/],
@@ -135,7 +150,10 @@ function configureWatchOptions({ config }) {
135
150
  delete watchOptions.watchPackages;
136
151
  return watchOptions;
137
152
  }
138
- function configureExperiments({ config, isEnvProduction, }) {
153
+ function configureExperiments({ config, isEnvProduction, isSsr, }) {
154
+ if (isSsr) {
155
+ return config.ssr?.moduleType === 'esm' ? { outputModule: true } : undefined;
156
+ }
139
157
  if (isEnvProduction) {
140
158
  return undefined;
141
159
  }
@@ -190,56 +208,69 @@ function createEntryArray(entry) {
190
208
  return [require.resolve('./public-path'), entry];
191
209
  }
192
210
  function addEntry(entry, file) {
193
- const newEntry = path.resolve(paths_1.default.appEntry, file);
194
211
  return {
195
212
  ...entry,
196
- [path.parse(file).name]: createEntryArray(newEntry),
213
+ [path.parse(file).name]: createEntryArray(file),
197
214
  };
198
215
  }
199
- function configureEntry({ config }) {
200
- let entries = fs.readdirSync(paths_1.default.appEntry).filter((file) => /\.[jt]sx?$/.test(file));
216
+ function configureEntry({ config, entriesDirectory }) {
217
+ let entries = fs.readdirSync(entriesDirectory).filter((file) => /\.[jt]sx?$/.test(file));
201
218
  if (Array.isArray(config.entryFilter) && config.entryFilter.length) {
202
219
  entries = entries.filter((entry) => config.entryFilter?.includes(entry.split('.')[0] ?? ''));
203
220
  }
204
221
  if (!entries.length) {
205
- throw new Error('No entries were found after applying UI_CORE_ENTRY_FILTER');
222
+ throw new Error('No entries were found after applying entry filter');
206
223
  }
207
- return entries.reduce((entry, file) => addEntry(entry, file), {});
224
+ return entries.reduce((entry, file) => addEntry(entry, path.resolve(entriesDirectory, file)), {});
208
225
  }
209
- function getFileNames({ isEnvProduction }) {
226
+ function getFileNames({ isEnvProduction, isSsr, config }) {
227
+ let ext = 'js';
228
+ if (isSsr) {
229
+ ext = config.ssr?.moduleType === 'esm' ? 'mjs' : 'cjs';
230
+ }
210
231
  return {
211
- filename: isEnvProduction ? 'js/[name].[contenthash:8].js' : 'js/[name].js',
232
+ filename: isEnvProduction ? `js/[name].[contenthash:8].${ext}` : `js/[name].${ext}`,
212
233
  chunkFilename: isEnvProduction
213
234
  ? 'js/[name].[contenthash:8].chunk.js'
214
235
  : 'js/[name].chunk.js',
215
236
  };
216
237
  }
217
- function configureOutput({ isEnvDevelopment, ...rest }) {
238
+ function configureOutput(options) {
239
+ let ssrOptions;
240
+ if (options.isSsr) {
241
+ ssrOptions = {
242
+ library: { type: options.config.ssr?.moduleType === 'esm' ? 'module' : 'commonjs2' },
243
+ chunkFormat: false,
244
+ };
245
+ }
218
246
  return {
219
- ...getFileNames({ isEnvDevelopment, ...rest }),
220
- path: paths_1.default.appBuild,
221
- pathinfo: isEnvDevelopment,
247
+ ...getFileNames(options),
248
+ path: options.buildDirectory,
249
+ pathinfo: options.isEnvDevelopment,
250
+ ...ssrOptions,
222
251
  };
223
252
  }
224
- function createJavaScriptLoader({ isEnvProduction, isEnvDevelopment, configType, config, }) {
253
+ function createJavaScriptLoader({ isEnvProduction, isEnvDevelopment, configType, config, isSsr, }) {
225
254
  const plugins = [];
226
- if (isEnvDevelopment && config.reactRefresh !== false) {
227
- plugins.push([
228
- require.resolve('react-refresh/babel'),
229
- config.devServer?.webSocketPath
230
- ? {
231
- overlay: {
232
- sockPath: config.devServer.webSocketPath,
233
- },
234
- }
235
- : undefined,
236
- ]);
237
- }
238
- if (isEnvProduction) {
239
- plugins.push([
240
- require.resolve('babel-plugin-import'),
241
- { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false },
242
- ]);
255
+ if (!isSsr) {
256
+ if (isEnvDevelopment && config.reactRefresh !== false) {
257
+ plugins.push([
258
+ require.resolve('react-refresh/babel'),
259
+ config.devServer?.webSocketPath
260
+ ? {
261
+ overlay: {
262
+ sockPath: config.devServer.webSocketPath,
263
+ },
264
+ }
265
+ : undefined,
266
+ ]);
267
+ }
268
+ if (isEnvProduction) {
269
+ plugins.push([
270
+ require.resolve('babel-plugin-import'),
271
+ { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false },
272
+ ]);
273
+ }
243
274
  }
244
275
  const transformOptions = config.babel({
245
276
  presets: [(0, babel_1.babelPreset)(config)],
@@ -339,7 +370,7 @@ function createStylesRule(options) {
339
370
  use: loaders,
340
371
  };
341
372
  }
342
- function getCssLoaders({ isEnvDevelopment, isEnvProduction, config }, additionalRules) {
373
+ function getCssLoaders({ isEnvDevelopment, isEnvProduction, config, isSsr }, additionalRules) {
343
374
  const loaders = [];
344
375
  if (!config.transformCssWithLightningCss) {
345
376
  loaders.push({
@@ -362,27 +393,32 @@ function getCssLoaders({ isEnvDevelopment, isEnvProduction, config }, additional
362
393
  loaders.unshift({
363
394
  loader: require.resolve('css-loader'),
364
395
  options: {
365
- esModule: false,
366
396
  sourceMap: !config.disableSourceMapGeneration,
367
397
  importLoaders,
368
398
  modules: {
369
399
  auto: true,
370
400
  localIdentName: '[name]__[local]--[hash:base64:5]',
371
401
  exportLocalsConvention: 'camelCase',
402
+ exportOnlyLocals: isSsr,
372
403
  },
373
404
  },
374
405
  });
375
406
  if (isEnvProduction) {
376
- loaders.unshift(mini_css_extract_plugin_1.default.loader);
407
+ loaders.unshift({ loader: mini_css_extract_plugin_1.default.loader, options: { emit: !isSsr } });
377
408
  }
378
409
  if (isEnvDevelopment) {
379
- loaders.unshift({
380
- loader: require.resolve('style-loader'),
381
- });
410
+ if (isSsr) {
411
+ loaders.unshift({ loader: mini_css_extract_plugin_1.default.loader, options: { emit: false } });
412
+ }
413
+ else {
414
+ loaders.unshift({
415
+ loader: require.resolve('style-loader'),
416
+ });
417
+ }
382
418
  }
383
419
  return loaders;
384
420
  }
385
- function createIconsRule({ isEnvProduction, config }, jsLoader) {
421
+ function createIconsRule({ isEnvProduction, config, isSsr }, jsLoader) {
386
422
  const iconIncludes = config.icons || [];
387
423
  return {
388
424
  // eslint-disable-next-line security/detect-unsafe-regex
@@ -419,11 +455,12 @@ function createIconsRule({ isEnvProduction, config }, jsLoader) {
419
455
  generator: {
420
456
  filename: 'assets/images/[name].[contenthash:8][ext]',
421
457
  publicPath: isEnvProduction ? '../' : undefined,
458
+ emit: !isSsr,
422
459
  },
423
460
  }),
424
461
  };
425
462
  }
426
- function createAssetsRules({ isEnvProduction, config }) {
463
+ function createAssetsRules({ isEnvProduction, config, isSsr }) {
427
464
  const imagesRule = {
428
465
  test: /\.(ico|bmp|gif|jpe?g|png|svg)$/,
429
466
  include: [paths_1.default.appClient, ...(config.images || [])],
@@ -435,6 +472,7 @@ function createAssetsRules({ isEnvProduction, config }) {
435
472
  },
436
473
  generator: {
437
474
  filename: 'assets/images/[name].[contenthash:8][ext]',
475
+ emit: !isSsr,
438
476
  },
439
477
  };
440
478
  const fontsRule = {
@@ -448,6 +486,7 @@ function createAssetsRules({ isEnvProduction, config }) {
448
486
  },
449
487
  generator: {
450
488
  filename: 'assets/fonts/[name].[contenthash:8][ext]',
489
+ emit: !isSsr,
451
490
  },
452
491
  };
453
492
  const rules = [imagesRule, fontsRule];
@@ -467,6 +506,7 @@ function createAssetsRules({ isEnvProduction, config }) {
467
506
  generator: {
468
507
  filename: 'assets/images/[name].[contenthash:8][ext]',
469
508
  publicPath: '../',
509
+ emit: !isSsr,
470
510
  },
471
511
  }, {
472
512
  test: /\.(ttf|eot|woff2?)$/,
@@ -481,17 +521,19 @@ function createAssetsRules({ isEnvProduction, config }) {
481
521
  generator: {
482
522
  filename: 'assets/fonts/[name].[contenthash:8][ext]',
483
523
  publicPath: '../',
524
+ emit: !isSsr,
484
525
  },
485
526
  });
486
527
  }
487
528
  return rules;
488
529
  }
489
- function createFallbackRules({ isEnvProduction }) {
530
+ function createFallbackRules({ isEnvProduction, isSsr }) {
490
531
  const rules = [
491
532
  {
492
533
  type: 'asset/resource',
493
534
  generator: {
494
535
  filename: 'assets/[name].[contenthash:8][ext]',
536
+ emit: !isSsr,
495
537
  },
496
538
  exclude: [/\.[jt]sx?$/, /\.json$/, /\.[cm]js$/, /\.ejs$/],
497
539
  },
@@ -506,6 +548,7 @@ function createFallbackRules({ isEnvProduction }) {
506
548
  generator: {
507
549
  filename: 'assets/[name].[contenthash:8][ext]',
508
550
  publicPath: '../',
551
+ emit: !isSsr,
509
552
  },
510
553
  });
511
554
  }
@@ -520,7 +563,7 @@ function createMomentTimezoneDataPlugin(options = {}) {
520
563
  return new moment_timezone_data_webpack_plugin_1.default({ ...options, startYear, endYear });
521
564
  }
522
565
  function configurePlugins(options) {
523
- const { isEnvDevelopment, isEnvProduction, config } = options;
566
+ const { isEnvDevelopment, isEnvProduction, config, isSsr } = options;
524
567
  const excludeFromClean = config.excludeFromClean || [];
525
568
  const manifestFile = 'assets-manifest.json';
526
569
  const plugins = [
@@ -544,9 +587,8 @@ function configurePlugins(options) {
544
587
  : {
545
588
  entrypoints: true,
546
589
  writeToDisk: true,
547
- output: path.resolve(paths_1.default.appBuild, manifestFile),
590
+ output: path.resolve(options.buildDirectory, manifestFile),
548
591
  }),
549
- createMomentTimezoneDataPlugin(config.momentTz),
550
592
  new webpack.DefinePlugin({
551
593
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
552
594
  ...config.definitions,
@@ -558,51 +600,12 @@ function configurePlugins(options) {
558
600
  if (process.env.WEBPACK_PROFILE === 'true') {
559
601
  plugins.push(new webpack.debug.ProfilingPlugin());
560
602
  }
561
- const contextReplacement = config.contextReplacement || {};
562
- plugins.push(new webpack.ContextReplacementPlugin(/moment[\\/]locale$/,
563
- // eslint-disable-next-line security/detect-non-literal-regexp
564
- new RegExp(`^\\./(${(contextReplacement.locale || ['ru']).join('|')})$`)));
565
- plugins.push(new webpack.ContextReplacementPlugin(/dayjs[\\/]locale$/,
566
- // eslint-disable-next-line security/detect-non-literal-regexp
567
- new RegExp(`^\\./(${(contextReplacement.locale || ['ru']).join('|')})\\.js$`)));
568
- if (contextReplacement['highlight.js']) {
569
- plugins.push(new webpack.ContextReplacementPlugin(/highlight\.js[\\/]lib[\\/]languages$/,
570
- // eslint-disable-next-line security/detect-non-literal-regexp
571
- new RegExp(`^\\./(${contextReplacement['highlight.js'].join('|')})$`)));
572
- }
573
- if (config.monaco) {
574
- const MonacoEditorWebpackPlugin = require('monaco-editor-webpack-plugin');
575
- plugins.push(new MonacoEditorWebpackPlugin({
576
- filename: isEnvProduction ? '[name].[hash:8].worker.js' : undefined,
577
- ...config.monaco,
578
- // currently, workers located on cdn are not working properly, so we are enforcing loading workers from
579
- // service instead
580
- publicPath: path.normalize(config.publicPathPrefix + '/build/'),
581
- }));
582
- }
583
- if (isEnvDevelopment && config.reactRefresh !== false) {
584
- const { webSocketPath = path.normalize(`/${config.publicPathPrefix}/build/sockjs-node`) } = config.devServer || {};
585
- plugins.push(new react_refresh_webpack_plugin_1.default(config.reactRefresh({
586
- overlay: { sockPath: webSocketPath },
587
- exclude: [/node_modules/, /\.worker\.[jt]sx?$/],
588
- })));
589
- }
590
- if (config.detectCircularDependencies) {
591
- let circularPluginOptions = {
592
- exclude: /node_modules/,
593
- allowAsyncCycles: true,
594
- };
595
- if (typeof config.detectCircularDependencies === 'object') {
596
- circularPluginOptions = config.detectCircularDependencies;
597
- }
598
- plugins.push(new circular_dependency_plugin_1.default(circularPluginOptions));
599
- }
600
603
  if (config.forkTsChecker !== false) {
601
604
  plugins.push(new fork_ts_checker_webpack_plugin_1.default({
602
605
  ...config.forkTsChecker,
603
606
  typescript: {
604
607
  typescriptPath: (0, utils_2.resolveTypescript)(),
605
- configFile: path.resolve(paths_1.default.app, 'src/ui/tsconfig.json'),
608
+ configFile: path.resolve(paths_1.default.appClient, 'tsconfig.json'),
606
609
  diagnosticOptions: {
607
610
  syntactic: true,
608
611
  },
@@ -611,19 +614,17 @@ function configurePlugins(options) {
611
614
  },
612
615
  }));
613
616
  }
614
- if (config.polyfill?.process) {
615
- plugins.push(new webpack.ProvidePlugin({ process: 'process/browser.js' }));
617
+ if (config.detectCircularDependencies) {
618
+ let circularPluginOptions = {
619
+ exclude: /node_modules/,
620
+ allowAsyncCycles: true,
621
+ };
622
+ if (typeof config.detectCircularDependencies === 'object') {
623
+ circularPluginOptions = config.detectCircularDependencies;
624
+ }
625
+ plugins.push(new circular_dependency_plugin_1.default(circularPluginOptions));
616
626
  }
617
627
  if (isEnvProduction) {
618
- plugins.push(new mini_css_extract_plugin_1.default({
619
- filename: 'css/[name].[contenthash:8].css',
620
- chunkFilename: 'css/[name].[contenthash:8].chunk.css',
621
- ignoreOrder: true,
622
- }));
623
- if (config.sentryConfig) {
624
- const sentryPlugin = require('@sentry/webpack-plugin').sentryWebpackPlugin;
625
- plugins.push(sentryPlugin({ ...config.sentryConfig }));
626
- }
627
628
  if (config.analyzeBundle === 'true') {
628
629
  plugins.push(new webpack_bundle_analyzer_1.BundleAnalyzerPlugin({
629
630
  openAnalyzer: false,
@@ -634,8 +635,8 @@ function configurePlugins(options) {
634
635
  if (config.analyzeBundle === 'statoscope') {
635
636
  const customStatoscopeConfig = config.statoscopeConfig || {};
636
637
  plugins.push(new webpack_plugin_1.default({
637
- saveReportTo: path.resolve(paths_1.default.appBuild, 'report.html'),
638
- saveStatsTo: path.resolve(paths_1.default.appBuild, 'stats.json'),
638
+ saveReportTo: path.resolve(options.buildDirectory, 'report.html'),
639
+ saveStatsTo: path.resolve(options.buildDirectory, 'stats.json'),
639
640
  open: false,
640
641
  statsOptions: {
641
642
  all: true,
@@ -644,50 +645,102 @@ function configurePlugins(options) {
644
645
  }));
645
646
  }
646
647
  }
647
- if (config.cdn) {
648
- let credentialsGlobal;
649
- if (process.env.FRONTEND_S3_ACCESS_KEY_ID && process.env.FRONTEND_S3_SECRET_ACCESS_KEY) {
650
- credentialsGlobal = {
651
- accessKeyId: process.env.FRONTEND_S3_ACCESS_KEY_ID,
652
- secretAccessKey: process.env.FRONTEND_S3_SECRET_ACCESS_KEY,
653
- };
648
+ if (isEnvProduction || isSsr) {
649
+ plugins.push(new mini_css_extract_plugin_1.default({
650
+ filename: 'css/[name].[contenthash:8].css',
651
+ chunkFilename: 'css/[name].[contenthash:8].chunk.css',
652
+ ignoreOrder: true,
653
+ }));
654
+ }
655
+ if (config.monaco) {
656
+ const MonacoEditorWebpackPlugin = require('monaco-editor-webpack-plugin');
657
+ plugins.push(new MonacoEditorWebpackPlugin({
658
+ filename: isEnvProduction ? '[name].[hash:8].worker.js' : undefined,
659
+ ...config.monaco,
660
+ // currently, workers located on cdn are not working properly, so we are enforcing loading workers from
661
+ // service instead
662
+ publicPath: path.normalize(config.publicPathPrefix + '/build/'),
663
+ }));
664
+ }
665
+ if (!isSsr) {
666
+ const contextReplacement = config.contextReplacement || {};
667
+ plugins.push(createMomentTimezoneDataPlugin(config.momentTz));
668
+ plugins.push(new webpack.ContextReplacementPlugin(/moment[\\/]locale$/,
669
+ // eslint-disable-next-line security/detect-non-literal-regexp
670
+ new RegExp(`^\\./(${(contextReplacement.locale || ['ru']).join('|')})$`)));
671
+ plugins.push(new webpack.ContextReplacementPlugin(/dayjs[\\/]locale$/,
672
+ // eslint-disable-next-line security/detect-non-literal-regexp
673
+ new RegExp(`^\\./(${(contextReplacement.locale || ['ru']).join('|')})\\.js$`)));
674
+ if (contextReplacement['highlight.js']) {
675
+ plugins.push(new webpack.ContextReplacementPlugin(/highlight\.js[\\/]lib[\\/]languages$/,
676
+ // eslint-disable-next-line security/detect-non-literal-regexp
677
+ new RegExp(`^\\./(${contextReplacement['highlight.js'].join('|')})$`)));
678
+ }
679
+ if (isEnvDevelopment && config.reactRefresh !== false) {
680
+ const { webSocketPath = path.normalize(`/${config.publicPathPrefix}/build/sockjs-node`), } = config.devServer || {};
681
+ plugins.push(new react_refresh_webpack_plugin_1.default(config.reactRefresh({
682
+ overlay: { sockPath: webSocketPath },
683
+ exclude: [/node_modules/, /\.worker\.[jt]sx?$/],
684
+ })));
685
+ }
686
+ if (config.polyfill?.process) {
687
+ plugins.push(new webpack.ProvidePlugin({ process: 'process/browser.js' }));
654
688
  }
655
- const cdns = Array.isArray(config.cdn) ? config.cdn : [config.cdn];
656
- for (let index = 0; index < cdns.length; index++) {
657
- const cdn = cdns[index];
658
- if (!cdn) {
659
- continue;
689
+ if (isEnvProduction) {
690
+ if (config.sentryConfig) {
691
+ const sentryPlugin = require('@sentry/webpack-plugin').sentryWebpackPlugin;
692
+ plugins.push(sentryPlugin({ ...config.sentryConfig }));
660
693
  }
661
- let credentials = credentialsGlobal;
662
- const accessKeyId = process.env[`FRONTEND_S3_ACCESS_KEY_ID_${index}`];
663
- const secretAccessKey = process.env[`FRONTEND_S3_SECRET_ACCESS_KEY_${index}`];
664
- if (accessKeyId && secretAccessKey) {
665
- credentials = {
666
- accessKeyId,
667
- secretAccessKey,
694
+ }
695
+ if (config.cdn) {
696
+ let credentialsGlobal;
697
+ if (process.env.FRONTEND_S3_ACCESS_KEY_ID &&
698
+ process.env.FRONTEND_S3_SECRET_ACCESS_KEY) {
699
+ credentialsGlobal = {
700
+ accessKeyId: process.env.FRONTEND_S3_ACCESS_KEY_ID,
701
+ secretAccessKey: process.env.FRONTEND_S3_SECRET_ACCESS_KEY,
668
702
  };
669
703
  }
670
- plugins.push(new s3_upload_1.S3UploadPlugin({
671
- exclude: config.hiddenSourceMap ? /\.map$/ : undefined,
672
- compress: cdn.compress,
673
- s3ClientOptions: {
674
- region: cdn.region,
675
- endpoint: cdn.endpoint,
676
- credentials,
677
- },
678
- s3UploadOptions: {
679
- bucket: cdn.bucket,
680
- targetPath: cdn.prefix,
681
- cacheControl: cdn.cacheControl,
682
- },
683
- additionalPattern: cdn.additionalPattern,
684
- logger: options.logger,
685
- }));
704
+ const cdns = Array.isArray(config.cdn) ? config.cdn : [config.cdn];
705
+ for (let index = 0; index < cdns.length; index++) {
706
+ const cdn = cdns[index];
707
+ if (!cdn) {
708
+ continue;
709
+ }
710
+ let credentials = credentialsGlobal;
711
+ const accessKeyId = process.env[`FRONTEND_S3_ACCESS_KEY_ID_${index}`];
712
+ const secretAccessKey = process.env[`FRONTEND_S3_SECRET_ACCESS_KEY_${index}`];
713
+ if (accessKeyId && secretAccessKey) {
714
+ credentials = {
715
+ accessKeyId,
716
+ secretAccessKey,
717
+ };
718
+ }
719
+ plugins.push(new s3_upload_1.S3UploadPlugin({
720
+ exclude: config.hiddenSourceMap ? /\.map$/ : undefined,
721
+ compress: cdn.compress,
722
+ s3ClientOptions: {
723
+ region: cdn.region,
724
+ endpoint: cdn.endpoint,
725
+ credentials,
726
+ },
727
+ s3UploadOptions: {
728
+ bucket: cdn.bucket,
729
+ targetPath: cdn.prefix,
730
+ cacheControl: cdn.cacheControl,
731
+ },
732
+ additionalPattern: cdn.additionalPattern,
733
+ logger: options.logger,
734
+ }));
735
+ }
686
736
  }
687
737
  }
688
738
  return plugins;
689
739
  }
690
- function configureOptimization({ config }) {
740
+ function configureOptimization({ config, isSsr }) {
741
+ if (isSsr) {
742
+ return {};
743
+ }
691
744
  const configVendors = config.vendors ?? [];
692
745
  let vendorsList = [
693
746
  'react',
@@ -1,3 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- __webpack_public_path__ = window.__PUBLIC_PATH__ ?? '/build/';
3
+ // @ts-expect-error
4
+ __webpack_public_path__ = globalThis.__PUBLIC_PATH__ ?? '/build/';
@@ -35,6 +35,7 @@ const css_minimizer_webpack_plugin_1 = __importDefault(require("css-minimizer-we
35
35
  const config_1 = require("./config");
36
36
  const config_2 = require("../config");
37
37
  const models_1 = require("../models");
38
+ const paths_1 = __importDefault(require("../paths"));
38
39
  async function configureServiceWebpackConfig(mode, storybookConfig) {
39
40
  const serviceConfig = await (0, config_2.getProjectConfig)(mode === "production" /* WebpackMode.Prod */ ? 'build' : 'dev', {
40
41
  storybook: true,
@@ -104,6 +105,9 @@ async function configureWebpackConfigForStorybook(mode, userConfig = {}, storybo
104
105
  isEnvProduction,
105
106
  config: config.client,
106
107
  configType: mode,
108
+ buildDirectory: paths_1.default.appBuild,
109
+ entriesDirectory: paths_1.default.appEntry,
110
+ isSsr: false,
107
111
  };
108
112
  return {
109
113
  module: {
@@ -1,6 +1,6 @@
1
1
  import type webpack from 'webpack';
2
2
  import type { Logger } from '../logger';
3
- export declare function webpackCompilerHandlerFactory(logger: Logger, onCompilationEnd?: () => void): (err?: Error | null, stats?: webpack.Stats) => Promise<void>;
3
+ export declare function webpackCompilerHandlerFactory(logger: Logger, onCompilationEnd?: () => void): (err?: Error | null, stats?: webpack.MultiStats) => Promise<void>;
4
4
  export declare function resolveTsconfigPathsToAlias(tsConfigPath: string): {
5
5
  aliases: Record<string, string[]>;
6
6
  modules: string[];
@@ -50,13 +50,21 @@ function webpackCompilerHandlerFactory(logger, onCompilationEnd) {
50
50
  if (onCompilationEnd) {
51
51
  await onCompilationEnd();
52
52
  }
53
- if (stats) {
54
- const time = stats.endTime - stats.startTime;
53
+ const [clientStats, ssrStats] = stats?.stats ?? [];
54
+ if (clientStats) {
55
+ const time = clientStats.endTime - clientStats.startTime;
55
56
  logger.success(`Client was successfully compiled in ${(0, pretty_time_1.prettyTime)(BigInt(time) * BigInt(1_000_000))}`);
56
57
  }
57
58
  else {
58
59
  logger.success(`Client was successfully compiled`);
59
60
  }
61
+ if (ssrStats) {
62
+ const time = ssrStats.endTime - ssrStats.startTime;
63
+ logger.success(`SSR: Client was successfully compiled in ${(0, pretty_time_1.prettyTime)(BigInt(time) * BigInt(1_000_000))}`);
64
+ }
65
+ else {
66
+ logger.success(`SSR: Client was successfully compiled`);
67
+ }
60
68
  };
61
69
  }
62
70
  const endStarRe = /\/?\*$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/app-builder",
3
- "version": "0.13.1",
3
+ "version": "0.14.0-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",
@@ -129,6 +129,7 @@
129
129
  "webpack-bundle-analyzer": "^4.10.2",
130
130
  "webpack-dev-server": "^5.1.0",
131
131
  "webpack-manifest-plugin": "^5.0.0",
132
+ "webpack-node-externals": "^3.0.0",
132
133
  "worker-loader": "^3.0.8",
133
134
  "yargs": "^17.7.2"
134
135
  },
@@ -152,6 +153,7 @@
152
153
  "@types/webpack-assets-manifest": "^5.1.4",
153
154
  "@types/webpack-bundle-analyzer": "^4.7.0",
154
155
  "@types/webpack-manifest-plugin": "^3.0.8",
156
+ "@types/webpack-node-externals": "^3.0.4",
155
157
  "@types/yargs": "17.0.11",
156
158
  "babel-plugin-tester": "^11.0.4",
157
159
  "eslint": "^8.57.0",