@oroinc/oro-webpack-config-builder 6.0.0-lts05 → 6.0.0-lts06

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
+ };
@@ -71,6 +71,8 @@ class ModulesConfigLoader {
71
71
  * @private
72
72
  */
73
73
  _collectThemes(themes, themesLocation, themeInfoFileName) {
74
+ this._processedFiles = [];
75
+
74
76
  this._bundles.forEach(bundle => {
75
77
  const source = bundle + themesLocation;
76
78
 
@@ -81,11 +83,20 @@ class ModulesConfigLoader {
81
83
  if (!fs.lstatSync(themePath).isDirectory()) {
82
84
  return;
83
85
  }
86
+
84
87
  const themeFile = path.resolve(themePath, themeInfoFileName);
85
- if (!fs.existsSync(themeFile)) return;
86
88
 
87
- const theme = yaml.load(fs.readFileSync(themeFile, 'utf8'));
88
- themes[name] = merge(themes[name] || {}, theme, {arrayMerge});
89
+ if (!fs.existsSync(themeFile)) {
90
+ return;
91
+ }
92
+
93
+ this._processedFiles.push(themeFile);
94
+
95
+ const doc = yaml.load(fs.readFileSync(themeFile, 'utf8'));
96
+
97
+ validation.checkSchema(themeFile, doc, name);
98
+
99
+ themes[name] = merge(themes[name] || {}, doc, {arrayMerge});
89
100
  });
90
101
  });
91
102
  }
@@ -9,8 +9,6 @@ const EntryPointFileWriter = require('./writer/scss-entry-point-file-writer');
9
9
  const LayoutModulesConfigLoader = require('./modules-config/layout-modules-config-loader');
10
10
  const LayoutStyleLoader = require('./style/layout-style-loader');
11
11
  const MapModulesPlugin = require('./plugin/map/map-modules-plugin');
12
- const IntegrityFilePlugin = require('./plugin/integrity/integrity-file-plugin');
13
- const {SubresourceIntegrityPlugin} = require('webpack-subresource-integrity');
14
12
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
15
13
  const ModulesConfigLoader = require('./modules-config/modules-config-loader');
16
14
  const DynamicImportsFileWriter = require('./writer/dynamic-imports-file-writer');
@@ -30,6 +28,7 @@ const EventEmitter = require('events');
30
28
  const ErrorHandler = require('./error-handler');
31
29
  const SVGSprite = require('./svg-sprite');
32
30
  const TerserPlugin = require('terser-webpack-plugin');
31
+ const jsToSCSS = require('./js-to-scss');
33
32
  require('resolve-url-loader');
34
33
  require('lezer-loader');
35
34
 
