@oroinc/oro-webpack-config-builder 6.1.0-lts05 → 6.1.0-lts07

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.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Converts JS object to SCSS list variable for theme fonts
3
+ * @example {
4
+ * main: {
5
+ * family: 'Jakarta',
6
+ * variants: [{
7
+ * path: '/font.woff2',
8
+ * style: 'normal',
9
+ * weight: '700'
10
+ * }]
11
+ * formats: ['woff2']
12
+ * }
13
+ * } => (
14
+ * 'main': (
15
+ * 'family': 'Jakarta',
16
+ * 'variants': (
17
+ * (
18
+ * 'path': '/font.woff2',
19
+ * 'style': 'normal',
20
+ * 'weight': '700',
21
+ * )
22
+ * )
23
+ * 'formats': ('woff2')
24
+ * )
25
+ * )
26
+ *
27
+ * @param {object} fonts
28
+ * @param {string }scssVarName
29
+ * @returns {string}
30
+ */
31
+ const fontObjToSCSS = ({fonts, scssVarName = 'global-theme-fonts'} = {}) => {
32
+ if (!Object.keys(fonts).length) {
33
+ return `$${scssVarName}: ()`;
34
+ }
35
+ const fontsVariantsVars = Object.entries(fonts).map(([name, font]) => {
36
+ return `
37
+ '${name}': (
38
+ 'family': '${font.family}',
39
+ 'variants': (
40
+ ${font.variants.map(item => `(
41
+ 'path': '${item.path}',
42
+ ${item.weight !== void 0 ? `'weight': ${item.weight},` : ''}
43
+ ${item.style !== void 0 ? `'style': ${item.style}` : ''}
44
+ )`).join(`, `)}
45
+ ),
46
+ 'formats': (${font.formats.map(format => `'${format}'`).join(', ')})
47
+ )
48
+ `;
49
+ });
50
+
51
+ return `
52
+ $${scssVarName}: (
53
+ ${fontsVariantsVars.join(`, `)}
54
+ );
55
+ `;
56
+ };
57
+
58
+ module.exports = fontObjToSCSS;
@@ -0,0 +1,5 @@
1
+ const createFontList = require('./create-font-list');
2
+
3
+ module.exports = {
4
+ createFontList
5
+ };
@@ -2,7 +2,25 @@ const path = require('path');
2
2
  const merge = require('deepmerge');
3
3
  const ModulesConfigLoader = require('./modules-config-loader');
4
4
 
5
+ const arrayMerge = (target, source) => target.concat(source.filter(item => !target.includes(item)));
6
+
5
7
  class LayoutModulesConfigLoader extends ModulesConfigLoader {
8
+ /**
9
+ * @inheritdoc
10
+ */
11
+ _collectThemes(themesLocation, themeInfoFileName) {
12
+ const themes = super._collectThemes(themesLocation, themeInfoFileName);
13
+
14
+ for (const [name] of Object.entries(themes)) {
15
+ const {parent: parentTheme} = themes[name];
16
+ if (typeof parentTheme === 'string') {
17
+ themes[name] = merge(themes[parentTheme], themes[name], {arrayMerge});
18
+ }
19
+ }
20
+
21
+ return themes;
22
+ }
23
+
6
24
  /**
7
25
  * @inheritdoc
8
26
  */
@@ -3,7 +3,6 @@ const fs = require('fs');
3
3
  const merge = require('deepmerge');
4
4
  const yaml = require('js-yaml');
5
5
  const validation = require('../validation');
6
-
7
6
  // merge only unique items
8
7
  const arrayMerge = (target, source) => target.concat(source.filter(item => !target.includes(item)));
9
8
 
@@ -52,25 +51,27 @@ class ModulesConfigLoader {
52
51
  themesLocation = [themesLocation];
53
52
  }
54
53
 
55
- const themes = {};
56
- const self = this;
54
+ let allThemes = {};
57
55
  themesLocation.forEach(themesLocation => {
58
- self._collectThemes(themes, themesLocation, themeInfoFileName);
56
+ allThemes = merge(allThemes, this._collectThemes(themesLocation, themeInfoFileName), {arrayMerge});
59
57
  });
60
58
 
61
- this._themes = themes;
59
+ this._themes = allThemes;
62
60
  this._processedFiles = [];
63
61
  }
