@oroinc/oro-webpack-config-builder 5.1.0-alpha25 → 5.1.0-alpha29

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.
@@ -17,6 +17,66 @@ class LayoutModulesConfigLoader extends ModulesConfigLoader {
17
17
 
18
18
  return themeConfig;
19
19
  }
20
+
21
+ /**
22
+ * All build names:
23
+ * - based on theme names
24
+ * - and theme's extra js builds
25
+ *
26
+ * @return {string[]}
27
+ */
28
+ get buildNames() {
29
+ const buildNames = super.buildNames;
30
+ this.themeNames.forEach(theme => {
31
+ buildNames.push(...this.extraJSBuildNames(theme));
32
+ });
33
+ return buildNames;
34
+ }
35
+
36
+ /**
37
+ * Collect extra js-build names for a theme
38
+ *
39
+ * @param {string} theme name on the theme
40
+ * @return {string[]}
41
+ */
42
+ extraJSBuildNames(theme) {
43
+ const {extra_js_builds: extraJSBuilds = []} = this._themes[theme] || {}
44
+ return [...extraJSBuilds.map(suffix => `${theme}-${suffix}`)];
45
+ }
46
+
47
+ /**
48
+ * Check if buildName is an extra js build of layout theme
49
+ *
50
+ * @param {string} buildName name of the build
51
+ * @return {boolean}
52
+ */
53
+ isExtraJSBuild(buildName) {
54
+ const [, suffix] = this.splitBuildName(buildName);
55
+ return suffix !== void 0;
56
+ }
57
+
58
+ /**
59
+ * Splits build name on parts theme name and suffix (name of extra js build)
60
+ * @param {string} buildName
61
+ * @return {[string]|[string, string]}
62
+ */
63
+ splitBuildName(buildName) {
64
+ // suffix can not contain '-'
65
+ const marches = buildName.match(/(.+)-([^\-]+)?/);
66
+ const result = [];
67
+ if (marches) {
68
+ const [, theme, suffix] = marches;
69
+ const {extra_js_builds: extraJSBuilds = []} = this._themes[theme] || {}
70
+ if (extraJSBuilds.includes(suffix)) {
71
+ result.push(marches[1], marches[2]);
72
+ } else {
73
+ result.push(buildName);
74
+ }
75
+ } else {
76
+ result.push(buildName);
77
+ }
78
+ return result;
79
+ }
20
80
  }
21
81
 
22
82
  module.exports = LayoutModulesConfigLoader;
@@ -3,6 +3,9 @@ const fs = require('fs');
3
3
  const merge = require('deepmerge');
4
4
  const yaml = require('js-yaml');
5
5
 