@@ -276,6 +275,7 @@ class ConfigBuilder {
276
275
 
277
276
  const webpackConfig = {
278
277
  watchOptions: {
278
+ followSymlinks: true,
279
279
  aggregateTimeout: 200,
280
280
  ignored: /[\/\\]node_modules[\/\\].*\.js$/
281
281
  },
@@ -283,8 +283,7 @@ class ConfigBuilder {
283
283
  output: {
284
284
  filename: '[name].js',
285
285
  // Because we use third party libraries 'chunkFilename' should include only [name]
286
- chunkFilename: this._getVersionedPath('chunk/[name].js', this.assetVersion),
287
- crossOriginLoading: "anonymous"
286
+ chunkFilename: this._getVersionedPath('chunk/[name].js', this.assetVersion)
288
287
  },
289
288
  devtool: !env.skipSourcemap && 'inline-cheap-module-source-map',
290
289
  mode: 'development',
@@ -322,44 +321,6 @@ class ConfigBuilder {
322
321
  /[\/\\]bundles[\/\\]\.*[\/\\]lib[\/\\](?!chaplin|bootstrap|jquery\.dialog).*\.js$/
323
322
  ],
324
323
  rules: [
325
- {
326
- test: /\.s?css$/,
327
- use: [{
328
- loader: args.hot ? 'style-loader' : MiniCssExtractPlugin.loader
329
- }, {
330
- loader: 'css-loader',
331
- options: {
332
- importLoaders: 1,
333
- sourceMap: true,
334
- // can't use esModule since resolve-url-loader needs file path to start with '~'
335
- esModule: false
336
- }
337
- }, {
338
- loader: 'resolve-url-loader'
339
- }, {
340
- loader: 'postcss-loader',
341
- options: {
342
- sourceMap: true,
343
- postcssOptions: {
344
- plugins: [
345
- require('autoprefixer')
346
- ]
347
- }
348
- }
349
- }, {
350
- loader: 'sass-loader',
351
- options: {
352
- implementation: require('sass'),
353
- sassOptions: {
354
- includePaths: [
355
- path.join(this.resolvedPublicPath, '/bundles')
356
- ],
357
- outputStyle: 'expanded'
358
- },
359
- sourceMap: true
360
- }
361
- }]
362
- },
363
324
  {
364
325
  test: /\.(eot|ttf|woff|woff2|cur|ico|svg|png|jpg|gif)$/,
365
326
  loader: 'url-loader',
@@ -391,10 +352,6 @@ class ConfigBuilder {
391
352
  new webpack.optimize.MinChunkSizePlugin({
392
353
  minChunkSize: 30000 // Minimum number of characters
393
354
  }),
394
- new SubresourceIntegrityPlugin(),
395
- new IntegrityFilePlugin({
396
- publicPath: this.resolvedPublicPath
397
- }),
398
355
  new AfterWebpackLogsPlugin(
399
356
  stats => this.emitter.emit('build:complete', stats)
400
357
  )
@@ -509,7 +466,10 @@ class ConfigBuilder {
509
466
  );
510
467
  }
511
468
  }
512
- let {rtl_support: rtlSupport = false, resolve_extra_paths: resolveExtraPaths = []} = themeDefinition;
469
+ let {
470
+ rtl_support: rtlSupport = false,
471
+ resolve_extra_paths: resolveExtraPaths = []
472
+ } = themeDefinition;
513
473
  const resolvedBuildPath = path.join(this.resolvedPublicPath, buildPublicPath);
514
474
 
515
475
  if (resolveExtraPaths.length) {
@@ -566,6 +526,45 @@ class ConfigBuilder {
566
526
  plugins,
567
527
  module: {
568
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
+ },
569
568
  {
570
569
  test: /[\/\\]configs\.json$/,
571
570
  loader: 'config-loader',
@@ -580,6 +579,18 @@ class ConfigBuilder {
580
579
  };
581
580
  }
582
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
+
583
594
  _initialize(args, env) {
584
595
  const entryPointFileWriter = new EntryPointFileWriter(this._publicPath);
585
596
 
@@ -611,6 +622,14 @@ class ConfigBuilder {
611
622
  this.resolvedProjectPath,
612
623
  this._publicPath
613
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
+ }
614
633
  this._layoutStyleLoader = new LayoutStyleLoader(this._layoutModulesConfigLoader, entryPointFileWriter);
615
634
  this._layoutThemeConfigFactory = new ThemeConfigFactory(
616
635
  this._layoutModulesConfigLoader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oroinc/oro-webpack-config-builder",
3
- "version": "6.0.0-lts05",
3
+ "version": "6.0.0-lts06",
4
4
  "author": "Oro, Inc. (https://oroinc.com)",
5
5
  "license": "MIT",
6
6
  "description": "An integration of OroPlatform based applications with the Webpack.",
@@ -49,7 +49,6 @@
49
49
  "webpack-cli": "~5.0.0",
50
50
  "webpack-dev-server": "^4.11.1",
51
51
  "webpack-merge": "~5.8.0",
52
- "webpack-subresource-integrity": "^5.2.0-rc.1",
53
52
  "wildcard": "~2.0.0"
54
53
  }
55
54
  }
@@ -0,0 +1,36 @@
1
+ const BaseError = require('./base-error');
2
+
3
+ class SvgIconsSchemaError extends BaseError {
4
+ /**
5
+ * @example
6
+ * Invalid Theme config: the "theme.yml" files in the "default" theme do not match the API schema.
7
+ * has an unknown property 'rest'.
8
+ * at default/theme.yml
9
+ *
10
+ * @example
11
+ * Invalid Theme config: the "theme.yml" files in the "default" theme do not match the API schema.
12
+ * misses the property 'label'.
13
+ * List of processed files for the "default" theme:
14
+ * /default/theme.yml
15
+ * /default/theme.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
+ });
@@ -1,42 +0,0 @@
1
- const { createHash } = require('crypto');
2
- const { Compilation, sources } = require('webpack');
3
- const fs = require('fs').promises;
4
- const path = require('path');
5
-
6
- class IntegrityFilePlugin {
7
- constructor({fileName = 'integrity.json', publicPath = '', algorithm = 'sha384'} = {}) {
8
- this.fileName = fileName;
9
- this.publicPath = publicPath;
10
- this.algorithm = algorithm;
11
- }
12
-
13
- apply(compiler) {
14
- compiler.hooks.thisCompilation.tap('IntegrityFilePlugin', (compilation) => {
15
- compilation.hooks.processAssets.tapPromise(
16
- { name: 'SubresourceIntegrityPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER },
17
- async () => {
18
- const integrityData = Object.fromEntries(
19
- Object.entries(compilation.assets)
20
- .filter(([assetName]) => assetName.endsWith('.css') || assetName.endsWith('.js'))
21
- .map(([assetName, asset]) => {
22
- const hash = createHash(this.algorithm).update(asset.source()).digest('base64');
23
- const assetNormalized = assetName.split('?')[0];
24
-
25
- return [
26
- path.join(compiler.options.output.publicPath || '', assetNormalized),
27
- `${this.algorithm}-${hash}`
28
- ];
29
- })
30
- );
31
- const jsonData = JSON.stringify(integrityData, null, 2);
32
- const outputPath = path.join(this.publicPath, compiler.options.output.publicPath, this.fileName);
33
-
34
- await fs.writeFile(outputPath, jsonData, 'utf8');
35
- compilation.emitAsset(outputPath, new sources.RawSource(jsonData));
36
- }
37
- );
38
- });
39
- }
40
- }
41
-
42
- module.exports = IntegrityFilePlugin;