@oroinc/oro-webpack-config-builder 5.1.0-alpha9 → 5.1.0-dev002

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