6
+ // merge only unique items
7
+ const arrayMerge = (target, source) => target.concat(source.filter(item => !target.includes(item)));
8
+
6
9
  class ModulesConfigLoader {
7
10
  /**
8
11
  * @returns {Array}
@@ -18,10 +21,17 @@ class ModulesConfigLoader {
18
21
  return Object.keys(this._themes);
19
22
  }
20
23
 
24
+ /**
25
+ * @returns {Array}
26
+ */
27
+ get buildNames() {
28
+ return this.themeNames;
29
+ }
30
+
21
31
  /**
22
32
  * @param {Array} bundles Array of ordered symfony bundle paths
23
33
  * @param {string} themesLocation Path inside the bundle, where to find the theme
24
- * @param {string} themeInfoFileName Yml File name with theme info
34
+ * @param {string} themeInfoFileName Yaml File name with theme info
25
35
  */
26
36
  constructor(bundles, themesLocation, themeInfoFileName) {
27
37
  this._bundles = bundles;
@@ -43,17 +53,15 @@ class ModulesConfigLoader {
43
53
  if (!fs.existsSync(source)) return;
44
54
 
45
55
  fs.readdirSync(source).forEach(name => {
46
- const theme = path.resolve(source, name);
47
- if (!fs.lstatSync(theme).isDirectory()) {
56
+ const themePath = path.resolve(source, name);
57
+ if (!fs.lstatSync(themePath).isDirectory()) {
48
58
  return;
49
59
  }
50
- const themeFile = path.resolve(theme, themeInfoFileName);
60
+ const themeFile = path.resolve(themePath, themeInfoFileName);
51
61
  if (!fs.existsSync(themeFile)) return;
52
62
 
53
- if (!(name in themes)) {
54
- themes[name] = null;
55
- }
56
- themes[name] = yaml.load(fs.readFileSync(themeFile, 'utf8'));
63
+ const theme = yaml.load(fs.readFileSync(themeFile, 'utf8'));
64
+ themes[name] = merge(themes[name] || {}, theme, {arrayMerge});
57
65
  });
58
66
  });
59
67
 
@@ -63,7 +71,7 @@ class ModulesConfigLoader {
63
71
  /**
64
72
  * @param {string} theme Theme name
65
73
  * @param {string|string[]} filePath Path (or paths with fallback) to the file inside bundle directory where to find the configs
66
- * @return {Object} Merged Configs loaded from all the bundles Yml files matched by filePath
74
+ * @return {Object} Merged Configs loaded from all the bundles Yaml files matched by filePath
67
75
  */
68
76
  loadConfig(theme, filePath) {
69
77
  let configs = {};
@@ -127,298 +127,346 @@ class ConfigBuilder {
127
127
  return (env = {}, args = {}) => {
128
128
  this._initialize(args, env);
129
129
 
130
- const {theme: selectedTheme} = env;
131
- this._validateThemeName(selectedTheme);
132
-
133
- let themes = [];
134
- // Admin themes
135
- if (selectedTheme === undefined) {
136
- // themes.push(this._adminTheme.split(".")[1]);
137
- themes.push(this._adminTheme);
138
- } else if (this._adminThemes.indexOf(selectedTheme) !== -1) {
139
- // themes.push(selectedTheme.split(".")[1]);
140
- themes.push(selectedTheme);
141
- }
130
+ const commonConfig = this._getCommonWebpackConfig(args, env);
131
+ const webpackConfigs = [];
132
+ const requestedBuildNames = env.theme ? env.theme.split(',') : [];
133
+ const buildNames = this._getBuildNames(requestedBuildNames);
134
+ buildNames.forEach(buildName => {
135
+ const buildConfig = this._getThemeWebpackConfig(buildName, args, env);
136
+ if (buildConfig) {
137
+ webpackConfigs.push(webpackMerge(buildConfig, commonConfig));
138
+ }
139
+ });
142
140
 
143
- // Layout Themes
144
- if (this._enableLayoutThemes) {
145
- if (selectedTheme === undefined) {
146
- // build all layout themes
147
- if (this._defaultLayoutThemes) {
148
- themes = [...themes, ...this._defaultLayoutThemes];
149
- } else {
150
- themes = [...themes, ...this._appConfig['themes']];
151
- }
152
- } else if (this._layoutThemes.indexOf(selectedTheme) !== -1) {
153
- // build single layout theme
154
- themes.push(selectedTheme);
141
+ return webpackConfigs;
142
+ };
143
+ }
144
+
145
+ get resolvedPublicPath() {
146
+ if (this._resolvedPublicPath === undefined) {
147
+ this._resolvedPublicPath = path.resolve(this._publicPath);
148
+ }
149
+ return this._resolvedPublicPath;
150
+ }
151
+
152
+ get resolvedNodeModulesPath() {
153
+ if (this._resolvedNodeModulesPath === undefined) {
154
+ this._resolvedNodeModulesPath = path.resolve('node_modules');
155
+ }
156
+ return this._resolvedNodeModulesPath;
157
+ }
158
+
159
+ get assetVersion() {
160
+ if (this._assetVersion === undefined) {
161
+ const filePath = path.join(this.resolvedPublicPath, '/build/build_version.txt');
162
+ this._assetVersion = fs.existsSync(filePath) ? String(fs.readFileSync(filePath)) : null;
163
+ }
164
+ return this._assetVersion;
165
+ }
166
+
167
+ _getBuildNames(requestedBuildNames = []) {
168
+ const buildNames = [];
169
+ requestedBuildNames.forEach(buildName => this._validateBuildName(buildName));
170
+
171
+ // Admin themes
172
+ if (!requestedBuildNames.length) {
173
+ buildNames.push(this._adminTheme);
174
+ } else {
175
+ requestedBuildNames.forEach(buildName => {
176
+ if (this._isAdminTheme(buildName)) {
177
+ buildNames.push(buildName);
155
178
  }
179
+ });
180
+ }
181
+
182
+ // Layout Themes
183
+ if (this._enableLayoutThemes) {
184
+ if (!requestedBuildNames.length) {
185
+ // build all layout themes
186
+ const themes = this._defaultLayoutThemes || this._appConfig.themes;
187
+ buildNames.push(...themes);
188
+ themes.forEach(theme => {
189
+ buildNames.push(...this._layoutModulesConfigLoader.extraJSBuildNames(theme));
190
+ });
191
+ } else {
192
+ // build single layout theme
193
+ requestedBuildNames.forEach(buildName => {
194
+ if (this._layoutModulesConfigLoader.buildNames.includes(buildName)) {
195
+ buildNames.push(buildName);
196
+ }
197
+ });
156
198
  }
199
+ }
157
200
 
158
- const resolvedPublicPath = path.resolve(this._publicPath);
159
- const resolvedNodeModulesPath = path.resolve('node_modules');
160
- const assetVersionFilePath = path.join(resolvedPublicPath, '/build/build_version.txt');
161
- const assetVersion = fs.existsSync(assetVersionFilePath) ? fs.readFileSync(assetVersionFilePath) : null;
162
-
163
- const stats = env.stats || {
164
- hash: false,
165
- version: false,
166
- // Do not write the information about files emitted from node_modules or public bundles,
167
- // and hide css/*/*.js files from the output.
168
- excludeAssets: [/^bundles\//, /^\.\.\/_static\//, /^css\/.+\.js/],
169
- children: false,
170
- entrypoints: false,
171
- performance: this._isProduction,
172
- chunks: false,
173
- modules: false,
174
- source: false,
175
- publicPath: true,
176
- builtAt: false,
177
- warnings: false
178
- };
179
- let webpackConfig = {
180
- watchOptions: {
181
- aggregateTimeout: 200,
182
- ignored: /[\/\\]node_modules[\/\\].*\.js$/
183
- },
184
- stats: stats,
185
- output: {
186
- filename: '[name].js',
187
- // Because we use third party libraries 'chunkFilename' should include only [name]
188
- chunkFilename: this._getVersionedPath('chunk/[name].js', assetVersion)
189
- },
190
- devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
191
- mode: 'development',
192
- optimization: {
193
- moduleIds: 'named',
194
- splitChunks: {
195
- cacheGroups: {
196
- defaultVendors: false
197
- }
201
+ return buildNames;
202
+ }
203
+
204
+ _getCommonWebpackConfig(args, env) {
205
+ const stats = env.stats || {
206
+ hash: false,
207
+ version: false,
208
+ // Do not write the information about files emitted from node_modules or public bundles,
209
+ // and hide css/*/*.js files from the output.
210
+ excludeAssets: [/^bundles\//, /^\.\.\/_static\//, /^css\/.+\.js/],
211
+ children: false,
212
+ entrypoints: false,
213
+ performance: this._isProduction,
214
+ chunks: false,
215
+ modules: false,
216
+ source: false,
217
+ publicPath: true,
218
+ builtAt: false,
219
+ warnings: false
220
+ };
221
+
222
+ const webpackConfig = {
223
+ watchOptions: {
224
+ aggregateTimeout: 200,
225
+ ignored: /[\/\\]node_modules[\/\\].*\.js$/
226
+ },
227
+ stats,
228
+ output: {
229
+ filename: '[name].js',
230
+ // Because we use third party libraries 'chunkFilename' should include only [name]
231
+ chunkFilename: this._getVersionedPath('chunk/[name].js', this.assetVersion)
232
+ },
233
+ devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
234
+ mode: 'development',
235
+ optimization: {
236
+ moduleIds: 'named',
237
+ splitChunks: {
238
+ cacheGroups: {
239
+ defaultVendors: false
198
240
  }
199
- },
200
- resolveLoader: {
201
- modules: [
202
- resolvedPublicPath,
203
- path.join(__dirname, './loader'),
204
- resolvedPublicPath + '/bundles',
205
- resolvedNodeModulesPath
206
- ]
207
- },
208
- module: {
209
- noParse: [
210
- /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
211
- ],
212
- rules: [
213
- {
214
- test: /\.s?css$/,
215
- use: [{
216
- loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
217
- }, {
218
- loader: 'css-loader',
219
- options: {
220
- importLoaders: 1,
221
- sourceMap: true
222
- }
223
- }, {
224
- loader: 'resolve-url-loader'
225
- }, {
226
- loader: 'postcss-loader',
227
- options: {
228
- sourceMap: true,
229
- postcssOptions: {
230
- plugins: [
231
- require('autoprefixer')
232
- ]
233
- }
234
- }
235
- }, {
236
- loader: 'sass-loader',
237
- options: {
238
- sassOptions: {
239
- includePaths: [
240
- resolvedPublicPath + '/bundles'
241
- ],
242
- outputStyle: 'expanded'
243
- },
244
- sourceMap: true
241
+ }
242
+ },
243
+ resolveLoader: {
244
+ modules: [
245
+ this.resolvedPublicPath,
246
+ path.join(__dirname, './loader'),
247
+ path.join(this.resolvedPublicPath, '/bundles'),
248
+ this.resolvedNodeModulesPath
249
+ ]
250
+ },
251
+ module: {
252
+ noParse: [
253
+ /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
254
+ ],
255
+ rules: [
256
+ {
257
+ test: /\.s?css$/,
258
+ use: [{
259
+ loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
260
+ }, {
261
+ loader: 'css-loader',
262
+ options: {
263
+ importLoaders: 1,
264
+ sourceMap: true,
265
+ // can't use esModule since resolve-url-loader needs file path to start with '~'
266
+ esModule: false
267
+ }
268
+ }, {
269
+ loader: 'resolve-url-loader'
270
+ }, {
271
+ loader: 'postcss-loader',
272
+ options: {
273
+ sourceMap: true,
274
+ postcssOptions: {
275
+ plugins: [
276
+ require('autoprefixer')
277
+ ]
245
278
  }
246
- }]
247
- },
248
- {
249
- test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
250
- loader: 'url-loader',
279
+ }
280
+ }, {
281
+ loader: 'sass-loader',
251
282
  options: {
252
- limit: 1,
253
- emitFile: true,
254
- outputPath: '../_static/',
255
- publicPath: '../../_static/',
256
- name: this._getVersionedPath('[path][name].[ext]', assetVersion)
283
+ sassOptions: {
284
+ includePaths: [
285
+ path.join(this.resolvedPublicPath, '/bundles')
286
+ ],
287
+ outputStyle: 'expanded'
288
+ },
289
+ sourceMap: true
257
290
  }
291
+ }]
292
+ },
293
+ {
294
+ test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
295
+ loader: 'url-loader',
296
+ options: {
297
+ limit: 1,
298
+ emitFile: true,
299
+ outputPath: '../_static/',
300
+ publicPath: '../../_static/',
301
+ name: this._getVersionedPath('[path][name].[ext]', this.assetVersion)
258
302
  }
259
- ]
260
- },
261
- performance: {hints: false},
262
- plugins: [
263
- new MiniCssExtractPlugin({
264
- filename: '[name].css'
265
- }),
266
- new CleanupStatsPlugin(),
267
- // Ignore all locale files of moment.js
268
- new webpack.IgnorePlugin({
269
- resourceRegExp: /^\.[\/\\]locale$/,
270
- contextRegExp: /moment$/
271
- }),
272
- new webpack.optimize.MinChunkSizePlugin({
273
- minChunkSize: 30000 // Minimum number of characters
274
- })
303
+ }
275
304
  ]
276
- };
277
-
278
- if (env.analyze) {
279
- webpackConfig.plugins.push(new BundleAnalyzerPlugin());
280
- }
305
+ },
306
+ performance: {hints: false},
307
+ plugins: [
308
+ new MiniCssExtractPlugin({
309
+ filename: '[name].css'
310
+ }),
311
+ new CleanupStatsPlugin(),
312
+ // Ignore all locale files of moment.js
313
+ new webpack.IgnorePlugin({
314
+ resourceRegExp: /^\.[\/\\]locale$/,
315
+ contextRegExp: /moment$/
316
+ }),
317
+ new webpack.optimize.MinChunkSizePlugin({
318
+ minChunkSize: 30000 // Minimum number of characters
319
+ })
320
+ ]
321
+ };
281
322
 
282
- if (!env.skipJS && !env.skipBabel) {
283
- const happyPackOptions = {
284
- id: 'babel',
285
- loaders: [
286
- {
287
- loader: 'babel-loader',
288
- options: this._babelConfig
289
- }
290
- ]
291
- };
292
-
293
- webpackConfig.plugins.push(new HappyPack(happyPackOptions));
294
-
295
- webpackConfig.module.rules.push({
296
- test: /\.js$/,
297
- exclude: [
298
- /[\/\\]platform[\/\\]build[\/\\]/,
299
- /[\/\\]node_modules[\/\\]/,
300
- /[\/\\]bundles[\/\\].+[\/\\]lib[\/\\]?/
301
- ],
302
- use: 'happypack/loader?id=babel'
303
- });
304
- }
323
+ if (env.analyze) {
324
+ webpackConfig.plugins.push(new BundleAnalyzerPlugin());
325
+ }
305
326
 
306
- if (args.hot) {
307
- const https = this._appConfig.devServerOptions.https;
308
- const schema = https ? 'https' : 'http';
309
- const devServerHost = this._appConfig.devServerOptions.host;
310
- const devServerPort = this._appConfig.devServerOptions.port;
311
- webpackConfig.devServer = {
312
- contentBase: resolvedPublicPath,
313
- host: devServerHost,
314
- port: devServerPort,
315
- https: https,
316
- compress: true,
317
- stats: stats,
318
- disableHostCheck: true,
319
- clientLogLevel: 'error',
320
- headers: {
321
- 'Access-Control-Allow-Origin': '*'
327
+ if (!env.skipJS && !env.skipBabel) {
328
+ const happyPackOptions = {
329
+ id: 'babel',
330
+ loaders: [
331
+ {
332
+ loader: 'babel-loader',
333
+ options: this._babelConfig
322
334
  }
323
- };
324
- webpackConfig.output.publicPath = `${schema}://${devServerHost}:${devServerPort}/`;
325
- }
326
-
327
- // Additional setting for production mode
328
- if (this._isProduction) {
329
- webpackConfig = webpackMerge(webpackConfig, {
330
- devtool: false,
335
+ ]
336
+ };
331
337
 
332
- plugins: [
333
- new CssMinimizerPlugin()
334
- ]
335
- });
336
- }
338
+ webpackConfig.plugins.push(new HappyPack(happyPackOptions));
337
339
 
338
- const webpackConfigs = [];
340
+ webpackConfig.module.rules.push({
341
+ test: /\.js$/,
342
+ exclude: [
343
+ /[\/\\]platform[\/\\]build[\/\\]/,
344
+ /[\/\\]node_modules[\/\\]/,
345
+ /[\/\\]bundles[\/\\].+[\/\\]lib[\/\\]?/
346
+ ],
347
+ use: 'happypack/loader?id=babel'
348
+ });
349
+ }
339
350
 
340
- themes.forEach(theme => {
341
- let themeDefinition;
342
- let themeConfig;
343
- let buildPublicPath;
344
- if (this._isAdminTheme(theme)) {
345
- themeDefinition = this._modulesConfigLoader.themes[theme.split('.')[1]];
346
- buildPublicPath = '/build/admin/';
347
- themeConfig = this._themeConfigFactory
348
- .create(theme, buildPublicPath,
349
- ['Resources/config/oro/jsmodules.yml', 'Resources/config/jsmodules.yml']);
350
- } else {
351
- themeDefinition = this._layoutModulesConfigLoader.themes[theme];
352
- buildPublicPath = `/build/${theme}/`;
353
- themeConfig = this._layoutThemeConfigFactory
354
- .create(theme, buildPublicPath, 'config/jsmodules.yml');
355
- }
356
- const {rtl_support: rtlSupport = false} = themeDefinition;
357
- const resolvedBuildPath = path.join(resolvedPublicPath, buildPublicPath);
358
-
359
- const resolverConfig = {
360
- modules: [
361
- resolvedBuildPath,
362
- resolvedPublicPath,
363
- resolvedPublicPath + '/bundles',
364
- resolvedPublicPath + '/js',
365
- resolvedNodeModulesPath
366
- ],
367
- alias: themeConfig.aliases,
368
- cacheWithContext: true,
369
- symlinks: false
370
- };
371
- const resolver = (resolver => {
372
- return moduleName => resolver({}, '', moduleName, {});
373
- })(resolve.create.sync({...resolverConfig}));
374
-
375
- const plugins = [];
376
- if (rtlSupport && !env.skipCSS && !env.skipRTL) {
377
- plugins.push(new RtlCssWebpackPlugin({
378
- filename: '[name].rtl.css'
379
- }));
351
+ if (args.hot) {
352
+ const https = this._appConfig.devServerOptions.https;
353
+ const schema = https ? 'https' : 'http';
354
+ const devServerHost = this._appConfig.devServerOptions.host;
355
+ const devServerPort = this._appConfig.devServerOptions.port;
356
+ webpackConfig.devServer = {
357
+ contentBase: this.resolvedPublicPath,
358
+ host: devServerHost,
359
+ port: devServerPort,
360
+ https: https,
361
+ compress: true,
362
+ stats: webpackConfig.stats,
363
+ disableHostCheck: true,
364
+ clientLogLevel: 'error',
365
+ headers: {
366
+ 'Access-Control-Allow-Origin': '*'
380
367
  }
368
+ };
369
+ webpackConfig.output.publicPath = `${schema}://${devServerHost}:${devServerPort}/`;
370
+ }
381
371
 
382
- const cssEntryPoints = !env.skipCSS ? this._getCssEntryPoints(theme, buildPublicPath) : {};
383
- const jsEntryPoints = !env.skipJS ? themeConfig.entry : {};
372
+ // Additional setting for production mode
373
+ if (this._isProduction) {
374
+ webpackConfig.devtool = false;
375
+ webpackConfig.plugins.push(new CssMinimizerPlugin());
376
+ }
384
377
 
385
- const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
386
- if (Object.keys(entryPoints).length === 0) {
387
- return;
388
- }
389
- webpackConfigs.push(webpackMerge({
390
- entry: entryPoints,
391
- name: theme + ' theme',
392
- output: {
393
- publicPath: buildPublicPath,
394
- path: resolvedBuildPath
395
- },
396
- context: resolvedPublicPath,
397
- resolve: {
398
- ...resolverConfig,
399
- plugins: [
400
- new MapModulesPlugin(prepareModulesMap(resolver, themeConfig.map))
401
- ]
402
- },
403
- plugins,
404
- module: {
405
- rules: [
406
- {
407
- test: /[\/\\]configs\.json$/,
408
- loader: 'config-loader',
409
- options: {
410
- resolver,
411
- relativeTo: resolvedPublicPath
412
- }
413
- },
414
- ...prepareModulesShim(resolver, themeConfig.shim)
415
- ]
416
- }
417
- }, webpackConfig));
418
- });
378
+ return webpackConfig;
379
+ }
419
380
 
420
- return webpackConfigs;
381
+ _getThemeWebpackConfig(buildName, args, env) {
382
+ let {skipCSS, skipJS, skipRTL} = env;
383
+ let themeDefinition;
384
+ let jsBuildConfig;
385
+ let buildPublicPath;
386
+ if (this._isAdminTheme(buildName)) {
387
+ themeDefinition = this._modulesConfigLoader.themes[buildName.split('.')[1]];
388
+ buildPublicPath = '/build/admin/';
389
+ const jsModulesConfig = this._themeConfigFactory.loadConfig(buildName,
390
+ ['Resources/config/oro/jsmodules.yml', 'Resources/config/jsmodules.yml']);
391
+ jsBuildConfig = this._themeConfigFactory.create(buildPublicPath, jsModulesConfig);
392
+ } else if (this._layoutModulesConfigLoader.isExtraJSBuild(buildName)) {
393
+ const [theme, suffix] = this._layoutModulesConfigLoader.splitBuildName(buildName);
394
+ skipCSS = true;
395
+ themeDefinition = this._layoutModulesConfigLoader.themes[theme];
396
+ buildPublicPath = `/build/${buildName}/`;
397
+ const baseConfig = this._layoutThemeConfigFactory.loadConfig(theme, 'config/jsmodules.yml');
398
+ const extraConfig = this._layoutThemeConfigFactory.loadConfig(theme, `config/jsmodules-${suffix}.yml`);
399
+ const jsModulesConfig = this._layoutThemeConfigFactory.extendConfig(baseConfig, extraConfig);
400
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
401
+ } else {
402
+ themeDefinition = this._layoutModulesConfigLoader.themes[buildName];
403
+ buildPublicPath = `/build/${buildName}/`;
404
+ const jsModulesConfig = this._layoutThemeConfigFactory.loadConfig(buildName, 'config/jsmodules.yml');
405
+ jsBuildConfig = this._layoutThemeConfigFactory.create(buildPublicPath, jsModulesConfig);
406
+ }
407
+ const {rtl_support: rtlSupport = false} = themeDefinition;
408
+ const resolvedBuildPath = path.join(this.resolvedPublicPath, buildPublicPath);
409
+
410
+ const resolverConfig = {
411
+ modules: [
412
+ resolvedBuildPath,
413
+ this.resolvedPublicPath,
414
+ path.join(this.resolvedPublicPath, '/bundles'),
415
+ path.join(this.resolvedPublicPath, '/js'),
416
+ this.resolvedNodeModulesPath
417
+ ],
418
+ alias: jsBuildConfig.aliases,
419
+ cacheWithContext: true,
420
+ symlinks: false
421
421
  };
422
+ const resolver = (resolver => {
423
+ return moduleName => resolver({}, '', moduleName, {});
424
+ })(resolve.create.sync({...resolverConfig}));
425
+
426
+ const plugins = [];
427
+ if (rtlSupport && !skipCSS && !skipRTL) {
428
+ plugins.push(new RtlCssWebpackPlugin({
429
+ filename: '[name].rtl.css'
430
+ }));
431
+ }
432
+
433
+ const cssEntryPoints = !skipCSS ? this._getCssEntryPoints(buildName, buildPublicPath) : {};
434
+ const jsEntryPoints = !skipJS ? jsBuildConfig.entry : {};
435
+
436
+ const entryPoints = {...cssEntryPoints, ...jsEntryPoints};
437
+ if (Object.keys(entryPoints).length === 0) {
438
+ return;
439
+ }
440
+
441
+ return {
442
+ entry: entryPoints,
443
+ name: buildName + ' theme',
444
+ output: {
445
+ publicPath: buildPublicPath,
446
+ path: resolvedBuildPath
447
+ },
448
+ context: this.resolvedPublicPath,
449
+ resolve: {
450
+ ...resolverConfig,
451
+ plugins: [
452
+ new MapModulesPlugin(prepareModulesMap(resolver, jsBuildConfig.map))
453
+ ]
454
+ },
455
+ plugins,
456
+ module: {
457
+ rules: [
458
+ {
459
+ test: /[\/\\]configs\.json$/,
460
+ loader: 'config-loader',
461
+ options: {
462
+ resolver,
463
+ relativeTo: this.resolvedPublicPath
464
+ }
465
+ },
466
+ ...prepareModulesShim(resolver, jsBuildConfig.shim)
467
+ ]
468
+ }
469
+ }
422
470
  }
423
471
 
424
472
  _initialize(args, env) {
@@ -447,7 +495,6 @@ class ConfigBuilder {
447
495
  '/Resources/views/layouts/',
448
496
  'theme.yml'
449
497
  );
450
- this._layoutThemes = this._layoutModulesConfigLoader.themeNames;
451
498
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
452
499
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
453
500
  this._layoutModulesConfigLoader,
@@ -472,25 +519,25 @@ class ConfigBuilder {
472
519
  return this._layoutStyleLoader.getThemeEntryPoints(theme, buildPath);
473
520
  }
474
521
 
475
- _validateThemeName(theme) {
476
- let existingThemes = this._adminThemes;
522
+ _validateBuildName(buildName) {
523
+ const buildNames = [...this._adminThemes];
477
524
  if (this._enableLayoutThemes) {
478
- existingThemes = existingThemes.concat(this._layoutThemes);
525
+ buildNames.push(...this._layoutModulesConfigLoader.buildNames);
479
526
  }
480
- if (theme === 'admin') {
527
+ if (buildName === 'admin') {
481
528
  throw new Error(
482
529
  'The "admin" is a reserved word and cannot be used as a theme name.'
483
530
  );
484
531
  }
485
- if (theme !== undefined && !existingThemes.includes(theme)) {
532
+ if (buildName !== undefined && !buildNames.includes(buildName)) {
486
533
  throw new Error(
487
- 'Theme "' + theme + '" doesn\'t exists. Existing themes:' + existingThemes.join(', ')
534
+ 'Theme "' + buildName + '" doesn\'t exists. Existing themes:' + buildNames.join(', ')
488
535
  );
489
536
  }
490
537
  }
491
538
 
492
539
  _isAdminTheme(theme) {
493
- return this._adminThemes.indexOf(theme) !== -1;
540
+ return this._adminThemes.includes(theme);
494
541
  }
495
542
  }
496
543
 
package/package.json CHANGED
@@ -1,48 +1,48 @@
1
1
  {
2
2
  "name": "@oroinc/oro-webpack-config-builder",
3
- "version": "5.1.0-alpha25",
3
+ "version": "5.1.0-alpha29",
4
4
  "author": "Oro, Inc (https://www.oroinc.com)",
5
5
  "license": "MIT",
6
6
  "description": "An integration of OroPlatform based applications with the Webpack.",
7
7
  "main": "oro-webpack-config.js",
8
8
  "dependencies": {
9
- "@babel/core": "^7.14.3",
10
- "@babel/plugin-transform-runtime": "^7.14.3",
11
- "@babel/preset-env": "^7.14.2",
12
- "autoprefixer": "^10.2.6",
13
- "babel-loader": "^8.1.0",
9
+ "@babel/core": "^7.16.0",
10
+ "@babel/plugin-transform-runtime": "^7.16.0",
11
+ "@babel/preset-env": "^7.16.0",
12
+ "autoprefixer": "^10.4.0",
13
+ "babel-loader": "^8.2.3",
14
14
  "bindings": "^1.5.0",
15
- "css-loader": "^5.2.6",
16
- "css-minimizer-webpack-plugin": "^3.0.0",
15
+ "css-loader": "^6.5.1",
16
+ "css-minimizer-webpack-plugin": "^3.1.3",
17
17
  "deepmerge": "^4.2.2",
18
- "exports-loader": "^3.0.0",
19
- "expose-loader": "^3.0.0",
18
+ "exports-loader": "^3.1.0",
19
+ "expose-loader": "^3.1.0",
20
20
  "extract-loader": "^5.1.0",
21
21
  "file-loader": "^6.2.0",
22
22
  "happypack": "^5.0.1",
23
- "html-webpack-plugin": "^5.2.0",
24
- "imports-loader": "^3.0.0",
23
+ "html-webpack-plugin": "^5.5.0",
24
+ "imports-loader": "^3.1.1",
25
25
  "js-yaml": "^4.1.0",
26
- "mini-css-extract-plugin": "^1.5.1",
26
+ "mini-css-extract-plugin": "^2.4.4",
27
27
  "minimist": "^1.2.3",
28
- "nan": "^2.14.2",
28
+ "nan": "^2.15.0",
29
29
  "path": "0.12.7",
30
- "postcss": "^8.3.0",
31
- "postcss-loader": "^5.3.0",
30
+ "postcss": "^8.3.11",
31
+ "postcss-loader": "^6.2.0",
32
32
  "printf": "^0.6.0",
33
33
  "resolve-url-loader": "^4.0.0",
34
34
  "rtlcss-webpack-plugin": "^4.0.6",
35
- "sass": "^1.42.1",
36
- "sass-loader": "^11.1.1",
37
- "style-loader": "^2.0.0",
38
- "terser": "^5.7.0",
35
+ "sass": "^1.43.4",
36
+ "sass-loader": "^12.3.0",
37
+ "style-loader": "^3.3.1",
38
+ "terser": "^5.9.0",
39
39
  "text-loader": "0.0.1",
40
40
  "underscore": "^1.13.1",
41
41
  "url-loader": "^4.1.1",
42
- "webpack": "^5.38.1",
43
- "webpack-bundle-analyzer": "^4.4.2",
44
- "webpack-cli": "^4.7.2",
45
- "webpack-dev-server": "^3.11.0",
42
+ "webpack": "^5.63.0",
43
+ "webpack-bundle-analyzer": "^4.5.0",
44
+ "webpack-cli": "^4.9.1",
45
+ "webpack-dev-server": "^4.4.0",
46
46
  "webpack-merge": "^5.8.0",
47
47
  "wildcard": "^2.0.0"
48
48
  }
@@ -10,7 +10,7 @@ class AdminStyleLoader extends StyleLoader {
10
10
  this._configLoader.loadConfig(themeName, 'Resources/public/themes/' + themeName + '/settings.yml');
11
11
  const baseThemeConfig = this._configLoader.loadConfig(themeName, 'Resources/config/oro/assets.yml');
12
12
  /** @type {Object.<string, ThemeGroupConfig>} */
13
- const themeConfig = merge({}, baseThemeConfig, extraThemeConfig);
13
+ const themeConfig = merge(baseThemeConfig, extraThemeConfig);
14
14
 
15
15
  return {
16
16
  themeConfig,
@@ -105,20 +105,33 @@ class StyleLoader {
105
105
  * @protected
106
106
  */
107
107
  _sortInputs(inputs) {
108
+ const primarySettingsInputs = [];
108
109
  const settingsInputs = [];
110
+ const primaryVariablesInputs = [];
109
111
  const variablesInputs = [];
110
112
  const restInputs = [];
111
113
 
112
114
  inputs.forEach(input => {
113
- if (input.indexOf('/settings/') > 0) {
115
+ if (input.indexOf('/settings/primary-settings') > 0) {
116
+ primarySettingsInputs.push(input);
117
+ } else if (input.indexOf('/settings/') > 0) {
114
118
  settingsInputs.push(input);
115
- } else if (input.indexOf('/variables/') > 0) {
119
+ } else if (input.indexOf('/variables/primary-variables') > 0) {
120
+ primaryVariablesInputs.push(input);
121
+ } else if (input.indexOf('/variables/') > 0) {
116
122
  variablesInputs.push(input);
117
123
  } else {
118
124
  restInputs.push(input);
119
125
  }
120
126
  });
121
- return [...settingsInputs, ...variablesInputs, ...restInputs];
127
+
128
+ return [
129
+ ...primarySettingsInputs,
130
+ ...settingsInputs,
131
+ ...primaryVariablesInputs,
132
+ ...variablesInputs,
133
+ ...restInputs
134
+ ];
122
135
  }
123
136
 
124
137
  /**
@@ -11,15 +11,52 @@ class ThemeConfigFactory {
11
11
  this._appModulesFileWriter = appModulesFileWriter;
12
12
  this._configsFileWriter = configsFileWriter;
13
13
  }
14
-
15
14
  /**
16
15
  * @param {string} theme Theme name
17
- * @param {string} buildPath Path to theme build folder
18
16
  * @param {string|string[]} configFilepath Path (or paths with fallback) to yaml config file in a bundle
19
- * @return {Object} List of Webpack entry-points
17
+ * @return {Object} Merged Configs loaded from all the bundles Yaml files matched by filePath
18
+ */
19
+ loadConfig(theme, configFilepath) {
20
+ return this._configLoader.loadConfig(theme, configFilepath);
21
+ }
22
+
23
+ extendConfig(baseConfig, extraConfig) {
24
+ const {
25
+ aliases = {},
26
+ configs,
27
+ map = {},
28
+ shim = {},
29
+ } = baseConfig;
30
+
31
+ const {
32
+ ['app-modules']: appModules,
33
+ ['dynamic-imports']: dynamicImports,
34
+ entry,
35
+ ...rest
36
+ } = extraConfig;
37
+
38
+ const beyondKeys = Object.keys(rest);
39
+ if (beyondKeys.length) {
40
+ throw new Error(`Sections ["${beyondKeys.join('", "')}"] are not allowed in extra js build definition`);
41
+ }
42
+
43
+ return {
44
+ aliases,
45
+ 'app-modules': appModules,
46
+ configs,
47
+ 'dynamic-imports': dynamicImports,
48
+ entry,
49
+ map,
50
+ shim
51
+ };
52
+ }
53
+
54
+ /**
55
+ * @param {string} buildPath Path to theme build folder
56
+ * @param {Object} jsModulesConfig configuration loaded from jsmodules files and merged together
57
+ * @return {Object} webpack configuration fragment
20
58
  */
21
- create(theme, buildPath, configFilepath) {
22
- const jsModulesConfig = this._configLoader.loadConfig(theme, configFilepath);
59
+ create(buildPath, jsModulesConfig) {
23
60
  const {
24
61
  entry,
25
62
  map = {},