@oroinc/oro-webpack-config-builder 4.2.1-dev8 → 5.1.0-1

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/error-handler.js +95 -0
  2. package/loader/config-loader.js +1 -2
  3. package/loader/inject-loader/LICENSE.md +21 -0
  4. package/loader/inject-loader/README.md +54 -0
  5. package/loader/inject-loader/index.js +10 -0
  6. package/loader/inject-loader/injectify.js +66 -0
  7. package/loader/inject-loader/package.json +55 -0
  8. package/loader/inject-loader/wrapper_template.js +32 -0
  9. package/modules-config/layout-modules-config-loader.js +66 -2
  10. package/modules-config/modules-config-loader.js +57 -15
  11. package/oro-webpack-config.js +416 -310
  12. package/package.json +36 -34
  13. package/plugin/logs/after-webpack-logs-plugin.js +25 -0
  14. package/prepare-modules-shim.js +15 -3
  15. package/style/admin-style-loader.js +23 -0
  16. package/style/layout-style-loader.js +12 -109
  17. package/style/style-loader.js +156 -46
  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,34 +3,44 @@ 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
- const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
15
+ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
16
16
  const prepareModulesShim = require('./prepare-modules-shim');
17
- const StyleLoader = require('./style/style-loader');
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
- const WebpackRTLPlugin = require('webpack-rtl-plugin');
25
+ const RtlCssWebpackPlugin = require('rtlcss-webpack-plugin');
26
+ const validation = require('./validation');
27
+ const EventEmitter = require('events');
28
+ const ErrorHandler = require('./error-handler');
29
+
30
+ require('resolve-url-loader');
25
31
 
26
32
  class ConfigBuilder {
27
33
  constructor() {
28
- this._publicPath = 'public/';
29
- this._adminTheme = 'admin.oro';
34
+ this._projectPath = '';
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,331 +146,411 @@ 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, ...this._appConfig['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 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);
154
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
+ });
155
249
  }
250
+ }
156
251
 
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: [
179
- /[\/\\]node_modules[\/\\].*\.js$/
180
- ]
181
- },
182
- stats: stats,
183
- output: {
184
- filename: '[name].js',
185
- // Because we use third party libraries 'chunkFilename' should include only [name]
186
- chunkFilename: printf(this._versionFormat, 'chunk/[name].js', assetVersion)
187
- },
188
- devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
189
- mode: 'development',
190
- optimization: {
191
- namedModules: true,
192
- splitChunks: {
193
- cacheGroups: {
194
- commons: {
195
- name: 'commons',
196
- minSize: 30,
197
- minChunks: 2,
198
- priority: 10,
199
- reuseExistingChunk: true
200
- },
201
- tinymce: {
202
- test: /tinymce/,
203
- name: 'tinymce.min',
204
- minChunks: 1
205
- },
206
- fusioncharts: {
207
- test: /fusioncharts/,
208
- name: 'fusioncharts',
209
- minChunks: 1
210
- }
211
- }
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
212
291
  }
213
- },
214
- resolveLoader: {
215
- modules: [
216
- resolvedPublicPath,
217
- path.join(__dirname, './loader'),
218
- resolvedPublicPath + '/bundles',
219
- resolvedNodeModulesPath
220
- ]
221
- },
222
- module: {
223
- noParse: [
224
- /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
225
- ],
226
- rules: [
227
- {
228
- test: /\.s?css$/,
229
- use: [{
230
- loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
231
- }, {
232
- loader: 'css-loader',
233
- options: {
234
- importLoaders: 1,
235
- sourceMap: true
236
- }
237
- }, {
238
- loader: 'postcss-loader',
239
- options: {
240
- 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: {
241
326
  plugins: [
242
327
  require('autoprefixer')
243
328
  ]
244
329
  }
245
- }, {
246
- loader: 'sass-loader',
247
- options: {
248
- sassOptions: {
249
- includePaths: [
250
- path.join(resolvedPublicPath, '/bundles')
251
- ],
252
- outputStyle: 'expanded'
253
- },
254
- sourceMap: true
255
- }
256
- }]
257
- },
258
- {
259
- test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
260
- loader: 'url-loader',
330
+ }
331
+ }, {
332
+ loader: 'sass-loader',
261
333
  options: {
262
- limit: 1,
263
- emitFile: true,
264
- outputPath: '../_static/',
265
- publicPath: '../../_static/',
266
- 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
267
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)
268
354
  }
269
- ]
270
- },
271
- performance: {hints: false},
272
- plugins: [
273
- new MiniCssExtractPlugin({
274
- filename: '[name].css'
275
- }),
276
- new CleanupStatsPlugin(),
277
- // Ignore all locale files of moment.js
278
- new webpack.IgnorePlugin({
279
- resourceRegExp: /^\.[\/\\]locale$/,
280
- contextRegExp: /moment$/
281
- }),
282
- new webpack.optimize.MinChunkSizePlugin({
283
- minChunkSize: 30000 // Minimum number of characters
284
- })
355
+ }
285
356
  ]
286
- };
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
+ };
287
377
 
