@oroinc/oro-webpack-config-builder 4.2.1-dev9 → 5.0.0-lts1

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