@oroinc/oro-webpack-config-builder 5.1.0-alpha8 → 5.1.0-dev001

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