@oroinc/oro-webpack-config-builder 5.1.0-alpha5 → 5.1.0-alpha51

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.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/error-handler.js +95 -0
  3. package/loader/config-loader.js +1 -2
  4. package/loader/inject-loader/LICENSE.md +21 -0
  5. package/loader/inject-loader/README.md +54 -0
  6. package/loader/inject-loader/index.js +10 -0
  7. package/loader/inject-loader/injectify.js +66 -0
  8. package/loader/inject-loader/package.json +55 -0
  9. package/loader/inject-loader/wrapper_template.js +32 -0
  10. package/modules-config/layout-modules-config-loader.js +67 -3
  11. package/modules-config/modules-config-loader.js +49 -14
  12. package/oro-webpack-config.js +406 -296
  13. package/package.json +38 -37
  14. package/plugin/logs/after-webpack-logs-plugin.js +25 -0
  15. package/prepare-modules-shim.js +13 -1
  16. package/style/admin-style-loader.js +22 -0
  17. package/style/layout-style-loader.js +12 -109
  18. package/style/style-loader.js +134 -46
  19. package/theme-config-factory.js +45 -6
  20. package/utils.js +30 -0
  21. package/validation/assets-validator.js +103 -0
  22. package/validation/errors/assets-input-file-error.js +24 -0
  23. package/validation/errors/assets-schema-error.js +40 -0
  24. package/validation/errors/base-error.js +37 -0
  25. package/validation/errors/jsmodules-extra-modules-error.js +22 -0
  26. package/validation/errors/jsmodules-schema-error.js +40 -0
  27. package/validation/errors/styles-error.js +24 -0
  28. package/validation/index.js +36 -0
  29. package/validation/jsmodules-validator.js +53 -0
  30. package/validation/schema-validator.js +62 -0
  31. package/validation/schemas/assets-schema-full.js +22 -0
  32. package/validation/schemas/assets-schema.js +32 -0
  33. package/validation/schemas/jsmodules-schema-full.js +11 -0
  34. package/validation/schemas/jsmodules-schema.js +76 -0
  35. package/writer/configs-file-writer.js +1 -1
  36. package/writer/dynamic-imports-file-writer.js +3 -3
  37. package/writer/scss-entry-point-file-writer.js +1 -1
@@ -3,6 +3,7 @@ const printf = require('printf');
3
3
  const AppConfigLoader = require('./app-config-loader');
4
4
  const AppModulesFileWriter = require('./writer/app-modules-file-writer');
5
5
  const CleanupStatsPlugin = require('./plugin/stats/cleanup-stats-plugin');
6
+ const AfterWebpackLogsPlugin = require('./plugin/logs/after-webpack-logs-plugin');
6
7
  const ConfigsFileWriter = require('./writer/configs-file-writer');
7
8
  const EntryPointFileWriter = require('./writer/scss-entry-point-file-writer');
8
9
  const LayoutModulesConfigLoader = require('./modules-config/layout-modules-config-loader');
@@ -14,23 +15,32 @@ const ModulesConfigLoader = require('./modules-config/modules-config-loader');
14
15
  const DynamicImportsFileWriter = require('./writer/dynamic-imports-file-writer');
15
16
  const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
16
17
  const prepareModulesShim = require('./prepare-modules-shim');
17
- const StyleLoader = require('./style/style-loader');
18
+ const AdminStyleLoader = require('./style/admin-style-loader');
18
19
  const ThemeConfigFactory = require('./theme-config-factory');
19
20
  const path = require('path');
21
+ const fs = require('fs');
20
22
  const prepareModulesMap = require('./plugin/map/prepare-modules-map');
21
23
  const resolve = require('enhanced-resolve');
22
24
  const {merge: webpackMerge} = require('webpack-merge');
23
25
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
24
26
  const RtlCssWebpackPlugin = require('rtlcss-webpack-plugin');
27
+ const validation = require('./validation');
28
+ const EventEmitter = require('events');
29
+ const ErrorHandler = require('./error-handler');
30
+
31
+ require('resolve-url-loader');
25
32
 
