@oroinc/oro-webpack-config-builder 5.1.0-alpha4 → 5.1.0-alpha40

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