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

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 (43) 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 +89 -2
  11. package/modules-config/modules-config-loader.js +83 -19
  12. package/oro-webpack-config.js +420 -299
  13. package/package.json +39 -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/svg-sprite/index.js +135 -0
  19. package/svg-sprite/svg-sprite-config.js +5 -0
  20. package/svg-sprite/svgo-config.js +32 -0
  21. package/theme-config-factory.js +45 -6
  22. package/utils.js +30 -0
  23. package/validation/assets-validator.js +104 -0
  24. package/validation/errors/assets-input-file-error.js +24 -0
  25. package/validation/errors/assets-schema-error.js +40 -0
  26. package/validation/errors/base-error.js +37 -0
  27. package/validation/errors/jsmodules-extra-modules-error.js +22 -0
  28. package/validation/errors/jsmodules-schema-error.js +40 -0
  29. package/validation/errors/styles-error.js +24 -0
  30. package/validation/errors/svg-icons-schema-error.js +36 -0
  31. package/validation/index.js +41 -0
  32. package/validation/jsmodules-validator.js +55 -0
  33. package/validation/schema-validator.js +62 -0
  34. package/validation/schemas/assets-schema-full.js +22 -0
  35. package/validation/schemas/assets-schema.js +32 -0
  36. package/validation/schemas/jsmodules-schema-full.js +11 -0
  37. package/validation/schemas/jsmodules-schema.js +76 -0
  38. package/validation/schemas/svg-icons-schema-full.js +18 -0
  39. package/validation/schemas/svg-icons-schema.js +17 -0
  40. package/validation/svg-icons-validator.js +53 -0
  41. package/writer/configs-file-writer.js +1 -1
  42. package/writer/dynamic-imports-file-writer.js +3 -3
  43. 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
+ const SVGSprite = require('./svg-sprite');
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,403 @@ 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));
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);
155
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, skipSVG} = 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
+ );
443
+ validation.jsmodulesValidator.checkFullSchema(
444
+ jsModulesConfig,
445
+ this._themeConfigFactory?._configLoader.processedFiles,
446
+ buildName
447
+ );
448
+ jsBuildConfig = this._themeConfigFactory.create(buildPublicPath, jsModulesConfig);
449
+ } else if (this._layoutModulesConfigLoader.isExtraJSBuild(buildName)) {
450
+ const [theme, suffix] = this._layoutModulesConfigLoader.splitBuildName(buildName);
451
+ skipCSS = true;
452
+ themeDefinition = this._layoutModulesConfigLoader.themes[theme];
453
+ buildPublicPath = `/build/${buildName}/`;
454
+ const baseConfig = this._layoutThemeConfigFactory.loadConfig(theme, 'config/jsmodules.yml');
455
+ const extraConfig = this._layoutThemeConfigFactory.loadConfig(theme, `config/jsmodules-${suffix}.yml`);
456
+ const jsModulesConfig = this._layoutThemeConfigFactory.extendConfig(baseConfig, extraConfig);
457
+ validation.jsmodulesValidator.checkFullSchema(
458
+ jsModulesConfig,
459
+ this._layoutThemeConfigFactory?._configLoader.processedFiles,
460
+ buildName
461
+ );
462
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
463
+ } else {
464
+ themeDefinition = this._layoutModulesConfigLoader.themes[buildName];
465
+ buildPublicPath = `/build/${buildName}/`;
466
+ const jsModulesConfig = this._layoutThemeConfigFactory.loadConfig(buildName, 'config/jsmodules.yml');
467
+ validation.jsmodulesValidator.checkFullSchema(
468
+ jsModulesConfig,
469
+ this._layoutThemeConfigFactory?._configLoader.processedFiles,
470
+ buildName
471
+ );
472
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
473
+
474
+ if (!skipSVG) {
475
+ new SVGSprite(
476
+ this._layoutModulesConfigLoader,
477
+ buildName,
478
+ this._publicPath,
479
+ buildPublicPath
480
+ );
481
+ }
482
+ }
483
+ const {rtl_support: rtlSupport = false} = themeDefinition;
484
+ const resolvedBuildPath = path.join(this.resolvedPublicPath, buildPublicPath);
485
+
486
+ const resolverConfig = {
487
+ modules: [
488
+ resolvedBuildPath,
489
+ this.resolvedPublicPath,
490
+ path.join(this.resolvedProjectPath, '/assets'),
491
+ path.join(this.resolvedPublicPath, '/bundles'),
492
+ path.join(this.resolvedPublicPath, '/js'),
493
+ this.resolvedNodeModulesPath
494
+ ],
495
+ alias: jsBuildConfig.aliases,
496
+ cacheWithContext: true,
497
+ symlinks: false
498
+ };
499
+ const resolver = (resolver => {
500
+ return moduleName => resolver({}, '', moduleName, {});
501
+ })(resolve.create.sync({...resolverConfig}));
502
+
503
+ const plugins = [];
504
+ if (rtlSupport && !skipCSS && !skipRTL) {
505
+ plugins.push(new RtlCssWebpackPlugin({
506
+ filename: '[name].rtl.css'
507
+ }));
508
+ }
351
509
 
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
- }
510
+ const cssEntryPoints = !skipCSS ? this._getCssEntryPoints(buildName, buildPublicPath) : {};
511
+ const jsEntryPoints = !skipJS ? jsBuildConfig.entry : {};
392
512
 