64
62
 
65
63
  /**
66
64
  * Collects list of themes with their parents into given storage(themes param)
67
65
  *
68
- * @param {Object} themes
69
66
  * @param {string} themesLocation
70
67
  * @param {string} themeInfoFileName
71
68
  * @private
69
+ * @returns {Object.<string|null>}
72
70
  */
73
- _collectThemes(themes, themesLocation, themeInfoFileName) {
71
+ _collectThemes(themesLocation, themeInfoFileName) {
72
+ const themes = {};
73
+ this._processedFiles = [];
74
+
74
75
  this._bundles.forEach(bundle => {
75
76
  const source = bundle + themesLocation;
76
77
 
@@ -82,12 +83,21 @@ class ModulesConfigLoader {
82
83
  return;
83
84
  }
84
85
  const themeFile = path.resolve(themePath, themeInfoFileName);
85
- if (!fs.existsSync(themeFile)) return;
86
+ if (!fs.existsSync(themeFile)) {
87
+ return;
88
+ }
89
+
90
+ this._processedFiles.push(themeFile);
91
+
92
+ const doc = yaml.load(fs.readFileSync(themeFile, 'utf8'));
86
93
 
87
- const theme = yaml.load(fs.readFileSync(themeFile, 'utf8'));
88
- themes[name] = merge(themes[name] || {}, theme, {arrayMerge});
94
+ validation.checkSchema(themeFile, doc, name);
95
+
96
+ themes[name] = merge(themes[name] || {}, doc, {arrayMerge});
89
97
  });
90
98
  });
99
+
100
+ return themes;
91
101
  }
92
102
 
