diff --git a/index.js b/index.js index 260d9ed..389eee5 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,79 @@ const loaderUtils = require('loader-utils'); const { optimize } = require('svgo'); +const { validate } = require('schema-utils'); const svgSpriteState = require('./utils/spriteState'); const transformSvg = require('./utils/transformSvg'); +const optionsSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'SvgSpriteGeneratorPlugin Options', + type: 'object', + properties: { + symbolId: { + description: + 'Sprite item (single icon) `` id attribute value. Can be a string or a function that takes the file path of the original icon and returns a string.', + oneOf: [ + { + type: 'string', + description: + "String value for symbol id, e.g. '[name]' which defaults to the icon filename without extension.", + }, + { + instanceof: 'Function', + description: + 'Function that takes the file path of the original icon and returns a string.', + }, + ], + }, + spriteFilePath: { + type: 'string', + description: + 'Path to sprite file. This path is relative to `webpack.output.path` and can include [interpolateName](https://github.com/webpack/loader-utils#interpolatename) patterns.', + }, + svgoOptimize: { + description: 'Enable or disable SVG optimization', + oneOf: [ + { + type: 'boolean', + description: + 'Enable or disable SVG optimization with default settings.', + }, + { + type: 'object', + description: 'Customize SVG optimization using svgo options.', + properties: { + plugins: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Name of the svgo plugin', + }, + params: { + type: 'object', + description: 'Parameters for the svgo plugin', + }, + }, + required: ['name'], + }, + }, + }, + }, + ], + }, + addContent: { + type: 'boolean', + description: + 'Add SVG content as property to transformed SVG object, which may increase bundle size.', + }, + }, + required: [], + additionalProperties: false, +}; + function svgSpriteGenerationLoader(source) { const options = { symbolId: '[name]', @@ -13,6 +83,13 @@ function svgSpriteGenerationLoader(source) { ...this.getOptions(), }; + validate(optionsSchema, options, { + name: 'svgSpriteGenerationLoader', + baseDataPath: 'options', + }); + + this.cacheable(false); + const isSvgoOptimizeEnabled = !!options.svgoOptimize; const svgoOptimizeConfig = options.svgoOptimize === true diff --git a/package.json b/package.json index ef4f0ff..f70ca50 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svg-sprite-generation-loader", - "version": "2.2.0", + "version": "2.2.1", "description": "Webpack loader for generating external svg symbol sprite files", "main": "index.js", "scripts": { @@ -36,6 +36,7 @@ "homepage": "https://github.com/vadymshymko/svg-sprite-generation-loader#readme", "dependencies": { "loader-utils": "^3.2.1", + "schema-utils": "^4.2.0", "svgo": "^3.0.2" }, "devDependencies": { diff --git a/plugin.js b/plugin.js index d45537b..b4fce43 100644 --- a/plugin.js +++ b/plugin.js @@ -21,29 +21,46 @@ class SvgSpriteGeneratorPlugin { SvgSpriteGeneratorPlugin.name, (compilation) => { compilation.hooks.processAssets.tap( - SvgSpriteGeneratorPlugin.name, - () => { + { + name: SvgSpriteGeneratorPlugin.name, + stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + }, + (assets, callback) => { if (compilation.options.loader.target === 'web') { - Object.keys(svgSpriteState.sprites).forEach((spriteFilePath) => { - const spriteContent = svgSpriteState.getSpriteContent( - spriteFilePath, - this.params - ); + try { + Object.keys(svgSpriteState.sprites).forEach( + (spriteFilePath) => { + const spriteContent = svgSpriteState.getSpriteContent( + spriteFilePath, + this.params + ); - const interpolatedPath = loaderUtils.interpolateName( - { resourcePath: spriteFilePath }, - spriteFilePath, - { - context: compilation.options.context, - content: spriteContent, - } - ); + const interpolatedPath = loaderUtils.interpolateName( + { resourcePath: spriteFilePath }, + spriteFilePath, + { + context: compilation.options.context, + content: spriteContent, + } + ); - compilation.emitAsset( - interpolatedPath, - new RawSource(spriteContent) + if (compilation.getAsset(interpolatedPath)) { + compilation.updateAsset( + interpolatedPath, + new RawSource(spriteContent) + ); + } else { + compilation.emitAsset( + interpolatedPath, + new RawSource(spriteContent) + ); + } + } ); - }); + } catch (error) { + console.error(error); + callback(error); + } } } ); diff --git a/yarn.lock b/yarn.lock index 55b7654..052aee4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,6 +84,11 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -104,6 +109,20 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -114,6 +133,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-escapes@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.0.tgz#8a13ce75286f417f1963487d86ba9f90dccf9947" @@ -782,6 +811,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -1245,6 +1279,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -1599,6 +1638,11 @@ regexp.prototype.flags@^1.5.0: define-properties "^1.2.0" functions-have-names "^1.2.3" +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -1664,6 +1708,16 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +schema-utils@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"