393
- const cssEntryPoints = !env.skipCSS ? this._getCssEntryPoints(theme, buildPublicPath) : {};
394
- const jsEntryPoints = !env.skipJS ? themeConfig.entry : {};
513
+ const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
514
+ if (Object.keys(entryPoints).length === 0) {
515
+ return;
516
+ }
395
517
 
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
- ]
518
+ return {
519
+ entry: entryPoints,
520
+ name: buildName + ' theme',
521
+ output: {
522
+ publicPath: buildPublicPath,
523
+ path: resolvedBuildPath
524
+ },
525
+ context: this.resolvedPublicPath,
526
+ resolve: {
527
+ ...resolverConfig,
528
+ plugins: [
529
+ new MapModulesPlugin(prepareModulesMap(resolver, jsBuildConfig.map))
530
+ ]
531
+ },
532
+ plugins,
533
+ module: {
534
+ rules: [
535
+ {
536
+ test: /[\/\\]configs\.json$/,
537
+ loader: 'config-loader',
538
+ options: {
539
+ resolver,
540
+ relativeTo: this.resolvedPublicPath
541
+ }
413
542
  },
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;
543
+ ...prepareModulesShim(resolver, jsBuildConfig.shim)
544
+ ]
545
+ }
432
546
  };
433
547
  }
434
548
 
@@ -438,10 +552,11 @@ class ConfigBuilder {
438
552
  this._isProduction = args.mode === 'production';
439
553
  this._symfonyEnv = env.symfony;
440
554
  this._appConfig = AppConfigLoader.getConfig(this._cachePath, this._symfonyEnv);
555
+ this._appConfig.paths.push(this.resolvedProjectPath);
441
556
 
442
557
  this._modulesConfigLoader = new ModulesConfigLoader(
443
558
  this._appConfig.paths,
444
- '/Resources/public/themes/',
559
+ ['/Resources/public/themes/', '/public/themes/admin/'],
445
560
  'settings.yml'
446
561
  );
447
562
  this._adminThemes = this._modulesConfigLoader.themeNames.map(themeName => 'admin.' + themeName);
@@ -455,10 +570,9 @@ class ConfigBuilder {
455
570
 
456
571
  this._layoutModulesConfigLoader = new LayoutModulesConfigLoader(
457
572
  this._appConfig.paths,
458
- '/Resources/views/layouts/',
573
+ ['/Resources/views/layouts/', '/templates/layouts/'],
459
574
  'theme.yml'
460
575
  );
461
- this._layoutThemes = this._layoutModulesConfigLoader.themeNames;
462
576
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
463
577
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
464
578
  this._layoutModulesConfigLoader,
@@ -468,6 +582,13 @@ class ConfigBuilder {
468
582
  );
469
583
  }
470
584
 
585
+ _getVersionedPath(name, assetVersion) {
586
+ if (!assetVersion) {
587
+ return name;
588
+ }
589
+ return printf(this._versionFormat, name, assetVersion);
590
+ }
591
+
471
592
  _getCssEntryPoints(theme, buildPath) {
472
593
  if (this._isAdminTheme(theme)) {
473
594
  return this._adminStyleLoader.getThemeEntryPoints(theme.split('.')[1], buildPath);
@@ -476,25 +597,25 @@ class ConfigBuilder {
476
597
  return this._layoutStyleLoader.getThemeEntryPoints(theme, buildPath);
477
598
  }
478
599
 
479
- _validateThemeName(theme) {
480
- let existingThemes = this._adminThemes;
600
+ _validateBuildName(buildName) {
601
+ const buildNames = [...this._adminThemes];
481
602
  if (this._enableLayoutThemes) {
482
- existingThemes = existingThemes.concat(this._layoutThemes);
603
+ buildNames.push(...this._layoutModulesConfigLoader.buildNames);
483
604
  }
484
- if (theme === 'admin') {
605
+ if (buildName === 'admin') {
485
606
  throw new Error(
486
607
  'The "admin" is a reserved word and cannot be used as a theme name.'
487
608
  );
488
609
  }
489
- if (theme !== undefined && !existingThemes.includes(theme)) {
610
+ if (buildName !== undefined && !buildNames.includes(buildName)) {
490
611
  throw new Error(
491
- 'Theme "' + theme + '" doesn\'t exists. Existing themes:' + existingThemes.join(', ')
612
+ 'Theme "' + buildName + '" doesn\'t exists. Existing themes:' + buildNames.join(', ')
492
613
  );
493
614
  }
494
615
  }
495
616
 
496
617
  _isAdminTheme(theme) {
497
- return this._adminThemes.indexOf(theme) !== -1;
618
+ return this._adminThemes.includes(theme);
498
619
  }
499
620
  }
500
621