93
103
  /**
@@ -28,6 +28,7 @@ const EventEmitter = require('events');
28
28
  const ErrorHandler = require('./error-handler');
29
29
  const SVGSprite = require('./svg-sprite');
30
30
  const TerserPlugin = require('terser-webpack-plugin');
31
+ const jsToSCSS = require('./js-to-scss');
31
32
  require('resolve-url-loader');
32
33
  require('lezer-loader');
33
34
 
@@ -274,6 +275,7 @@ class ConfigBuilder {
274
275
 
275
276
  const webpackConfig = {
276
277
  watchOptions: {
278
+ followSymlinks: true,
277
279
  aggregateTimeout: 200,
278
280
  ignored: /[\/\\]node_modules[\/\\].*\.js$/
279
281
  },
@@ -319,44 +321,6 @@ class ConfigBuilder {
319
321
  /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
320
322
  ],
321
323
  rules: [
322
- {
323
- test: /\.s?css$/,
324
- use: [{
325
- loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
326
- }, {
327
- loader: 'css-loader',
328
- options: {
329
- importLoaders: 1,
330
- sourceMap: true,
331
- // can't use esModule since resolve-url-loader needs file path to start with '~'
332
- esModule: false
333
- }
334
- }, {
335
- loader: 'resolve-url-loader'
336
- }, {
337
- loader: 'postcss-loader',
338
- options: {
339
- sourceMap: true,
340
- postcssOptions: {
341
- plugins: [
342
- require('autoprefixer')
343
- ]
344
- }
345
- }
346
- }, {
347
- loader: 'sass-loader',
348
- options: {
349
- implementation: require('sass'),
350
- sassOptions: {
351
- includePaths: [
352
- path.join(this.resolvedPublicPath, '/bundles')
353
- ],
354
- outputStyle: 'expanded'
355
- },
356
- sourceMap: true
357
- }
358
- }]
359
- },
360
324
  {
361
325
  test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
362
326
  loader: 'url-loader',
@@ -502,7 +466,10 @@ class ConfigBuilder {
502
466
  );
503
467
  }
504
468
  }
505
- let {rtl_support: rtlSupport = false, resolve_extra_paths: resolveExtraPaths = []} = themeDefinition;
469
+ let {
470
+ rtl_support: rtlSupport = false,
471
+ resolve_extra_paths: resolveExtraPaths = []
472
+ } = themeDefinition;
506
473
  const resolvedBuildPath = path.join(this.resolvedPublicPath, buildPublicPath);
507
474
 
508
475
  if (resolveExtraPaths.length) {
@@ -559,6 +526,45 @@ class ConfigBuilder {
559
526
  plugins,
560
527
  module: {
561
528
  rules: [
529
+ {
530
+ test: /\.s?css$/,
531
+ use: [{
532
+ loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
533
+ }, {
534
+ loader: 'css-loader',
535
+ options: {
536
+ importLoaders: 1,
537
+ sourceMap: true,
538
+ // can't use esModule since resolve-url-loader needs file path to start with '~'
539
+ esModule: false
540
+ }
541
+ }, {
542
+ loader: 'resolve-url-loader'
543
+ }, {
544
+ loader: 'postcss-loader',
545
+ options: {
546
+ sourceMap: true,
547
+ postcssOptions: {
548
+ plugins: [
549
+ require('autoprefixer')
550
+ ]
551
+ }
552
+ }
553
+ }, {
554
+ loader: 'sass-loader',
555
+ options: {
556
+ implementation: require('sass'),
557
+ sassOptions: {
558
+ includePaths: [
559
+ path.join(this.resolvedPublicPath, '/bundles')
560
+ ],
561
+ outputStyle: 'expanded'
562
+ },
563
+ sourceMap: true,
564
+ additionalData: this.prepareAdditionalSCSS.bind(this, themeDefinition)
565
+ }
566
+ }]
567
+ },
562
568
  {
563
569
  test: /[\/\\]configs\.json$/,
564
570
  loader: 'config-loader',
@@ -573,6 +579,18 @@ class ConfigBuilder {
573
579
  };
574
580
  }
575
581
 
582
+ prepareAdditionalSCSS(theme, content, loaderContext) {
583
+ if (typeof theme.fonts === 'object') {
584
+ const fonts = {};
585
+ for (const [name, {family, formats, variants}] of Object.entries(theme.fonts)) {
586
+ fonts[name] = {family, formats, variants};
587
+ }
588
+ return jsToSCSS.createFontList({fonts}) + content;
589
+ }
590
+
591
+ return content;
592
+ }
593
+
576
594
  _initialize(args, env) {
577
595
  const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
578
596
 
@@ -604,6 +622,14 @@ class ConfigBuilder {
604
622
  this.resolvedProjectPath,
605
623
  this._publicPath
606
624
  );
625
+
626
+ for (const [theme, config] of Object.entries(this._layoutModulesConfigLoader.themes)) {
627
+ validation.themeValidator.checkFullSchema(
628
+ config,
629
+ this._layoutModulesConfigLoader.processedFiles,
630
+ theme
631
+ );
632
+ }
607
633
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
608
634
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
609
635
  this._layoutModulesConfigLoader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oroinc/oro-webpack-config-builder",
3
- "version": "6.1.0-lts05",
3
+ "version": "6.1.0-lts07",
4
4
  "author": "Oro, Inc. (https://oroinc.com)",
5
5
  "license": "MIT",
6
6
  "description": "An integration of OroPlatform based applications with the Webpack.",
@@ -16,10 +16,9 @@
16
16
  "css-loader": "^6.8.1",
17
17
  "css-minimizer-webpack-plugin": "~5.0.0",
18
18
  "deepmerge": "~4.3.1",
19
- "esbuild": "^0.25.2",
20
- "esbuild-loader": "^4.3.0",
21
19
  "exports-loader": "~4.0.0",
22
20
  "expose-loader": "~4.1.0",
21
+ "esbuild-loader": "^4.3.0",
23
22
  "file-loader": "~6.2.0",
24
23
  "html-webpack-plugin": "~5.5.0",
25
24
  "imports-loader": "~4.0.1",
@@ -0,0 +1,36 @@
1
+ const BaseError = require('./base-error');
2
+
3
+ class SvgIconsSchemaError extends BaseError {
4
+ /**
5
+ * @example
6
+ * Invalid SVG config: the "svg-icons.yml" files in the "default" theme do not match the API schema.
7
+ * has an unknown property 'rest'.
8
+ * at default/config/svg-icons.yml
9
+ *
10
+ * @example
11
+ * Invalid SVG config: the "svg-icons.yml" files in the "default" theme do not match the API schema.
12
+ * misses the property 'exclude'.
13
+ * List of processed files for the "default" theme:
14
+ * /default/config/svg-icons.yml
15
+ * /default/config/svg-icons.yml
16
+ *
17
+ * @param {string} reason
18
+ * @param {Array} files
19
+ * @param {string} theme
20
+ */
21
+ constructor(reason, files, theme) {
22
+ let msg = `the "theme.yml" files in the "${theme}" theme do not match the API schema.\n${reason}`;
23
+
24
+ const filesListMsg = BaseError.generateFilesListMsg(files, theme);
25
+
26
+ if (filesListMsg.length) {
27
+ msg = `${msg}\n${filesListMsg}`;
28
+ }
29
+
30
+ super(msg);
31
+
32
+ this.name = 'Invalid Theme config';
33
+ }
34
+ }
35
+
36
+ module.exports = SvgIconsSchemaError;
@@ -1,15 +1,18 @@
1
1
  const assetsValidation = require('./assets-validator');