26
33
  class ConfigBuilder {
27
34
  constructor() {
28
- this._publicPath = 'public/';
29
- this._adminTheme = 'admin.oro';
30
35
  this._enableLayoutThemes = false;
31
36
  this._defaultLayoutThemes = null;
32
- this._versionFormat = '%s?version=%s';
33
- this._babelConfig = {
37
+ this.emitter = new EventEmitter();
38
+ this.errorHandler = new ErrorHandler();
39
+ this.addListeners();
40
+ this.setPublicPath('public/');
41
+ this.setAdminTheme('admin.oro');
42
+ this.setVersionFormat('%s?v=%s');
43
+ this.setBabelConfig({
34
44
  sourceType: 'unambiguous',
35
45
  presets: [
36
46
  [
@@ -46,7 +56,12 @@ class ConfigBuilder {
46
56
  plugins: [
47
57
  '@babel/plugin-transform-runtime'
48
58
  ]
49
- };
59
+ });
60
+ }
61
+
62
+ addListeners() {
63
+ this.emitter.on('publicPath:updated', path => validation.setPublicPath(path));
64
+ this.emitter.on('build:complete', stats => this.errorHandler.onBuildComplete(stats));
50
65
  }
51
66
 
52
67
  /**
@@ -56,6 +71,7 @@ class ConfigBuilder {
56
71
  */
57
72
  setPublicPath(publicPath) {
58
73
  this._publicPath = publicPath;
74
+ this.emitter.emit('publicPath:updated', publicPath);
59
75
  return this;
60
76
  }
61
77
 
@@ -71,12 +87,13 @@ class ConfigBuilder {
71
87
 
72
88
  /**
73
89
  * Specifies a sprintf pattern that will be used with the version option to construct an asset’s path.
74
- * By default ts `%s?version=%s`
90
+ * By default ts `%s?v=%s`
75
91
  * @param {string} versionFormat
76
92
  * @returns {ConfigBuilder}
77
93
  */
78
94
  setVersionFormat(versionFormat) {
79
95
  this._versionFormat = versionFormat;
96
+ this.emitter.emit('versionFormat:updated', versionFormat);
80
97
  return this;
81
98
  }
82
99
 
@@ -93,6 +110,7 @@ class ConfigBuilder {
93
110
  }
94
111
 
95
112
  this._adminTheme = adminTheme;
113
+ this.emitter.emit('adminTheme:updated', adminTheme);
96
114
  return this;
97
115
  }
98
116
 
@@ -102,8 +120,10 @@ class ConfigBuilder {
102
120
  enableLayoutThemes(themes) {
103
121
  if (themes) {
104
122
  this._defaultLayoutThemes = themes;
123
+ this.emitter.emit('defaultLayoutThemes:updated', themes);
105
124
  }
106
125
  this._enableLayoutThemes = true;
126
+ this.emitter.emit('enableLayoutThemes:updated', true);
107
127
  return this;
108
128
  }
109
129
 
@@ -114,6 +134,7 @@ class ConfigBuilder {
114
134
  */
115
135
  setBabelConfig(babelConfig) {
116
136
  this._babelConfig = babelConfig;
137
+ this.emitter.emit('babelConfig:updated', babelConfig);
117
138
  return this;
118
139
  }
119
140
 
@@ -125,310 +146,394 @@ class ConfigBuilder {
125
146
  return (env = {}, args = {}) => {
126
147
  this._initialize(args, env);
127
148
 
128
- const {assetVersion} = this._appConfig;
129
- const {theme: selectedTheme} = env;
130
- this._validateThemeName(selectedTheme);
131
-
132
- let themes = [];
133
- // Admin themes
134
- if (selectedTheme === undefined) {
135
- // themes.push(this._adminTheme.split(".")[1]);
136
- themes.push(this._adminTheme);
137
- } else if (this._adminThemes.indexOf(selectedTheme) !== -1) {
138
- // themes.push(selectedTheme.split(".")[1]);
139
- themes.push(selectedTheme);
149
+ let commonConfig = {};
150
+ try {
151
+ commonConfig = this._getCommonWebpackConfig(args, env);
152
+ } catch (e) {
153
+ this.errorHandler.displayError(e);
154
+ process.exit(1);
140
155
  }
141
156
 
142
- // Layout Themes
143
- if (this._enableLayoutThemes) {
144
- if (selectedTheme === undefined) {
145
- // build all layout themes
146
- if (this._defaultLayoutThemes) {
147
- themes = [...themes, ...this._defaultLayoutThemes];
148
- } else {
149
- themes = [...themes, ...Object.keys(this._layoutModulesConfigLoader.themes)];
150
- }
151
- } else if (this._layoutThemes.indexOf(selectedTheme) !== -1) {
152
- // build single layout theme
153
- themes.push(selectedTheme);
157
+ const webpackConfigs = [];
158
+ const requestedBuildNames = env.theme ? env.theme.split(',') : [];
159
+ const buildNames = this._getBuildNames(requestedBuildNames);
160
+
161
+ const buildErrors = [];
162
+ validation.jsmodulesValidator.emitter.on('error', error => buildErrors.push(error));
163
+ validation.assetsValidation.emitter.on('error', error => buildErrors.push(error));
164
+ buildNames.forEach(buildName => {
165
+ let buildConfig;
166
+ // flush all collected errors from previews builds
167
+ buildErrors.splice(0, buildErrors.length);
168
+
169
+ try {
170
+ buildConfig = this._getThemeWebpackConfig(buildName, args, env);
171
+ } catch (error) {
172
+ buildErrors.push(error);
173
+ }
174
+
175
+ if (buildErrors.length) {
176
+ this.errorHandler.displayError(buildErrors, buildName);
177
+ return;
178
+ }
179
+
180
+ if (buildConfig) {
181
+ webpackConfigs.push(webpackMerge(buildConfig, commonConfig));
182
+ }
183
+ });
184
+
185
+ return webpackConfigs;
186
+ };
187
+ }
188
+
189
+ get resolvedPublicPath() {
190
+ if (this._resolvedPublicPath === undefined) {
191
+ this._resolvedPublicPath = path.resolve(this._publicPath);
192
+ }
193
+ return this._resolvedPublicPath;
194
+ }
195
+
196
+ get resolvedNodeModulesPath() {
197
+ if (this._resolvedNodeModulesPath === undefined) {
198
+ this._resolvedNodeModulesPath = path.resolve('node_modules');
199
+ }
200
+ return this._resolvedNodeModulesPath;
201
+ }
202
+
203
+ get assetVersion() {
204
+ if (this._assetVersion === undefined) {
205
+ const filePath = path.join(this.resolvedPublicPath, '/build/build_version.txt');
206
+ this._assetVersion = fs.existsSync(filePath) ? String(fs.readFileSync(filePath)) : null;
207
+ }
208
+ return this._assetVersion;
209
+ }
210
+
211
+ _getBuildNames(requestedBuildNames = []) {
212
+ const buildNames = [];
213
+ requestedBuildNames.forEach(buildName => this._validateBuildName(buildName));
214
+
215
+ // Admin themes
216
+ if (!requestedBuildNames.length) {
217
+ buildNames.push(this._adminTheme);
218
+ } else {
219
+ requestedBuildNames.forEach(buildName => {
220
+ if (this._isAdminTheme(buildName)) {
221
+ buildNames.push(buildName);
154
222
  }
223
+ });
224
+ }
225
+
226
+ // Layout Themes
227
+ if (this._enableLayoutThemes) {
228
+ if (!requestedBuildNames.length) {
229
+ // build all layout themes
230
+ const themes = this._defaultLayoutThemes || this._appConfig.themes;
231
+ buildNames.push(...themes);
232
+ themes.forEach(theme => {
233
+ buildNames.push(...this._layoutModulesConfigLoader.extraJSBuildNames(theme));
234
+ });
235
+ } else {
236
+ // build single layout theme
237
+ requestedBuildNames.forEach(buildName => {
238
+ if (this._layoutModulesConfigLoader.buildNames.includes(buildName)) {
239
+ buildNames.push(buildName);
240
+ }
241
+ });
155
242
  }
243
+ }
156
244
 
157
- const resolvedPublicPath = path.resolve(this._publicPath);
158
- const resolvedNodeModulesPath = path.resolve('node_modules');
159
- const stats = env.stats || {
160
- hash: false,
161
- version: false,
162
- // Do not write the information about files emitted from node_modules or public bundles,
163
- // and hide css/*/*.js files from the output.
164
- excludeAssets: [/^bundles\//, /^\.\.\/_static\//, /^css\/.+\.js/],
165
- children: false,
166
- entrypoints: false,
167
- performance: this._isProduction,
168
- chunks: false,
169
- modules: false,
170
- source: false,
171
- publicPath: true,
172
- builtAt: false,
173
- warnings: false
174
- };
175
- let webpackConfig = {
176
- watchOptions: {
177
- aggregateTimeout: 200,
178
- ignored: /[\/\\]node_modules[\/\\].*\.js$/
179
- },
180
- stats: stats,
181
- output: {
182
- filename: '[name].js',
183
- // Because we use third party libraries 'chunkFilename' should include only [name]
184
- chunkFilename: printf(this._versionFormat, 'chunk/[name].js', assetVersion)
185
- },
186
- devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
187
- mode: 'development',
188
- optimization: {
189
- moduleIds: 'named',
190
- splitChunks: {
191
- cacheGroups: {
192
- commons: {
193
- minSize: 30,
194
- minChunks: 2,
195
- priority: 10,
196
- reuseExistingChunk: true
197
- },
198
- tinymce: {
199
- test: /tinymce/,
200
- name: 'tinymce.min',
201
- minChunks: 1
202
- },
203
- fusioncharts: {
204
- test: /fusioncharts/,
205
- name: 'fusioncharts',
206
- minChunks: 1
207
- }
208
- }
245
+ return buildNames;
246
+ }
247
+
248
+ _getCommonWebpackConfig(args, env) {
249
+ const stats = env.stats || {
250
+ hash: false,
251
+ version: false,
252
+ // Do not write the information about files emitted from node_modules or public bundles,
253
+ // and hide css/*/*.js files from the output.
254
+ excludeAssets: [/^bundles\//, /^\.\.\/_static\//, /^css\/.+\.js/],
255
+ children: false,
256
+ entrypoints: false,
257
+ performance: this._isProduction,
258
+ chunks: false,
259
+ modules: false,
260
+ source: false,
261
+ publicPath: true,
262
+ builtAt: false,
263
+ warnings: false
264
+ };
265
+
266
+ const webpackConfig = {
267
+ watchOptions: {
268
+ aggregateTimeout: 200,
269
+ ignored: /[\/\\]node_modules[\/\\].*\.js$/
270
+ },
271
+ stats,
272
+ output: {
273
+ filename: '[name].js',
274
+ // Because we use third party libraries 'chunkFilename' should include only [name]
275
+ chunkFilename: this._getVersionedPath('chunk/[name].js', this.assetVersion)
276
+ },
277
+ devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
278
+ mode: 'development',
279
+ optimization: {
280
+ moduleIds: 'named',
281
+ splitChunks: {
282
+ cacheGroups: {
283
+ defaultVendors: false
209
284
  }
210
- },
211
- resolveLoader: {
212
- modules: [
213
- resolvedPublicPath,
214
- path.join(__dirname, './loader'),
215
- resolvedPublicPath + '/bundles',
216
- resolvedNodeModulesPath
217
- ]
218
- },
219
- module: {
220
- noParse: [
221
- /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
222
- ],
223
- rules: [
224
- {
225
- test: /\.s?css$/,
226
- use: [{
227
- loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
228
- }, {
229
- loader: 'css-loader',
230
- options: {
231
- importLoaders: 1,
232
- sourceMap: true
233
- }
234
- }, {
235
- loader: 'postcss-loader',
236
- options: {
237
- sourceMap: true,
238
- postcssOptions: {
239
- plugins: [
240
- require('autoprefixer')
241
- ]
242
- }
243
- }
244
- }, {
245
- loader: 'sass-loader',
246
- options: {
247
- sassOptions: {
248
- includePaths: [
249
- resolvedPublicPath + '/bundles'
250
- ],
251
- outputStyle: 'expanded'
252
- },
253
- sourceMap: true
285
+ }
286
+ },
287
+ resolveLoader: {
288
+ modules: [
289
+ this.resolvedPublicPath,
290
+ path.join(__dirname, './loader'),
291
+ path.join(this.resolvedPublicPath, '/bundles'),
292
+ this.resolvedNodeModulesPath
293
+ ]
294
+ },
295
+ module: {
296
+ noParse: [
297
+ /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
298
+ ],
299
+ rules: [
300
+ {
301
+ test: /\.s?css$/,
302
+ use: [{
303
+ loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
304
+ }, {
305
+ loader: 'css-loader',
306
+ options: {
307
+ importLoaders: 1,
308
+ sourceMap: true,
309
+ // can't use esModule since resolve-url-loader needs file path to start with '~'
310
+ esModule: false
311
+ }
312
+ }, {
313
+ loader: 'resolve-url-loader'
314
+ }, {
315
+ loader: 'postcss-loader',
316
+ options: {
317
+ sourceMap: true,
318
+ postcssOptions: {
319
+ plugins: [
320
+ require('autoprefixer')
321
+ ]
254
322
  }
255
- }]
256
- },
257
- {
258
- test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
259
- loader: 'url-loader',
323
+ }
324
+ }, {
325
+ loader: 'sass-loader',
260
326
  options: {
261
- limit: 1,
262
- emitFile: true,
263
- outputPath: '../_static/',
264
- publicPath: '../../_static/',
265
- name: printf(this._versionFormat, '[path][name].[ext]', assetVersion)
327
+ sassOptions: {
328
+ includePaths: [
329
+ path.join(this.resolvedPublicPath, '/bundles')
330
+ ],
331
+ outputStyle: 'expanded'
332
+ },
333
+ sourceMap: true
266
334
  }
335
+ }]
336
+ },
337
+ {
338
+ test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
339
+ loader: 'url-loader',
340
+ options: {
341
+ limit: 1,
342
+ emitFile: true,
343
+ outputPath: '../_static/',
344
+ publicPath: '../../_static/',
345
+ name: this._getVersionedPath('[path][name].[ext]', this.assetVersion)
267
346
  }
268
- ]
269
- },
270
- performance: {hints: false},
271
- plugins: [
272
- new MiniCssExtractPlugin({
273
- filename: '[name].css'
274
- }),
275
- new CleanupStatsPlugin(),
276
- // Ignore all locale files of moment.js
277
- new webpack.IgnorePlugin({
278
- resourceRegExp: /^\.[\/\\]locale$/,
279
- contextRegExp: /moment$/
280
- }),
281
- new webpack.optimize.MinChunkSizePlugin({
282
- minChunkSize: 30000 // Minimum number of characters
283
- })
347
+ }
348
+ ]
349
+ },
350
+ performance: {hints: false},
351
+ plugins: [
352
+ new MiniCssExtractPlugin({
353
+ filename: '[name].css'
354
+ }),
355
+ new CleanupStatsPlugin(),
356
+ // Ignore all locale files of moment.js
357
+ new webpack.IgnorePlugin({
358
+ resourceRegExp: /^\.[\/\\]locale$/,
359
+ contextRegExp: /moment$/
360
+ }),
361
+ new webpack.optimize.MinChunkSizePlugin({
362
+ minChunkSize: 30000 // Minimum number of characters
363
+ }),
364
+ new AfterWebpackLogsPlugin(
365
+ stats => this.emitter.emit('build:complete', stats)
366
+ )
367
+ ]
368
+ };
369
+
370
+ if (env.analyze) {
371
+ webpackConfig.plugins.push(new BundleAnalyzerPlugin());
372
+ }
373
+
374
+ if (!env.skipJS && !env.skipBabel) {
375
+ const happyPackOptions = {
376
+ id: 'babel',
377
+ loaders: [
378
+ {
379
+ loader: 'babel-loader',
380
+ options: this._babelConfig
381
+ }
284
382
  ]
285
383
  };
286
384
 
287
- if (env.analyze) {
288
- webpackConfig.plugins.push(new BundleAnalyzerPlugin());
289
- }
385
+ webpackConfig.plugins.push(new HappyPack(happyPackOptions));
290
386
 
291
- if (!env.skipJS && !env.skipBabel) {
292
- const happyPackOptions = {
293
- id: 'babel',
294
- loaders: [
295
- {
296
- loader: 'babel-loader',
297
- options: this._babelConfig
298
- }
299
- ]
300
- };
301
-
302
- webpackConfig.plugins.push(new HappyPack(happyPackOptions));
303
-
304
- webpackConfig.module.rules.push({
305
- test: /\.js$/,
306
- exclude: [
307
- /[\/\\]platform[\/\\]build[\/\\]/,
308
- /[\/\\]node_modules[\/\\]/,
309
- /[\/\\]bundles[\/\\].+[\/\\]lib[\/\\]?/
310
- ],
311
- use: 'happypack/loader?id=babel'
312
- });
313
- }
387
+ webpackConfig.module.rules.push({
388
+ test: /\.js$/,
389
+ exclude: [
390
+ /[\/\\]platform[\/\\]build[\/\\]/,
391
+ /[\/\\]node_modules[\/\\]/,
392
+ /[\/\\]bundles[\/\\].+[\/\\]lib[\/\\]?/
393
+ ],
394
+ use: 'happypack/loader?id=babel'
395
+ });
396
+ }
314
397
 
315
- if (args.hot) {
316
- const https = this._appConfig.devServerOptions.https;
317
- const schema = https ? 'https' : 'http';
318
- const devServerHost = this._appConfig.devServerOptions.host;
319
- const devServerPort = this._appConfig.devServerOptions.port;
320
- webpackConfig.devServer = {
321
- contentBase: resolvedPublicPath,
322
- host: devServerHost,
323
- port: devServerPort,
324
- https: https,
325
- compress: true,
326
- stats: stats,
327
- disableHostCheck: true,
328
- clientLogLevel: 'error',
329
- headers: {
330
- 'Access-Control-Allow-Origin': '*'
331
- }
332
- };
333
- webpackConfig.output.publicPath = `${schema}://${devServerHost}:${devServerPort}/`;
334
- }
398
+ if (args.hot) {
399
+ const https = this._appConfig.devServerOptions.https;
400
+ const schema = https ? 'https' : 'http';
401
+ const devServerHost = this._appConfig.devServerOptions.host;
402
+ const devServerPort = this._appConfig.devServerOptions.port;
403
+ webpackConfig.devServer = {
404
+ contentBase: this.resolvedPublicPath,
405
+ host: devServerHost,
406
+ port: devServerPort,
407
+ https: https,
408
+ compress: true,
409
+ stats: webpackConfig.stats,
410
+ disableHostCheck: true,
411
+ clientLogLevel: 'error',
412
+ headers: {
413
+ 'Access-Control-Allow-Origin': '*'
414
+ }
415
+ };
416
+ webpackConfig.output.publicPath = `${schema}://${devServerHost}:${devServerPort}/`;
417
+ }
335
418
 
336
- // Additional setting for production mode
337
- if (this._isProduction) {
338
- webpackConfig = webpackMerge(webpackConfig, {
339
- devtool: false,
419
+ // Additional setting for production mode
420
+ if (this._isProduction) {
421
+ webpackConfig.devtool = false;
422
+ webpackConfig.plugins.push(new CssMinimizerPlugin());
423
+ }
340
424
 
341
- plugins: [
342
- new CssMinimizerPlugin()
343
- ]
344
- });
345
- }
425
+ return webpackConfig;
426
+ }
346
427
 
347
- const webpackConfigs = [];
428
+ _getThemeWebpackConfig(buildName, args, env) {
429
+ let {skipCSS, skipJS, skipRTL} = env;
430
+ let themeDefinition;
431
+ let jsBuildConfig;
432
+ let buildPublicPath;
433
+ if (this._isAdminTheme(buildName)) {
434
+ themeDefinition = this._modulesConfigLoader.themes[buildName.split('.')[1]];
435
+ buildPublicPath = '/build/admin/';
436
+ const jsModulesConfig = this._themeConfigFactory.loadConfig(buildName,
437
+ ['Resources/config/oro/jsmodules.yml', 'Resources/config/jsmodules.yml']);
438
+ validation.jsmodulesValidator.checkFullSchema(
439
+ jsModulesConfig,
440
+ this._themeConfigFactory?._configLoader.processedFiles,
441
+ buildName
442
+ );
443
+ jsBuildConfig = this._themeConfigFactory.create(buildPublicPath, jsModulesConfig);
444
+ } else if (this._layoutModulesConfigLoader.isExtraJSBuild(buildName)) {
445
+ const [theme, suffix] = this._layoutModulesConfigLoader.splitBuildName(buildName);
446
+ skipCSS = true;
447
+ themeDefinition = this._layoutModulesConfigLoader.themes[theme];
448
+ buildPublicPath = `/build/${buildName}/`;
449
+ const baseConfig = this._layoutThemeConfigFactory.loadConfig(theme, 'config/jsmodules.yml');
450
+ const extraConfig = this._layoutThemeConfigFactory.loadConfig(theme, `config/jsmodules-${suffix}.yml`);
451
+ const jsModulesConfig = this._layoutThemeConfigFactory.extendConfig(baseConfig, extraConfig);
452
+ validation.jsmodulesValidator.checkFullSchema(
453
+ jsModulesConfig,
454
+ this._layoutThemeConfigFactory?._configLoader.processedFiles,
455
+ buildName
456
+ );
457
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
458
+ } else {
459
+ themeDefinition = this._layoutModulesConfigLoader.themes[buildName];
460
+ buildPublicPath = `/build/${buildName}/`;
461
+ const jsModulesConfig = this._layoutThemeConfigFactory.loadConfig(buildName, 'config/jsmodules.yml');
462
+ validation.jsmodulesValidator.checkFullSchema(
463
+ jsModulesConfig,
464
+ this._layoutThemeConfigFactory?._configLoader.processedFiles,
465
+ buildName
466
+ );
467
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
468
+ }
469
+ const {rtl_support: rtlSupport = false} = themeDefinition;
470
+ const resolvedBuildPath = path.join(this.resolvedPublicPath, buildPublicPath);
471
+
472
+ const resolverConfig = {
473
+ modules: [
474
+ resolvedBuildPath,
475
+ this.resolvedPublicPath,
476
+ path.join(this.resolvedPublicPath, '/bundles'),
477
+ path.join(this.resolvedPublicPath, '/js'),
478
+ this.resolvedNodeModulesPath
479
+ ],
480
+ alias: jsBuildConfig.aliases,
481
+ cacheWithContext: true,
482
+ symlinks: false
483
+ };
484
+ const resolver = (resolver => {
485
+ return moduleName => resolver({}, '', moduleName, {});
486
+ })(resolve.create.sync({...resolverConfig}));
487
+
488
+ const plugins = [];
489
+ if (rtlSupport && !skipCSS && !skipRTL) {
490
+ plugins.push(new RtlCssWebpackPlugin({
491
+ filename: '[name].rtl.css'
492
+ }));
493
+ }
348
494
 
349
- themes.forEach(theme => {
350
- let themeDefinition;
351
- let themeConfig;
352
- let buildPublicPath;
353
- if (this._isAdminTheme(theme)) {
354
- themeDefinition = this._modulesConfigLoader.themes[theme.split('.')[1]];
355
- buildPublicPath = '/build/admin/';
356
- themeConfig = this._themeConfigFactory
357
- .create(theme, buildPublicPath, '/Resources/config/jsmodules.yml');
358
- } else {
359
- themeDefinition = this._layoutModulesConfigLoader.themes[theme];
360
- buildPublicPath = `/build/${theme}/`;
361
- themeConfig = this._layoutThemeConfigFactory
362
- .create(theme, buildPublicPath, '/config/jsmodules.yml');
363
- }
364
- const {rtl_support: rtlSupport = false} = themeDefinition;
365
- const resolvedBuildPath = path.join(resolvedPublicPath, buildPublicPath);
366
-
367
- const resolverConfig = {
368
- modules: [
369
- resolvedBuildPath,
370
- resolvedPublicPath,
371
- resolvedPublicPath + '/bundles',
372
- resolvedPublicPath + '/js',
373
- resolvedNodeModulesPath
374
- ],
375
- alias: themeConfig.aliases,
376
- symlinks: false
377
- };
378
- const resolver = (resolver => {
379
- return moduleName => resolver({}, '', moduleName, {});
380
- })(resolve.create.sync({...resolverConfig}));
381
-
382
- const plugins = [];
383
- if (rtlSupport && !env.skipCSS && !env.skipRTL) {
384
- plugins.push(new RtlCssWebpackPlugin({
385
- filename: '[name].rtl.css'
386
- }));
387
- }
495
+ const cssEntryPoints = !skipCSS ? this._getCssEntryPoints(buildName, buildPublicPath) : {};
496
+ const jsEntryPoints = !skipJS ? jsBuildConfig.entry : {};
388
497
 
389
- const cssEntryPoints = !env.skipCSS ? this._getCssEntryPoints(theme, buildPublicPath) : {};
390
- const jsEntryPoints = !env.skipJS ? themeConfig.entry : {};
498
+ const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
499
+ if (Object.keys(entryPoints).length === 0) {
500
+ return;
501
+ }
391
502
 
392
- const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
393
- if (Object.keys(entryPoints).length === 0) {
394
- return;
395
- }
396
- webpackConfigs.push(webpackMerge({
397
- entry: entryPoints,
398
- name: theme + ' theme',
399
- output: {
400
- publicPath: buildPublicPath,
401
- path: resolvedBuildPath
402
- },
403
- context: resolvedPublicPath,
404
- resolve: {
405
- ...resolverConfig,
406
- plugins: [
407
- new MapModulesPlugin(prepareModulesMap(resolver, themeConfig.map))
408
- ]
503
+ return {
504
+ entry: entryPoints,
505
+ name: buildName + ' theme',
506
+ output: {
507
+ publicPath: buildPublicPath,
508
+ path: resolvedBuildPath
509
+ },
510
+ context: this.resolvedPublicPath,
511
+ resolve: {
512
+ ...resolverConfig,
513
+ plugins: [
514
+ new MapModulesPlugin(prepareModulesMap(resolver, jsBuildConfig.map))
515
+ ]
516
+ },
517
+ plugins,
518
+ module: {
519
+ rules: [
520
+ {
521
+ test: /[\/\\]configs\.json$/,
522
+ loader: 'config-loader',
523
+ options: {
524
+ resolver,
525
+ relativeTo: this.resolvedPublicPath
526
+ }
409
527
  },
410
- plugins,
411
- module: {
412
- rules: [
413
- {
414
- test: /[\/\\]configs\.json$/,
415
- loader: 'config-loader',
416
- options: {
417
- resolver,
418
- relativeTo: resolvedPublicPath
419
- }
420
- },
421
- ...prepareModulesShim(resolver, themeConfig.shim)
422
- ]
423
- }
424
- }, webpackConfig));
425
- });
426
-
427
- return webpackConfigs;
528
+ ...prepareModulesShim(resolver, jsBuildConfig.shim)
529
+ ]
530
+ }
428
531
  };
429
532
  }
430
533
 
431
534
  _initialize(args, env) {
535
+ const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
536
+
432
537
  this._isProduction = args.mode === 'production';
433
538
  this._symfonyEnv = env.symfony;
434
539
  this._appConfig = AppConfigLoader.getConfig(this._cachePath, this._symfonyEnv);
@@ -439,7 +544,7 @@ class ConfigBuilder {
439
544
  'settings.yml'
440
545
  );
441
546
  this._adminThemes = this._modulesConfigLoader.themeNames.map(themeName => 'admin.' + themeName);
442
- this._styleLoader = new StyleLoader(this._modulesConfigLoader);
547
+ this._adminStyleLoader = new AdminStyleLoader(this._modulesConfigLoader, entryPointFileWriter);
443
548
  this._themeConfigFactory = new ThemeConfigFactory(
444
549
  this._modulesConfigLoader,
445
550
  new DynamicImportsFileWriter(this._publicPath),
@@ -452,8 +557,6 @@ class ConfigBuilder {
452
557
  '/Resources/views/layouts/',
453
558
  'theme.yml'
454
559
  );
455
- this._layoutThemes = this._layoutModulesConfigLoader.themeNames;
456
- const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
457
560
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
458
561
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
459
562
  this._layoutModulesConfigLoader,
@@ -463,33 +566,40 @@ class ConfigBuilder {
463
566
  );
464
567
  }
465
568
 
569
+ _getVersionedPath(name, assetVersion) {
570
+ if (!assetVersion) {
571
+ return name;
572
+ }
573
+ return printf(this._versionFormat, name, assetVersion);
574
+ }
575
+
466
576
  _getCssEntryPoints(theme, buildPath) {
467
577
  if (this._isAdminTheme(theme)) {
468
- return this._styleLoader.getThemeEntryPoints(theme.split('.')[1]);
578
+ return this._adminStyleLoader.getThemeEntryPoints(theme.split('.')[1], buildPath);
469
579
  }
470
580
 
471
581
  return this._layoutStyleLoader.getThemeEntryPoints(theme, buildPath);
472
582
  }
473
583
 
474
- _validateThemeName(theme) {
475
- let existingThemes = this._adminThemes;
584
+ _validateBuildName(buildName) {
585
+ const buildNames = [...this._adminThemes];
476
586
  if (this._enableLayoutThemes) {
477
- existingThemes = existingThemes.concat(this._layoutThemes);
587
+ buildNames.push(...this._layoutModulesConfigLoader.buildNames);
478
588
  }
479
- if (theme === 'admin') {
589
+ if (buildName === 'admin') {
480
590
  throw new Error(
481
591
  'The "admin" is a reserved word and cannot be used as a theme name.'
482
592
  );
483
593
  }
484
- if (theme !== undefined && !existingThemes.includes(theme)) {
594
+ if (buildName !== undefined && !buildNames.includes(buildName)) {
485
595
  throw new Error(
486
- 'Theme "' + theme + '" doesn\'t exists. Existing themes:' + existingThemes.join(', ')
596
+ 'Theme "' + buildName + '" doesn\'t exists. Existing themes:' + buildNames.join(', ')
487
597
  );
488
598
  }
489
599
  }
490
600
 
491
601
  _isAdminTheme(theme) {
492
- return this._adminThemes.indexOf(theme) !== -1;
602
+ return this._adminThemes.includes(theme);
493
603
  }
494
604
  }
495
605