├── .nvmrc ├── .eslintignore ├── test ├── fixtures │ ├── entry.css │ ├── entry.js │ ├── entry-es6-import.js │ ├── entry-with-styles.js │ ├── entry-with-styles2.js │ ├── styles.css │ ├── styles2.css │ ├── styles4.css │ ├── extract-text-webpack-plugin │ │ └── with-commons-chunk-plugin │ │ │ ├── entry.js │ │ │ ├── entry2.js │ │ │ ├── entry3.js │ │ │ └── styles.css │ ├── custom-runtime-generator.js │ ├── entry.html │ ├── styles3.css │ ├── html-webpack-plugin │ │ └── template.ejs │ └── img │ │ ├── image.svg │ │ └── image2.svg ├── _setup.js ├── .eslintrc ├── _config.js ├── utils │ ├── create-compiler.js │ └── index.js ├── configurator.test.js ├── utils.test.js └── loader.test.js ├── .mocharc.json ├── plugin.js ├── runtime ├── .eslintrc ├── sprite.js ├── package.json ├── types.d.ts ├── browser-sprite.js ├── sprite.build.js └── browser-sprite.build.js ├── scripts ├── .eslintrc ├── bootstrap.js ├── utils.js ├── build-runtime.js ├── build.sh └── select-env.js ├── .npmignore ├── .gitignore ├── lib ├── utils │ ├── get-matched-rule.js │ ├── generate-sprite-placeholder.js │ ├── generate-export.js │ ├── stringify-symbol.js │ ├── stringify.js │ ├── is-webpack-1.js │ ├── interpolate.js │ ├── generate-import.js │ ├── replace-in-module-source.js │ ├── replace-sprite-placeholder.js │ ├── normalize-rule.js │ ├── get-loader-options.js │ ├── get-matched-rule-5.js │ ├── get-matched-rule-4.js │ ├── index.js │ ├── is-module-should-be-extracted.js │ ├── get-module-chunk.js │ ├── get-all-modules.js │ └── mapped-list.js ├── exceptions.js ├── configurator.js ├── runtime-generator.js ├── loader.js ├── config.js └── plugin.js ├── .travis.yml ├── .nycrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── .editorconfig ├── env ├── webpack-1 │ └── package.json ├── webpack-2 │ └── package.json ├── webpack-3 │ └── package.json ├── webpack-4 │ └── package.json └── webpack-5 │ └── package.json ├── .eslintrc ├── LICENSE ├── karma.conf.js ├── package.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── 2.0.md ├── README.md └── CHANGELOG.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.20.0 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | runtime/*.build.js 3 | -------------------------------------------------------------------------------- /test/fixtures/entry.css: -------------------------------------------------------------------------------- 1 | @import "styles.css"; 2 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "test/_setup.js" 3 | } -------------------------------------------------------------------------------- /plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/plugin'); 2 | -------------------------------------------------------------------------------- /test/fixtures/entry.js: -------------------------------------------------------------------------------- 1 | require('./img/image.svg'); 2 | -------------------------------------------------------------------------------- /test/fixtures/entry-es6-import.js: -------------------------------------------------------------------------------- 1 | import './img/image.svg'; 2 | -------------------------------------------------------------------------------- /test/fixtures/entry-with-styles.js: -------------------------------------------------------------------------------- 1 | require('./styles.css'); 2 | -------------------------------------------------------------------------------- /test/fixtures/entry-with-styles2.js: -------------------------------------------------------------------------------- 1 | require('./styles2.css'); 2 | -------------------------------------------------------------------------------- /runtime/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/styles.css: -------------------------------------------------------------------------------- 1 | .img { 2 | background-image: url('img/image.svg'); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/styles2.css: -------------------------------------------------------------------------------- 1 | .img { 2 | background-image: url('img/image2.svg'); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/styles4.css: -------------------------------------------------------------------------------- 1 | .img { 2 | background-image: url('img/image.svg?sprite'); 3 | } 4 | -------------------------------------------------------------------------------- /scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-extraneous-dependencies": "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/extract-text-webpack-plugin/with-commons-chunk-plugin/entry.js: -------------------------------------------------------------------------------- 1 | require('./styles.css'); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-text-webpack-plugin/with-commons-chunk-plugin/entry2.js: -------------------------------------------------------------------------------- 1 | require('./styles.css'); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-text-webpack-plugin/with-commons-chunk-plugin/entry3.js: -------------------------------------------------------------------------------- 1 | require('./styles.css'); 2 | -------------------------------------------------------------------------------- /test/_setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const chai = require('chai'); 3 | 4 | chai.should(); 5 | -------------------------------------------------------------------------------- /test/fixtures/extract-text-webpack-plugin/with-commons-chunk-plugin/styles.css: -------------------------------------------------------------------------------- 1 | .img { 2 | background-image: url('../../img/image.svg'); 3 | } 4 | -------------------------------------------------------------------------------- /runtime/sprite.js: -------------------------------------------------------------------------------- 1 | import Sprite from '@workato/svg-baker-runtime/src/sprite'; 2 | 3 | export default new Sprite({ attrs: { id: '__SVG_SPRITE_NODE__' } }); 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | test 17 | .idea -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-unused-expressions": "off", 7 | "import/no-extraneous-dependencies": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/custom-runtime-generator.js: -------------------------------------------------------------------------------- 1 | const { generateExport, stringify } = require('../../lib/utils'); 2 | 3 | module.exports = () => { 4 | return generateExport(stringify('olala')); 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/entry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/styles3.css: -------------------------------------------------------------------------------- 1 | .a {background-image: url('img/image.svg');} 2 | .a2 {background-image: url('img/image.svg');} 3 | .b {background-image: url('img/image2.svg');} 4 | .b2 {background-image: url('img/image2.svg');} 5 | -------------------------------------------------------------------------------- /test/_config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports.fixturesPath = path.resolve(__dirname, 'fixtures'); 4 | module.exports.loaderPath = require.resolve('..'); 5 | module.exports.rootDir = path.resolve(__dirname, '..'); 6 | -------------------------------------------------------------------------------- /runtime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workato/svg-sprite-loader-runtime", 3 | "version": "6.1.0", 4 | "main": "browser-sprite.build.js", 5 | "types": "types.d.ts", 6 | "files": [ 7 | "browser-sprite.build.js", 8 | "types.d.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/html-webpack-plugin/template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.iml 10 | *~ 11 | .DS_Store 12 | 13 | pids 14 | logs 15 | results 16 | 17 | npm-debug.log 18 | node_modules 19 | coverage 20 | .idea 21 | .current-env 22 | .nyc_output 23 | .vscode/launch.json 24 | -------------------------------------------------------------------------------- /lib/utils/get-matched-rule.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable global-require */ 2 | 3 | let getMatchedRule = null; 4 | 5 | try { 6 | getMatchedRule = require('./get-matched-rule-4'); 7 | } catch (e) { 8 | getMatchedRule = require('./get-matched-rule-5'); 9 | } 10 | 11 | module.exports = getMatchedRule; 12 | -------------------------------------------------------------------------------- /lib/utils/generate-sprite-placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Because of extract-text-webpack-plugin interop returns just absolute path to filepath 3 | * @param {string} filepath 4 | * @return {string} 5 | */ 6 | function generateSpritePlaceholder(filepath) { 7 | return filepath; 8 | } 9 | 10 | module.exports = generateSpritePlaceholder; 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | 4 | node_js: 5 | - 12.18.1 6 | 7 | branches: 8 | except: 9 | - v0 10 | 11 | env: 12 | global: 13 | - ISTANBUL_COVERAGE: yes 14 | 15 | script: 16 | - nvm install 8.17.0 17 | - sh scripts/build.sh webpack-4 18 | - yarn lint 19 | - yarn test:webpack-2 20 | - yarn test:webpack-3 21 | - yarn test:webpack-4 22 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "check-coverage": false, 3 | "per-file": true, 4 | "statements": 85, 5 | "branches": 75, 6 | "functions": 85, 7 | "lines": 85, 8 | "reporter": [ 9 | "lcov", 10 | "text", 11 | "html" 12 | ], 13 | "cache": true, 14 | "include": [ 15 | "lib/**" 16 | ], 17 | "exclude": [ 18 | "lib/utils/generate-import.js", 19 | "lib/utils/get-all-modules.js" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/generate-export.js: -------------------------------------------------------------------------------- 1 | const loaderDefaults = require('../config').loader; 2 | 3 | /** 4 | * @param {string} content 5 | * @param {boolean} [esModule=false] 6 | * @return {string} 7 | */ 8 | function generateExport(content, esModule = loaderDefaults.esModule) { 9 | return esModule ? 10 | `export default ${content}` : 11 | `module.exports = ${content}`; 12 | } 13 | 14 | module.exports = generateExport; 15 | -------------------------------------------------------------------------------- /lib/utils/stringify-symbol.js: -------------------------------------------------------------------------------- 1 | const stringify = require('./stringify'); 2 | 3 | /** 4 | * @param {SpriteSymbol} symbol 5 | * @return {string} 6 | */ 7 | function stringifySymbol(symbol) { 8 | return stringify({ 9 | id: symbol.id, 10 | use: symbol.useId, 11 | viewBox: symbol.viewBox, 12 | content: symbol.render(), 13 | ...symbol.dimensions 14 | }); 15 | } 16 | 17 | module.exports = stringifySymbol; 18 | -------------------------------------------------------------------------------- /lib/utils/stringify.js: -------------------------------------------------------------------------------- 1 | const stringifiedRegexp = /^'|".*'|"$/; 2 | 3 | /** 4 | * If already stringified - return original content 5 | * @param {Object|Array} content 6 | * @return {string} 7 | */ 8 | function stringify(content) { 9 | if (typeof content === 'string' && stringifiedRegexp.test(content)) { 10 | return content; 11 | } 12 | return JSON.stringify(content, null, 2); 13 | } 14 | 15 | module.exports = stringify; 16 | -------------------------------------------------------------------------------- /runtime/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SvgIcon { 2 | id: string; 3 | width?: number; 4 | height?: number; 5 | } 6 | 7 | export interface SvgSprite { 8 | symbols: SvgSpriteSymbol[]; 9 | find: (id: SvgIcon['id']) => SvgIcon; 10 | } 11 | 12 | export interface SvgSpriteSymbol { 13 | id: string; 14 | viewBox: string; 15 | width: number; 16 | height: number; 17 | content: string; 18 | } 19 | 20 | declare const sprite: SvgSprite; 21 | 22 | export default sprite; 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What kind of change does this PR introduce? (bugfix, feature, docs update, improvement)** 2 | 3 | **What is the current behavior? (You can also link to an open issue here)** 4 | 5 | **What is the new behavior (if this is a feature change)?** 6 | 7 | **Does this PR introduce a breaking change?** 8 | 9 | **Please check if the PR fulfills [contributing guidelines](https://github.com/JetBrains/svg-sprite-loader/blob/master/CONTRIBUTING.md#develop)** 10 | -------------------------------------------------------------------------------- /lib/utils/is-webpack-1.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved, global-require 2 | const webpackPkg = require('webpack/package.json') || {}; 3 | // eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved, global-require 4 | const webpackVersion = webpackPkg.version || require('webpack').version; 5 | 6 | const webpackMajorVersion = parseInt(webpackVersion.split('.')[0], 10); 7 | 8 | module.exports = webpackMajorVersion === 1; 9 | -------------------------------------------------------------------------------- /lib/utils/interpolate.js: -------------------------------------------------------------------------------- 1 | const { interpolateName } = require('loader-utils'); 2 | 3 | /** 4 | * @param {string} pattern 5 | * @param {Object} options 6 | * @param {string} options.resourcePath 7 | * @param {string} [options.context] 8 | * @param {string} [options.content] 9 | */ 10 | function interpolate(pattern, options) { 11 | const { resourcePath, context, content } = options; 12 | return interpolateName({ resourcePath }, pattern, { context, content }); 13 | } 14 | 15 | module.exports = interpolate; 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /env/webpack-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "extract-text-webpack-plugin": "1", 5 | "file-loader": "^0.11.1", 6 | "html-loader": "^0.4.5", 7 | "html-webpack-plugin": "2.28.0", 8 | "webpack": "1", 9 | "eslint": "^3.18.0" 10 | }, 11 | "packagesToLink": [ 12 | "enhanced-resolve", 13 | "extract-text-webpack-plugin", 14 | "file-loader", 15 | "html-loader", 16 | "html-webpack-plugin", 17 | "webpack", 18 | "eslint" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /env/webpack-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "extract-text-webpack-plugin": "2", 5 | "file-loader": "^0.11.1", 6 | "html-loader": "^0.4.5", 7 | "html-webpack-plugin": "2.28.0", 8 | "webpack": "2", 9 | "eslint": "^3.18.0" 10 | }, 11 | "packagesToLink": [ 12 | "enhanced-resolve", 13 | "extract-text-webpack-plugin", 14 | "file-loader", 15 | "html-loader", 16 | "html-webpack-plugin", 17 | "webpack", 18 | "eslint" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /env/webpack-3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "extract-text-webpack-plugin": "3", 5 | "file-loader": "^0.11.1", 6 | "html-loader": "^0.4.5", 7 | "html-webpack-plugin": "2.29.0", 8 | "webpack": "3", 9 | "eslint": "^3.18.0" 10 | }, 11 | "packagesToLink": [ 12 | "enhanced-resolve", 13 | "extract-text-webpack-plugin", 14 | "file-loader", 15 | "html-loader", 16 | "html-webpack-plugin", 17 | "webpack", 18 | "eslint" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /env/webpack-4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "eslint": "6.8.0", 5 | "extract-text-webpack-plugin": "next", 6 | "file-loader": "1.1.10", 7 | "html-loader": "^0.4.5", 8 | "html-webpack-plugin": "^3.0.6", 9 | "webpack": "^4.43.0" 10 | }, 11 | "packagesToLink": [ 12 | "enhanced-resolve", 13 | "extract-text-webpack-plugin", 14 | "file-loader", 15 | "html-loader", 16 | "html-webpack-plugin", 17 | "webpack", 18 | "eslint" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb-base" 4 | ], 5 | "env": { 6 | "es6": true, 7 | "node": true 8 | }, 9 | "rules": { 10 | "max-len": ["error", 120], 11 | "no-unneeded-ternary": "error", 12 | "arrow-body-style": "off", 13 | "no-return-assign": "off", 14 | "no-param-reassign": "off", 15 | "no-underscore-dangle": "off", 16 | "comma-dangle": ["error", "never"], 17 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["utils/*.js", "**/*.test.js"]}] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/utils/generate-import.js: -------------------------------------------------------------------------------- 1 | const loaderDefaults = require('../config').loader; 2 | const stringify = require('./stringify'); 3 | 4 | /** 5 | * @param {string} symbol - Symbol name 6 | * @param {string} module - Module name 7 | * @param {boolean} esModule 8 | * @return {string} 9 | */ 10 | function generateImport(symbol, module, esModule = loaderDefaults.esModule) { 11 | return esModule ? 12 | `import ${symbol} from ${stringify(module)}` : 13 | `var ${symbol} = require(${stringify(module)})`; 14 | } 15 | 16 | module.exports = generateImport; 17 | -------------------------------------------------------------------------------- /env/webpack-5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "mini-css-extract-plugin": "^0.6.0", 5 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 6 | "file-loader": "1.1.10", 7 | "html-loader": "^0.4.5", 8 | "html-webpack-plugin": "^4.5.0", 9 | "webpack": "^5.0.0", 10 | "eslint":"6.8.0" 11 | }, 12 | "packagesToLink": [ 13 | "enhanced-resolve", 14 | "mini-css-extract-plugin", 15 | "extract-text-webpack-plugin", 16 | "file-loader", 17 | "html-loader", 18 | "html-webpack-plugin", 19 | "webpack", 20 | "eslint" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const shell = require('shelljs'); 3 | const { getCliArgs, getEnvsList } = require('./utils'); 4 | 5 | const { cd, echo, exec } = shell; 6 | const projectDir = path.resolve(__dirname, '..'); 7 | 8 | const envFromCli = getCliArgs().env; 9 | const envs = getEnvsList(projectDir); 10 | const envToSelect = envFromCli 11 | ? envs.find(e => e.name === envFromCli) 12 | : envs.find(e => e.name === 'webpack-3'); 13 | 14 | exec('yarn'); 15 | 16 | envs.forEach((env) => { 17 | cd(env.path); 18 | exec('yarn'); 19 | echo(`${env.name} env installed`); 20 | }); 21 | 22 | cd(projectDir); 23 | exec(`node ${projectDir}/scripts/select-env ${envToSelect.name}`); 24 | -------------------------------------------------------------------------------- /lib/utils/replace-in-module-source.js: -------------------------------------------------------------------------------- 1 | const replaceSpritePlaceholder = require('./replace-sprite-placeholder'); 2 | 3 | /** 4 | * @param {NormalModule|ExtractedModule} module 5 | * @param {Object} replacements 6 | * @return {NormalModule|ExtractedModule} 7 | */ 8 | function replaceInModuleSource(module, replacements) { 9 | const source = module._source; 10 | 11 | if (typeof source === 'string') { 12 | module._source = replaceSpritePlaceholder(source, replacements); 13 | } else if (typeof source === 'object' && typeof source._value === 'string') { 14 | source._value = replaceSpritePlaceholder(source._value, replacements); 15 | } 16 | 17 | return module; 18 | } 19 | 20 | module.exports = replaceInModuleSource; 21 | -------------------------------------------------------------------------------- /lib/utils/replace-sprite-placeholder.js: -------------------------------------------------------------------------------- 1 | const escapeRegExpSpecialChars = require('escape-string-regexp'); 2 | 3 | const isWindows = /^win/i.test(process.platform); 4 | 5 | /** 6 | * @param {string} content 7 | * @param {Object} replacements 8 | * @return {string} 9 | */ 10 | function replaceSpritePlaceholder(content, replacements) { 11 | let result = content; 12 | Object.keys(replacements) 13 | .forEach((subj) => { 14 | let re = new RegExp(escapeRegExpSpecialChars(subj), 'g'); 15 | result = result.replace(re, replacements[subj]); 16 | 17 | if (isWindows) { 18 | re = new RegExp(escapeRegExpSpecialChars(subj), 'g'); 19 | result = result.replace(/\\\\/g, '\\').replace(re, replacements[subj]); 20 | } 21 | }); 22 | 23 | return result; 24 | } 25 | 26 | module.exports = replaceSpritePlaceholder; 27 | -------------------------------------------------------------------------------- /lib/utils/normalize-rule.js: -------------------------------------------------------------------------------- 1 | const { parseQuery } = require('loader-utils'); 2 | const isWebpack1 = require('./is-webpack-1'); 3 | 4 | /** 5 | * webpack 1 compat rule normalizer 6 | * @param {string|Rule} rule (string - webpack 1, Object - webpack 2) 7 | * @return {Object} 8 | */ 9 | function normalizeRule(rule) { 10 | if (!rule) { 11 | throw new Error('Rule should be string or object'); 12 | } 13 | 14 | let data; 15 | 16 | if (typeof rule === 'string') { 17 | const parts = rule.split('?'); 18 | data = { 19 | loader: parts[0], 20 | options: parts[1] ? parseQuery(`?${parts[1]}`) : null 21 | }; 22 | } else { 23 | const options = isWebpack1 ? rule.query : rule.options; 24 | data = { 25 | loader: rule.loader, 26 | options: options || null 27 | }; 28 | } 29 | 30 | return data; 31 | } 32 | 33 | module.exports = normalizeRule; 34 | -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const minimist = require('minimist'); 3 | const glob = require('glob'); 4 | 5 | /** 6 | * @return {Array} 7 | */ 8 | function getCliArgs() { 9 | return minimist(process.argv.slice(2)); 10 | } 11 | 12 | /** 13 | * @param {string} dir Absolute path to folder with env directories 14 | * @return {Array} 15 | */ 16 | function getEnvsList(dir) { 17 | return glob 18 | .sync(`${dir}/env/*/`) 19 | .map((envPath) => { 20 | return { 21 | name: path.basename(envPath), 22 | path: envPath 23 | }; 24 | }) 25 | .sort((left, right) => { 26 | const l = left.name; 27 | const r = right.name; 28 | if (l === r) { 29 | return 0; 30 | } 31 | return l < r ? -1 : 1; 32 | }); 33 | } 34 | 35 | module.exports.getCliArgs = getCliArgs; 36 | module.exports.getEnvsList = getEnvsList; 37 | -------------------------------------------------------------------------------- /test/fixtures/img/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /test/fixtures/img/image2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a feature, report a bug or ask a question?** 2 | 3 | **What is the current behavior?** 4 | 5 | **What is the expected behavior?** 6 | 7 | **If the current behavior is a bug, please provide the steps to reproduce, at least part of webpack config with loader configuration and piece of your code.** 8 | **The best way is to create repo with minimal setup to demonstrate a problem (package.json, webpack config and your code).** 9 | **It you don't want to create a repository - create a [gist](http://gist.github.com) with multiple files** 10 | 11 | **If this is a feature request, what is motivation or use case for changing the behavior?** 12 | 13 | **Please tell us about your environment:** 14 | 15 | - Node.js version: ? 16 | - webpack version: ? 17 | - svg-sprite-loader version: ? 18 | - OS type & version: ? 19 | 20 | **Other information (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, gitter, etc)** 21 | -------------------------------------------------------------------------------- /lib/utils/get-loader-options.js: -------------------------------------------------------------------------------- 1 | const normalizeRule = require('./normalize-rule'); 2 | const isWebpack1 = require('./is-webpack-1'); 3 | 4 | /** 5 | * webpack 1 compat loader options finder. Returns normalized options. 6 | * @param {string} loaderPath 7 | * @param {Object|Rule} rule 8 | * @return {Object|null} 9 | */ 10 | function getLoaderOptions(loaderPath, rule) { 11 | let multiRuleProp; 12 | 13 | if (isWebpack1) { 14 | multiRuleProp = 'loaders'; 15 | } else if (rule.oneOf) { 16 | multiRuleProp = 'oneOf'; 17 | } else { 18 | multiRuleProp = 'use'; 19 | } 20 | 21 | const multiRule = typeof rule === 'object' && Array.isArray(rule[multiRuleProp]) ? rule[multiRuleProp] : null; 22 | let options; 23 | 24 | if (multiRule) { 25 | const rules = [].concat(...multiRule.map(r => (r.use || r))); 26 | options = rules.map(normalizeRule).find(r => loaderPath.includes(r.loader)).options; 27 | } else { 28 | options = normalizeRule(rule).options; 29 | } 30 | 31 | return options; 32 | } 33 | 34 | module.exports = getLoaderOptions; 35 | -------------------------------------------------------------------------------- /lib/utils/get-matched-rule-5.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | /* eslint-disable import/no-unresolved */ 3 | /* eslint-disable no-restricted-syntax */ 4 | // eslint-disable-next-line import/no-extraneous-dependencies 5 | 6 | const isSpriteLoader = (rule) => { 7 | if (!Object.prototype.hasOwnProperty.call(rule, 'loader')) return false; 8 | return /svg-sprite-loader/.test(rule.loader); 9 | }; 10 | 11 | module.exports = (compiler) => { 12 | const rawRules = compiler.options.module.rules; 13 | let spriteLoader = null; 14 | for (const rawRule of rawRules) { 15 | if (isSpriteLoader(rawRule)) { 16 | spriteLoader = rawRule; 17 | } else if (Object.prototype.hasOwnProperty.call(rawRule, 'use')) { 18 | for (const subLoader of rawRule.use) { 19 | if (isSpriteLoader(subLoader)) { 20 | spriteLoader = subLoader; 21 | } 22 | } 23 | } 24 | if (spriteLoader !== null) break; 25 | } 26 | return (spriteLoader !== null && spriteLoader.options !== undefined) ? spriteLoader.options : {}; 27 | }; 28 | -------------------------------------------------------------------------------- /lib/utils/get-matched-rule-4.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-unresolved */ 2 | const path = require('path'); 3 | 4 | const ruleSetDir = path.dirname(require.resolve('webpack/lib/RuleSet')); 5 | const webpackDir = path.dirname(require.resolve('webpack')); 6 | 7 | if (ruleSetDir !== webpackDir) { 8 | throw new Error('RuleSet not found in local webpack installation'); 9 | } 10 | 11 | // eslint-disable-next-line import/no-extraneous-dependencies 12 | const RuleSet = require('webpack/lib/RuleSet'); 13 | 14 | const flattenAndExtractUse = rules => rules.reduce((pre, rule) => { 15 | if ('rules' in rule || 'oneOf' in rule) { 16 | return pre.concat(flattenAndExtractUse(rule.rules || rule.oneOf)); 17 | } 18 | 19 | return pre.concat(rule.use || []); 20 | }, []); 21 | 22 | module.exports = (compiler) => { 23 | const rawRules = compiler.options.module.rules; 24 | const { rules } = new RuleSet(rawRules); 25 | const rule = flattenAndExtractUse(rules) 26 | .find((item) => { 27 | return /svg-sprite-loader/.test(item.loader); 28 | }) || {}; 29 | 30 | return rule.options || {}; 31 | }; 32 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports.generateExport = require('./generate-export'); 2 | module.exports.generateImport = require('./generate-import'); 3 | module.exports.generateSpritePlaceholder = require('./generate-sprite-placeholder'); 4 | module.exports.getAllModules = require('./get-all-modules'); 5 | // module.exports.getLoaderOptions = require('./get-loader-options'); 6 | module.exports.getModuleChunk = require('./get-module-chunk'); 7 | module.exports.getMatchedRule = require('./get-matched-rule'); 8 | module.exports.isModuleShouldBeExtracted = require('./is-module-should-be-extracted'); 9 | module.exports.interpolate = require('./interpolate'); 10 | module.exports.isWebpack1 = require('./is-webpack-1'); 11 | module.exports.MappedList = require('./mapped-list'); 12 | module.exports.normalizeRule = require('./normalize-rule'); 13 | module.exports.replaceInModuleSource = require('./replace-in-module-source'); 14 | module.exports.replaceSpritePlaceholder = require('./replace-sprite-placeholder'); 15 | module.exports.stringify = require('./stringify'); 16 | module.exports.stringifySymbol = require('./stringify-symbol'); 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Stas Kurilov (kisenka) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/build-runtime.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const path = require('path'); 3 | const Promise = require('bluebird'); 4 | const rollup = require('rollup'); 5 | const resolvePlugin = require('rollup-plugin-node-resolve'); 6 | const commonjsPlugin = require('rollup-plugin-commonjs'); 7 | const bublePlugin = require('rollup-plugin-buble'); 8 | 9 | const root = path.resolve(__dirname, '..'); 10 | const runtimeDir = path.resolve(root, 'runtime'); 11 | 12 | const entries = [ 13 | { 14 | src: `${runtimeDir}/sprite.js`, 15 | dest: `${runtimeDir}/sprite.build.js`, 16 | moduleName: 'Sprite' 17 | }, 18 | { 19 | src: `${runtimeDir}/browser-sprite.js`, 20 | dest: `${runtimeDir}/browser-sprite.build.js`, 21 | moduleName: 'BrowserSprite' 22 | } 23 | ]; 24 | 25 | Promise.map(entries, ({ src, dest, moduleName }) => { 26 | return rollup.rollup({ 27 | entry: src, 28 | plugins: [ 29 | resolvePlugin(), 30 | commonjsPlugin(), 31 | bublePlugin() 32 | ] 33 | }).then((bundle) => { 34 | return bundle.write({ 35 | dest, 36 | moduleName, 37 | format: 'umd' 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /runtime/browser-sprite.js: -------------------------------------------------------------------------------- 1 | import BrowserSprite from '@workato/svg-baker-runtime/src/browser-sprite'; 2 | import domready from 'domready'; 3 | 4 | const spriteNodeId = '__SVG_SPRITE_NODE__'; 5 | const spriteGlobalVarName = '__SVG_SPRITE__'; 6 | const isSpriteExists = !!window[spriteGlobalVarName]; 7 | 8 | // eslint-disable-next-line import/no-mutable-exports 9 | let sprite; 10 | 11 | if (isSpriteExists) { 12 | sprite = window[spriteGlobalVarName]; 13 | } else { 14 | sprite = new BrowserSprite({ 15 | attrs: { 16 | id: spriteNodeId, 17 | 'aria-hidden': 'true' 18 | } 19 | }); 20 | window[spriteGlobalVarName] = sprite; 21 | } 22 | 23 | const loadSprite = () => { 24 | /** 25 | * Check for page already contains sprite node 26 | * If found - attach to and reuse it's content 27 | * If not - render and mount the new sprite 28 | */ 29 | const existing = document.getElementById(spriteNodeId); 30 | 31 | if (existing) { 32 | sprite.attach(existing); 33 | } else { 34 | sprite.mount(document.body, true); 35 | } 36 | }; 37 | 38 | if (document.body) { 39 | loadSprite(); 40 | } else { 41 | domready(loadSprite); 42 | } 43 | 44 | export default sprite; 45 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | ### 2 | # @Description: 3 | # @Version: 2.0 4 | # @Autor: mayako 5 | # @Date: 2020-05-29 14:27:16 6 | # @LastEditors: mayako 7 | # @LastEditTime: 2020-06-28 14:44:04 8 | ### 9 | #! /bin/bash 10 | 11 | #cd path && install modules 12 | install() { 13 | path=$1 14 | NV=$2 15 | cd $path 16 | nvm exec $NV yarn 17 | echo "$path env installed" 18 | } 19 | . ~/.nvm/nvm.sh 20 | 21 | DEFAULT="12.18.1" 22 | SELECT="webpack-3" 23 | if [ "$1" = "" ] 24 | then 25 | SELECT='webpack-3' 26 | else 27 | SELECT=$1 28 | fi 29 | echo $(pwd) 30 | nvm exec $DEFAULT yarn 31 | nvm exec $DEFAULT node ./node_modules/husky/bin/install 32 | 33 | cd env 34 | CRTDIR=$(pwd) 35 | for file in $(ls $CRTDIR); do 36 | if test -d $file; then 37 | NV="8.17.0" 38 | case $file in 39 | "webpack-1") 40 | NV="8.17.0" 41 | ;; 42 | "webpack-2") 43 | NV="8.17.0" 44 | ;; 45 | "webpack-3") 46 | NV="8.17.0" 47 | ;; 48 | "webpack-4") 49 | NV="12.18.1" 50 | ;; 51 | "webpack-5") 52 | NV="12.18.1" 53 | ;; 54 | *) 55 | NV="8.17.0" 56 | ;; 57 | esac 58 | install $file $NV 59 | cd .. 60 | fi 61 | done 62 | 63 | cd .. 64 | node scripts/select-env $SELECT 65 | 66 | -------------------------------------------------------------------------------- /lib/utils/is-module-should-be-extracted.js: -------------------------------------------------------------------------------- 1 | const defaults = require('../config'); 2 | const normalizeRule = require('./normalize-rule'); 3 | 4 | const spriteLoaderPath = require.resolve('../loader'); 5 | 6 | /** 7 | * @param {NormalModule} module 8 | * @param {moduleGraph} module graph 9 | * @return {boolean} 10 | */ 11 | function isModuleShouldBeExtracted(module, moduleGraph) { 12 | const { request, loaders } = module; 13 | let rule = null; 14 | 15 | let issuer; 16 | if (moduleGraph && moduleGraph.getIssuer) { 17 | issuer = moduleGraph.getIssuer(module); 18 | } else { 19 | issuer = module.issuer; 20 | } 21 | 22 | if (Array.isArray(loaders) && loaders.length > 0) { 23 | // Find loader rule 24 | rule = loaders.map(normalizeRule).find(data => data.loader === spriteLoaderPath); 25 | } 26 | 27 | let issuerResource = null; 28 | if (issuer) { 29 | // webpack 1 compat 30 | issuerResource = typeof issuer === 'string' ? issuer : issuer.resource; 31 | } 32 | 33 | if (typeof request === 'string' && (!request.includes(spriteLoaderPath) || !rule)) { 34 | return false; 35 | } 36 | 37 | return !!( 38 | (issuer && defaults.EXTRACTABLE_MODULE_ISSUER_PATTERN.test(issuerResource)) || 39 | (rule && rule.options && rule.options.extract) 40 | ); 41 | } 42 | 43 | module.exports = isModuleShouldBeExtracted; 44 | -------------------------------------------------------------------------------- /lib/exceptions.js: -------------------------------------------------------------------------------- 1 | const { PACKAGE_NAME } = require('./config'); 2 | 3 | class LoaderException extends Error { 4 | constructor(message = '') { 5 | super(`${PACKAGE_NAME} exception. ${message}`); 6 | 7 | this.name = this.constructor.name; 8 | 9 | if (typeof Error.captureStackTrace === 'function') { 10 | Error.captureStackTrace(this, this.constructor); 11 | } else { 12 | this.stack = (new Error(message)).stack; 13 | } 14 | } 15 | } 16 | 17 | class InvalidSvg extends LoaderException { 18 | constructor(content) { 19 | super(`\n\n${content}`); 20 | } 21 | } 22 | 23 | class ExtractPluginMissingException extends LoaderException { 24 | constructor() { 25 | super(`${PACKAGE_NAME} in extract mode requires the corresponding plugin`); 26 | } 27 | } 28 | 29 | class InvalidRuntimeException extends LoaderException {} 30 | 31 | class RemainingLoadersInExtractModeException extends LoaderException { 32 | constructor() { 33 | super(`Some loaders will be applied after ${PACKAGE_NAME} in extract mode`); 34 | } 35 | } 36 | 37 | exports.LoaderException = LoaderException; 38 | exports.InvalidSvg = InvalidSvg; 39 | exports.ExtractPluginMissingException = ExtractPluginMissingException; 40 | exports.InvalidRuntimeException = InvalidRuntimeException; 41 | exports.RemainingLoadersInExtractModeException = RemainingLoadersInExtractModeException; 42 | -------------------------------------------------------------------------------- /lib/utils/get-module-chunk.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Find nearest module chunk (not sure that is reliable method, but who cares). 3 | * @see http://stackoverflow.com/questions/43202761/how-to-determine-all-module-chunks-in-webpack 4 | * @param {NormalModule} module 5 | * @param {NormalModule[]} modules - webpack 1 compat 6 | * @param {moduleGraph} module graph 7 | * @param {chunkGraph} chunk graph 8 | * @return {Chunk|null} 9 | */ 10 | function getModuleChunk(module, modules, moduleGraph, chunkGraph) { 11 | let chunks; 12 | 13 | if (chunkGraph && chunkGraph.getOrderedModuleChunksIterable) { 14 | chunks = Array.from(chunkGraph.getOrderedModuleChunksIterable(module)); 15 | } else if (module.chunksIterable) { 16 | chunks = Array.from(module.chunksIterable); 17 | } else if (module.mapChunks) { 18 | chunks = module.mapChunks(); 19 | } else { 20 | chunks = module.chunks; 21 | } 22 | 23 | let issuer; 24 | if (moduleGraph && moduleGraph.getIssuer) { 25 | issuer = moduleGraph.getIssuer(module); 26 | } else { 27 | // webpack 1 compat 28 | issuer = typeof module.issuer === 'string' 29 | ? modules.find(m => m.request === module.issuer) 30 | : module.issuer; 31 | } 32 | 33 | if (Array.isArray(chunks) && chunks.length > 0) { 34 | return chunks[chunks.length - 1]; 35 | } else if (issuer) { 36 | return getModuleChunk(issuer, modules); 37 | } 38 | 39 | return null; 40 | } 41 | 42 | module.exports = getModuleChunk; 43 | -------------------------------------------------------------------------------- /test/utils/create-compiler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('deepmerge'); 3 | const Promise = require('bluebird'); 4 | const { InMemoryCompiler, MemoryFileSystem, createCachedInputFileSystem } = require('webpack-toolkit'); 5 | const packageName = require('../../lib/config').PACKAGE_NAME; 6 | const { fixturesPath, rootDir } = require('../_config'); 7 | 8 | /** 9 | * @param {Object} [config] 10 | * @return {Promise} 11 | */ 12 | function createCompiler(config = {}) { 13 | const cfg = merge({ 14 | context: fixturesPath, 15 | resolve: { 16 | alias: { 17 | [packageName]: rootDir 18 | } 19 | } 20 | }, config); 21 | 22 | if (!cfg.files) { 23 | const inputFS = createCachedInputFileSystem(); 24 | return Promise.resolve( 25 | new InMemoryCompiler(cfg, { inputFS }) 26 | ); 27 | } 28 | 29 | const promisedInputFS = new MemoryFileSystem(); 30 | 31 | return Promise.map(Object.keys(cfg.files), (filename) => { 32 | const rawContent = cfg.files[filename]; 33 | const content = rawContent instanceof Buffer === false ? new Buffer(rawContent) : rawContent; 34 | const filepath = path.resolve(cfg.context, filename); 35 | 36 | return promisedInputFS.writep(filepath, content); 37 | }) 38 | .then(() => { 39 | delete cfg.files; 40 | return new InMemoryCompiler(cfg, { inputFS: promisedInputFS.fs }); 41 | }); 42 | } 43 | 44 | module.exports = createCompiler; 45 | -------------------------------------------------------------------------------- /scripts/select-env.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const shell = require('shelljs'); 4 | 5 | const { cd, echo, exec } = shell; 6 | const rootDir = path.resolve(__dirname, '..'); 7 | const env = process.argv[2]; 8 | 9 | if (!env) { 10 | throw new Error('env name should be provided as exclusive argument, e.g. node scripts/select-env webpack-1'); 11 | } 12 | const envDir = path.resolve(rootDir, `${rootDir}/env/${env}`); 13 | // eslint-disable-next-line import/no-dynamic-require 14 | const packagesToLink = require(`${envDir}/package.json`).packagesToLink; 15 | 16 | const envData = { 17 | name: env, 18 | linkedPackages: [] 19 | }; 20 | 21 | packagesToLink.forEach((packageName) => { 22 | const packageDir = `${envDir}/node_modules/${packageName}`; 23 | // eslint-disable-next-line import/no-dynamic-require,global-require 24 | const version = require(`${packageDir}/package.json`).version; 25 | envData.linkedPackages.push(`${packageName}@${version}`); 26 | 27 | cd(packageDir); 28 | exec('yarn unlink || true'); 29 | exec('yarn link'); 30 | }); 31 | 32 | cd(rootDir); 33 | packagesToLink.forEach(packageName => exec(`yarn link ${packageName} || true`)); 34 | 35 | const envFileData = JSON.stringify(envData, null, 2); 36 | 37 | fs.writeFileSync(`${rootDir}/.current-env`, envFileData); 38 | 39 | echo(` 40 | Env "${env}" was selected, linked packages are: 41 | 42 | - ${envData.linkedPackages.join('\n - ')} 43 | 44 | Data saved in ${rootDir}/.current-env 45 | `); 46 | -------------------------------------------------------------------------------- /lib/utils/get-all-modules.js: -------------------------------------------------------------------------------- 1 | let ConcatenatedModule; 2 | try { 3 | // eslint-disable-next-line global-require,import/no-unresolved,import/no-extraneous-dependencies 4 | ConcatenatedModule = require('webpack/lib/optimize/ConcatenatedModule'); 5 | // eslint-disable-next-line no-empty 6 | } catch (e) {} 7 | 8 | /** 9 | * Get all modules from main & child compilations. 10 | * Merge modules from ConcatenatedModule (when webpack.optimize.ModuleConcatenationPlugin is used) 11 | * @param {Compilation} compilation 12 | * @return {NormalModule[]} 13 | */ 14 | function getAllModules(compilation) { 15 | let modules = Array.from(compilation.modules); 16 | 17 | // Look up in child compilations 18 | if (compilation.children.length > 0) { 19 | const childModules = compilation.children.map(getAllModules) 20 | .reduce((acc, compilationModules) => acc.concat(compilationModules), []); 21 | 22 | modules = modules.concat(childModules); 23 | } 24 | 25 | // Merge modules from ConcatenatedModule 26 | if (ConcatenatedModule) { 27 | const concatenatedModules = modules 28 | .filter(m => m instanceof ConcatenatedModule) 29 | .reduce((acc, m) => { 30 | /** 31 | * @see https://git.io/v7XDu 32 | * In webpack@3.5.1 `modules` public property was removed 33 | * To workaround this private `_orderedConcatenationList` property is used to collect modules 34 | */ 35 | const subModules = 'modules' in m 36 | ? m.modules 37 | : m._orderedConcatenationList.map(entry => entry.module); 38 | 39 | return acc.concat(subModules); 40 | }, []); 41 | 42 | if (concatenatedModules.length > 0) { 43 | modules = modules.concat(concatenatedModules); 44 | } 45 | } 46 | 47 | return modules; 48 | } 49 | 50 | module.exports = getAllModules; 51 | -------------------------------------------------------------------------------- /lib/configurator.js: -------------------------------------------------------------------------------- 1 | const merge = require('deepmerge'); 2 | const defaults = require('./config'); 3 | const utils = require('./utils'); 4 | 5 | const loaderDefaults = defaults.loader; 6 | const isomorphicSpriteModule = require.resolve('../runtime/sprite.build'); 7 | const isomorphicSymbolModule = require.resolve('@workato/svg-baker-runtime/symbol'); 8 | 9 | const isTargetBrowser = target => target === 'web' || target === 'electron-renderer' 10 | || target === 'node-webkit' || target === 'nwjs'; 11 | 12 | /** 13 | * @param {Object} params 14 | * @param {Object} [params.config] Parsed loader config {@see SVGSpriteLoaderConfig} 15 | * @param {LoaderContext} context Loader context {@see https://webpack.js.org/api/loaders/#the-loader-context} 16 | * @return {Object} 17 | */ 18 | module.exports = function configurator({ config, context, target }) { 19 | const module = context._module; 20 | const compiler = context._compiler; 21 | const compilation = context._compilation; 22 | const compilerName = compiler.name; 23 | 24 | const autoConfigured = { 25 | spriteModule: isTargetBrowser(target) ? loaderDefaults.spriteModule : isomorphicSpriteModule, 26 | symbolModule: isTargetBrowser(target) ? loaderDefaults.symbolModule : isomorphicSymbolModule, 27 | extract: utils.isModuleShouldBeExtracted(module, compilation && compilation.moduleGraph), 28 | esModule: context.version && context.version >= 2 29 | }; 30 | 31 | const finalConfig = merge.all([loaderDefaults, autoConfigured, config || {}]); 32 | 33 | /** 34 | * esModule should be `false` when compiles via extract-text-webpack-plugin or html-webpack-plugin. 35 | * Because this compilers executes module as usual node module so export should be always in commonjs style. 36 | * This could be dropped when Node.js will support ES modules natively :) 37 | * @see https://git.io/vS7Sn 38 | * @see https://git.io/v9w60 39 | */ 40 | if (compilerName && ( 41 | compilerName.includes('extract-text-webpack-plugin') || 42 | compilerName.includes('html-webpack-plugin') 43 | )) { 44 | finalConfig.esModule = false; 45 | } 46 | 47 | return finalConfig; 48 | }; 49 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const webpackConfig = require('./webpack-runtime.config.js'); 2 | 3 | module.exports = (config) => { 4 | config.set({ 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: '', 7 | 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['mocha'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/runtime/*.test.js' 15 | ], 16 | 17 | // preprocess matching files before serving them to the browser 18 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 19 | preprocessors: { 20 | 'test/runtime/*.test.js': ['webpack', 'sourcemap'] 21 | }, 22 | 23 | // test results reporter to use 24 | // possible values: 'dots', 'progress' 25 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 26 | reporters: ['progress'], 27 | 28 | // web server port 29 | port: 9876, 30 | 31 | // enable / disable colors in the output (reporters and logs) 32 | colors: true, 33 | 34 | // level of logging 35 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 36 | logLevel: config.LOG_INFO, 37 | 38 | // enable / disable watching file and executing tests whenever any file changes 39 | autoWatch: true, 40 | 41 | // start these browsers 42 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 43 | browsers: ['PhantomJS'], 44 | 45 | // Continuous Integration mode 46 | // if true, Karma captures browsers, runs the tests and exits 47 | singleRun: true, 48 | 49 | // If browser does not capture in given timeout [ms], kill it 50 | captureTimeout: 60000, 51 | 52 | // Increase timeout because of webpack 53 | // See https://github.com/karma-runner/karma/issues/598 54 | browserNoActivityTimeout: 60000, 55 | 56 | // cli runner port 57 | runnerPort: 9100, 58 | 59 | // Webpack config 60 | webpack: webpackConfig, 61 | webpackServer: { 62 | stats: { 63 | colors: true 64 | }, 65 | noInfo: true, 66 | quiet: true 67 | } 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /lib/runtime-generator.js: -------------------------------------------------------------------------------- 1 | const { isAbsolute, join } = require('path'); 2 | const { stringifyRequest } = require('loader-utils'); 3 | const { 4 | stringify, 5 | stringifySymbol, 6 | generateImport, 7 | generateExport, 8 | generateSpritePlaceholder 9 | } = require('./utils'); 10 | 11 | /** 12 | * @param {Object} params 13 | * @param {SpriteSymbol} params.symbol - Sprite symbol instance {@see https://git.io/v9k8g} 14 | * @param {SVGSpriteLoaderConfig} params.config - Parsed loader config 15 | * @param {string} params.context - Context folder of current processing module 16 | * @param {Object} params.loaderContext {@see https://webpack.js.org/api/loaders/#the-loader-context} 17 | * @return {string} 18 | */ 19 | function runtimeGenerator(params) { 20 | const { symbol, config, context, loaderContext } = params; 21 | const { extract, esModule, spriteModule, symbolModule, runtimeCompat, publicPath } = config; 22 | let runtime; 23 | 24 | if (extract) { 25 | const spritePlaceholder = generateSpritePlaceholder(symbol.request.file); 26 | const path = stringify(publicPath) || '__webpack_public_path__'; 27 | const data = `{ 28 | id: ${stringify(symbol.useId)}, 29 | viewBox: ${stringify(symbol.viewBox)}, 30 | url: ${path} + ${stringify(spritePlaceholder)}, 31 | toString: function () { 32 | return this.url; 33 | } 34 | }`; 35 | runtime = generateExport(data, esModule); 36 | } else { 37 | const spriteModuleAbsPath = isAbsolute(spriteModule) ? spriteModule : join(context, spriteModule); 38 | const symbolModuleAbsPath = isAbsolute(symbolModule) ? symbolModule : join(context, symbolModule); 39 | 40 | const spriteModuleImport = stringifyRequest(loaderContext, spriteModuleAbsPath); 41 | const symbolModuleImport = stringifyRequest(loaderContext, symbolModuleAbsPath); 42 | 43 | runtime = [ 44 | generateImport('SpriteSymbol', symbolModuleImport, esModule), 45 | generateImport('sprite', spriteModuleImport, esModule), 46 | 47 | `var symbol = new SpriteSymbol(${stringifySymbol(symbol)})`, 48 | 'var result = sprite.add(symbol)', 49 | 50 | generateExport(runtimeCompat ? '"#" + symbol.id' : 'symbol', esModule) 51 | ].join(';\n'); 52 | } 53 | 54 | return runtime; 55 | } 56 | 57 | module.exports = runtimeGenerator; 58 | -------------------------------------------------------------------------------- /test/configurator.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const { strictEqual } = require('assert'); 3 | const configure = require('../lib/configurator'); 4 | const defaults = require('../lib/config'); 5 | 6 | const loaderDefaults = defaults.loader; 7 | 8 | describe('configurator', () => { 9 | let context; 10 | 11 | beforeEach(() => { 12 | context = { 13 | version: 2, 14 | 15 | target: 'web', 16 | 17 | options: { 18 | target: 'web' 19 | }, 20 | 21 | _module: { 22 | issuer: { 23 | resource: '/foo/index.js' 24 | } 25 | }, 26 | 27 | _compiler: { 28 | name: undefined 29 | } 30 | }; 31 | }); 32 | 33 | it('should properly autodetect runtime modules', () => { 34 | let config; 35 | 36 | config = configure({ context, target: 'web' }); 37 | strictEqual(config.spriteModule, loaderDefaults.spriteModule); 38 | strictEqual(config.symbolModule, loaderDefaults.symbolModule); 39 | 40 | config = configure({ context, target: 'node' }); 41 | strictEqual(config.spriteModule, require.resolve('../runtime/sprite.build')); 42 | strictEqual(config.symbolModule, require.resolve('@workato/svg-baker-runtime/symbol')); 43 | }); 44 | 45 | it('should properly autodetect extract mode', () => { 46 | const issuer = context._module.issuer; 47 | 48 | ['css', 'scss', 'sass', 'styl', 'less', 'html'].forEach((ext) => { 49 | issuer.resource = `styles.${ext}`; 50 | strictEqual(configure({ context }).extract, true); 51 | }); 52 | 53 | ['js', 'jsx', 'ts'].forEach((ext) => { 54 | issuer.resource = `index.${ext}`; 55 | strictEqual(configure({ context }).extract, false); 56 | }); 57 | }); 58 | 59 | it('should properly autodetect if export should be transpilers friendly', () => { 60 | context.version = 2; 61 | strictEqual(configure({ context }).esModule, true); 62 | 63 | context.version = 1; 64 | strictEqual(configure({ context }).esModule, false); 65 | 66 | /** 67 | * Should always be falsy if compiled with extract-text-webpack-plugin or html-webpack-plugin 68 | */ 69 | context.version = 2; 70 | context._compiler.name = 'extract-text-webpack-plugin'; 71 | strictEqual(configure({ context }).esModule, false); 72 | 73 | context._compiler.name = 'html-webpack-plugin'; 74 | strictEqual(configure({ context }).esModule, false); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workato/svg-sprite-loader", 3 | "version": "6.1.2", 4 | "description": "Webpack loader for creating SVG sprites", 5 | "keywords": [ 6 | "svg", 7 | "sprite", 8 | "svg sprite", 9 | "svg stack", 10 | "webpack", 11 | "webpack2", 12 | "webpack3", 13 | "webpack loader", 14 | "webpack plugin" 15 | ], 16 | "homepage": "https://github.com/JetBrains/svg-sprite-loader#readme", 17 | "bugs": "https://github.com/JetBrains/svg-sprite-loader/issues", 18 | "license": "MIT", 19 | "author": "kisenka", 20 | "repository": "JetBrains/svg-sprite-loader", 21 | "main": "lib/loader.js", 22 | "files": [ 23 | "examples/**", 24 | "lib/**", 25 | "runtime/*.js", 26 | "plugin.js", 27 | "README.md", 28 | "LICENSE" 29 | ], 30 | "engines": { 31 | "node": ">=6" 32 | }, 33 | "dependencies": { 34 | "@workato/svg-baker": "1.7.2", 35 | "@workato/svg-baker-runtime": "1.4.7", 36 | "bluebird": "^3.5.0", 37 | "deepmerge": "^1.5.2", 38 | "domready": "1.0.8", 39 | "escape-string-regexp": "1.0.5", 40 | "loader-utils": "^2.0.4", 41 | "url-slug": "2.0.0" 42 | }, 43 | "peerDependencies": { 44 | "@workato/svg-sprite-loader-runtime": "^6" 45 | }, 46 | "devDependencies": { 47 | "eslint-config-airbnb-base": "^15.0.0", 48 | "eslint-plugin-import": "^2.29.1", 49 | "chai": "^4.5.0", 50 | "css-loader": "^3.6.0", 51 | "glob": "7.1.1", 52 | "memory-fs": "^0.5.0", 53 | "minimist": "^1.2.0", 54 | "mocha": "^10.7.0", 55 | "rollup": "^0.41.6", 56 | "rollup-plugin-buble": "^0.15.0", 57 | "rollup-plugin-commonjs": "^8.0.2", 58 | "rollup-plugin-node-resolve": "^3.0.0", 59 | "shelljs": "^0.8.5", 60 | "svgo": "^3.3.2", 61 | "svgo-loader": "^3.0.3", 62 | "webpack-toolkit": "^1.0.0" 63 | }, 64 | "scripts": { 65 | "build:runtime": "node scripts/build-runtime.js", 66 | "env": "node scripts/select-env", 67 | "lint": "eslint --quiet lib runtime scripts test", 68 | "precommit": "yarn lint", 69 | "pretest": "yarn build:runtime", 70 | "prerelease": "yarn build:runtime && yarn lint && yarn test:all", 71 | "test": "mocha test/*.test.js", 72 | "test:all": "yarn test:webpack-2 && yarn test:webpack-3 && yarn test:webpack-4", 73 | "test:webpack-2": "yarn env webpack-2 && env WEBPACK_VERSION=2 yarn test", 74 | "test:webpack-3": "yarn env webpack-3 && env WEBPACK_VERSION=3 yarn test", 75 | "test:webpack-4": "yarn env webpack-4 && env WEBPACK_VERSION=4 yarn test" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | const { interpolateName, getOptions } = require('loader-utils'); 2 | const urlSlug = require('url-slug'); 3 | const SVGCompiler = require('@workato/svg-baker'); 4 | 5 | const { NAMESPACE } = require('./config'); 6 | const configure = require('./configurator'); 7 | const Exceptions = require('./exceptions'); 8 | 9 | let svgCompiler = new SVGCompiler(); 10 | 11 | // eslint-disable-next-line consistent-return 12 | module.exports = function loader(content) { 13 | if (this.cacheable) { 14 | this.cacheable(); 15 | } 16 | 17 | const done = this.async(); 18 | const loaderContext = this; 19 | const { resourcePath, loaderIndex } = loaderContext; 20 | // webpack 1 compat 21 | const resourceQuery = loaderContext.resourceQuery || ''; 22 | const compiler = loaderContext._compiler; 23 | const isChildCompiler = compiler.isChild(); 24 | const parentCompiler = isChildCompiler ? compiler.parentCompilation.compiler : null; 25 | const matchedRules = getOptions(loaderContext); 26 | 27 | if (!content.includes(' p.NAMESPACE && p.NAMESPACE === NAMESPACE) 44 | : this[NAMESPACE]; 45 | 46 | if (typeof plugin === 'undefined') { 47 | throw new Exceptions.ExtractPluginMissingException(); 48 | } 49 | 50 | if (loaderIndex > 0) { 51 | this.emitWarning(new Exceptions.RemainingLoadersInExtractModeException()); 52 | } 53 | 54 | svgCompiler = plugin.svgCompiler; 55 | } 56 | 57 | let runtimeGenerator; 58 | try { 59 | runtimeGenerator = require(config.runtimeGenerator); // eslint-disable-line import/no-dynamic-require,global-require 60 | } catch (e) { 61 | throw new Exceptions.InvalidRuntimeException(e.message); 62 | } 63 | 64 | let id; 65 | if (typeof config.symbolId === 'function') { 66 | id = config.symbolId(resourcePath, resourceQuery); 67 | } else { 68 | const idPattern = config.symbolId + (resourceQuery ? `--${urlSlug(resourceQuery)}` : ''); 69 | id = interpolateName(loaderContext, idPattern, { 70 | content, 71 | context: compiler.context, 72 | regExp: config.symbolRegExp 73 | }); 74 | } 75 | svgCompiler.addSymbol({ id, content, path: resourcePath + resourceQuery }) 76 | .then((symbol) => { 77 | const runtime = runtimeGenerator({ symbol, config, context: loaderContext.context, loaderContext }); 78 | done(null, runtime); 79 | }).catch(done); 80 | }; 81 | 82 | module.exports.NAMESPACE = NAMESPACE; 83 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const fs = require('fs'); 3 | const PACKAGE_NAME = require('../package.json').name; 4 | 5 | module.exports = { 6 | PACKAGE_NAME, 7 | NAMESPACE: fs.realpathSync(__dirname), 8 | EXTRACTABLE_MODULE_ISSUER_PATTERN: /\.(css|sass|scss|less|styl|html)$/i, 9 | SPRITE_PLACEHOLDER_PATTERN: /\{\{sprite-filename\|([^}}]*)\}\};?/gi, 10 | 11 | /** 12 | * Overridable loader options 13 | * @typedef {Object} SVGSpriteLoaderConfig 14 | */ 15 | loader: { 16 | /** 17 | * How `` should be named. 18 | * Full list of supported patterns see at [loader-utils#interpolatename docs](https://github.com/webpack/loader-utils#interpolatename). 19 | * @type {string} 20 | */ 21 | symbolId: '[name]', 22 | 23 | /** 24 | * Regular expression passed to interpolateName. 25 | * Supports the interpolateName [N] pattern inserting the N-th match. 26 | * @type {string} 27 | */ 28 | symbolRegExp: '', 29 | 30 | /** 31 | * Path to Node.js module which generates client runtime. 32 | * @type {string} 33 | */ 34 | runtimeGenerator: require.resolve('./runtime-generator'), 35 | 36 | /** 37 | * Arbitrary data passed to runtime generator. 38 | * @type {*} 39 | */ 40 | runtimeOptions: undefined, 41 | 42 | /** 43 | * Should runtime be compatible with earlier v0.* loader versions. 44 | * Will be removed in 3 version. 45 | * @type {boolean} 46 | * @deprecated 47 | */ 48 | runtimeCompat: false, 49 | 50 | /** 51 | * Path to sprite module which will be compiled and executed at runtime. 52 | * By default depends on 'target' webpack config option: 53 | * - `svg-sprite-loader/runtime/browser-sprite.build` for 'web' target. 54 | * - `svg-sprite-loader/runtime/sprite.build` for all other targets. 55 | * @type {string} 56 | * @autoconfigured 57 | */ 58 | spriteModule: require.resolve('@workato/svg-sprite-loader-runtime'), 59 | 60 | /** 61 | * Path to symbol module. 62 | * By default depends on 'target' webpack config option: 63 | * - `svg-baker-runtime/browser-symbol` for 'web' target. 64 | * - `svg-baker-runtime/symbol` for all other targets. 65 | * @type {string} 66 | * @autoconfigured 67 | */ 68 | symbolModule: require.resolve('@workato/svg-baker-runtime/browser-symbol'), 69 | 70 | /** 71 | * Generated export format: 72 | * - when `true` loader will produce `export default ...`. 73 | * - when `false` the result is `module.exports = ...`. 74 | * By default depends on used webpack version. `true` for Webpack >= 2, `false` otherwise. 75 | * @type {boolean} 76 | * @autoconfigured 77 | */ 78 | esModule: true, 79 | 80 | /** 81 | * Turns loader in extract mode. 82 | * Enables automatically if SVG image was imported from css/scss/sass/less/styl/html. 83 | * @type {boolean} 84 | * @autoconfigured 85 | */ 86 | extract: false, 87 | 88 | /** 89 | * Filename for generated sprite. `[chunkname]` placeholder can be used. 90 | * @type {string} 91 | */ 92 | spriteFilename: 'sprite.svg' 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting [github.com/kisenka](https://github.com/kisenka). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const { strictEqual, ok } = require('assert'); 3 | const { loaderPath } = require('./_config'); 4 | const { 5 | generateSpritePlaceholder, 6 | replaceSpritePlaceholder, 7 | replaceInModuleSource, 8 | isModuleShouldBeExtracted, 9 | getMatchedRule, 10 | isWebpack1 11 | } = require('../lib/utils'); 12 | 13 | describe('utils', () => { 14 | describe('generateSpritePlaceholder', () => { 15 | it('should work like a charm', () => { 16 | ok(generateSpritePlaceholder('tralala').includes('tralala')); 17 | }); 18 | }); 19 | 20 | describe('replaceSpritePlaceholder', () => { 21 | it('should work like a charm', () => { 22 | const fixture = generateSpritePlaceholder('foo'); 23 | const replacements = { foo: 'bar' }; 24 | const expected = 'bar'; 25 | 26 | strictEqual(replaceSpritePlaceholder(fixture, replacements), expected); 27 | strictEqual(replaceSpritePlaceholder('qwe', replacements), 'qwe'); 28 | }); 29 | }); 30 | 31 | describe('replaceInModuleSource', () => { 32 | const fixture = `module.exports = "${generateSpritePlaceholder('foo')}"`; 33 | const replacements = { foo: 'bar' }; 34 | const expected = 'module.exports = "bar"'; 35 | 36 | it('should replace if module source is text', () => { 37 | const mock = { _source: fixture }; 38 | strictEqual(replaceInModuleSource(mock, replacements)._source, expected); 39 | }); 40 | 41 | it('should replace if module source is object', () => { 42 | const mock = { _source: { _value: fixture } }; 43 | strictEqual(replaceInModuleSource(mock, replacements)._source._value, expected); 44 | }); 45 | }); 46 | 47 | describe('isModuleShouldBeExtracted', () => { 48 | const request = `${loaderPath}!./img.svg`; 49 | const optionsProp = isWebpack1 ? 'query' : 'options'; 50 | 51 | isModuleShouldBeExtracted({ 52 | request, 53 | loaders: [{ loader: loaderPath }] 54 | }).should.be.false; 55 | 56 | isModuleShouldBeExtracted({ 57 | request, 58 | loaders: [{ loader: loaderPath, [optionsProp]: { extract: true } }] 59 | }).should.be.true; 60 | 61 | // webpack 1 format 62 | isModuleShouldBeExtracted({ 63 | request, 64 | loaders: [`${loaderPath}?extract=true`] 65 | }).should.be.true; 66 | 67 | isModuleShouldBeExtracted({ 68 | request, 69 | loaders: [{ loader: loaderPath }], 70 | issuer: { resource: 'style.css' } 71 | }).should.be.true; 72 | }); 73 | 74 | describe('getMatchedRule', () => { 75 | const compilerMock = { 76 | options: { 77 | module: { 78 | rules: [ 79 | { test: /\.svg$/, loader: '@workato/svg-sprite-loader', options: { extra: true } }, 80 | { test: /\.svg$/, loader: 'another-loader' }, 81 | { test: /\.foo$/, loader: 'foo-loader' } 82 | ] 83 | } 84 | } 85 | }; 86 | 87 | const compilerMock2 = { 88 | options: { 89 | module: { 90 | rules: [ 91 | { 92 | test: /\.svg$/, 93 | use: [ 94 | { loader: '@workato/svg-sprite-loader', options: { extra: true, a: 1 } }, 95 | 'svgo-loader' 96 | ] 97 | }, 98 | { test: /\.svg$/, loader: 'another-loader' }, 99 | { test: /\.foo$/, loader: 'foo-loader' } 100 | ] 101 | } 102 | } 103 | }; 104 | 105 | it('should get the right options', () => { 106 | const rule = getMatchedRule(compilerMock); 107 | rule.should.be.deep.equal({ extra: true }); 108 | }); 109 | 110 | it('should get the right options when mutiple loader', () => { 111 | const rule = getMatchedRule(compilerMock2); 112 | rule.should.be.deep.equal({ extra: true, a: 1 }); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | const merge = require('deepmerge'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const { isWebpack1 } = require('../../lib/utils'); 4 | const { loaderPath } = require('../_config'); 5 | const createCompiler = require('./create-compiler'); 6 | 7 | /** 8 | * @param {Object} [config] 9 | * @return {Promise} 10 | */ 11 | function compile(config) { 12 | return createCompiler(config).then(compiler => compiler.run()); 13 | } 14 | 15 | /** 16 | * @param {Object} [config] 17 | * @return {Promise} 18 | */ 19 | function compileAndNotReject(config) { 20 | return createCompiler(config).then(compiler => compiler.run(false)); 21 | } 22 | 23 | function rules(...data) { 24 | return { 25 | [isWebpack1 ? 'loaders' : 'rules']: [...data] 26 | }; 27 | } 28 | 29 | function rule(data) { 30 | if (isWebpack1) { 31 | data.query = data.options; 32 | delete data.options; 33 | } 34 | 35 | return data; 36 | } 37 | 38 | function multiRule(data) { 39 | if (isWebpack1) { 40 | data.loaders = data.use.map((ruleData) => { 41 | return typeof ruleData !== 'string' ? 42 | `${ruleData.loader}?${JSON.stringify(ruleData.options)}` : 43 | ruleData; 44 | }); 45 | delete data.use; 46 | } 47 | 48 | return data; 49 | } 50 | 51 | function svgRule(opts) { 52 | const options = merge({}, opts || {}); 53 | 54 | return rule({ 55 | test: /\.svg$/, 56 | loader: loaderPath, 57 | options 58 | }); 59 | } 60 | 61 | function svgInsideOneOfRule(opts) { 62 | const options = merge({}, opts || {}); 63 | 64 | return rule({ 65 | oneOf: [{ 66 | test: /\.svg$/, 67 | loader: loaderPath, 68 | options 69 | }] 70 | }); 71 | } 72 | 73 | function svgInsideRulesRule(opts) { 74 | const options = merge({}, opts || {}); 75 | 76 | return rule({ 77 | rules: [{ 78 | test: /\.svg$/, 79 | loader: loaderPath, 80 | options 81 | }] 82 | }); 83 | } 84 | 85 | /** 86 | * @see for webpack 1 - https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/webpack-1/README.md#api 87 | * @see for webpack 2 - https://github.com/webpack-contrib/extract-text-webpack-plugin#options 88 | * @param {string} filename 89 | * @param {Object} [options] 90 | * @return {ExtractTextPlugin} 91 | */ 92 | function extractPlugin(filename, options) { 93 | // webpack 1 compat 94 | if (isWebpack1) { 95 | return new ExtractTextPlugin(filename, options); 96 | } 97 | 98 | let args = filename; 99 | if (typeof options !== 'object') { 100 | args = Object.assign({}, options); 101 | args.filename = filename; 102 | } 103 | 104 | return new ExtractTextPlugin(args); 105 | } 106 | 107 | /** 108 | * @see for webpack 1 - https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/webpack-1/README.md#api 109 | * @see for webpack 2 - https://github.com/webpack-contrib/extract-text-webpack-plugin#options 110 | * @param {ExtractTextPlugin} plugin 111 | * @return {Rule} 112 | */ 113 | function extractCSSRule(plugin) { 114 | // webpack 1 compat 115 | return multiRule({ 116 | test: /\.css$/, 117 | use: isWebpack1 118 | ? plugin.extract('css-loader').split('!') 119 | : plugin.extract('css-loader') 120 | }); 121 | } 122 | 123 | function extractHTMLRule(plugin) { 124 | return multiRule({ 125 | test: /\.html$/, 126 | use: isWebpack1 127 | ? plugin.extract('html-loader').split('!') 128 | : plugin.extract('html-loader') 129 | }); 130 | } 131 | 132 | module.exports.rule = rule; 133 | module.exports.rules = rules; 134 | module.exports.multiRule = multiRule; 135 | module.exports.svgRule = svgRule; 136 | module.exports.svgInsideOneOfRule = svgInsideOneOfRule; 137 | module.exports.svgInsideRulesRule = svgInsideRulesRule; 138 | module.exports.compile = compile; 139 | module.exports.compileAndNotReject = compileAndNotReject; 140 | module.exports.createCompiler = createCompiler; 141 | module.exports.extractHTMLRule = extractHTMLRule; 142 | module.exports.extractCSSRule = extractCSSRule; 143 | module.exports.extractPlugin = extractPlugin; 144 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | svg-sprite-loader is a free and open source, and we appreciate any help you're willing to give - whether it's 4 | fixing bugs, improving documentation, or suggesting new features. 5 | 6 | Table of contents 7 | - [Code of conduct](#code-of-conduct) 8 | - [What's up?](#whatsup) 9 | - [Code contribution](#code-contribution) 10 | - [Setup](#setup) 11 | - [Develop](#develop) 12 | - [Releasing](#releasing) 13 | 14 | ## Code of conduct 15 | 16 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 17 | By participating in this project you agree to abide by its terms. 18 | 19 | ## What's up? 20 | 21 | ### Got a question or problem with setup? 22 | If you have questions about how to use svg-sprite-loader, please read the [docs](README.md) and [question issues](https://github.com/JetBrains/svg-sprite-loader/issues?q=is:issue+label:question) first. 23 | 24 | ### Found a bug or inconsistency? 25 | If you find a bug in the source code or a mistake in the documentation, you can help us by [submitting an issue](https://github.com/JetBrains/svg-sprite-loader/issues/new). 26 | __But even better you can submit a [pull request with a fix](#code-contribution).__ 27 | 28 | ### Want a feature? 29 | You can request a new feature by [submitting an issue](https://github.com/JetBrains/svg-sprite-loader/issues/new). 30 | If you would like to implement a new feature then follow the [code contribution steps](#code-contribution). 31 | 32 | 33 | ## Code contribution 34 | 35 | Please follow these steps to contribute effectively. 36 | 37 | ### Setup 38 | 39 | 1. **Fork & clone** a repo ([how to](https://help.github.com/articles/fork-a-repo)). 40 | 2. **Add an upstream remote repo** (original repository that we forked from): 41 | 42 | ```bash 43 | git remote add upstream https://github.com/JetBrains/svg-sprite-loader.git 44 | ``` 45 | 46 | 3. **Keep your fork up to date**: 47 | 48 | ```bash 49 | git pull --ff upstream master 50 | ``` 51 | 52 | 4. **Setup project** properly: 53 | now u need install nvm 54 | 55 | ```bash 56 | sh scripts/build.sh 57 | ``` 58 | 59 | It will: 60 | 1. Install project dependencies. 61 | 2. Install git hooks. 62 | 3. Install dependencies for testing in `webpack-1` and `webpack-2` environments. 63 | 4. Set `webpack-3` environment as current. 64 | 65 | Don't use `yarn install` or `npm install`. 66 | 67 | ### Develop 68 | 69 | 1. **Make changes in a new git branch** (don't use master!): 70 | 71 | ```bash 72 | git checkout -b my-fix master 73 | ``` 74 | 75 | 2. **Lint your code** to check it's following our code style: 76 | 77 | ```bash 78 | yarn lint 79 | ``` 80 | 81 | Linter runs each time before the commit happens (via git hook). 82 | 83 | 3. **Test your code** in each environment (webpack-1 and webpack-2): 84 | 85 | ```bash 86 | # It will run tests in webpack-1 & webpack-2 environments 87 | yarn test:all 88 | 89 | # Test only in webpack-2 90 | yarn test:webpack-2 91 | ``` 92 | 93 | Test coverage is collected in each test run. If it decrease too much, tests will fails. 94 | 95 | 4. **Commit your changes** using a [Angular commit convention](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format): 96 | 97 | ```bash 98 | # Run it when you have changes staged for commit 99 | yarn commit 100 | ``` 101 | 102 | Commit messages validated each time when commit happens. 103 | 104 | 5. **Push your branch to GitHub**: 105 | 106 | ```bash 107 | git push origin my-fix 108 | ``` 109 | 110 | 6. **Create a pull request on GitHub** ([how to](https://help.github.com/articles/creating-a-pull-request)). 111 | 7. **Cleanup after pull request is merged**: 112 | 113 | ```bash 114 | # Delete the remote branch on GitHub 115 | git push origin --delete my-fix 116 | 117 | # Check out the master branch 118 | git checkout master -f 119 | 120 | # Delete the local branch 121 | git branch -D my-fix 122 | ``` 123 | 124 | 125 | ## Releasing 126 | 127 | * Commits of type `fix` will trigger bugfix releases, e.g. `0.0.1`. 128 | * Commits of type `feat` will trigger feature releases, e.g. `0.1.0`. 129 | * Commits with `BREAKING CHANGE` in body or footer will trigger breaking releases, e.g. `1.0.0`. 130 | 131 | All other commit types will trigger no new release. 132 | -------------------------------------------------------------------------------- /lib/utils/mapped-list.js: -------------------------------------------------------------------------------- 1 | // TODO refactor this smelly code! 2 | const loaderDefaults = require('../config').loader; 3 | const getAllModules = require('./get-all-modules'); 4 | const isModuleShouldBeExtracted = require('./is-module-should-be-extracted'); 5 | const getModuleChunk = require('./get-module-chunk'); 6 | const interpolate = require('./interpolate'); 7 | const getMatchedRule = require('./get-matched-rule'); 8 | 9 | class MappedListItem { 10 | /** 11 | * @param {SpriteSymbol} symbol 12 | * @param {NormalModule} module 13 | * @param {string} spriteFilename 14 | */ 15 | constructor(symbol, module, spriteFilename) { 16 | this.symbol = symbol; 17 | this.module = module; 18 | this.resource = symbol.request.file; 19 | this.spriteFilename = spriteFilename; 20 | } 21 | 22 | get url() { 23 | return `${this.spriteFilename}#${this.symbol.id}`; 24 | } 25 | 26 | get useUrl() { 27 | return `${this.spriteFilename}#${this.symbol.useId}`; 28 | } 29 | } 30 | 31 | class MappedList { 32 | /** 33 | * @param {SpriteSymbol[]} symbols 34 | * @param {Compilation} compilation 35 | */ 36 | constructor(symbols, compilation, shouldLog = false) { 37 | const { compiler, moduleGraph, chunkGraph } = compilation; 38 | 39 | this.symbols = symbols; 40 | this.rule = getMatchedRule(compiler); 41 | this.allModules = getAllModules(compilation); 42 | this.moduleGraph = moduleGraph; 43 | this.chunkGraph = chunkGraph; 44 | this.spriteModules = this.allModules.filter(module => isModuleShouldBeExtracted(module, moduleGraph)); 45 | this.shouldLog = shouldLog; 46 | this.items = this.create(); 47 | } 48 | 49 | /** 50 | * @param {MappedListItem[]} data 51 | * @return {Object} 52 | */ 53 | static groupItemsBySpriteFilename(data) { 54 | return data 55 | .map(item => item.spriteFilename) 56 | .filter((value, index, self) => self.indexOf(value) === index) 57 | .reduce((acc, spriteFilename) => { 58 | acc[spriteFilename] = data.filter(item => item.spriteFilename === spriteFilename); 59 | return acc; 60 | }, {}); 61 | } 62 | 63 | /** 64 | * @param {MappedListItem[]} data 65 | * @param {Function} [mapper] Custom grouper function 66 | * @return {Object} 67 | */ 68 | static groupItemsBySymbolFile(data, mapper) { 69 | return data.reduce((acc, item) => { 70 | if (mapper) { 71 | mapper(acc, item); 72 | } else { 73 | acc[item.resource] = item; 74 | } 75 | return acc; 76 | }, {}); 77 | } 78 | 79 | /** 80 | * @return {MappedListItem[]} 81 | */ 82 | create() { 83 | const { symbols, spriteModules, allModules, rule, moduleGraph, chunkGraph } = this; 84 | 85 | const data = symbols.reduce((acc, symbol) => { 86 | const resource = symbol.request.file; 87 | const module = spriteModules.find((m) => { 88 | return 'resource' in m ? m.resource.split('?')[0] === resource : false; 89 | }); 90 | 91 | let spriteFilename = rule.spriteFilename || loaderDefaults.spriteFilename; 92 | 93 | const chunk = module ? getModuleChunk(module, allModules, moduleGraph, chunkGraph) : null; 94 | 95 | if (typeof spriteFilename !== 'function' && chunk && chunk.name) { 96 | spriteFilename = spriteFilename.replace('[chunkname]', chunk.name); 97 | } else if (typeof spriteFilename === 'function') { 98 | spriteFilename = spriteFilename(resource); 99 | } 100 | 101 | if (rule && module) { 102 | acc.push(new MappedListItem(symbol, module, spriteFilename)); 103 | } 104 | 105 | return acc; 106 | }, []); 107 | 108 | // Additional pass to interpolate [hash] in spriteFilename 109 | const itemsBySpriteFilename = MappedList.groupItemsBySpriteFilename(data); 110 | const filenames = Object.keys(itemsBySpriteFilename); 111 | 112 | filenames.forEach((filename) => { 113 | if (!filename.includes('hash')) { 114 | return; 115 | } 116 | 117 | const items = itemsBySpriteFilename[filename]; 118 | const spriteSymbols = items.map(item => item.symbol); 119 | const content = spriteSymbols.map(s => s.render()).join(''); 120 | const interpolatedName = interpolate(filename, { 121 | resourcePath: filename, 122 | content 123 | }); 124 | 125 | items 126 | .filter(item => item.spriteFilename !== interpolatedName) 127 | .forEach(item => item.spriteFilename = interpolatedName); 128 | }); 129 | 130 | return data; 131 | } 132 | 133 | /** 134 | * @return {Object} 135 | */ 136 | groupItemsBySpriteFilename() { 137 | return MappedList.groupItemsBySpriteFilename(this.items); 138 | } 139 | 140 | /** 141 | * @param {Function} [mapper] Custom grouper function 142 | * @return {Object} 143 | */ 144 | groupItemsBySymbolFile(mapper) { 145 | return MappedList.groupItemsBySymbolFile(this.items, mapper); 146 | } 147 | } 148 | 149 | module.exports = MappedList; 150 | -------------------------------------------------------------------------------- /2.0.md: -------------------------------------------------------------------------------- 1 | # svg-sprite-loader 2.0 overview 2 | 3 | ## Breaking changes & how to migrate 4 | 5 | tl;dr: 6 | - Node.js >= 6 is required. 7 | - `name` option was renamed to `symbolId`. 8 | - `regExp`, `prefixize`, `angularBaseWorkaround` options was removed and will have no effect. 9 | Prefixize applies for symbol elements by default. Workaround for Angular enables automatically when `'angular' in window === true`. 10 | - Runtime API has changed, but compatible mode available via `runtimeCompat: true`. 11 | 12 | In most cases following config should work: 13 | 14 | ```js 15 | // webpack 1 16 | module.exports = { 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.svg$/, 21 | loader: 'svg-sprite-loader?runtimeCompat=true' 22 | } 23 | ] 24 | } 25 | } 26 | 27 | // webpack 2 28 | module.exports = { 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.svg$/, 33 | loader: 'svg-sprite-loader', 34 | options: { 35 | runtimeCompat: true 36 | } 37 | } 38 | ] 39 | } 40 | } 41 | ``` 42 | 43 | ## Features, improvements and bugfixes 44 | 45 | ### Auto configuring 46 | 47 | Some magic now [happens](https://github.com/JetBrains/svg-sprite-loader/tree/master/lib/configurator.js#L20) by default, viz: 48 | 49 | - Used runtime module depends on webpack 'target' config option: [browser sprite module](https://github.com/JetBrains/svg-sprite-loader/blob/85ce360c37fa09ce6932c6d9d32635d2eed9756a/runtime/browser-sprite.js) will be used for 'web' target, and [isomorphic sprite module](https://github.com/JetBrains/svg-sprite-loader/blob/85ce360c37fa09ce6932c6d9d32635d2eed9756a/runtime/sprite.js) for all other targets. 50 | - Loader switches in extract mode automatically if SVG image was imported from css/scss/html etc (see [EXTRACTABLE_MODULE_ISSUER_PATTERN](https://github.com/JetBrains/svg-sprite-loader/blob/85ce360c37fa09ce6932c6d9d32635d2eed9756a/lib/config.js#L8)). 51 | - Generated export format depends on webpack version, `module.exports = ...` for webpack 1, `export default ...` for webpack 2. 52 | 53 | ### Sprite generator 54 | 55 | - Sprite/symbol generator was moved to separate project [svg-baker](https://github.com/JetBrains/svg-baker) and fully reworked. 56 | 57 | ### Client runtime 58 | 59 | - New runtime API. Instead of symbol id runtime module now returns an object (class instance actually) which contains `id`, `viewBox` and `content` fields. 60 | Reason: make runtime more flexible, also it was requested in [#32](https://github.com/JetBrains/svg-sprite-loader/issues/32). 61 | 62 | ```js 63 | // old 64 | import symbolId from './image.svg'; 65 | // symbolId === '#image' 66 | 67 | const rendered = ` 68 | 69 | 70 | `; 71 | 72 | 73 | // new 74 | import symbol from './image.svg'; 75 | // symbol === SpriteSymbol 76 | 77 | const rendered = ` 78 | 79 | 80 | `; 81 | ``` 82 | 83 | If you need old behaviour, set `runtimeCompat` option to `true`. 84 | 85 | - Sprite/symbol javascript runtime was moved to separate project [svg-baker-runtime](https://github.com/JetBrains/svg-baker/tree/master/packages/svg-baker-runtime) and fully reworked. 86 | - Added ability to specify custom runtime generator via `runtimeGenerator` option (check default [runtime generator](https://github.com/JetBrains/svg-sprite-loader/blob/85ce360c37fa09ce6932c6d9d32635d2eed9756a/lib/runtime-generator.js) for example). 87 | - Runtime symbol is an object now (class instance actually). It contains `id`, `viewBox` and `content` fields. See [SpriteSymbol class](https://github.com/JetBrains/svg-baker/blob/edb3814a5ec2d11ef940955739d86d4af7a2474d/packages/svg-baker-runtime/src/symbol.js). Fixes [#32](https://github.com/JetBrains/svg-sprite-loader/issues/32). 88 | - Base URL fix in `style` attributes ([svg-baker-runtime@efd32](https://github.com/JetBrains/svg-baker/blob/efd324c4d9dfc2f82b5fe7ecf71c3ff07812593b/packages/svg-baker-runtime/test/utils.test.js#L132)). Fixes [#7](https://github.com/JetBrains/svg-sprite-loader/issues/7). 89 | - Encode special chars in url when modifying attributes ([svg-baker-runtime@efd32](https://github.com/JetBrains/svg-baker/blob/efd324c4d9dfc2f82b5fe7ecf71c3ff07812593b/packages/svg-baker-runtime/test/utils.test.js#L164)). Fixes [#79](https://github.com/JetBrains/svg-sprite-loader/issues/79). 90 | 91 | ### Server side rendering 92 | 93 | - Server side rendering done right! See [example](https://github.com/JetBrains/svg-sprite-loader/tree/master/examples/server-side-rendering). Fixes [#19](https://github.com/JetBrains/svg-sprite-loader/issues/19). 94 | 95 | ### Extract sprite/sprites as separate file/files 96 | 97 | - Extract sprite as separate file done right! See [example](https://github.com/JetBrains/svg-sprite-loader/tree/master/examples/extract-sprite). Fixes [#66](https://github.com/JetBrains/svg-sprite-loader/issues/66), [#73](https://github.com/JetBrains/svg-sprite-loader/issues/73), [#83](https://github.com/JetBrains/svg-sprite-loader/issues/32). 98 | - Ability to extract multiple separate sprites by webpack loader config (example will be soon). 99 | - Ability to extract sprite for each chunk, like extract-text-webpack-plugin (example will be soon). Experimental feature, should be used with caution. 100 | 101 | 102 | -------------------------------------------------------------------------------- /runtime/sprite.build.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.Sprite = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 8 | 9 | 10 | 11 | 12 | 13 | function createCommonjsModule(fn, module) { 14 | return module = { exports: {} }, fn(module, module.exports), module.exports; 15 | } 16 | 17 | var deepmerge$1 = createCommonjsModule(function (module, exports) { 18 | (function (root, factory) { 19 | if (typeof undefined === 'function' && undefined.amd) { 20 | undefined(factory); 21 | } else { 22 | module.exports = factory(); 23 | } 24 | }(commonjsGlobal, function () { 25 | 26 | function isMergeableObject(val) { 27 | var nonNullObject = val && typeof val === 'object'; 28 | 29 | return nonNullObject 30 | && Object.prototype.toString.call(val) !== '[object RegExp]' 31 | && Object.prototype.toString.call(val) !== '[object Date]' 32 | } 33 | 34 | function emptyTarget(val) { 35 | return Array.isArray(val) ? [] : {} 36 | } 37 | 38 | function cloneIfNecessary(value, optionsArgument) { 39 | var clone = optionsArgument && optionsArgument.clone === true; 40 | return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value 41 | } 42 | 43 | function defaultArrayMerge(target, source, optionsArgument) { 44 | var destination = target.slice(); 45 | source.forEach(function(e, i) { 46 | if (typeof destination[i] === 'undefined') { 47 | destination[i] = cloneIfNecessary(e, optionsArgument); 48 | } else if (isMergeableObject(e)) { 49 | destination[i] = deepmerge(target[i], e, optionsArgument); 50 | } else if (target.indexOf(e) === -1) { 51 | destination.push(cloneIfNecessary(e, optionsArgument)); 52 | } 53 | }); 54 | return destination 55 | } 56 | 57 | function mergeObject(target, source, optionsArgument) { 58 | var destination = {}; 59 | if (isMergeableObject(target)) { 60 | Object.keys(target).forEach(function (key) { 61 | destination[key] = cloneIfNecessary(target[key], optionsArgument); 62 | }); 63 | } 64 | Object.keys(source).forEach(function (key) { 65 | if (!isMergeableObject(source[key]) || !target[key]) { 66 | destination[key] = cloneIfNecessary(source[key], optionsArgument); 67 | } else { 68 | destination[key] = deepmerge(target[key], source[key], optionsArgument); 69 | } 70 | }); 71 | return destination 72 | } 73 | 74 | function deepmerge(target, source, optionsArgument) { 75 | var array = Array.isArray(source); 76 | var options = optionsArgument || { arrayMerge: defaultArrayMerge }; 77 | var arrayMerge = options.arrayMerge || defaultArrayMerge; 78 | 79 | if (array) { 80 | return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument) 81 | } else { 82 | return mergeObject(target, source, optionsArgument) 83 | } 84 | } 85 | 86 | deepmerge.all = function deepmergeAll(array, optionsArgument) { 87 | if (!Array.isArray(array) || array.length < 2) { 88 | throw new Error('first argument should be an array with at least two elements') 89 | } 90 | 91 | // we are sure there are at least 2 values, so it is safe to have no initial value 92 | return array.reduce(function(prev, next) { 93 | return deepmerge(prev, next, optionsArgument) 94 | }) 95 | }; 96 | 97 | return deepmerge 98 | 99 | })); 100 | }); 101 | 102 | var namespaces_1 = createCommonjsModule(function (module, exports) { 103 | var namespaces = { 104 | svg: { 105 | name: 'xmlns', 106 | uri: 'http://www.w3.org/2000/svg' 107 | }, 108 | xlink: { 109 | name: 'xmlns:xlink', 110 | uri: 'http://www.w3.org/1999/xlink' 111 | } 112 | }; 113 | 114 | exports.default = namespaces; 115 | module.exports = exports.default; 116 | }); 117 | 118 | /** 119 | * @param {Object} attrs 120 | * @return {string} 121 | */ 122 | var objectToAttrsString = function (attrs) { 123 | return Object.keys(attrs).map(function (attr) { 124 | var value = attrs[attr].toString().replace(/"/g, '"'); 125 | return (attr + "=\"" + value + "\""); 126 | }).join(' '); 127 | }; 128 | 129 | var svg = namespaces_1.svg; 130 | var xlink = namespaces_1.xlink; 131 | 132 | var defaultAttrs = {}; 133 | defaultAttrs[svg.name] = svg.uri; 134 | defaultAttrs[xlink.name] = xlink.uri; 135 | 136 | /** 137 | * @param {string} [content] 138 | * @param {Object} [attributes] 139 | * @return {string} 140 | */ 141 | var wrapInSvgString = function (content, attributes) { 142 | if ( content === void 0 ) content = ''; 143 | 144 | var attrs = deepmerge$1(defaultAttrs, attributes || {}); 145 | var attrsRendered = objectToAttrsString(attrs); 146 | return ("" + content + ""); 147 | }; 148 | 149 | var svg$1 = namespaces_1.svg; 150 | var xlink$1 = namespaces_1.xlink; 151 | 152 | var defaultConfig = { 153 | attrs: ( obj = { 154 | style: 'display: none', 155 | 'aria-hidden': 'true' 156 | }, obj[svg$1.name] = svg$1.uri, obj[xlink$1.name] = xlink$1.uri, obj ) 157 | }; 158 | var obj; 159 | 160 | var Sprite = function Sprite(config) { 161 | this.config = deepmerge$1(defaultConfig, config || {}); 162 | this.symbols = []; 163 | }; 164 | 165 | /** 166 | * Add new symbol. If symbol with the same id exists it will be replaced. 167 | * @param {SpriteSymbol} symbol 168 | * @return {boolean} `true` - symbol was added, `false` - replaced 169 | */ 170 | Sprite.prototype.add = function add (symbol) { 171 | var ref = this; 172 | var symbols = ref.symbols; 173 | var existing = this.find(symbol.id); 174 | 175 | if (existing) { 176 | symbols[symbols.indexOf(existing)] = symbol; 177 | return false; 178 | } 179 | 180 | symbols.push(symbol); 181 | return true; 182 | }; 183 | 184 | /** 185 | * Remove symbol & destroy it 186 | * @param {string} id 187 | * @return {boolean} `true` - symbol was found & successfully destroyed, `false` - otherwise 188 | */ 189 | Sprite.prototype.remove = function remove (id) { 190 | var ref = this; 191 | var symbols = ref.symbols; 192 | var symbol = this.find(id); 193 | 194 | if (symbol) { 195 | symbols.splice(symbols.indexOf(symbol), 1); 196 | symbol.destroy(); 197 | return true; 198 | } 199 | 200 | return false; 201 | }; 202 | 203 | /** 204 | * @param {string} id 205 | * @return {SpriteSymbol|null} 206 | */ 207 | Sprite.prototype.find = function find (id) { 208 | return this.symbols.filter(function (s) { return s.id === id; })[0] || null; 209 | }; 210 | 211 | /** 212 | * @param {string} id 213 | * @return {boolean} 214 | */ 215 | Sprite.prototype.has = function has (id) { 216 | return this.find(id) !== null; 217 | }; 218 | 219 | /** 220 | * @return {string} 221 | */ 222 | Sprite.prototype.stringify = function stringify () { 223 | var ref = this.config; 224 | var attrs = ref.attrs; 225 | var stringifiedSymbols = this.symbols.map(function (s) { return s.stringify(); }).join(''); 226 | return wrapInSvgString(stringifiedSymbols, attrs); 227 | }; 228 | 229 | /** 230 | * @return {string} 231 | */ 232 | Sprite.prototype.toString = function toString () { 233 | return this.stringify(); 234 | }; 235 | 236 | Sprite.prototype.destroy = function destroy () { 237 | this.symbols.forEach(function (s) { return s.destroy(); }); 238 | }; 239 | 240 | var sprite = new Sprite({ attrs: { id: '__SVG_SPRITE_NODE__' } }); 241 | 242 | return sprite; 243 | 244 | }))); 245 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const merge = require('deepmerge'); 3 | const Promise = require('bluebird'); 4 | const SVGCompiler = require('@workato/svg-baker'); 5 | const spriteFactory = require('@workato/svg-baker/lib/sprite-factory'); 6 | const Sprite = require('@workato/svg-baker/lib/sprite'); 7 | const { NAMESPACE } = require('./config'); 8 | const { 9 | MappedList, 10 | replaceInModuleSource, 11 | replaceSpritePlaceholder, 12 | getMatchedRule 13 | } = require('./utils'); 14 | 15 | const defaultConfig = { 16 | plainSprite: false, 17 | spriteAttrs: {} 18 | }; 19 | 20 | class SVGSpritePlugin { 21 | constructor(cfg = {}) { 22 | const config = merge.all([defaultConfig, cfg]); 23 | this.config = config; 24 | 25 | const spriteFactoryOptions = { 26 | attrs: config.spriteAttrs 27 | }; 28 | 29 | if (config.plainSprite) { 30 | spriteFactoryOptions.styles = false; 31 | spriteFactoryOptions.usages = false; 32 | } 33 | 34 | this.factory = ({ symbols }) => { 35 | const opts = merge.all([spriteFactoryOptions, { symbols }]); 36 | return spriteFactory(opts); 37 | }; 38 | 39 | this.svgCompiler = new SVGCompiler(); 40 | this.rules = {}; 41 | } 42 | 43 | /** 44 | * This need to find plugin from loader context 45 | */ 46 | // eslint-disable-next-line class-methods-use-this 47 | get NAMESPACE() { 48 | return NAMESPACE; 49 | } 50 | 51 | getReplacements() { 52 | const isPlainSprite = this.config.plainSprite === true; 53 | const replacements = this.map.groupItemsBySymbolFile((acc, item) => { 54 | acc[item.resource] = isPlainSprite ? item.url : item.useUrl; 55 | }); 56 | return replacements; 57 | } 58 | 59 | // TODO optimize MappedList instantiation in each hook 60 | apply(compiler) { 61 | this.rules = getMatchedRule(compiler); 62 | 63 | const path = this.rules.outputPath ? this.rules.outputPath : this.rules.publicPath; 64 | this.filenamePrefix = path 65 | ? path.replace(/^\//, '') 66 | : ''; 67 | 68 | if (compiler.hooks) { 69 | compiler.hooks 70 | .thisCompilation 71 | .tap(NAMESPACE, (compilation) => { 72 | try { 73 | // eslint-disable-next-line global-require 74 | const NormalModule = require('webpack/lib/NormalModule'); 75 | NormalModule.getCompilationHooks(compilation).loader 76 | .tap(NAMESPACE, loaderContext => loaderContext[NAMESPACE] = this); 77 | } catch (e) { 78 | compilation.hooks 79 | .normalModuleLoader 80 | .tap(NAMESPACE, loaderContext => loaderContext[NAMESPACE] = this); 81 | } 82 | 83 | compilation.hooks 84 | .afterOptimizeChunks 85 | .tap(NAMESPACE, () => this.afterOptimizeChunks(compilation)); 86 | 87 | if (compilation.hooks.optimizeExtractedChunks) { 88 | compilation.hooks 89 | .optimizeExtractedChunks 90 | .tap(NAMESPACE, chunks => this.optimizeExtractedChunks(chunks)); 91 | } 92 | 93 | compilation.hooks 94 | .additionalAssets 95 | .tapPromise(NAMESPACE, () => { 96 | return this.additionalAssets(compilation); 97 | }); 98 | }); 99 | 100 | compiler.hooks 101 | .compilation 102 | .tap(NAMESPACE, (compilation) => { 103 | if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) { 104 | compilation.hooks 105 | .htmlWebpackPluginBeforeHtmlGeneration 106 | .tapAsync(NAMESPACE, (htmlPluginData, callback) => { 107 | htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation); 108 | 109 | callback(null, htmlPluginData); 110 | }); 111 | } 112 | 113 | if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) { 114 | compilation.hooks 115 | .htmlWebpackPluginBeforeHtmlProcessing 116 | .tapAsync(NAMESPACE, (htmlPluginData, callback) => { 117 | htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData); 118 | 119 | callback(null, htmlPluginData); 120 | }); 121 | } 122 | }); 123 | } else { 124 | // Handle only main compilation 125 | compiler.plugin('this-compilation', (compilation) => { 126 | // Share svgCompiler with loader 127 | compilation.plugin('normal-module-loader', (loaderContext) => { 128 | loaderContext[NAMESPACE] = this; 129 | }); 130 | 131 | // Replace placeholders with real URL to symbol (in modules processed by svg-sprite-loader) 132 | compilation.plugin('after-optimize-chunks', () => this.afterOptimizeChunks(compilation)); 133 | 134 | // Hook into extract-text-webpack-plugin to replace placeholders with real URL to symbol 135 | compilation.plugin('optimize-extracted-chunks', chunks => this.optimizeExtractedChunks(chunks)); 136 | 137 | // Hook into html-webpack-plugin to add `sprites` variable into template context 138 | compilation.plugin('html-webpack-plugin-before-html-generation', (htmlPluginData, done) => { 139 | htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation); 140 | 141 | done(null, htmlPluginData); 142 | }); 143 | 144 | // Hook into html-webpack-plugin to replace placeholders with real URL to symbol 145 | compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, done) => { 146 | htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData); 147 | done(null, htmlPluginData); 148 | }); 149 | 150 | // Create sprite chunk 151 | compilation.plugin('additional-assets', (done) => { 152 | return this.additionalAssets(compilation) 153 | .then(() => { 154 | done(); 155 | return true; 156 | }) 157 | .catch(e => done(e)); 158 | }); 159 | }); 160 | } 161 | } 162 | 163 | additionalAssets(compilation) { 164 | const itemsBySprite = this.map.groupItemsBySpriteFilename(); 165 | const filenames = Object.keys(itemsBySprite); 166 | 167 | return Promise.map(filenames, (filename) => { 168 | const spriteSymbols = itemsBySprite[filename].map(item => item.symbol); 169 | 170 | return Sprite.create({ 171 | symbols: spriteSymbols, 172 | factory: this.factory 173 | }) 174 | .then((sprite) => { 175 | const content = sprite.render(); 176 | 177 | compilation.assets[`${this.filenamePrefix}${filename}`] = { 178 | source() { return content; }, 179 | size() { return content.length; }, 180 | updateHash(bulkUpdateDecorator) { bulkUpdateDecorator.update(content); } 181 | }; 182 | }); 183 | }); 184 | } 185 | 186 | afterOptimizeChunks(compilation) { 187 | const { symbols } = this.svgCompiler; 188 | this.map = new MappedList(symbols, compilation); 189 | const replacements = this.getReplacements(); 190 | this.map.items.forEach(item => replaceInModuleSource(item.module, replacements)); 191 | } 192 | 193 | optimizeExtractedChunks(chunks) { 194 | const replacements = this.getReplacements(); 195 | 196 | chunks.forEach((chunk) => { 197 | let modules; 198 | 199 | if (chunk.modulesIterable) { 200 | modules = Array.from(chunk.modulesIterable); 201 | } else { 202 | modules = chunk.modules; 203 | } 204 | 205 | modules 206 | // dirty hack to identify modules extracted by extract-text-webpack-plugin 207 | // TODO refactor 208 | .filter(module => '_originalModule' in module) 209 | .forEach(module => replaceInModuleSource(module, replacements)); 210 | }); 211 | } 212 | 213 | beforeHtmlGeneration(compilation) { 214 | const itemsBySprite = this.map.groupItemsBySpriteFilename(); 215 | 216 | const sprites = Object.keys(itemsBySprite).reduce((acc, filename) => { 217 | acc[this.filenamePrefix + filename] = compilation.assets[this.filenamePrefix + filename].source(); 218 | return acc; 219 | }, {}); 220 | 221 | return sprites; 222 | } 223 | 224 | beforeHtmlProcessing(htmlPluginData) { 225 | const replacements = this.getReplacements(); 226 | return replaceSpritePlaceholder(htmlPluginData.html, replacements); 227 | } 228 | } 229 | 230 | module.exports = SVGSpritePlugin; 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVG sprite loader 2 | [![NPM version][version-img]][versions-img] [![Build status][ci-img]][ci-url] [![Documentation score][docs-coverage-img]][docs-coverage-url] [![Dependencies status][deps-img]][deps-url] [![Dev dependencies status][dev-deps-img]][dev-deps-url] [![NPM downloads][downloads-img]][npm-url] 3 | 4 | Webpack loader for creating SVG sprites. 5 | 6 | > :tada: 2.0 is out, please read the [migration guide & overview](2.0.md). 7 | 8 | > :warning: For old v0.x versions see [the README in the v0 branch](https://github.com/JetBrains/svg-sprite-loader/blob/v0/README.md). 9 | 10 | ## Table of contents 11 | 12 | - [Why it's cool](#why-its-cool) 13 | - [Installation](#installation) 14 | - [Configuration](#configuration) 15 | - [`symbolId`](#symbol-id) 16 | - [`symbolRegExp`](#symbol-regexp) 17 | - [`esModule`](#es-module) 18 | - [Runtime configuration](#runtime-configuration) 19 | - [`spriteModule`](#sprite-module) 20 | - [`symbolModule`](#symbol-module) 21 | - [`runtimeGenerator`](#runtime-generator) 22 | - [`runtimeCompat`](#runtime-compat) (deprecated) 23 | - [`runtimeOptions`](#runtime-options) 24 | - [Extract configuration](#extract-configuration) 25 | - [`extract`](#extract) 26 | - [`spriteFilename`](#sprite-filename) 27 | - [`publicPath`](#public-path) 28 | - [`outputPath`](#output-path) 29 | - [`plainSprite`](#plain-sprite) 30 | - [`spriteAttrs`](#sprite-attrs) 31 | - [Examples](#examples) 32 | - [Contributing guidelines](#contributing-guidelines) 33 | - [License](#license) 34 | - [Credits](#credits) 35 | 36 | ## Why it's cool 37 | 38 | - **Minimum initial configuration**. Most of the options are configured automatically. 39 | - **Runtime for browser**. Sprites are rendered and injected in pages automatically, you just refer to images via ``. 40 | - **Isomorphic runtime for node/browser**. Can render sprites on server or in browser manually. 41 | - **Customizable**. Write/extend runtime module to implement custom sprite behaviour. Write/extend runtime generator to produce your own runtime, e.g. React component configured with imported symbol. 42 | - **External sprite file** is generated for images imported from css/scss/sass/less/styl/html ([SVG stacking technique](https://css-tricks.com/svg-fragment-identifiers-work/#article-header-id-4)). 43 | 44 | ## Installation 45 | 46 | ```bash 47 | npm install svg-sprite-loader -D 48 | # via yarn 49 | yarn add svg-sprite-loader -D 50 | ``` 51 | 52 | ## Configuration 53 | 54 | ```js 55 | // webpack 1 56 | { 57 | test: /\.svg$/, 58 | loader: 'svg-sprite-loader', 59 | query: { ... } 60 | } 61 | 62 | // webpack 1 multiple loaders 63 | { 64 | test: /\.svg$/, 65 | loaders: [ 66 | `svg-sprite-loader?${JSON.stringify({ ... })}`, 67 | 'svg-transform-loader', 68 | 'svgo-loader' 69 | ] 70 | } 71 | 72 | // webpack >= 2 73 | { 74 | test: /\.svg$/, 75 | loader: 'svg-sprite-loader', 76 | options: { ... } 77 | } 78 | 79 | // webpack >= 2 multiple loaders 80 | { 81 | test: /\.svg$/, 82 | use: [ 83 | { loader: 'svg-sprite-loader', options: { ... } }, 84 | 'svg-transform-loader', 85 | 'svgo-loader' 86 | ] 87 | } 88 | ``` 89 | 90 | 91 | ### `symbolId` (`string | function(path, query)`, default `[name]`) 92 | 93 | How `` `id` attribute should be named. All patterns from [loader-utils#interpolateName](https://github.com/webpack/loader-utils#interpolatename) 94 | are supported. Also can be a function which accepts 2 args - file path and query string and return symbol id: 95 | 96 | ```js 97 | { 98 | symbolId: filePath => path.basename(filePath) 99 | } 100 | ``` 101 | 102 | 103 | ### `symbolRegExp` (default `''`) 104 | Passed to the symbolId interpolator to support the [N] pattern in the loader-utils name interpolator 105 | 106 | 107 | ### `esModule` (default `true`, autoconfigured) 108 | 109 | Generated export format: 110 | - when `true` loader will produce `export default ...`. 111 | - when `false` the result is `module.exports = ...`. 112 | 113 | By default depends on used webpack version: `true` for webpack >= 2, `false` otherwise. 114 | 115 | ## Runtime configuration 116 | 117 | When you require an image, loader transforms it to SVG ``, adds it to the special sprite storage and returns class instance 118 | that represents symbol. It contains `id`, `viewBox` and `content` (`id`, `viewBox` and `url` in extract mode) 119 | fields and can later be used for referencing the sprite image, e.g: 120 | 121 | ```js 122 | import twitterLogo from './logos/twitter.svg'; 123 | // twitterLogo === SpriteSymbol 124 | // Extract mode: SpriteSymbol 125 | 126 | const rendered = ` 127 | 128 | 129 | `; 130 | ``` 131 | 132 | When browser event `DOMContentLoaded` is fired, sprite will be automatically rendered and injected in the `document.body`. 133 | If custom behaviour is needed (e.g. a different mounting target) default sprite module could be overridden via `spriteModule` option. Check example below. 134 | 135 | 136 | ### `spriteModule` (autoconfigured) 137 | 138 | Path to sprite module that will be compiled and executed at runtime. 139 | By default it depends on [`target`](https://webpack.js.org/configuration/target) webpack config option: 140 | - `svg-sprite-loader/runtime/browser-sprite.build` for 'web' target. 141 | - `svg-sprite-loader/runtime/sprite.build` for other targets. 142 | 143 | If you need custom behavior, use this option to specify a path of your sprite implementation module. 144 | Path will be resolved relative to the current webpack build folder, e.g. `utils/sprite.js` placed in current project dir should be written as `./utils/sprite`. 145 | 146 | Example of sprite with custom mounting target (copypasted from [browser-sprite](https://github.com/JetBrains/svg-sprite-loader/blob/master/runtime/browser-sprite.js)): 147 | 148 | ```js 149 | import BrowserSprite from '@workato/svg-baker-runtime/src/browser-sprite'; 150 | import domready from 'domready'; 151 | 152 | const sprite = new BrowserSprite(); 153 | domready(() => sprite.mount('#my-custom-mounting-target')); 154 | 155 | export default sprite; // don't forget to export! 156 | ``` 157 | 158 | It's highly recommended to extend default sprite classes: 159 | - [for browser-specific env](https://github.com/JetBrains/svg-baker/blob/master/packages/svg-baker-runtime/src/browser-sprite.js) 160 | - [for isomorphic env](https://github.com/JetBrains/svg-baker/blob/master/packages/svg-baker-runtime/src/sprite.js) 161 | 162 | 163 | ### `symbolModule` (autoconfigured) 164 | 165 | Same as `spriteModule`, but for sprite symbol. By default also depends on `target` webpack config option: 166 | - `svg-baker-runtime/browser-symbol` for 'web' target. 167 | - `svg-baker-runtime/symbol` for other targets. 168 | 169 | 170 | ### `runtimeGenerator` ([default generator](https://github.com/JetBrains/svg-sprite-loader/blob/master/lib/runtime-generator.js)) 171 | 172 | Path to node.js script that generates client runtime. 173 | Use this option if you need to produce your own runtime, e.g. React component configured with imported symbol. [Example](https://github.com/JetBrains/svg-sprite-loader/tree/master/examples/custom-runtime-generator). 174 | 175 | 176 | ### `runtimeCompat` (default `false`, deprecated) 177 | 178 | Should runtime be compatible with earlier v0.x loader versions. This option will be removed in the next major version release. 179 | 180 | 181 | ### `runtimeOptions` 182 | 183 | Arbitrary data passed to runtime generator. Reserved for future use when other runtime generators will be created. 184 | 185 | ## Extract configuration 186 | 187 | In the extract mode loader should be configured with plugin, otherwise an error is thrown. Example: 188 | 189 | ```js 190 | // webpack.config.js 191 | const SpriteLoaderPlugin = require('svg-sprite-loader/plugin'); 192 | 193 | ... 194 | 195 | { 196 | plugins: [ 197 | new SpriteLoaderPlugin() 198 | ] 199 | } 200 | ``` 201 | 202 | 203 | ### `extract` (default `false`, autoconfigured) 204 | 205 | Switches loader to the extract mode. 206 | Enabled automatically for images imported from css/scss/sass/less/styl/html files. 207 | 208 | 209 | ### `spriteFilename` (type `string|Function`,default `sprite.svg`) 210 | 211 | Filename of extracted sprite. Multiple sprites can be generated by specifying different loader rules restricted with `include` option or 212 | by providing custom function which recieves SVG file absolute path, e.g.: 213 | 214 | ```js 215 | { 216 | test: /\.svg$/, 217 | loader: 'svg-sprite-loader', 218 | options: { 219 | extract: true, 220 | spriteFilename: svgPath => `sprite${svgPath.substr(-4)}` 221 | } 222 | } 223 | ``` 224 | 225 | `[hash]` in sprite filename will be replaced by it's content hash. 226 | It is also possible to generate sprite for each chunk by using `[chunkname]` pattern in spriteFilename option. This is experimental feature, use it with caution! 227 | 228 | 229 | ### `publicPath` (type: `string`, default: `__webpack_public_path__`) 230 | 231 | Custom public path for sprite file. 232 | 233 | ```js 234 | { 235 | test: /\.svg$/, 236 | loader: 'svg-sprite-loader', 237 | options: { 238 | extract: true, 239 | publicPath: '/' 240 | } 241 | } 242 | ``` 243 | 244 | 245 | ### `outputPath` (type: `string`, default: null`) 246 | 247 | Custom output path for sprite file. 248 | By default it will use `publicPath`. 249 | This param is useful if you want to store sprite is a directory with a custom http access. 250 | 251 | ```js 252 | { 253 | test: /\.svg$/, 254 | loader: 'svg-sprite-loader', 255 | options: { 256 | extract: true, 257 | outputPath: 'custom-dir/sprites/' 258 | publicPath: 'sprites/' 259 | } 260 | } 261 | ``` 262 | 263 | 264 | ### Plain sprite 265 | 266 | You can render plain sprite in extract mode without styles and usages. Pass `plainSprite: true` option to plugin constructor: 267 | 268 | ```js 269 | { 270 | plugins: [ 271 | new SpriteLoaderPlugin({ plainSprite: true }) 272 | ] 273 | } 274 | ``` 275 | 276 | 277 | ### Sprite attributes 278 | 279 | Sprite `` tag attributes can be specified via `spriteAttrs` plugin option: 280 | 281 | ```js 282 | { 283 | plugins: [ 284 | new SpriteLoaderPlugin({ 285 | plainSprite: true, 286 | spriteAttrs: { 287 | id: 'my-custom-sprite-id' 288 | } 289 | }) 290 | ] 291 | } 292 | ``` 293 | 294 | ## Examples 295 | 296 | See [examples](examples) folder. 297 | 298 | ## Contributing guidelines 299 | 300 | See [CONTRIBUTING.md](CONTRIBUTING.md). 301 | 302 | ## License 303 | 304 | See [LICENSE](LICENSE) 305 | 306 | ## Credits 307 | 308 | Huge thanks for [all this people](https://github.com/JetBrains/svg-sprite-loader/graphs/contributors). 309 | 310 | [npm-url]: https://www.npmjs.com/package/svg-sprite-loader 311 | [version-img]: https://img.shields.io/npm/v/svg-sprite-loader.svg?style=flat-square 312 | [versions-img]: https://libraries.io/npm/svg-sprite-loader/versions 313 | [downloads-img]: https://img.shields.io/npm/dm/svg-sprite-loader.svg?style=flat-square 314 | [deps-url]: https://david-dm.org/JetBrains/svg-sprite-loader 315 | [deps-img]: https://img.shields.io/david/JetBrains/svg-sprite-loader.svg?style=flat-square 316 | [dev-deps-url]: https://david-dm.org/JetBrains/svg-sprite-loader?type=dev 317 | [dev-deps-img]: https://img.shields.io/david/dev/JetBrains/svg-sprite-loader.svg?style=flat-square 318 | [ci-url]: https://travis-ci.org/JetBrains/svg-sprite-loader 319 | [ci-img]: https://img.shields.io/travis/JetBrains/svg-sprite-loader.svg?style=flat-square 320 | [docs-coverage-url]: https://inch-ci.org/github/JetBrains/svg-sprite-loader 321 | [docs-coverage-img]: https://inch-ci.org/github/JetBrains/svg-sprite-loader.svg?branch=master&style=flat-square 322 | -------------------------------------------------------------------------------- /test/loader.test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlPlugin = require('html-webpack-plugin'); 4 | 5 | const { isWebpack1 } = require('../lib/utils'); 6 | const { loaderPath, fixturesPath } = require('./_config'); 7 | const { 8 | rule, 9 | rules, 10 | multiRule, 11 | svgRule, 12 | svgInsideOneOfRule, 13 | svgInsideRulesRule, 14 | compile, 15 | extractPlugin, 16 | extractCSSRule, 17 | extractHTMLRule, 18 | compileAndNotReject 19 | } = require('./utils'); 20 | 21 | const SpritePlugin = require('../lib/plugin'); 22 | const loaderDefaults = require('../lib/config').loader; 23 | const Exceptions = require('../lib/exceptions'); 24 | 25 | const defaultSpriteFilename = loaderDefaults.spriteFilename; 26 | 27 | describe('loader and plugin', () => { 28 | describe('normal mode', () => { 29 | describe('exceptions', () => { 30 | it('should emit error if invalid runtime passed', async () => { 31 | const { errors } = await compileAndNotReject({ 32 | entry: './entry', 33 | module: rules( 34 | svgRule({ runtimeGenerator: 'qwe', symbolId: 'qwe' }) 35 | ) 36 | }); 37 | 38 | errors.should.be.lengthOf(1); 39 | errors[0].error.should.be.instanceOf(Exceptions.InvalidRuntimeException); 40 | }); 41 | }); 42 | 43 | it('should allow to use custom runtime generator', async () => { 44 | const customRuntimeGeneratorPath = path.resolve(fixturesPath, 'custom-runtime-generator.js'); 45 | 46 | const { assets } = await compile({ 47 | entry: './entry', 48 | module: rules( 49 | svgRule({ runtimeGenerator: customRuntimeGeneratorPath }) 50 | ) 51 | }); 52 | 53 | assets['main.js'].source().should.contain('olala'); 54 | }); 55 | }); 56 | 57 | describe('extract mode', () => { 58 | function cssRule() { 59 | return rule({ test: /\.css$/, loader: 'css-loader' }); 60 | } 61 | 62 | describe('exceptions', () => { 63 | it('should emit error if loader used without plugin in extract mode', async () => { 64 | const { errors } = await compileAndNotReject({ 65 | entry: './entry', 66 | module: rules( 67 | svgRule({ extract: true }) 68 | ) 69 | }); 70 | 71 | errors.should.be.lengthOf(1); 72 | errors[0].error.should.be.instanceOf(Exceptions.ExtractPluginMissingException); 73 | }); 74 | 75 | it('should warn if there is remaining loaders in extract mode', async () => { 76 | const v4Config = {}; 77 | if (process.env.WEBPACK_VERSION === '4') { 78 | v4Config.mode = 'development'; 79 | v4Config.devtool = false; 80 | } 81 | 82 | const { warnings } = await compile(Object.assign(v4Config, { 83 | entry: './entry', 84 | module: rules( 85 | multiRule({ 86 | test: /\.svg$/, 87 | use: [ 88 | 'file-loader', 89 | { loader: loaderPath, options: { extract: true } }, 90 | 'svgo-loader' 91 | ] 92 | }) 93 | ), 94 | plugins: [new SpritePlugin()] 95 | })); 96 | 97 | warnings.should.be.lengthOf(1); 98 | warnings[0].warning.should.be.instanceOf(Exceptions.RemainingLoadersInExtractModeException); 99 | }); 100 | }); 101 | 102 | describe('extract-text-webpack-plugin interop', () => { 103 | it('should properly extract sprite file and refer to it', async () => { 104 | const spriteFilename = defaultSpriteFilename; 105 | const extractor = extractPlugin('[name].css'); 106 | const { assets } = await compile({ 107 | entry: './styles3.css', 108 | module: rules( 109 | svgRule({ spriteFilename }), 110 | extractCSSRule(extractor) 111 | ), 112 | plugins: [new SpritePlugin(), extractor] 113 | }); 114 | 115 | const cssSource = assets['main.css'].source(); 116 | 117 | Object.keys(assets).should.be.lengthOf(3); 118 | assets.should.have.property(spriteFilename); 119 | 120 | cssSource.should.be.equal(`.a {background-image: url(${spriteFilename}#image-usage);} 121 | .a2 {background-image: url(${spriteFilename}#image-usage);} 122 | .b {background-image: url(${spriteFilename}#image2-usage);} 123 | .b2 {background-image: url(${spriteFilename}#image2-usage);} 124 | `); 125 | }); 126 | 127 | it('should properly extract sprite file and refer to it in `plainSprite` mode', async () => { 128 | const spriteFilename = defaultSpriteFilename; 129 | const extractor = extractPlugin('[name].css'); 130 | const { assets } = await compile({ 131 | entry: './styles3.css', 132 | module: rules( 133 | svgRule({ spriteFilename }), 134 | extractCSSRule(extractor) 135 | ), 136 | plugins: [ 137 | new SpritePlugin({ 138 | plainSprite: true 139 | }), 140 | extractor 141 | ] 142 | }); 143 | 144 | const cssSource = assets['main.css'].source(); 145 | 146 | Object.keys(assets).should.be.lengthOf(3); 147 | assets.should.have.property(spriteFilename); 148 | 149 | cssSource.should.be.equal(`.a {background-image: url(${spriteFilename}#image);} 150 | .a2 {background-image: url(${spriteFilename}#image);} 151 | .b {background-image: url(${spriteFilename}#image2);} 152 | .b2 {background-image: url(${spriteFilename}#image2);} 153 | `); 154 | }); 155 | 156 | it('should properly extract sprite file by function spriteFilename `svgPath -> spritePath`', async () => { 157 | const spriteFilename = svgPath => `sprite${svgPath.substr(-4)}`; 158 | const universalResult = spriteFilename('path/to/some/icon.svg'); 159 | const extractor = extractPlugin('[name].css'); 160 | const { assets } = await compile({ 161 | entry: './styles3.css', 162 | module: rules( 163 | svgRule({ spriteFilename }), 164 | extractCSSRule(extractor) 165 | ), 166 | plugins: [new SpritePlugin(), extractor] 167 | }); 168 | 169 | const cssSource = assets['main.css'].source(); 170 | 171 | Object.keys(assets).should.be.lengthOf(3); 172 | assets.should.have.property(universalResult); 173 | 174 | cssSource.should.be.equal(`.a {background-image: url(${universalResult}#image-usage);} 175 | .a2 {background-image: url(${universalResult}#image-usage);} 176 | .b {background-image: url(${universalResult}#image2-usage);} 177 | .b2 {background-image: url(${universalResult}#image2-usage);} 178 | `); 179 | }); 180 | 181 | it('should work properly with `allChunks: true` config option', async () => { 182 | const spriteFilename = 'qwe.svg'; 183 | const extractor = extractPlugin('[name].css', { allChunks: true }); 184 | 185 | const { assets } = await compile({ 186 | entry: { 187 | styles: './styles.css', 188 | styles2: './styles2.css' 189 | }, 190 | module: rules( 191 | svgRule({ spriteFilename }), 192 | extractCSSRule(extractor) 193 | ), 194 | plugins: [ 195 | new SpritePlugin(), 196 | extractor 197 | ] 198 | }); 199 | 200 | Object.keys(assets).should.be.lengthOf(5); 201 | assets.should.have.property(spriteFilename); 202 | assets['styles.css'].source().should.contain(spriteFilename); 203 | assets['styles2.css'].source().should.contain(spriteFilename); 204 | }); 205 | 206 | /** 207 | * TODO test in webpack 1 with allChunks: true and [chunkname] 208 | * Currently [chunkname] will not work in webpack 1 with extract-text-webpack-plugin(allChunks: true) 209 | */ 210 | it('should emit sprite for each extracted chunk if [chunkname] provided in `spriteFilename`', async () => { 211 | const extractor = extractPlugin('[name].css', { allChunks: true }); 212 | 213 | const v4Config = {}; 214 | if (process.env.WEBPACK_VERSION === '4') { 215 | v4Config.mode = 'development'; 216 | v4Config.devtool = false; 217 | } 218 | 219 | const { assets } = await compile(Object.assign(v4Config, { 220 | entry: { 221 | entry: './entry-with-styles', 222 | entry2: './entry-with-styles2' 223 | }, 224 | module: rules( 225 | svgRule({ spriteFilename: '[chunkname].svg' }), 226 | extractCSSRule(extractor) 227 | ), 228 | plugins: [extractor, new SpritePlugin()] 229 | })); 230 | 231 | /** 232 | * Chunk.name is undefined in webpack 1 with extract-text-webpack-plugin(allChunks: true), 233 | * so it will be only 1 sprite file instead of 2 234 | */ 235 | Object.keys(assets).should.be.lengthOf(isWebpack1 ? 5 : 6); 236 | if (isWebpack1) { 237 | return; 238 | } 239 | 240 | assets.should.have.property('entry.svg'); 241 | assets.should.have.property('entry2.svg'); 242 | assets['entry.css'].source().should.contain('entry.svg'); 243 | assets['entry2.css'].source().should.contain('entry2.svg'); 244 | }); 245 | 246 | it('should work in combination with CommonsChunkPlugin', async () => { 247 | try { 248 | const extractor = extractPlugin('[name].css'); 249 | const { assets } = await compile({ 250 | context: path.resolve(fixturesPath, 'extract-text-webpack-plugin/with-commons-chunk-plugin'), 251 | entry: { 252 | entry: './entry', 253 | entry2: './entry2', 254 | entry3: './entry3' 255 | }, 256 | module: rules( 257 | svgRule({ spriteFilename: '[chunkname].svg' }), 258 | extractCSSRule(extractor) 259 | ), 260 | plugins: [ 261 | extractor, 262 | new SpritePlugin(), 263 | new webpack.optimize.CommonsChunkPlugin({ 264 | name: 'common' 265 | }) 266 | ] 267 | }); 268 | 269 | assets['common.css'].source().should.contain('common.svg'); 270 | } catch (e) { 271 | e.message.should.contain('webpack.optimize.CommonsChunkPlugin has been removed'); 272 | } 273 | }); 274 | }); 275 | 276 | describe('html-loader interop', () => { 277 | it('should work in combination with html-loader and extract-text-webpack-plugin', async () => { 278 | const spriteFilename = defaultSpriteFilename; 279 | const extractor = extractPlugin('[name].html'); 280 | 281 | const { assets } = await compile({ 282 | entry: './entry.html', 283 | module: rules( 284 | svgRule({ spriteFilename }), 285 | extractHTMLRule(extractor) 286 | ), 287 | plugins: [ 288 | new SpritePlugin(), 289 | extractor 290 | ] 291 | }); 292 | 293 | assets['main.html'].source().should.contain(`img src="${spriteFilename}#`); 294 | }); 295 | }); 296 | 297 | describe('html-webpack-plugin interop', () => { 298 | it('should work', async () => { 299 | const { assets } = await compile({ 300 | entry: './entry', 301 | module: rules( 302 | svgRule({ extract: true }), 303 | rule({ 304 | test: /template\.ejs$/, 305 | loader: 'html-loader' 306 | }) 307 | ), 308 | plugins: [ 309 | new HtmlPlugin({ 310 | filename: 'index.html', 311 | template: path.resolve(fixturesPath, 'html-webpack-plugin/template.ejs') 312 | }), 313 | new SpritePlugin() 314 | ] 315 | }); 316 | 317 | assets['index.html'].source().should.contain(defaultSpriteFilename); 318 | }); 319 | }); 320 | 321 | // webpack 3 scope hoisting interop 322 | if (process.env.WEBPACK_VERSION === '3') { 323 | // eslint-disable-next-line global-require,import/no-unresolved 324 | const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin'); 325 | 326 | describe('webpack ModuleConcatenationPlugin interop', () => { 327 | it('should work', async () => { 328 | const spriteFilename = 'qwe.svg'; 329 | const { assets } = await compile({ 330 | entry: './entry-es6-import', 331 | module: rules( 332 | svgRule({ extract: true, spriteFilename }) 333 | ), 334 | plugins: [ 335 | new SpritePlugin(), 336 | new ModuleConcatenationPlugin() 337 | ] 338 | }); 339 | 340 | Object.keys(assets).should.be.lengthOf(2); 341 | assets.should.have.property(spriteFilename); 342 | }); 343 | 344 | // TODO 345 | it('should properly interpolate [chunkname]', () => { 346 | 347 | }); 348 | }); 349 | } 350 | 351 | it('should automatically detect modules to extract', async () => { 352 | const { assets } = await compile({ 353 | entry: './entry', 354 | module: rules( 355 | svgRule() 356 | ), 357 | plugins: [new SpritePlugin()] 358 | }); 359 | 360 | Object.keys(assets).should.be.lengthOf(1); 361 | }); 362 | 363 | it('should support `oneOf` composition of rule', async () => { 364 | const { assets } = await compile({ 365 | entry: './entry', 366 | module: rules( 367 | svgInsideOneOfRule() 368 | ), 369 | plugins: [new SpritePlugin()] 370 | }); 371 | 372 | Object.keys(assets).should.be.lengthOf(1); 373 | }); 374 | 375 | it('should support `rules` composition of rule', async () => { 376 | const { assets } = await compile({ 377 | entry: './entry', 378 | module: rules( 379 | svgInsideRulesRule() 380 | ), 381 | plugins: [new SpritePlugin()] 382 | }); 383 | 384 | Object.keys(assets).should.be.lengthOf(1); 385 | }); 386 | 387 | it('should allow to specify custom sprite filename', async () => { 388 | const spriteFilename = 'qwe.svg'; 389 | 390 | const { assets } = await compile({ 391 | entry: './styles.css', 392 | module: rules( 393 | svgRule({ spriteFilename }), 394 | cssRule() 395 | ), 396 | plugins: [new SpritePlugin()] 397 | }); 398 | 399 | Object.keys(assets).should.be.lengthOf(2); 400 | assets.should.have.property(spriteFilename); 401 | }); 402 | 403 | it('should emit sprite for each chunk if [chunkname] presented in sprite filename', async () => { 404 | const { assets } = await compile({ 405 | entry: { 406 | entry: './entry-with-styles', 407 | entry2: './entry-with-styles2' 408 | }, 409 | module: rules( 410 | multiRule({ 411 | test: /\.svg$/, 412 | use: [ 413 | { loader: loaderPath, options: { spriteFilename: '[chunkname]-sprite.svg' } }, 414 | 'svgo-loader' 415 | ] 416 | }), 417 | cssRule() 418 | ), 419 | plugins: [new SpritePlugin()] 420 | }); 421 | 422 | Object.keys(assets).should.be.lengthOf(4); 423 | assets.should.have.property('entry-sprite.svg'); 424 | assets.should.have.property('entry2-sprite.svg'); 425 | }); 426 | 427 | // Fails when webpack buildin runtime will change 428 | it('should replace with proper publicPath', async () => { 429 | const publicPath = '/olala/'; 430 | const spriteFilename = defaultSpriteFilename; 431 | 432 | const v4Config = {}; 433 | if (process.env.WEBPACK_VERSION === '4') { 434 | v4Config.mode = 'development'; 435 | v4Config.devtool = false; 436 | } 437 | 438 | const { assets } = await compile(Object.assign(v4Config, { 439 | entry: './entry', 440 | output: { publicPath }, 441 | module: rules( 442 | svgRule({ extract: true, spriteFilename }) 443 | ), 444 | plugins: [new SpritePlugin()] 445 | })); 446 | 447 | assets['main.js'].source().should.contain(`__webpack_require__.p + "${spriteFilename}`); 448 | }); 449 | 450 | it('should generate asset with output path without changing publicPath', async () => { 451 | const publicPath = '/olala/'; 452 | const spriteFilename = defaultSpriteFilename; 453 | 454 | const v4Config = {}; 455 | if (process.env.WEBPACK_VERSION === '4') { 456 | v4Config.mode = 'development'; 457 | v4Config.devtool = false; 458 | } 459 | 460 | const { assets } = await compile(Object.assign(v4Config, { 461 | entry: './entry', 462 | output: { publicPath }, 463 | module: rules( 464 | svgRule({ extract: true, spriteFilename, outputPath: '/foo/' }) 465 | ), 466 | plugins: [new SpritePlugin()] 467 | })); 468 | 469 | assets['main.js'].source().should.contain(`__webpack_require__.p + "${spriteFilename}`); 470 | }); 471 | 472 | it('should emit only built chunks', () => { 473 | // TODO test with webpack-recompilation-emulator 474 | }); 475 | 476 | it('should emit sprite svg when using resourceQuery', async () => { 477 | const { assets } = await compile({ 478 | entry: './styles4.css', 479 | module: rules( 480 | Object.assign(svgRule({ extract: true }), { 481 | resourceQuery: /sprite/ 482 | }), 483 | cssRule() 484 | ), 485 | plugins: [new SpritePlugin()] 486 | }); 487 | 488 | Object.keys(assets).should.be.lengthOf(2); 489 | }); 490 | }); 491 | }); 492 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [6.0.9](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.8...v6.0.9) (2021-06-23) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **utils:** force get-matched-rule-4 to use nearest webpack installation ([#463](https://github.com/JetBrains/svg-sprite-loader/issues/463)) ([dcaa65a](https://github.com/JetBrains/svg-sprite-loader/commit/dcaa65a)) 12 | 13 | 14 | 15 | 16 | ## [6.0.8](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.7...v6.0.8) (2021-06-21) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * **utils:** fix: some options had no effect with webpack5 ([#460](https://github.com/JetBrains/svg-sprite-loader/issues/460)) ([839f878](https://github.com/JetBrains/svg-sprite-loader/commit/839f878)), closes [#446](https://github.com/JetBrains/svg-sprite-loader/issues/446) 22 | 23 | 24 | 25 | 26 | ## [6.0.7](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.6...v6.0.7) (2021-05-28) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **utils:** Account for rule property "generator" ([#454](https://github.com/JetBrains/svg-sprite-loader/issues/454)) ([9fc86f1](https://github.com/JetBrains/svg-sprite-loader/commit/9fc86f1)), closes [#428](https://github.com/JetBrains/svg-sprite-loader/issues/428) [vuejs/vue-loader#1753](https://github.com/vuejs/vue-loader/issues/1753) 32 | 33 | 34 | 35 | 36 | ## [6.0.6](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.5...v6.0.6) (2021-04-23) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **utils:** use webpack object version if package can't be required ([f4b82c2](https://github.com/JetBrains/svg-sprite-loader/commit/f4b82c2)) 42 | 43 | 44 | 45 | 46 | ## [6.0.5](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.4...v6.0.5) (2021-04-10) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **utils:** use moduleGraph in is-module-should-be-extracted.js ([6099812](https://github.com/JetBrains/svg-sprite-loader/commit/6099812)) 52 | 53 | 54 | 55 | 56 | ## [6.0.4](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.3...v6.0.4) (2021-04-10) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * **utils:** NormalModule for webpack 5 ([00886bc](https://github.com/JetBrains/svg-sprite-loader/commit/00886bc)) 62 | 63 | 64 | 65 | 66 | ## [6.0.3](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.2...v6.0.3) (2021-04-10) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * **utils:** fix 'Properties descriptionData are unknown' with webpack5 ([5a4fc6a](https://github.com/JetBrains/svg-sprite-loader/commit/5a4fc6a)) 72 | * **utils:** use moduleGraph and chunkGraph if possible ([9e25d18](https://github.com/JetBrains/svg-sprite-loader/commit/9e25d18)) 73 | 74 | 75 | 76 | 77 | ## [6.0.2](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.1...v6.0.2) (2021-03-18) 78 | 79 | 80 | 81 | 82 | ## [6.0.1](https://github.com/JetBrains/svg-sprite-loader/compare/v6.0.0...v6.0.1) (2021-03-18) 83 | 84 | 85 | 86 | 87 | # [6.0.0](https://github.com/JetBrains/svg-sprite-loader/compare/v5.2.1...v6.0.0) (2021-03-13) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * **additional-assets:** adds missing method ([8db9c4d](https://github.com/JetBrains/svg-sprite-loader/commit/8db9c4d)) 93 | * **loader:** get rid of webpack--version ([54a0c6b](https://github.com/JetBrains/svg-sprite-loader/commit/54a0c6b)), closes [#437](https://github.com/JetBrains/svg-sprite-loader/issues/437) [#417](https://github.com/JetBrains/svg-sprite-loader/issues/417) 94 | 95 | 96 | ### BREAKING CHANGES 97 | 98 | * **loader:** delete get-webpack-version.js 99 | 100 | 101 | 102 | 103 | ## [5.2.1](https://github.com/JetBrains/svg-sprite-loader/compare/v5.2.0...v5.2.1) (2020-12-14) 104 | 105 | 106 | 107 | 108 | # [5.2.0](https://github.com/JetBrains/svg-sprite-loader/compare/v5.1.1...v5.2.0) (2020-12-14) 109 | 110 | 111 | ### Features 112 | 113 | * update-dependency ([d93c5cf](https://github.com/JetBrains/svg-sprite-loader/commit/d93c5cf)) 114 | 115 | 116 | 117 | 118 | ## [5.1.1](https://github.com/JetBrains/svg-sprite-loader/compare/v5.1.0...v5.1.1) (2020-12-05) 119 | 120 | 121 | 122 | 123 | # [5.1.0](https://github.com/JetBrains/svg-sprite-loader/compare/v5.0.0...v5.1.0) (2020-12-05) 124 | 125 | 126 | ### Features 127 | 128 | * add support for webpack5 ([b34b2b2](https://github.com/JetBrains/svg-sprite-loader/commit/b34b2b2)) 129 | * nodejs update ([00f4fc2](https://github.com/JetBrains/svg-sprite-loader/commit/00f4fc2)) 130 | 131 | 132 | 133 | 134 | # [5.0.0](https://github.com/JetBrains/svg-sprite-loader/compare/v4.3.0...v5.0.0) (2020-05-11) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * pass proper context param to runtime generator ([c084ec7](https://github.com/JetBrains/svg-sprite-loader/commit/c084ec7)), closes [#186](https://github.com/JetBrains/svg-sprite-loader/issues/186) 140 | 141 | 142 | ### BREAKING CHANGES 143 | 144 | * Possible breaks third-party runtime generators. Earlier `context` param was containing _path to compilation root context_, e.g. folder where webpack compilation occurs. This is wrong behaviour, because meaning of this param is a _folder where svg image is located_. So it was changed in this commit. 145 | If your custom runtime generator breaks after this update use `loaderContext.rootContext` option instead of `context`. 146 | 147 | 148 | 149 | 150 | # [4.3.0](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.7...v4.3.0) (2020-05-03) 151 | 152 | 153 | ### Features 154 | 155 | * **outputPath:** add possibility to define output path ([2c7eceb](https://github.com/JetBrains/svg-sprite-loader/commit/2c7eceb)) 156 | 157 | 158 | 159 | 160 | ## [4.2.7](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.6...v4.2.7) (2020-04-28) 161 | 162 | 163 | ### Bug Fixes 164 | 165 | * move `mask` & `clipPath` elements outside symbol ([ae70786](https://github.com/JetBrains/svg-sprite-loader/commit/ae70786)), closes [#325](https://github.com/JetBrains/svg-sprite-loader/issues/325) 166 | 167 | 168 | 169 | 170 | ## [4.2.6](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.5...v4.2.6) (2020-04-26) 171 | 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * drop webpack version detector ([7131578](https://github.com/JetBrains/svg-sprite-loader/commit/7131578)) 177 | 178 | 179 | 180 | 181 | ## [4.2.5](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.4...v4.2.5) (2020-04-12) 182 | 183 | 184 | ### Bug Fixes 185 | 186 | * refers to transpiled code in svg-baker-runtime ([65ece05](https://github.com/JetBrains/svg-sprite-loader/commit/65ece05)), closes [#385](https://github.com/JetBrains/svg-sprite-loader/issues/385) 187 | 188 | 189 | 190 | 191 | ## [4.2.4](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.3...v4.2.4) (2020-04-12) 192 | 193 | 194 | ### Bug Fixes 195 | 196 | * don't create additional chunk for sprite ([80ebfa3](https://github.com/JetBrains/svg-sprite-loader/commit/80ebfa3)), closes [#364](https://github.com/JetBrains/svg-sprite-loader/issues/364) 197 | 198 | 199 | 200 | 201 | ## [4.2.3](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.2...v4.2.3) (2020-04-08) 202 | 203 | 204 | ### Bug Fixes 205 | 206 | * check properly when window.angular properly ([1c76824](https://github.com/JetBrains/svg-sprite-loader/commit/1c76824)) 207 | 208 | 209 | 210 | 211 | ## [4.2.2](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.1...v4.2.2) (2020-04-02) 212 | 213 | 214 | ### Bug Fixes 215 | 216 | * add aria-hidden attribute to sprite node for proper accessibility behaviour ([bb08665](https://github.com/JetBrains/svg-sprite-loader/commit/bb08665)), closes [#315](https://github.com/JetBrains/svg-sprite-loader/issues/315) 217 | 218 | 219 | 220 | 221 | ## [4.2.1](https://github.com/JetBrains/svg-sprite-loader/compare/v4.2.0...v4.2.1) (2020-01-31) 222 | 223 | 224 | ### Bug Fixes 225 | 226 | * drop npm-shrinkwrap.json due to it causes installation of old dependencies when npm is used ([7439e61](https://github.com/JetBrains/svg-sprite-loader/commit/7439e61)), closes [#378](https://github.com/JetBrains/svg-sprite-loader/issues/378) [#379](https://github.com/JetBrains/svg-sprite-loader/issues/379) 227 | 228 | 229 | 230 | 231 | # [4.2.0](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.6...v4.2.0) (2020-01-24) 232 | 233 | 234 | ### Bug Fixes 235 | 236 | * get-webpack-version.js ([6508469](https://github.com/JetBrains/svg-sprite-loader/commit/6508469)) 237 | 238 | 239 | ### Features 240 | 241 | * add clipPath & mask to "move-from-symbol-to-root" transform defaults ([02d0c33](https://github.com/JetBrains/svg-sprite-loader/commit/02d0c33)), closes [#288](https://github.com/JetBrains/svg-sprite-loader/issues/288) [#325](https://github.com/JetBrains/svg-sprite-loader/issues/325) 242 | 243 | 244 | 245 | 246 | ## [4.1.6](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.5...v4.1.6) (2019-04-27) 247 | 248 | 249 | ### Bug Fixes 250 | 251 | * incorrect items order after sorting ([ded8146](https://github.com/JetBrains/svg-sprite-loader/commit/ded8146)) 252 | 253 | 254 | 255 | 256 | ## [4.1.5](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.4...v4.1.5) (2019-04-27) 257 | 258 | 259 | ### Bug Fixes 260 | 261 | * replace all instances of urls in attribute ([d6fdf94](https://github.com/JetBrains/svg-sprite-loader/commit/d6fdf94)), closes [#300](https://github.com/JetBrains/svg-sprite-loader/issues/300) 262 | 263 | 264 | 265 | 266 | ## [4.1.4](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.3...v4.1.4) (2019-04-27) 267 | 268 | 269 | ### Bug Fixes 270 | 271 | * incorrect detection webpack version, closes [#309](https://github.com/JetBrains/svg-sprite-loader/issues/309) 272 | * Fix the bug of publicPath, closes [#311](https://github.com/JetBrains/svg-sprite-loader/issues/311) 273 | * **configuration** make possible to use `oneOf` in loader rules 274 | * **configuration:** make default config work with yarn PnP ([dc931e2](https://github.com/JetBrains/svg-sprite-loader/commit/dc931e2)) 275 | * **runtime-generator:** fix module not found errors for custom spriteModule or symbolModule ([44bbcfe](https://github.com/JetBrains/svg-sprite-loader/commit/44bbcfe)) 276 | 277 | 278 | 279 | 280 | ## [4.1.3](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.2...v4.1.3) (2018-10-29) 281 | 282 | 283 | ### Bug Fixes 284 | 285 | * update svg-baker to fix security vulnerability ([4408ccd](https://github.com/JetBrains/svg-sprite-loader/commit/4408ccd)) 286 | 287 | 288 | 289 | 290 | ## [4.1.2](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.1...v4.1.2) (2018-09-28) 291 | 292 | 293 | ### Bug Fixes 294 | 295 | * drop webpack dependency ([6254f9c](https://github.com/JetBrains/svg-sprite-loader/commit/6254f9c)) 296 | 297 | 298 | 299 | 300 | ## [4.1.1](https://github.com/JetBrains/svg-sprite-loader/compare/v4.1.0...v4.1.1) (2018-09-19) 301 | 302 | 303 | ### Bug Fixes 304 | 305 | * interpolate publicPath properly ([22f10e6](https://github.com/JetBrains/svg-sprite-loader/commit/22f10e6)) 306 | 307 | 308 | 309 | 310 | # [4.1.0](https://github.com/JetBrains/svg-sprite-loader/compare/v4.0.0...v4.1.0) (2018-09-19) 311 | 312 | 313 | ### Features 314 | 315 | * add ARIA attrs to whitelist symbol attributes ([4bbccab](https://github.com/JetBrains/svg-sprite-loader/commit/4bbccab)), closes [#304](https://github.com/JetBrains/svg-sprite-loader/issues/304) 316 | 317 | 318 | 319 | 320 | # [4.0.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.9.0...v4.0.0) (2018-09-19) 321 | 322 | 323 | 324 | 325 | # [3.9.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.8.0...v3.9.0) (2018-05-26) 326 | 327 | 328 | ### Features 329 | 330 | * support symbol id interpolator as function ([3691d86](https://github.com/JetBrains/svg-sprite-loader/commit/3691d86)) 331 | 332 | 333 | 334 | 335 | # [3.8.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.7.3...v3.8.0) (2018-05-26) 336 | 337 | 338 | ### Bug Fixes 339 | 340 | * error link in pr template ([7b6cf30](https://github.com/JetBrains/svg-sprite-loader/commit/7b6cf30)) 341 | 342 | 343 | ### Features 344 | 345 | * support `publicPath` option ([793a7bf](https://github.com/JetBrains/svg-sprite-loader/commit/793a7bf)) 346 | * support custom outputPath ([80f7520](https://github.com/JetBrains/svg-sprite-loader/commit/80f7520)) 347 | 348 | 349 | 350 | 351 | ## [3.7.3](https://github.com/JetBrains/svg-sprite-loader/compare/v3.7.2...v3.7.3) (2018-03-19) 352 | 353 | 354 | ### Bug Fixes 355 | 356 | * **utils:** fix linting errors ([f5239a0](https://github.com/JetBrains/svg-sprite-loader/commit/f5239a0)) 357 | * **utils:** prevent errors for modules without resources ([467daa6](https://github.com/JetBrains/svg-sprite-loader/commit/467daa6)) 358 | 359 | 360 | 361 | 362 | ## [3.7.2](https://github.com/JetBrains/svg-sprite-loader/compare/v3.7.1...v3.7.2) (2018-03-19) 363 | 364 | 365 | 366 | 367 | ## [3.7.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.7.0...v3.7.1) (2018-03-12) 368 | 369 | 370 | ### Bug Fixes 371 | 372 | * **loader:** wrong build target ([66e98f9](https://github.com/JetBrains/svg-sprite-loader/commit/66e98f9)) 373 | 374 | 375 | 376 | 377 | # [3.7.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.6.2...v3.7.0) (2018-03-10) 378 | 379 | 380 | ### Features 381 | 382 | * add webpack 4 support ([20f59ca](https://github.com/JetBrains/svg-sprite-loader/commit/20f59ca)) 383 | 384 | 385 | 386 | 387 | ## [3.6.2](https://github.com/JetBrains/svg-sprite-loader/compare/v3.6.1...v3.6.2) (2017-12-27) 388 | 389 | 390 | ### Bug Fixes 391 | 392 | * **plugin:** prevent outer symbol ast modifications ([0c8c3d0](https://github.com/JetBrains/svg-sprite-loader/commit/0c8c3d0)) 393 | 394 | 395 | 396 | 397 | ## [3.6.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.6.0...v3.6.1) (2017-12-25) 398 | 399 | 400 | ### Bug Fixes 401 | 402 | * **plugin:** don't hide sprite by default ([fd629bd](https://github.com/JetBrains/svg-sprite-loader/commit/fd629bd)) 403 | 404 | 405 | 406 | 407 | # [3.6.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.5.4...v3.6.0) (2017-12-22) 408 | 409 | 410 | ### Features 411 | 412 | * **plugin:** add ability to specify sprite attributes in extract mode ([a6a5e30](https://github.com/JetBrains/svg-sprite-loader/commit/a6a5e30)) 413 | 414 | 415 | 416 | 417 | ## [3.5.4](https://github.com/JetBrains/svg-sprite-loader/compare/v3.5.3...v3.5.4) (2017-12-19) 418 | 419 | 420 | ### Bug Fixes 421 | 422 | * **plugin:** refer to sprite file properly in plainSprite mode ([e4789a4](https://github.com/JetBrains/svg-sprite-loader/commit/e4789a4)) 423 | 424 | 425 | 426 | 427 | ## [3.5.3](https://github.com/JetBrains/svg-sprite-loader/compare/v3.5.2...v3.5.3) (2017-12-19) 428 | 429 | 430 | ### Bug Fixes 431 | 432 | * **loader:** drop "2 applied rules" warning ([e18f7d6](https://github.com/JetBrains/svg-sprite-loader/commit/e18f7d6)) 433 | 434 | 435 | 436 | 437 | ## [3.5.2](https://github.com/JetBrains/svg-sprite-loader/compare/v3.5.1...v3.5.2) (2017-12-12) 438 | 439 | 440 | ### Bug Fixes 441 | 442 | * **loader:** #203 support use in rule.oneOf ([cb3d2da](https://github.com/JetBrains/svg-sprite-loader/commit/cb3d2da)) 443 | 444 | 445 | 446 | 447 | ## [3.5.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.5.0...v3.5.1) (2017-12-08) 448 | 449 | 450 | ### Bug Fixes 451 | 452 | * **package:** drop postinstall script ([8a46c78](https://github.com/JetBrains/svg-sprite-loader/commit/8a46c78)) 453 | 454 | 455 | 456 | 457 | # [3.5.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.4.1...v3.5.0) (2017-12-08) 458 | 459 | 460 | ### Features 461 | 462 | * **loader:** add functional type of spriteFilename ([297a957](https://github.com/JetBrains/svg-sprite-loader/commit/297a957)) 463 | 464 | 465 | 466 | 467 | ## [3.4.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.4.0...v3.4.1) (2017-10-23) 468 | 469 | 470 | ### Bug Fixes 471 | 472 | * **plugin:** make sprite file hash more stable ([7e9c53e](https://github.com/JetBrains/svg-sprite-loader/commit/7e9c53e)) 473 | 474 | 475 | 476 | 477 | # [3.4.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.3.1...v3.4.0) (2017-10-19) 478 | 479 | 480 | ### Features 481 | 482 | * better html-webpack-plugin interop in extract mode ([8a2d63e](https://github.com/JetBrains/svg-sprite-loader/commit/8a2d63e)) 483 | 484 | 485 | 486 | 487 | ## [3.3.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.3.0...v3.3.1) (2017-10-16) 488 | 489 | 490 | ### Bug Fixes 491 | 492 | * **loader:** check this.cacheable existence before call it ([c1ed50a](https://github.com/JetBrains/svg-sprite-loader/commit/c1ed50a)) 493 | 494 | 495 | 496 | 497 | # [3.3.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.6...v3.3.0) (2017-10-16) 498 | 499 | 500 | ### Features 501 | 502 | * **plugin:** allow to render external sprite without styles and usages ([fcf3939](https://github.com/JetBrains/svg-sprite-loader/commit/fcf3939)) 503 | 504 | 505 | 506 | 507 | ## [3.2.6](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.5...v3.2.6) (2017-09-28) 508 | 509 | 510 | ### Bug Fixes 511 | 512 | * **loader:** #187 support rule.oneOf config ([75df92e](https://github.com/JetBrains/svg-sprite-loader/commit/75df92e)) 513 | * **loader:** support resourceQuery in extract mode with webpack version above 1 ([6652d78](https://github.com/JetBrains/svg-sprite-loader/commit/6652d78)) 514 | 515 | 516 | 517 | 518 | ## [3.2.5](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.4...v3.2.5) (2017-08-31) 519 | 520 | 521 | ### Bug Fixes 522 | 523 | * **runtime:** don't encode "(", ")" and "~" symbols when replacing urls in references ([388ce74](https://github.com/JetBrains/svg-sprite-loader/commit/388ce74)) 524 | 525 | 526 | 527 | 528 | ## [3.2.4](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.3...v3.2.4) (2017-08-20) 529 | 530 | 531 | ### Bug Fixes 532 | 533 | * preserve \`fill\` and \`stroke\` attrs when transform svg to symbol ([d845aa3](https://github.com/JetBrains/svg-sprite-loader/commit/d845aa3)) 534 | 535 | 536 | 537 | 538 | ## [3.2.3](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.2...v3.2.3) (2017-08-18) 539 | 540 | 541 | ### Bug Fixes 542 | 543 | * deal with deprecation warnings from webpack 3 ([d150035](https://github.com/JetBrains/svg-sprite-loader/commit/d150035)) 544 | 545 | 546 | 547 | 548 | ## [3.2.2](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.1...v3.2.2) (2017-08-17) 549 | 550 | 551 | ### Bug Fixes 552 | 553 | * **plugin:** webpack-manifest-plugin compatibility ([d88ac31](https://github.com/JetBrains/svg-sprite-loader/commit/d88ac31)) 554 | 555 | 556 | 557 | 558 | ## [3.2.1](https://github.com/JetBrains/svg-sprite-loader/compare/v3.2.0...v3.2.1) (2017-08-16) 559 | 560 | 561 | ### Bug Fixes 562 | 563 | * **runtime:** apply styles in dynamically appended symbols in Edge ([299bfe2](https://github.com/JetBrains/svg-sprite-loader/commit/299bfe2)) 564 | 565 | 566 | 567 | 568 | # [3.2.0](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.7...v3.2.0) (2017-08-16) 569 | 570 | 571 | ### Features 572 | 573 | * **runtime:** add ability to create sprite from existing DOM node ([4056d7b](https://github.com/JetBrains/svg-sprite-loader/commit/4056d7b)) 574 | 575 | 576 | 577 | 578 | ## [3.1.7](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.6...v3.1.7) (2017-08-15) 579 | 580 | 581 | ### Bug Fixes 582 | 583 | * **runtime:** store global sprite object in window which allows to mount symbols in one place betwee ([13e3d47](https://github.com/JetBrains/svg-sprite-loader/commit/13e3d47)) 584 | 585 | 586 | 587 | 588 | ## [3.1.6](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.5...v3.1.6) (2017-08-10) 589 | 590 | 591 | ### Bug Fixes 592 | 593 | * **loader:** interpolate sprite filename properly ([b17e5e0](https://github.com/JetBrains/svg-sprite-loader/commit/b17e5e0)) 594 | 595 | 596 | 597 | 598 | ## [3.1.5](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.4...v3.1.5) (2017-08-10) 599 | 600 | 601 | ### Bug Fixes 602 | 603 | * **loader:** throw an exception if input is not valid SVG ([413246e](https://github.com/JetBrains/svg-sprite-loader/commit/413246e)), closes [#170](https://github.com/JetBrains/svg-sprite-loader/issues/170) 604 | 605 | 606 | 607 | 608 | ## [3.1.4](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.3...v3.1.4) (2017-08-09) 609 | 610 | 611 | ### Bug Fixes 612 | 613 | * **loader:** quick workaround for breaking changes in webpack@3.5 (`modules` prop dropped in ConcatenatedModule) ([f15030d](https://github.com/JetBrains/svg-sprite-loader/commit/f15030d)) 614 | 615 | 616 | 617 | 618 | ## [3.1.3](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.2...v3.1.3) (2017-08-08) 619 | 620 | 621 | ### Bug Fixes 622 | 623 | * **loader:** throw proper error message when runtime not found ([ef83fac](https://github.com/JetBrains/svg-sprite-loader/commit/ef83fac)) 624 | 625 | 626 | 627 | 628 | ## [3.1.2](https://github.com/JetBrains/svg-sprite-loader/compare/v3.1.1...v3.1.2) (2017-08-05) 629 | 630 | 631 | ### Bug Fixes 632 | 633 | * **loader:** decode entities in