2
2
  const jsmodulesValidator = require('./jsmodules-validator');
3
3
  const svgIconsValidator = require('./svg-icons-validator');
4
+ const themeValidator = require('./theme-validator');
4
5
 
5
6
  const isJSModulesPath = path => /jsmodules([-a-zA-Z\d]*)\.yml$/.test(path);
6
7
  const isAssetsPath = path => /assets\.yml$/.test(path);
7
8
  const isSvgIconsPath = path => /svg-icons\.yml$/.test(path);
9
+ const isThemePath = path => /theme\.yml$/.test(path);
8
10
 
9
11
  module.exports = {
10
12
  assetsValidation,
11
13
  jsmodulesValidator,
12
14
  svgIconsValidator,
15
+ themeValidator,
13
16
 
14
17
  /**
15
18
  * set public path for all validators
@@ -36,6 +39,8 @@ module.exports = {
36
39
  }
37
40
  } else if (isSvgIconsPath(filePath)) {
38
41
  svgIconsValidator.checkSchema(filePath, doc, theme);
42
+ } else if (isThemePath(filePath)) {
43
+ themeValidator.checkSchema(filePath, doc, theme);
39
44
  }
40
45
  }
41
46
  };
@@ -10,7 +10,7 @@ module.exports = {
10
10
  type: 'object',
11
11
  properties: {
12
12
  exclude: {
13
- description: 'The "exclude" property is an array of svg files to exclude from svg sprite.',
13
+ description: 'The "exclude" property is an array of svg files to exclude from svg sprite.',
14
14
  type: 'array',
15
15
  minItems: 0
16
16
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Schema to validate theme.yml files
3
+ *
4
+ * @description
5
+ * Full scheme complements "theme-schema" one.
6
+ * It should have only rules which are not defined in "theme-schema" schema due to avoid duplicates in error messages
7
+ */
8
+ module.exports = {
9
+ type: 'object',
10
+ required: ['label']
11
+ };
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Schema to validate theme.yml files
3
+ */
4
+ module.exports = {
5
+ type: 'object',
6
+ properties: {
7
+ label: {
8
+ type: 'string'
9
+ },
10
+ description: {
11
+ type: 'string'
12
+ },
13
+ groups: {
14
+ type: 'array',
15
+ items: {
16
+ type: 'string'
17
+ }
18
+ },
19
+ icon: {
20
+ type: 'string'
21
+ },
22
+ logo: {
23
+ type: 'string'
24
+ },
25
+ logo_small: {
26
+ type: 'string'
27
+ },
28
+ rtl_support: {
29
+ type: 'boolean'
30
+ },
31
+ svg_icons_support: {
32
+ type: 'boolean'
33
+ },
34
+ fonts: {
35
+ type: 'object',
36
+ patternProperties: {
37
+ '.*': {
38
+ type: 'object',
39
+ required: ['family', 'variants', 'formats'],
40
+ additionalProperties: false,
41
+ properties: {
42
+ family: {
43
+ type: 'string'
44
+ },
45
+ variants: {
46
+ type: 'array',
47
+ items: {
48
+ type: 'object',
49
+ required: ['path'],
50
+ additionalProperties: false,
51
+ properties: {
52
+ path: {
53
+ type: 'string'
54
+ },
55
+ weight: {
56
+ anyOf: [{
57
+ type: 'string'
58
+ }, {
59
+ type: 'integer'
60
+ }]
61
+ },
62
+ style: {
63
+ type: 'string'
64
+ }
65
+ }
66
+ }
67
+ },
68
+ formats: {
69
+ type: 'array',
70
+ items: {
71
+ type: 'string'
72
+ }
73
+ },
74
+ preload: {
75
+ type: 'boolean'
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ };
83
+
@@ -4,7 +4,7 @@ const schemaValidator = require('./schema-validator');
4
4
  const {isProdMode} = require('../utils');
5
5
  const EventEmitter = require('events');
6
6
  const emitter = new EventEmitter();
7
- const SvgIsonsSchemaError = require('./errors/svg-icons-schema-error');
7
+ const SvgIconsSchemaError = require('./errors/svg-icons-schema-error');
8
8
 
9
9
  module.exports = Object.assign({}, schemaValidator, {
10
10
  emitter,
@@ -22,7 +22,7 @@ module.exports = Object.assign({}, schemaValidator, {
22
22
  const result = this.validateSchema(schema, doc);
23
23
 
24
24
  if (!result.valid) {
25
- const error = new SvgIsonsSchemaError(result.formattedError, [filePath], theme);
25
+ const error = new SvgIconsSchemaError(result.formattedError, [filePath], theme);
26
26
 
27
27
  this.emitter.emit('error', error);
28
28
  }
@@ -43,7 +43,7 @@ module.exports = Object.assign({}, schemaValidator, {
43
43
  const result = this.validateSchema(fullSchema, doc);
44
44
 
45
45
  if (!result.valid) {
46
- const error = new SvgIsonsSchemaError(result.formattedError, files, theme);
46
+ const error = new SvgIconsSchemaError(result.formattedError, files, theme);
47
47
 
48
48
  this.emitter.emit('error', error);
49
49
  }
@@ -0,0 +1,53 @@
1
+ const schema = require('./schemas/theme-schema');
2
+ const fullSchema = require('./schemas/theme-schema-full');
3
+ const schemaValidator = require('./schema-validator');
4
+ const {isProdMode} = require('../utils');
5
+ const EventEmitter = require('events');
6
+ const emitter = new EventEmitter();
7
+ const ThemeSchemaError = require('./errors/theme-scheme-error');
8
+
9
+ module.exports = Object.assign({}, schemaValidator, {
10
+ emitter,
11
+
12
+ /**
13
+ * @param {string} filePath
14
+ * @param {Object} doc
15
+ * @param {string} theme
16
+ * @returns {boolean|undefined}
17
+ */
18
+ checkSchema(filePath, doc, theme) {
19
+ if (isProdMode()) {
20
+ return;
21
+ }
22
+ const result = this.validateSchema(schema, doc);
23
+
24
+ if (!result.valid) {
25
+ const error = new ThemeSchemaError(result.formattedError, [filePath], theme);
26
+
27
+ this.emitter.emit('error', error);
28
+ }
29
+
30
+ return result.valid;
31
+ },
32
+
33
+ /**
34
+ * @param {Object} doc
35
+ * @param {Array} files
36
+ * @param {string} theme
37
+ * @returns {boolean|undefined}
38
+ */
39
+ checkFullSchema(doc, files = [], theme) {
40
+ if (isProdMode()) {
41
+ return;
42
+ }
43
+ const result = this.validateSchema(fullSchema, doc);
44
+
45
+ if (!result.valid) {
46
+ const error = new ThemeSchemaError(result.formattedError, files, theme);
47
+
48
+ this.emitter.emit('error', error);
49
+ }
50
+
51
+ return result.valid;
52
+ }
53
+ });