├── .npmignore ├── .gitignore ├── docs ├── package.json ├── .vuepress │ ├── enhanceApp.js │ ├── public │ │ ├── vue.svg │ │ ├── webpack.svg │ │ ├── svgo.svg │ │ └── logo.svg │ ├── styles │ │ └── index.styl │ ├── config.js │ └── Example.vue ├── README.md └── faq.md ├── index.js ├── package.json ├── LICENSE ├── README.md └── yarn.lock /.npmignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs/.vuepress/dist 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vuepress dev", 5 | "build": "vuepress build" 6 | }, 7 | "dependencies": { 8 | "vue-tabs-component": "^1.5.0", 9 | "vuepress": "^1.8.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | import { Tabs, Tab } from 'vue-tabs-component'; 2 | import Logo from './public/logo.svg'; 3 | import Example from './Example'; 4 | 5 | export default ({ 6 | Vue, 7 | }) => { 8 | Vue.component('Tabs', Tabs); 9 | Vue.component('Tab', Tab); 10 | Vue.component('Logo', Logo); 11 | Vue.component('Example', Example); 12 | }; 13 | -------------------------------------------------------------------------------- /docs/.vuepress/public/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | .home .hero img 2 | max-height 140px 3 | 4 | .tabs-component-tabs 5 | list-style none 6 | display flex 7 | padding-left 0 8 | 9 | .tabs-component-tab 10 | margin-right 25px 11 | 12 | .tabs-component-tab-a 13 | color desaturate(lighten($textColor, 45%), 10%) 14 | 15 | .tabs-component-tab.is-active 16 | .tabs-component-tab-a 17 | color $accentColor 18 | 19 | .tabs-component-panels 20 | margin-bottom 40px 21 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Documentation', 3 | chainWebpack: (config) => { 4 | const svgRule = config.module.rule('svg'); 5 | 6 | svgRule.uses.clear(); 7 | 8 | svgRule 9 | .use('vue-svg-loader') 10 | .loader(require.resolve('../../index')); 11 | }, 12 | themeConfig: { 13 | repo: 'visualfanatic/vue-svg-loader', 14 | editLinks: true, 15 | nav: [ 16 | { 17 | text: 'FAQ', 18 | link: '/faq', 19 | }, 20 | ], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { optimize } = require('svgo'); 2 | const { getOptions } = require('loader-utils'); 3 | const { version } = require('vue'); 4 | const semverMajor = require('semver/functions/major') 5 | 6 | module.exports = function vueSvgLoader(svg) { 7 | const { svgo: svgoConfig } = getOptions(this) || {}; 8 | 9 | if (svgoConfig !== false) { 10 | ({ data: svg } = optimize(svg, { 11 | path: this.resourcePath, 12 | ...svgoConfig 13 | })); 14 | } 15 | 16 | if (semverMajor(version) === 2) { 17 | svg = svg.replace('${svg}`; 21 | }; 22 | -------------------------------------------------------------------------------- /docs/.vuepress/public/webpack.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-svg-loader", 3 | "description": "Use SVG files as Vue Components", 4 | "version": "0.17.0-beta.2", 5 | "keywords": [ 6 | "vue-svg-loader", 7 | "svg", 8 | "svgo", 9 | "loader", 10 | "webpack", 11 | "vue" 12 | ], 13 | "bugs": "https://github.com/visualfanatic/vue-svg-loader/issues", 14 | "repository": "visualfanatic/vue-svg-loader", 15 | "license": "MIT", 16 | "author": "Damian Stasik ", 17 | "main": "index.js", 18 | "dependencies": { 19 | "loader-utils": "^2.0.0", 20 | "semver": "^7.3.5", 21 | "svgo": "^2.2.2" 22 | }, 23 | "peerDependencies": { 24 | "vue": "^2.5.0 || ^3.0.0-0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 Damian Stasik 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 | -------------------------------------------------------------------------------- /docs/.vuepress/Example.vue: -------------------------------------------------------------------------------- 1 | 17 | 31 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

vue-svg-loader

3 |

webpack loader that lets you use SVG files as Vue components

4 |

5 | Documentation - 6 | FAQ 7 |

8 | 9 | ## Installation 10 | ``` bash 11 | npm i -D vue-svg-loader@beta 12 | 13 | yarn add --dev vue-svg-loader@beta 14 | ``` 15 | 16 | ## Basic configuration 17 | ### webpack 18 | ``` js 19 | module.exports = { 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.svg$/, 24 | use: [ 25 | 'vue-loader', 26 | 'vue-svg-loader', 27 | ], 28 | }, 29 | ], 30 | }, 31 | }; 32 | ``` 33 | ### Vue CLI 34 | ``` js 35 | module.exports = { 36 | chainWebpack: (config) => { 37 | const svgRule = config.module.rule('svg'); 38 | 39 | svgRule.uses.clear(); 40 | 41 | svgRule 42 | .use('vue-loader') 43 | .loader('vue-loader') // or `vue-loader-v16` if you are using a preview support of Vue 3 in Vue CLI 44 | .end() 45 | .use('vue-svg-loader') 46 | .loader('vue-svg-loader'); 47 | }, 48 | }; 49 | ``` 50 | 51 | ### Nuxt.js (1.x / 2.x) 52 | ``` js 53 | module.exports = { 54 | build: { 55 | extend: (config) => { 56 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')); 57 | 58 | svgRule.test = /\.(png|jpe?g|gif|webp)$/; 59 | 60 | config.module.rules.push({ 61 | test: /\.svg$/, 62 | use: [ 63 | 'vue-loader', 64 | 'vue-svg-loader', 65 | ], 66 | }); 67 | }, 68 | }, 69 | }; 70 | ``` 71 | 72 | ## Example usage 73 | ``` vue 74 | 90 | 104 | ``` 105 | 106 | ## License 107 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvisualfanatic%2Fvue-svg-loader.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvisualfanatic%2Fvue-svg-loader?ref=badge_large) 108 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: vue-svg-loader 4 | heroImage: /logo.svg 5 | actionText: Example → 6 | tagline: Use SVG files as Vue components 7 | actionLink: /#example 8 | features: 9 | - title: Easily styleable 10 | details: This loader inlines the SVGs which enables you to style aspects like for example stroke/fill color. 11 | - title: Optimized 12 | details: Each SVG you import is optimized on-the-fly using powerful SVGO without you having to do anything. 13 | - title: SSR ready 14 | details: You can import the SVG components inside the code that is going to be rendered on the server side. 15 | --- 16 | 17 | ## Installation 18 | 19 | 20 | 21 | 22 | ``` bash 23 | yarn add -D vue-svg-loader@beta 24 | ``` 25 | 26 | 27 | 28 | 29 | ``` bash 30 | npm i -D vue-svg-loader@beta 31 | ``` 32 | 33 | 34 | 35 | 36 | ## Configuration 37 | 38 | 39 | 40 | 41 | ::: warning 42 | Make sure that your current configuration is not already processing the SVG files. 43 | Check this [FAQ](/faq.html#how-to-use-both-inline-and-external-svgs) section if you want to use both inline and external SVGs. 44 | ::: 45 | 46 | ``` js 47 | module.exports = { 48 | module: { 49 | rules: [ 50 | { 51 | test: /\.svg$/, 52 | use: ['vue-loader', 'vue-svg-loader'], 53 | }, 54 | ], 55 | }, 56 | }; 57 | ``` 58 | 59 | 60 | 61 | 62 | By default Vue CLI uses the `file-loader` to process the SVG files, you can replace it in `vue.config.js`: 63 | 64 | ``` js 65 | module.exports = { 66 | chainWebpack: (config) => { 67 | const svgRule = config.module.rule('svg'); 68 | 69 | svgRule.uses.clear(); 70 | 71 | svgRule 72 | .use('vue-loader') 73 | .loader('vue-loader') 74 | .end() 75 | .use('vue-svg-loader') 76 | .loader('vue-svg-loader'); 77 | }, 78 | }; 79 | ``` 80 | 81 | 82 | 83 | 84 | Similarly to Vue CLI, you need to modify existing rule (in `nuxt.config.js`) that processes the SVG files: 85 | 86 | ``` js 87 | module.exports = { 88 | build: { 89 | extend: (config) => { 90 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')); 91 | 92 | svgRule.test = /\.(png|jpe?g|gif|webp)$/; 93 | 94 | config.module.rules.push({ 95 | test: /\.svg$/, 96 | use: ['vue-loader', 'vue-svg-loader'], 97 | }); 98 | }, 99 | }, 100 | }; 101 | ``` 102 | 103 | 104 | 105 | 106 | ## Example 107 | 108 | ### Preview 109 | 110 | 111 | 112 | ### Code 113 | 114 | <<< @/docs/.vuepress/Example.vue{4,8,12,18-20,25-27} 115 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar: auto 3 | --- 4 | 5 | # Frequently Asked Questions 6 | 7 | ## How to use both inline and external SVGs? 8 | 9 | 10 | 11 | 12 | ``` js 13 | module.exports = { 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.svg$/, 18 | oneOf: [ 19 | { 20 | resourceQuery: /inline/, 21 | use: [ 22 | 'vue-loader', 23 | 'vue-svg-loader', 24 | ], 25 | }, 26 | { 27 | loader: 'file-loader', 28 | query: { 29 | name: 'assets/[name].[hash:8].[ext]', 30 | }, 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | }; 37 | ``` 38 | 39 | 40 | 41 | 42 | ``` js 43 | module.exports = { 44 | chainWebpack: (config) => { 45 | const svgRule = config.module.rule('svg'); 46 | 47 | svgRule.uses.clear(); 48 | 49 | svgRule 50 | .oneOf('inline') 51 | .resourceQuery(/inline/) 52 | .use('vue-loader') 53 | .loader('vue-loader') 54 | .end() 55 | .use('vue-svg-loader') 56 | .loader('vue-svg-loader') 57 | .end() 58 | .end() 59 | .oneOf('external') 60 | .use('file-loader') 61 | .loader('file-loader') 62 | .options({ 63 | name: 'assets/[name].[hash:8].[ext]', 64 | }); 65 | }, 66 | }; 67 | ``` 68 | 69 | 70 | 71 | 72 | ``` js 73 | module.exports = { 74 | build: { 75 | extend: (config) => { 76 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')); 77 | 78 | svgRule.test = /\.(png|jpe?g|gif|webp)$/; 79 | 80 | config.module.rules.push({ 81 | test: /\.svg$/, 82 | oneOf: [ 83 | { 84 | resourceQuery: /inline/, 85 | use: [ 86 | 'vue-loader', 87 | 'vue-svg-loader', 88 | ], 89 | }, 90 | { 91 | loader: 'file-loader', 92 | query: { 93 | name: 'assets/[name].[hash:8].[ext]', 94 | }, 95 | }, 96 | ], 97 | }); 98 | }, 99 | }, 100 | }; 101 | ``` 102 | 103 | 104 | 105 | 106 | ``` vue 107 | 114 | 130 | 135 | ``` 136 | 137 | ## How to prefix `id` attributes? 138 | To avoid the situation where two or more SVGs are using the same `id` attribute, you can use the `prefixIds` option provided by `SVGO`. 139 | 140 | 141 | 142 | 143 | ``` js 144 | module.exports = { 145 | module: { 146 | rules: [ 147 | { 148 | test: /\.svg$/, 149 | use: [ 150 | 'vue-loader', 151 | { 152 | loader: 'vue-svg-loader', 153 | options: { 154 | svgo: { 155 | plugins: [ 156 | { prefixIds: true }, 157 | ], 158 | }, 159 | }, 160 | }, 161 | ], 162 | }, 163 | ], 164 | }, 165 | }; 166 | ``` 167 | 168 | 169 | 170 | 171 | ``` js 172 | module.exports = { 173 | chainWebpack: (config) => { 174 | const svgRule = config.module.rule('svg'); 175 | 176 | svgRule.uses.clear(); 177 | 178 | svgRule 179 | .use('vue-loader') 180 | .loader('vue-loader') 181 | .end() 182 | .use('vue-svg-loader') 183 | .loader('vue-svg-loader') 184 | .options({ 185 | svgo: { 186 | plugins: [ 187 | { prefixIds: true }, 188 | ], 189 | }, 190 | }); 191 | }, 192 | }; 193 | ``` 194 | 195 | 196 | 197 | 198 | ``` js 199 | module.exports = { 200 | build: { 201 | extend: (config) => { 202 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')); 203 | 204 | svgRule.test = /\.(png|jpe?g|gif|webp)$/; 205 | 206 | config.module.rules.push({ 207 | test: /\.svg$/, 208 | use: [ 209 | 'vue-loader', 210 | { 211 | loader: 'vue-svg-loader', 212 | options: { 213 | svgo: { 214 | plugins: [ 215 | { prefixIds: true }, 216 | ], 217 | }, 218 | }, 219 | }, 220 | ], 221 | }); 222 | }, 223 | }, 224 | }; 225 | ``` 226 | 227 | 228 | 229 | 230 | If you want to customize generated IDs, you can pass a function instead of `true` to the `prefixIds` plugin. 231 | Here is an example for generating IDs that are prefixed by the file name: 232 | 233 | 234 | 235 | 236 | ``` js 237 | const { basename } = require('path'); 238 | 239 | module.exports = { 240 | module: { 241 | rules: [ 242 | { 243 | test: /\.svg$/, 244 | use: [ 245 | 'vue-loader', 246 | { 247 | loader: 'vue-svg-loader', 248 | options: { 249 | svgo: { 250 | plugins: [ 251 | { 252 | prefixIds: { 253 | prefix: (node, { path }) => basename(path, '.svg'), 254 | delim: '-', 255 | }, 256 | }, 257 | ], 258 | }, 259 | }, 260 | }, 261 | ], 262 | }, 263 | ], 264 | }, 265 | }; 266 | ``` 267 | 268 | 269 | 270 | 271 | ``` js 272 | const { basename } = require('path'); 273 | 274 | module.exports = { 275 | chainWebpack: (config) => { 276 | const svgRule = config.module.rule('svg'); 277 | 278 | svgRule.uses.clear(); 279 | 280 | svgRule 281 | .use('vue-loader') 282 | .loader('vue-loader') 283 | .end() 284 | .use('vue-svg-loader') 285 | .loader('vue-svg-loader') 286 | .options({ 287 | svgo: { 288 | plugins: [ 289 | { 290 | prefixIds: { 291 | prefix: (node, { path }) => basename(path, '.svg'), 292 | delim: '-', 293 | }, 294 | }, 295 | ], 296 | }, 297 | }); 298 | }, 299 | }; 300 | ``` 301 | 302 | 303 | 304 | 305 | ``` js 306 | const { basename } = require('path'); 307 | 308 | module.exports = { 309 | build: { 310 | extend: (config) => { 311 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')); 312 | 313 | svgRule.test = /\.(png|jpe?g|gif|webp)$/; 314 | 315 | config.module.rules.push({ 316 | test: /\.svg$/, 317 | use: [ 318 | 'vue-loader', 319 | { 320 | loader: 'vue-svg-loader', 321 | options: { 322 | svgo: { 323 | plugins: [ 324 | { 325 | prefixIds: { 326 | prefix: (node, { path }) => basename(path, '.svg'), 327 | delim: '-', 328 | }, 329 | }, 330 | ], 331 | }, 332 | }, 333 | }, 334 | ], 335 | }); 336 | }, 337 | }, 338 | }; 339 | ``` 340 | 341 | 342 | 343 | 344 | 345 | ## How to use this loader with TypeScript? 346 | 347 | If you want to use this loader in a project that is written in TypeScript, you will get the "Cannot find module" error. 348 | To fix that you need to provide a type definition which is needed by TypeScript to know how to handle SVG components. 349 | 350 | ``` ts 351 | declare module '*.svg' { 352 | import Vue, {VueConstructor} from 'vue'; 353 | const content: VueConstructor; 354 | export default content; 355 | } 356 | ``` 357 | 358 | ## How to use this loader with Jest? 359 | 360 | There is one major issue when it comes to integrating vue-svg-loader with Jest, and it is async behaviour. Jest's transforms are synchronous, webpack loaders can be both. That means we cannot use SVGO to process the SVG files, which can be bad in some cases. It is always good idea to always pass the SVG files through SVGO before putting them in a project [(for example using this great tool)](https://jakearchibald.github.io/svgomg/), so that the end result does not contain: 361 | 362 | - XML declaration, 363 | - `