288
- if (env.analyze) {
289
- webpackConfig.plugins.push(new BundleAnalyzerPlugin());
290
- }
378
+ if (env.analyze) {
379
+ webpackConfig.plugins.push(new BundleAnalyzerPlugin());
380
+ }
291
381
 
292
- if (!env.skipJS && !env.skipBabel) {
293
- const happyPackOptions = {
294
- id: 'babel',
295
- loaders: [
296
- {
297
- loader: 'babel-loader',
298
- options: this._babelConfig
299
- }
300
- ]
301
- };
302
-
303
- webpackConfig.plugins.push(new HappyPack(happyPackOptions));
304
-
305
- webpackConfig.module.rules.push({
306
- test: /\.js$/,
307
- exclude: [
308
- /[\/\\]platform[\/\\]build[\/\\]/,
309
- /[\/\\]node_modules[\/\\]/,
310
- /[\/\\]bundles[\/\\].+[\/\\]lib[\/\\]?/
311
- ],
312
- use: 'happypack/loader?id=babel'
313
- });
314
- }
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
+ }
315
396
 
316
- if (args.hot) {
317
- const https = this._appConfig.devServerOptions.https;
318
- const schema = https ? 'https' : 'http';
319
- const devServerHost = this._appConfig.devServerOptions.host;
320
- const devServerPort = this._appConfig.devServerOptions.port;
321
- webpackConfig.devServer = {
322
- contentBase: resolvedPublicPath,
323
- host: devServerHost,
324
- port: devServerPort,
325
- https: https,
326
- compress: true,
327
- stats: stats,
328
- disableHostCheck: true,
329
- clientLogLevel: 'error',
330
- headers: {
331
- 'Access-Control-Allow-Origin': '*'
332
- }
333
- };
334
- webpackConfig.output.publicPath = `${schema}://${devServerHost}:${devServerPort}/`;
335
- }
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
+ }
336
417
 
337
- // Additional setting for production mode
338
- if (this._isProduction) {
339
- webpackConfig = webpackMerge(webpackConfig, {
340
- devtool: false,
341
-
342
- plugins: [
343
- new OptimizeCssAssetsPlugin({
344
- cssProcessorPluginOptions: {
345
- preset: ['default', {
346
- // preserve comments for WebpackRTLPlugin, comments will be removed anyway later
347
- discardComments: false,
348
- }],
349
- }
350
- })
351
- ]
352
- });
353
- }
418
+ // Additional setting for production mode
419
+ if (this._isProduction) {
420
+ webpackConfig.devtool = false;
421
+ webpackConfig.plugins.push(new CssMinimizerPlugin());
422
+ }
354
423
 
355
- const webpackConfigs = [];
424
+ return webpackConfig;
425
+ }
356
426
 
357
- themes.forEach(theme => {
358
- let themeDefinition;
359
- let themeConfig;
360
- let buildPublicPath;
361
- if (this._isAdminTheme(theme)) {
362
- themeDefinition = this._modulesConfigLoader.themes[theme.split('.')[1]];
363
- buildPublicPath = '/build/admin/';
364
- themeConfig = this._themeConfigFactory
365
- .create(theme, buildPublicPath, '/Resources/config/jsmodules.yml');
366
- } else {
367
- themeDefinition = this._layoutModulesConfigLoader.themes[theme];
368
- buildPublicPath = `/build/${theme}/`;
369
- themeConfig = this._layoutThemeConfigFactory
370
- .create(theme, buildPublicPath, '/config/jsmodules.yml');
371
- }
372
- const {rtl_support: rtlSupport = false} = themeDefinition;
373
- const resolvedBuildPath = path.join(resolvedPublicPath, buildPublicPath);
374
-
375
- const resolverConfig = {
376
- modules: [
377
- resolvedBuildPath,
378
- resolvedPublicPath,
379
- resolvedPublicPath + '/bundles',
380
- resolvedPublicPath + '/js',
381
- resolvedNodeModulesPath
382
- ],
383
- alias: themeConfig.aliases,
384
- symlinks: false
385
- };
386
- const resolver = (resolver => {
387
- return moduleName => resolver({}, '', moduleName, {});
388
- })(resolve.create.sync({...resolverConfig}));
389
-
390
- const plugins = [];
391
- if (rtlSupport && !env.skipCSS && !env.skipRTL) {
392
- plugins.push(new WebpackRTLPlugin({
393
- filename: '[name].rtl.css',
394
- // RTL all chunks, except those that already support RTL
395
- test: '(?<!(-rtl-ready))\\.css'
396
- }));
397
- }
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
+ }
398
499
 
399
- const cssEntryPoints = !env.skipCSS ? this._getCssEntryPoints(theme, buildPublicPath) : {};
400
- const jsEntryPoints = !env.skipJS ? themeConfig.entry : {};
500
+ const cssEntryPoints = !skipCSS ? this._getCssEntryPoints(buildName, buildPublicPath) : {};
501
+ const jsEntryPoints = !skipJS ? jsBuildConfig.entry : {};
401
502
 
402
- const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
403
- if (Object.keys(entryPoints).length === 0) {
404
- return;
405
- }
406
- webpackConfigs.push(webpackMerge({
407
- entry: entryPoints,
408
- name: theme + ' theme',
409
- output: {
410
- publicPath: buildPublicPath,
411
- path: resolvedBuildPath
412
- },
413
- context: resolvedPublicPath,
414
- resolve: {
415
- ...resolverConfig,
416
- plugins: [
417
- new MapModulesPlugin(prepareModulesMap(resolver, themeConfig.map))
418
- ]
419
- },
420
- plugins,
421
- module: {
422
- rules: [
423
- {
424
- test: /[\/\\]configs\.json$/,
425
- loader: 'config-loader',
426
- options: {
427
- resolver,
428
- relativeTo: resolvedPublicPath
429
- }
430
- },
431
- ...prepareModulesShim(resolver, themeConfig.shim)
432
- ]
433
- }
434
- }, webpackConfig));
435
- });
503
+ const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
504
+ if (Object.keys(entryPoints).length === 0) {
505
+ return;
506
+ }
436
507
 
