@gravity-ui/app-builder 0.22.0 → 0.23.1-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
@@ -177,6 +177,9 @@ With this `{rootDir}/src/ui/tsconfig.json`:
177
177
  - `icons` (`string[]`) — Additional paths for svg icons. By default, all svgs with paths including `icons/` will be processed.
178
178
  Example: `icons: [node_modules/@fortawesome/fontawesome-pro/svgs]`
179
179
  - `publicPathPrefix` (`string`) — publicPath prefix, will be added to `/build/`
180
+ - `publicPath` (`string`) — publicPath for bundler, this option has higher priority than publicPathPrefix
181
+ - `outputPath` (`string`) — Build directory for output, default: `dist/public/build` and `dist/ssr` - for SSR
182
+ - `assetsManifestFile` (`string`) — File name for assets manifest, default: `assets-manifest.json`
180
183
  - `symlinks` (`boolean`) — Follow symbolic links while looking for a file. [more](https://webpack.js.org/configuration/resolve/#resolvesymlinks)
181
184
  - `externals` — specify dependencies that shouldn't be resolved by webpack, but should become dependencies of the resulting bundle. [more](https://webpack.js.org/configuration/externals/)
182
185
  - `node` — include polyfills or mocks for various node stuff. [more](https://webpack.js.org/configuration/node/)
@@ -187,6 +190,7 @@ With this `{rootDir}/src/ui/tsconfig.json`:
187
190
  - `definitions` — add additional options to DefinePlugin. [more](https://webpack.js.org/plugins/define-plugin/#usage)
188
191
  - `newJsxTransform` (`boolean=true`) — use new JSX Transform.
189
192
  - `svgr` (`SvgrConfig`) — svgr plugin options. [more](https://react-svgr.com/docs/options/)
193
+ - `entry` (`string | string[] | Record<string, string | string[]>`) — entry for bundler, overrides entry which is generated from entries directory
190
194
  - `entryFilter` (`string[]`) — filter used entrypoints.
191
195
  - `excludeFromClean` (`string[]`) — do not clean provided paths before build.
192
196
  - `forkTsCheker` (`false | ForkTsCheckerWebpackPluginOptions`) - config for ForkTsCheckerWebpackPlugin [more](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options). If `false`, ForkTsCheckerWebpackPlugin will be disabled.
@@ -49,7 +49,8 @@ async function watchClientCompilation(config, onManifestReady) {
49
49
  async function buildDevServer(config) {
50
50
  const bundler = config.client.bundler;
51
51
  const logger = new logger_1.Logger('client', config.verbose);
52
- const { webSocketPath = path.normalize(`/${config.client.publicPathPrefix}/build/sockjs-node`), writeToDisk, ...devServer } = config.client.devServer || {};
52
+ const { publicPath } = config.client;
53
+ const { webSocketPath = path.normalize(`/${publicPath}/sockjs-node`), writeToDisk, ...devServer } = config.client.devServer || {};
53
54
  const normalizedConfig = { ...config.client, devServer: { ...devServer, webSocketPath } };
54
55
  const isSsr = Boolean(normalizedConfig.ssr);
55
56
  let webpackConfigs = [];
@@ -90,7 +91,6 @@ async function buildDevServer(config) {
90
91
  }));
91
92
  }
92
93
  }
93
- const publicPath = path.normalize(config.client.publicPathPrefix + '/build/');
94
94
  const staticFolder = path.resolve(paths_1.default.appDist, 'public');
95
95
  const options = {
96
96
  static: staticFolder,
@@ -170,6 +170,7 @@ async function buildDevServer(config) {
170
170
  if (bundler === 'rspack') {
171
171
  // Rspack multicompiler dont work with lazy compilation.
172
172
  // Pass a single config to avoid multicompiler when SSR disabled.
173
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
173
174
  const compiler = (0, core_1.rspack)(isSsr ? rspackConfigs : rspackConfigs[0]);
174
175
  server = new dev_server_1.RspackDevServer(options, compiler);
175
176
  }
@@ -197,11 +198,11 @@ function subscribeToManifestReadyEvent(compiler, onManifestReady) {
197
198
  const compilers = 'compilers' in compiler ? compiler.compilers : [compiler];
198
199
  for (let i = 0; i < options.length; i++) {
199
200
  const config = options[i];
200
- const compiler = compilers[i];
201
- if (!config || !compiler) {
201
+ const currentCompiler = compilers[i];
202
+ if (!config || !currentCompiler) {
202
203
  throw new Error('Something goes wrong!');
203
204
  }
204
- if (!isRspackCompiler(compiler)) {
205
+ if (!isRspackCompiler(currentCompiler)) {
205
206
  const assetsManifestPlugin = config.plugins.find((plugin) => plugin instanceof webpack_assets_manifest_1.default);
206
207
  if (assetsManifestPlugin) {
207
208
  const assetsManifestReady = (0, utils_1.deferredPromise)();
@@ -211,12 +212,12 @@ function subscribeToManifestReadyEvent(compiler, onManifestReady) {
211
212
  }
212
213
  const manifestReady = (0, utils_1.deferredPromise)();
213
214
  promises.push(manifestReady.promise);
214
- if (isRspackCompiler(compiler)) {
215
- const { afterEmit } = (0, rspack_manifest_plugin_1.getCompilerHooks)(compiler);
215
+ if (isRspackCompiler(currentCompiler)) {
216
+ const { afterEmit } = (0, rspack_manifest_plugin_1.getCompilerHooks)(currentCompiler);
216
217
  afterEmit.tap('app-builder', manifestReady.resolve);
217
218
  }
218
219
  else {
219
- const { afterEmit } = (0, webpack_manifest_plugin_1.getCompilerHooks)(compiler);
220
+ const { afterEmit } = (0, webpack_manifest_plugin_1.getCompilerHooks)(currentCompiler);
220
221
  afterEmit.tap('app-builder', manifestReady.resolve);
221
222
  }
222
223
  }
@@ -189,7 +189,8 @@ async function normalizeClientConfig(client, mode) {
189
189
  ? false
190
190
  : (client.reactRefresh ?? ((options) => options)),
191
191
  newJsxTransform: client.newJsxTransform ?? true,
192
- publicPathPrefix: client.publicPathPrefix || '',
192
+ publicPath: client.publicPath || path.normalize(`${client.publicPathPrefix || ''}/build/`),
193
+ assetsManifestFile: client.assetsManifestFile || 'assets-manifest.json',
193
194
  modules: client.modules && remapPaths(client.modules),
194
195
  includes: client.includes && remapPaths(client.includes),
195
196
  images: client.images && remapPaths(client.images),
@@ -79,9 +79,24 @@ export interface ClientConfig {
79
79
  devServer?: DevServerConfig;
80
80
  contextReplacement?: ContextReplacement;
81
81
  /**
82
- * publicPath prefix, will be added to '/build/'
82
+ * publicPath prefix, will be added to '/build/'
83
83
  */
84
84
  publicPathPrefix?: string;
85
+ /**
86
+ * publicPath for bundler
87
+ * This option has higher priority than publicPathPrefix
88
+ */
89
+ publicPath?: string;
90
+ /**
91
+ * Build directory for output
92
+ * Default: 'dist/public/build' and 'dist/ssr' - for SSR
93
+ */
94
+ outputPath?: string;
95
+ /**
96
+ * File name for assets manifest
97
+ * Default: 'assets-manifest.json'
98
+ */
99
+ assetsManifestFile?: string;
85
100
  /**
86
101
  * Add monaco-editor support
87
102
  */
@@ -92,7 +107,7 @@ export interface ClientConfig {
92
107
  customLanguages?: IFeatureDefinition[];
93
108
  };
94
109
  /**
95
- * if false - source maps will be generated for prod builds,
110
+ * if false - source maps will be generated for prod builds
96
111
  */
97
112
  hiddenSourceMap?: boolean;
98
113
  /**
@@ -131,6 +146,11 @@ export interface ClientConfig {
131
146
  * svgr plugin options.
132
147
  */
133
148
  svgr?: SvgrConfig;
149
+ /**
150
+ * entry for bundler
151
+ * Overrides entry which is generated from entries directory
152
+ */
153
+ entry?: string | string[] | Record<string, string | string[]>;
134
154
  entryFilter?: string[];
135
155
  excludeFromClean?: string[];
136
156
  analyzeBundle?: 'true' | 'statoscope' | 'rsdoctor';
@@ -252,10 +272,11 @@ export interface ServiceConfig {
252
272
  lib?: never;
253
273
  verbose?: boolean;
254
274
  }
255
- export type NormalizedClientConfig = Omit<ClientConfig, 'publicPathPrefix' | 'hiddenSourceMap' | 'svgr' | 'lazyCompilation' | 'devServer' | 'disableForkTsChecker' | 'disableReactRefresh'> & {
275
+ export type NormalizedClientConfig = Omit<ClientConfig, 'publicPathPrefix' | 'publicPath' | 'assetsManifestFile' | 'hiddenSourceMap' | 'svgr' | 'lazyCompilation' | 'devServer' | 'disableForkTsChecker' | 'disableReactRefresh'> & {
256
276
  bundler: Bundler;
257
277
  javaScriptLoader: JavaScriptLoader;
258
- publicPathPrefix: string;
278
+ publicPath: string;
279
+ assetsManifestFile: string;
259
280
  hiddenSourceMap: boolean;
260
281
  svgr: NonNullable<ClientConfig['svgr']>;
261
282
  lazyCompilation?: LazyCompilationConfig;
@@ -9,6 +9,8 @@ export interface HelperOptions {
9
9
  isEnvProduction: boolean;
10
10
  configType: `${WebpackMode}`;
11
11
  buildDirectory: string;
12
+ assetsManifestFile: string;
13
+ entry?: string | string[] | Record<string, string | string[]>;
12
14
  entriesDirectory: string;
13
15
  isSsr: boolean;
14
16
  }
@@ -60,7 +60,6 @@ const node_externals_1 = require("./node-externals");
60
60
  const statoscope_1 = require("./statoscope");
61
61
  const imagesSizeLimit = 2048;
62
62
  const fontSizeLimit = 8192;
63
- const assetsManifestFile = 'assets-manifest.json';
64
63
  function getHelperOptions({ webpackMode, config, logger, isSsr = false, }) {
65
64
  const isEnvDevelopment = webpackMode === "development" /* WebpackMode.Dev */;
66
65
  const isEnvProduction = webpackMode === "production" /* WebpackMode.Prod */;
@@ -70,7 +69,9 @@ function getHelperOptions({ webpackMode, config, logger, isSsr = false, }) {
70
69
  isEnvDevelopment,
71
70
  isEnvProduction,
72
71
  configType: webpackMode,
73
- buildDirectory: isSsr ? paths_1.default.appSsrBuild : paths_1.default.appBuild,
72
+ buildDirectory: config.outputPath || (isSsr ? paths_1.default.appSsrBuild : paths_1.default.appBuild),
73
+ assetsManifestFile: config.assetsManifestFile,
74
+ entry: config.entry,
74
75
  entriesDirectory: isSsr ? paths_1.default.appSsrEntry : paths_1.default.appEntry,
75
76
  isSsr,
76
77
  };
@@ -88,6 +89,16 @@ function configureExternals({ config, isSsr }) {
88
89
  }
89
90
  return externals;
90
91
  }
92
+ function configureWebpackCache(options) {
93
+ const { config } = options;
94
+ if (typeof config.cache === 'object' && config.cache.type === 'filesystem') {
95
+ return {
96
+ ...config.cache,
97
+ buildDependencies: getCacheBuildDependencies(options),
98
+ };
99
+ }
100
+ return config.cache;
101
+ }
91
102
  async function webpackConfigFactory(options) {
92
103
  const { config } = options;
93
104
  const helperOptions = getHelperOptions(options);
@@ -120,7 +131,7 @@ async function webpackConfigFactory(options) {
120
131
  snapshot: {
121
132
  managedPaths: config.watchOptions?.watchPackages ? [] : undefined,
122
133
  },
123
- cache: config.cache,
134
+ cache: configureWebpackCache(helperOptions),
124
135
  };
125
136
  webpackConfig = await config.webpack(webpackConfig, {
126
137
  configType: isEnvProduction ? 'production' : 'development',
@@ -210,6 +221,34 @@ function configureWatchOptions({ config }) {
210
221
  delete watchOptions.watchPackages;
211
222
  return watchOptions;
212
223
  }
224
+ function getCacheBuildDependencies({ config }) {
225
+ const buildDependencies = {};
226
+ const dependenciesGroups = {
227
+ appBuilderConfig: [
228
+ path.join(paths_1.default.app, 'app-builder.config.ts'),
229
+ path.join(paths_1.default.app, 'app-builder.config.js'),
230
+ ],
231
+ packageJson: [path.join(paths_1.default.app, 'package.json')],
232
+ tsconfig: [
233
+ path.join(paths_1.default.app, 'tsconfig.json'),
234
+ path.join(paths_1.default.appClient, 'tsconfig.json'),
235
+ ],
236
+ };
237
+ for (const [group, filePaths] of Object.entries(dependenciesGroups)) {
238
+ for (const filePath of filePaths) {
239
+ if (fs.existsSync(filePath)) {
240
+ buildDependencies[group] = [...(buildDependencies[group] || []), filePath];
241
+ }
242
+ }
243
+ }
244
+ const userBuildDependencies = typeof config.cache === 'object' && config.cache.type === 'filesystem'
245
+ ? config.cache.buildDependencies
246
+ : {};
247
+ return {
248
+ ...buildDependencies,
249
+ ...userBuildDependencies,
250
+ };
251
+ }
213
252
  function configureExperiments({ config, isEnvProduction, isSsr, }) {
214
253
  if (isSsr) {
215
254
  return config.ssr?.moduleType === 'esm' ? { outputModule: true } : undefined;
@@ -243,7 +282,8 @@ function configureExperiments({ config, isEnvProduction, isSsr, }) {
243
282
  lazyCompilation,
244
283
  };
245
284
  }
246
- function configureRspackExperiments({ config, isEnvProduction, isSsr, }) {
285
+ function configureRspackExperiments(options) {
286
+ const { config, isSsr, isEnvProduction } = options;
247
287
  if (isSsr) {
248
288
  return config.ssr?.moduleType === 'esm' ? { outputModule: true } : undefined;
249
289
  }
@@ -289,6 +329,7 @@ function configureRspackExperiments({ config, isEnvProduction, isSsr, }) {
289
329
  ? config.cache.cacheDirectory
290
330
  : undefined,
291
331
  },
332
+ buildDependencies: Object.values(getCacheBuildDependencies(options)).flat(),
292
333
  },
293
334
  lazyCompilation,
294
335
  };
@@ -315,7 +356,10 @@ function configureResolve({ isEnvProduction, config }) {
315
356
  };
316
357
  }
317
358
  function createEntryArray(entry) {
318
- return [require.resolve('./public-path'), entry];
359
+ if (typeof entry === 'string') {
360
+ return [require.resolve('./public-path'), entry];
361
+ }
362
+ return [require.resolve('./public-path'), ...entry];
319
363
  }
320
364
  function addEntry(entry, file) {
321
365
  return {
@@ -323,15 +367,24 @@ function addEntry(entry, file) {
323
367
  [path.parse(file).name]: createEntryArray(file),
324
368
  };
325
369
  }
326
- function configureEntry({ config, entriesDirectory }) {
370
+ function configureEntry({ config, entry, entriesDirectory }) {
371
+ if (typeof entry === 'string' || Array.isArray(entry)) {
372
+ return createEntryArray(entry);
373
+ }
374
+ if (typeof entry === 'object') {
375
+ return Object.entries(entry).reduce((acc, [key, value]) => ({
376
+ ...acc,
377
+ [key]: createEntryArray(value),
378
+ }), {});
379
+ }
327
380
  let entries = fs.readdirSync(entriesDirectory).filter((file) => /\.[jt]sx?$/.test(file));
328
381
  if (Array.isArray(config.entryFilter) && config.entryFilter.length) {
329
- entries = entries.filter((entry) => config.entryFilter?.includes(entry.split('.')[0] ?? ''));
382
+ entries = entries.filter((file) => config.entryFilter?.includes(file.split('.')[0] ?? ''));
330
383
  }
331
384
  if (!entries.length) {
332
385
  throw new Error('No entries were found after applying entry filter');
333
386
  }
334
- return entries.reduce((entry, file) => addEntry(entry, path.resolve(entriesDirectory, file)), {});
387
+ return entries.reduce((acc, file) => addEntry(acc, path.resolve(entriesDirectory, file)), {});
335
388
  }
336
389
  function getFileNames({ isEnvProduction, isSsr, config }) {
337
390
  let ext = 'js';
@@ -609,6 +662,7 @@ function getCssLoaders({ isEnvDevelopment, isEnvProduction, config, isSsr }, add
609
662
  else {
610
663
  loaders.unshift({
611
664
  loader: require.resolve('style-loader'),
665
+ options: { insert: require.resolve('./insert-style-tag.js') },
612
666
  });
613
667
  }
614
668
  }
@@ -864,7 +918,7 @@ function configureCommonPlugins(options, bundlerPlugins) {
864
918
  ...config.monaco,
865
919
  // currently, workers located on cdn are not working properly, so we are enforcing loading workers from
866
920
  // service instead
867
- publicPath: path.normalize(config.publicPathPrefix + '/build/'),
921
+ publicPath: config.publicPath,
868
922
  }));
869
923
  }
870
924
  plugins.push(createMomentTimezoneDataPlugin(config.momentTz));
@@ -928,17 +982,17 @@ function configureWebpackPlugins(options) {
928
982
  new webpack_assets_manifest_1.default(isEnvProduction
929
983
  ? {
930
984
  entrypoints: true,
931
- output: assetsManifestFile,
985
+ output: options.assetsManifestFile,
932
986
  }
933
987
  : {
934
988
  entrypoints: true,
935
989
  writeToDisk: true,
936
- output: path.resolve(options.buildDirectory, assetsManifestFile),
990
+ output: path.resolve(options.buildDirectory, options.assetsManifestFile),
937
991
  }),
938
992
  ...(process.env.WEBPACK_PROFILE === 'true' ? [new webpack.debug.ProfilingPlugin()] : []),
939
993
  ];
940
994
  if (!isSsr && isEnvDevelopment && config.reactRefresh !== false) {
941
- const { webSocketPath = path.normalize(`/${config.publicPathPrefix}/build/sockjs-node`) } = config.devServer || {};
995
+ const { webSocketPath = path.normalize(`/${config.publicPath}/sockjs-node`) } = config.devServer || {};
942
996
  const reactRefreshConfig = config.reactRefresh({
943
997
  overlay: { sockPath: webSocketPath },
944
998
  exclude: [/node_modules/, /\.worker\.[jt]sx?$/],
@@ -963,8 +1017,8 @@ function configureRspackPlugins(options) {
963
1017
  ...configureCommonPlugins(options, plugins),
964
1018
  new rspack_manifest_plugin_1.RspackManifestPlugin({
965
1019
  fileName: isEnvProduction
966
- ? assetsManifestFile
967
- : path.resolve(options.buildDirectory, assetsManifestFile),
1020
+ ? options.assetsManifestFile
1021
+ : path.resolve(options.buildDirectory, options.assetsManifestFile),
968
1022
  writeToFileEmit: true,
969
1023
  useLegacyEmit: true,
970
1024
  publicPath: '',
@@ -972,7 +1026,7 @@ function configureRspackPlugins(options) {
972
1026
  }),
973
1027
  ];
974
1028
  if (!isSsr && isEnvDevelopment && config.reactRefresh !== false) {
975
- const { webSocketPath = path.normalize(`/${config.publicPathPrefix}/build/sockjs-node`) } = config.devServer || {};
1029
+ const { webSocketPath = path.normalize(`/${config.publicPath}/sockjs-node`) } = config.devServer || {};
976
1030
  const { overlay, ...reactRefreshConfig } = config.reactRefresh({
977
1031
  overlay: { sockPath: webSocketPath },
978
1032
  exclude: [/node_modules/, /\.worker\.[jt]sx?$/],
@@ -0,0 +1,9 @@
1
+ export = insertStyleTag;
2
+ /**
3
+ * This function inserts a style tag into the document head if it doesn't already exist.
4
+ * It is needed to do not allow duplicate style tags.
5
+ *
6
+ * @param {HTMLStyleElement} $targetStyle - The style element to be inserted
7
+ * @returns {void}
8
+ */
9
+ declare function insertStyleTag($targetStyle: HTMLStyleElement): void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /* eslint-env browser */
3
+ /**
4
+ * This function inserts a style tag into the document head if it doesn't already exist.
5
+ * It is needed to do not allow duplicate style tags.
6
+ *
7
+ * @param {HTMLStyleElement} $targetStyle - The style element to be inserted
8
+ * @returns {void}
9
+ */
10
+ function insertStyleTag($targetStyle) {
11
+ // setTimeout is used to queue the insertion of the style tag
12
+ // to the end of the current event loop, allowing other
13
+ setTimeout(() => {
14
+ const $head = document.head;
15
+ let targetStyleExists = false;
16
+ $head.querySelectorAll('style').forEach(($style) => {
17
+ if ($style.innerHTML === $targetStyle.innerHTML) {
18
+ targetStyleExists = true;
19
+ }
20
+ });
21
+ if (!targetStyleExists) {
22
+ $head.appendChild($targetStyle);
23
+ }
24
+ });
25
+ }
26
+ module.exports = insertStyleTag;
@@ -112,7 +112,9 @@ async function configureWebpackConfigForStorybook(mode, userConfig = {}, storybo
112
112
  isEnvProduction,
113
113
  config: config.client,
114
114
  configType: mode,
115
- buildDirectory: paths_1.default.appBuild,
115
+ buildDirectory: config.client.outputPath || paths_1.default.appBuild,
116
+ assetsManifestFile: config.client.assetsManifestFile,
117
+ entry: config.client.entry,
116
118
  entriesDirectory: paths_1.default.appEntry,
117
119
  isSsr: false,
118
120
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/app-builder",
3
- "version": "0.22.0",
3
+ "version": "0.23.1-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",