437
- return webpackConfigs;
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
+ }
532
+ },
533
+ ...prepareModulesShim(resolver, jsBuildConfig.shim)
534
+ ]
535
+ }
438
536
  };
439
537
  }
440
538
 
441
539
  _initialize(args, env) {
540
+ const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
541
+
442
542
  this._isProduction = args.mode === 'production';
443
543
  this._symfonyEnv = env.symfony;
444
544
  this._appConfig = AppConfigLoader.getConfig(this._cachePath, this._symfonyEnv);
545
+ this._appConfig.paths.push(this.resolvedProjectPath);
445
546
 
446
547
  this._modulesConfigLoader = new ModulesConfigLoader(
447
548
  this._appConfig.paths,
448
- '/Resources/public/themes/',
549
+ ['/Resources/public/themes/', '/public/themes/admin/'],
449
550
  'settings.yml'
450
551
  );
451
552
  this._adminThemes = this._modulesConfigLoader.themeNames.map(themeName => 'admin.' + themeName);
452
- this._styleLoader = new StyleLoader(this._modulesConfigLoader);
553
+ this._adminStyleLoader = new AdminStyleLoader(this._modulesConfigLoader, entryPointFileWriter);
453
554
  this._themeConfigFactory = new ThemeConfigFactory(
454
555
  this._modulesConfigLoader,
455
556
  new DynamicImportsFileWriter(this._publicPath),
@@ -459,11 +560,9 @@ class ConfigBuilder {
459
560
 
460
561
  this._layoutModulesConfigLoader = new LayoutModulesConfigLoader(
461
562
  this._appConfig.paths,
462
- '/Resources/views/layouts/',
563
+ ['/Resources/views/layouts/', '/templates/layouts/'],
463
564
  'theme.yml'
464
565
  );
465
- this._layoutThemes = this._layoutModulesConfigLoader.themeNames;
466
- const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
467
566
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
468
567
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
469
568
  this._layoutModulesConfigLoader,
@@ -473,33 +572,40 @@ class ConfigBuilder {
473
572
  );
474
573
  }
475
574
 
575
+ _getVersionedPath(name, assetVersion) {
576
+ if (!assetVersion) {
577
+ return name;
578
+ }
579
+ return printf(this._versionFormat, name, assetVersion);
580
+ }
581
+
476
582
  _getCssEntryPoints(theme, buildPath) {
477
583
  if (this._isAdminTheme(theme)) {
478
- return this._styleLoader.getThemeEntryPoints(theme.split('.')[1]);
584
+ return this._adminStyleLoader.getThemeEntryPoints(theme.split('.')[1], buildPath);
479
585
  }
480
586
 
481
587
  return this._layoutStyleLoader.getThemeEntryPoints(theme, buildPath);
482
588
  }
483
589
 
484
- _validateThemeName(theme) {
485
- let existingThemes = this._adminThemes;
590
+ _validateBuildName(buildName) {
591
+ const buildNames = [...this._adminThemes];
486
592
  if (this._enableLayoutThemes) {
487
- existingThemes = existingThemes.concat(this._layoutThemes);
593
+ buildNames.push(...this._layoutModulesConfigLoader.buildNames);
488
594
  }
489
- if (theme === 'admin') {
595
+ if (buildName === 'admin') {
490
596
  throw new Error(
491
597
  'The "admin" is a reserved word and cannot be used as a theme name.'
492
598
  );
493
599
  }
494
- if (theme !== undefined && !existingThemes.includes(theme)) {
600
+ if (buildName !== undefined && !buildNames.includes(buildName)) {
495
601
  throw new Error(
496
- 'Theme "' + theme + '" doesn\'t exists. Existing themes:' + existingThemes.join(', ')
602
+ 'Theme "' + buildName + '" doesn\'t exists. Existing themes:' + buildNames.join(', ')
497
603
  );
498
604
  }
499
605
  }
500
606
 
501
607
  _isAdminTheme(theme) {
502
- return this._adminThemes.indexOf(theme) !== -1;
608
+ return this._adminThemes.includes(theme);
503
609
  }
504
610
  }
505
611