├── .babelrc ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── _config.yml ├── demo ├── App.vue ├── assets │ ├── dinosaur.svg │ ├── robot.svg │ └── ufo.svg ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ ├── webpack.prod.conf.js │ └── webpack.test.conf.js ├── components │ ├── ChangeSrcButton.vue │ └── SvgButton.vue ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── main.js └── template.html ├── dist └── plugin.js ├── package-lock.json ├── package.json ├── rollup.config.js └── src └── plugin.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-2" 7 | ], 8 | "plugins": ["transform-runtime"], 9 | "env": { 10 | "test": { 11 | "presets": ["env", "stage-2"], 12 | "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | /test/unit/coverage/ 7 | /test/e2e/reports/ 8 | selenium-debug.log 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "postcss-import": {}, 7 | "autoprefixer": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Seiya Kobayashi 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-simple-svg (V2) 2 | A simple Vue.js plugin that allows you to use a component that dynamically loads a .svg file as an inline SVG so you can easily control its style programmatically. No jQuery required. 3 | 4 | I recommend using [vue-svg-loader](https://www.npmjs.com/package/vue-svg-loader) for many cases when you just need to load a SVG file as a component. This plugin is built to cover some other cases the library doesn't fit, which are: 5 | - loading a SVG file dynamically. You don't have to hardcode the filename in the source code. Instead you can specify it at rendering time or change it even after the component is rendered. 6 | - changing fill color or stroke color of the SVG programmatically with ease and no global css usage. 7 | 8 | ### Installation: 9 | ```sh 10 | $ npm install vue-simple-svg 11 | ``` 12 | 13 | ### Usage: 14 | 1. initialize in your main file, 15 | ```javascript 16 | // as a plugin 17 | import VueSimpleSVG from 'vue-simple-svg' 18 | Vue.use(VueSimpleSVG) 19 | 20 | // or as a component 21 | import {SimpleSVG} from 'vue-simple-svg' 22 | Vue.component('simple-svg', SimpleSVG) 23 | ``` 24 | 25 | 2. specify which elements in the SVG will be manipulated their fill and stroke colors by setting dedicated class names to them 26 | ```html 27 | 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | 3. and use it in your component, 36 | ```html 37 | 49 | ``` 50 | 51 | ### Available props and events: 52 | | props | type | description | default | 53 | | ------ | ------ | ------ | ------ | 54 | | src | string | path to your SVG file | *required | 55 | | fillClassName | string | class name set to the elements in your SVG file whose fill color you want to change | '' | 56 | | fill | string | CSS-valid fill color value | '' | 57 | | strokeClassName | string | class name set to the elements in your SVG file whose stroke color you want to change | '' | 58 | | stroke | string | CSS-valid stroke color value | '' | 59 | | width | string | root SVG element's style width | 'auto' | 60 | | height | string | root SVG element's style height | 'auto' | 61 | | customId | string | root SVG element's id | '' | 62 | | customClassName | string | root SVG element's class | '' | 63 | 64 | | events | description | 65 | | ------ | ------ | 66 | | @load | called when the inline SVG is generated | 67 | 68 | 69 | ### Notes: 70 | - To generate the inline SVG properly, you need to manually clean up and edit your SVG files beforehand. Tips: remove all hardcoded inline styles and unnecessary attributes, especially the ones specifying colors. 71 | 72 | ### Demo: 73 | ![result](https://media.giphy.com/media/S9RVyPr2L9D76hpDui/giphy.gif) 74 | 75 | To run demo in your local environment, 76 | ```sh 77 | $ npm run dev-demo 78 | ``` 79 | You can see the example of how to use simple-svg component at demo/components/SvgButton.vue 80 | 81 | ### Reference: 82 | - Loading a SVG with XMLHttpRequest and DOMParser https://github.com/jonnyhaynes/inline-svg 83 | - Parsing inline svg tags https://github.com/MMF-FE/vue-svgicon 84 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /demo/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 45 | 46 | 69 | -------------------------------------------------------------------------------- /demo/assets/dinosaur.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/assets/robot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/assets/ufo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /demo/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /demo/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /demo/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seiyable/vue-simple-svg/451596980332e19d684bebb0c584f6005d799bef/demo/build/logo.png -------------------------------------------------------------------------------- /demo/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../../package.json') // '../package.json' /* --changed-- */ 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /demo/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: 'eslint-loader', 14 | enforce: 'pre', 15 | /* --changed-- */ 16 | include: [resolve('demo'), resolve('test')], // 'src' 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | /* --changed-- */ 27 | app: './main.js' // ./src/main.js 28 | }, 29 | output: { 30 | path: config.build.assetsRoot, 31 | filename: '[name].js', 32 | publicPath: process.env.NODE_ENV === 'production' 33 | ? config.build.assetsPublicPath 34 | : config.dev.assetsPublicPath 35 | }, 36 | resolve: { 37 | extensions: ['.js', '.vue', '.json'], 38 | alias: { 39 | 'vue$': 'vue/dist/vue.esm.js', 40 | '@': resolve('demo'), 41 | } 42 | }, 43 | module: { 44 | rules: [ 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | /* --changed-- */ 54 | include: [resolve('demo'), resolve('test')] // 'src' 55 | }, 56 | { 57 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 58 | loader: 'url-loader', 59 | options: { 60 | limit: 10000, 61 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 62 | } 63 | }, 64 | { 65 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 66 | loader: 'url-loader', 67 | options: { 68 | limit: 10000, 69 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 70 | } 71 | }, 72 | { 73 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 74 | loader: 'url-loader', 75 | options: { 76 | limit: 10000, 77 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 78 | } 79 | } 80 | ] 81 | }, 82 | node: { 83 | // prevent webpack from injecting useless setImmediate polyfill because Vue 84 | // source contains it (although only uses it if it's native). 85 | setImmediate: false, 86 | // prevent webpack from injecting mocks to Node native modules 87 | // that does not make sense for the client 88 | dgram: 'empty', 89 | fs: 'empty', 90 | net: 'empty', 91 | tls: 'empty', 92 | child_process: 'empty' 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /demo/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | const portfinder = require('portfinder') 10 | 11 | const HOST = process.env.HOST 12 | const PORT = process.env.PORT && Number(process.env.PORT) 13 | 14 | const devWebpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: config.dev.devtool, 20 | 21 | // these devServer options should be customized in /config/index.js 22 | devServer: { 23 | clientLogLevel: 'warning', 24 | historyApiFallback: true, 25 | hot: true, 26 | compress: true, 27 | host: HOST || config.dev.host, 28 | port: PORT || config.dev.port, 29 | open: config.dev.autoOpenBrowser, 30 | overlay: config.dev.errorOverlay 31 | ? { warnings: false, errors: true } 32 | : false, 33 | publicPath: config.dev.assetsPublicPath, 34 | proxy: config.dev.proxyTable, 35 | quiet: true, // necessary for FriendlyErrorsPlugin 36 | watchOptions: { 37 | poll: config.dev.poll, 38 | } 39 | }, 40 | plugins: [ 41 | new webpack.DefinePlugin({ 42 | 'process.env': require('../config/dev.env') 43 | }), 44 | new webpack.HotModuleReplacementPlugin(), 45 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 46 | new webpack.NoEmitOnErrorsPlugin(), 47 | // https://github.com/ampedandwired/html-webpack-plugin 48 | new HtmlWebpackPlugin({ 49 | /* --changed-- */ 50 | filename: 'index.html', // 'index.html' 51 | template: 'template.html', // 'index.html' 52 | inject: true 53 | }), 54 | ] 55 | }) 56 | 57 | module.exports = new Promise((resolve, reject) => { 58 | portfinder.basePort = process.env.PORT || config.dev.port 59 | portfinder.getPort((err, port) => { 60 | if (err) { 61 | reject(err) 62 | } else { 63 | // publish the new Port, necessary for e2e tests 64 | process.env.PORT = port 65 | // add port to devServer config 66 | devWebpackConfig.devServer.port = port 67 | 68 | // Add FriendlyErrorsPlugin 69 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 70 | compilationSuccessInfo: { 71 | /* --changed-- */ 72 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}/demo`], 73 | }, 74 | onErrors: config.dev.notifyOnErrors 75 | ? utils.createNotifierCallback() 76 | : undefined 77 | })) 78 | 79 | resolve(devWebpackConfig) 80 | } 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /demo/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = process.env.NODE_ENV === 'testing' 15 | ? require('../config/test.env') 16 | : require('../config/prod.env') 17 | 18 | const webpackConfig = merge(baseWebpackConfig, { 19 | module: { 20 | rules: utils.styleLoaders({ 21 | sourceMap: config.build.productionSourceMap, 22 | extract: true, 23 | usePostCSS: true 24 | }) 25 | }, 26 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 30 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 31 | }, 32 | plugins: [ 33 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 34 | new webpack.DefinePlugin({ 35 | 'process.env': env 36 | }), 37 | new UglifyJsPlugin({ 38 | uglifyOptions: { 39 | compress: { 40 | warnings: false 41 | } 42 | }, 43 | sourceMap: config.build.productionSourceMap, 44 | parallel: true 45 | }), 46 | // extract css into its own file 47 | new ExtractTextPlugin({ 48 | filename: utils.assetsPath('css/[name].[contenthash].css'), 49 | // set the following option to `true` if you want to extract CSS from 50 | // codesplit chunks into this main css file as well. 51 | // This will result in *all* of your app's CSS being loaded upfront. 52 | allChunks: false, 53 | }), 54 | // Compress extracted CSS. We are using this plugin so that possible 55 | // duplicated CSS from different components can be deduped. 56 | new OptimizeCSSPlugin({ 57 | cssProcessorOptions: config.build.productionSourceMap 58 | ? { safe: true, map: { inline: false } } 59 | : { safe: true } 60 | }), 61 | // generate dist index.html with correct asset hash for caching. 62 | // you can customize output by editing /index.html 63 | // see https://github.com/ampedandwired/html-webpack-plugin 64 | new HtmlWebpackPlugin({ 65 | /* --changed-- */ 66 | filename: process.env.NODE_ENV === 'testing' 67 | ? 'demo/index.html' // 'index.html' 68 | : config.build.index, 69 | template: 'demo/template.html', // 'index.html' 70 | inject: true, 71 | minify: { 72 | removeComments: true, 73 | collapseWhitespace: true, 74 | removeAttributeQuotes: true 75 | // more options: 76 | // https://github.com/kangax/html-minifier#options-quick-reference 77 | }, 78 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 79 | chunksSortMode: 'dependency' 80 | }), 81 | // keep module.id stable when vender modules does not change 82 | new webpack.HashedModuleIdsPlugin(), 83 | // enable scope hoisting 84 | new webpack.optimize.ModuleConcatenationPlugin(), 85 | // split vendor js into its own file 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'vendor', 88 | minChunks (module) { 89 | // any required modules inside node_modules are extracted to vendor 90 | return ( 91 | module.resource && 92 | /\.js$/.test(module.resource) && 93 | module.resource.indexOf( 94 | path.join(__dirname, '../node_modules') 95 | ) === 0 96 | ) 97 | } 98 | }), 99 | // extract webpack runtime and module manifest to its own file in order to 100 | // prevent vendor hash from being updated whenever app bundle is updated 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'manifest', 103 | minChunks: Infinity 104 | }), 105 | // This instance extracts shared chunks from code splitted chunks and bundles them 106 | // in a separate chunk, similar to the vendor chunk 107 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 108 | new webpack.optimize.CommonsChunkPlugin({ 109 | name: 'app', 110 | async: 'vendor-async', 111 | children: true, 112 | minChunks: 3 113 | }), 114 | 115 | // copy custom static assets 116 | new CopyWebpackPlugin([ 117 | { 118 | from: path.resolve(__dirname, '../static'), 119 | to: config.build.assetsSubDirectory, 120 | ignore: ['.*'] 121 | } 122 | ]) 123 | ] 124 | }) 125 | 126 | if (config.build.productionGzip) { 127 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 128 | 129 | webpackConfig.plugins.push( 130 | new CompressionWebpackPlugin({ 131 | asset: '[path].gz[query]', 132 | algorithm: 'gzip', 133 | test: new RegExp( 134 | '\\.(' + 135 | config.build.productionGzipExtensions.join('|') + 136 | ')$' 137 | ), 138 | threshold: 10240, 139 | minRatio: 0.8 140 | }) 141 | ) 142 | } 143 | 144 | if (config.build.bundleAnalyzerReport) { 145 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 146 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 147 | } 148 | 149 | module.exports = webpackConfig 150 | -------------------------------------------------------------------------------- /demo/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is the webpack config used for unit tests. 3 | 4 | const utils = require('./utils') 5 | const webpack = require('webpack') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | 9 | const webpackConfig = merge(baseWebpackConfig, { 10 | // use inline sourcemap for karma-sourcemap-loader 11 | module: { 12 | rules: utils.styleLoaders() 13 | }, 14 | devtool: '#inline-source-map', 15 | resolveLoader: { 16 | alias: { 17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 19 | 'scss-loader': 'sass-loader' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': require('../config/test.env') 25 | }) 26 | ] 27 | }) 28 | 29 | // no need for app entry during tests 30 | delete webpackConfig.entry 31 | 32 | module.exports = webpackConfig 33 | -------------------------------------------------------------------------------- /demo/components/ChangeSrcButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /demo/components/SvgButton.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 64 | 65 | 90 | -------------------------------------------------------------------------------- /demo/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /demo/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.5 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | /* --changed-- */ 12 | assetsSubDirectory: 'static', 13 | assetsPublicPath: '/demo', // / 14 | proxyTable: {}, 15 | 16 | // Various Dev Server settings 17 | host: 'localhost', // can be overwritten by process.env.HOST 18 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 19 | autoOpenBrowser: false, 20 | errorOverlay: true, 21 | notifyOnErrors: true, 22 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 23 | 24 | // Use Eslint Loader? 25 | // If true, your code will be linted during bundling and 26 | // linting errors and warnings will be shown in the console. 27 | useEslint: true, 28 | // If true, eslint errors and warnings will also be shown in the error overlay 29 | // in the browser. 30 | showEslintErrorsInOverlay: false, 31 | 32 | /** 33 | * Source Maps 34 | */ 35 | 36 | // https://webpack.js.org/configuration/devtool/#development 37 | devtool: 'eval-source-map', 38 | 39 | // If you have problems debugging vue-files in devtools, 40 | // set this to false - it *may* help 41 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 42 | cacheBusting: true, 43 | 44 | // CSS Sourcemaps off by default because relative paths are "buggy" 45 | // with this option, according to the CSS-Loader README 46 | // (https://github.com/webpack/css-loader#sourcemaps) 47 | // In our experience, they generally work as expected, 48 | // just be aware of this issue when enabling this option. 49 | cssSourceMap: false, 50 | }, 51 | 52 | build: { 53 | // Template for index.html 54 | /* --changed-- */ 55 | index: path.resolve(__dirname, '../demo/template.html'), // ../dist/index.html 56 | 57 | // Paths 58 | /* --changed-- */ 59 | assetsRoot: path.resolve(__dirname, '../demo/dist'), // /dist 60 | assetsSubDirectory: 'static', 61 | assetsPublicPath: '/demo', // / 62 | 63 | /** 64 | * Source Maps 65 | */ 66 | 67 | productionSourceMap: true, 68 | // https://webpack.js.org/configuration/devtool/#production 69 | devtool: '#source-map', 70 | 71 | // Gzip off by default as many popular static hosts such as 72 | // Surge or Netlify already gzip all static assets for you. 73 | // Before setting to `true`, make sure to: 74 | // npm install --save-dev compression-webpack-plugin 75 | productionGzip: false, 76 | productionGzipExtensions: ['js', 'css'], 77 | 78 | // Run the build command with an extra argument to 79 | // View the bundle analyzer report after build finishes: 80 | // `npm run build --report` 81 | // Set to `true` or `false` to always turn it on or off 82 | bundleAnalyzerReport: process.env.npm_config_report 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /demo/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /demo/config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | // import VueSimpleSVG from '../dist/plugin.js' 5 | import VueSimpleSVG from '../src/plugin.js' 6 | Vue.use(VueSimpleSVG) 7 | 8 | new Vue({ 9 | el: '#app', 10 | template: '', 11 | components: { App } 12 | }) 13 | -------------------------------------------------------------------------------- /demo/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-template 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dist/plugin.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var CSSOM=require("cssom"),SimpleSVG={render:function(e){return e("svg")},name:"simple-svg",props:{src:{type:String,required:!0},fillClassName:{type:String,default:""},fill:{type:String,default:""},strokeClassName:{type:String,default:""},stroke:{type:String,default:""},width:{type:String,default:"auto"},height:{type:String,default:"auto"},customId:{type:String,default:""},customClassName:{type:String,default:""}},mounted:function(){this.generateInlineSVG()},watch:{src:function(e){this.generateInlineSVG()},fill:function(e){this.updateFillColor(e)},stroke:function(e){this.updateStrokeColor(e)},width:function(e){this.$el.style.width=e},height:function(e){this.$el.style.width=e}},methods:{generateInlineSVG:function(){var e=this;this.resetInlineSVG();var t=new XMLHttpRequest;t.open("GET",this.src,!0),t.onload=function(){if(t.status>=200&&t.status<400){var r=(new DOMParser).parseFromString(t.responseText,"text/xml").getElementsByTagName("svg")[0];if(!r)return void console.error("No SVG element found in the given file: "+e.filepath);var i=r.getAttributeNames(),l=!0,s=!1,n=void 0;try{for(var o,a=i[Symbol.iterator]();!(l=(o=a.next()).done);l=!0){var u=o.value,f=r.getAttribute(u);e.$el.setAttribute(u,f)}}catch(e){s=!0,n=e}finally{try{!l&&a.return&&a.return()}finally{if(s)throw n}}e.customId&&(e.$el.id=e.customId),e.customClassName&&e.$el.setAttribute("class",e.customClassName),e.$el.style.width=e.width,e.$el.style.height=e.height;for(var h=r.children.length-1;h>=0;h--){var d=r.children.item(h);e.$el.appendChild(d)}e.updateFillColor(e.fill),e.updateStrokeColor(e.stroke),e.$emit("load")}else console.error("There was an error retrieving the source of the SVG.")},t.onerror=function(){console.error("There was on XML Http Request")},t.send()},resetInlineSVG:function(){for(;this.$el.firstChild;)this.$el.removeChild(this.$el.firstChild);var e=this.$el.getAttributeNames(),t=!0,r=!1,i=void 0;try{for(var l,s=e[Symbol.iterator]();!(t=(l=s.next()).done);t=!0){var n=l.value;this.$el.removeAttribute(n)}}catch(e){r=!0,i=e}finally{try{!t&&s.return&&s.return()}finally{if(r)throw i}}},updateFillColor:function(e){if(this.fillClassName){var t=this.$el.getElementsByClassName(this.fillClassName),r=!0,i=!1,l=void 0;try{for(var s,n=t[Symbol.iterator]();!(r=(s=n.next()).done);r=!0){s.value.style.fill=e}}catch(e){i=!0,l=e}finally{try{!r&&n.return&&n.return()}finally{if(i)throw l}}}},updateStrokeColor:function(e){if(this.strokeClassName){var t=this.$el.getElementsByClassName(this.strokeClassName),r=!0,i=!1,l=void 0;try{for(var s,n=t[Symbol.iterator]();!(r=(s=n.next()).done);r=!0){s.value.style.stroke=e}}catch(e){i=!0,l=e}finally{try{!r&&n.return&&n.return()}finally{if(i)throw l}}}}}},plugin={install:function(e,t){e.component("simple-svg",SimpleSVG)}};exports.default=plugin,exports.SimpleSVG=SimpleSVG; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-simple-svg", 3 | "version": "2.0.2", 4 | "description": "A simple Vue.js plugin that allows you to use a component that loads a SVG image as an inline SVG so you can easily control its fill color from the parent component.", 5 | "main": "dist/plugin.js", 6 | "scripts": { 7 | "rollup": "rollup --config rollup.config.js", 8 | "dev-demo": "webpack-dev-server --inline --progress --config demo/build/webpack.dev.conf.js", 9 | "start": "npm run dev-demo", 10 | "build-demo": "node demo/build/build.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/seiyable/vue-simple-svg.git" 15 | }, 16 | "keywords": [ 17 | "vue svg", 18 | "vuejs svg", 19 | "vue.js svg", 20 | "vue inline svg" 21 | ], 22 | "author": "Seiya Kobayashi", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "autoprefixer": "^7.2.6", 26 | "babel-cli": "^6.26.0", 27 | "babel-core": "^6.26.3", 28 | "babel-loader": "^7.1.4", 29 | "babel-plugin-dynamic-import-node": "^1.2.0", 30 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-preset-env": "^1.7.0", 33 | "babel-preset-es2015-rollup": "^3.0.0", 34 | "babel-preset-stage-2": "^6.22.0", 35 | "babel-register": "^6.22.0", 36 | "chalk": "^2.4.1", 37 | "chromedriver": "^2.38.2", 38 | "copy-webpack-plugin": "^4.5.1", 39 | "cross-spawn": "^5.0.1", 40 | "css-loader": "^3.2.0", 41 | "eventsource-polyfill": "^0.9.6", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.11", 44 | "friendly-errors-webpack-plugin": "^1.7.0", 45 | "html-webpack-plugin": "^2.30.1", 46 | "node-notifier": "^5.2.1", 47 | "optimize-css-assets-webpack-plugin": "^5.0.3", 48 | "ora": "^1.4.0", 49 | "portfinder": "^1.0.13", 50 | "postcss-import": "^11.1.0", 51 | "postcss-loader": "^2.1.4", 52 | "reset.css": "^2.0.2", 53 | "rimraf": "^2.6.0", 54 | "rollup": "^0.52.3", 55 | "rollup-plugin-babel": "^3.0.4", 56 | "rollup-plugin-uglify": "^2.0.1", 57 | "selenium-server": "^3.11.0", 58 | "semver": "^5.5.0", 59 | "shelljs": "^0.7.6", 60 | "uglifyjs-webpack-plugin": "^1.2.5", 61 | "url-loader": "^1.1.1", 62 | "vue": "^2.5.16", 63 | "vue-loader": "^13.7.1", 64 | "vue-style-loader": "^3.1.2", 65 | "vue-template-compiler": "^2.5.16", 66 | "webpack": "^3.11.0", 67 | "webpack-bundle-analyzer": "^3.5.2", 68 | "webpack-dev-server": "^2.11.5", 69 | "webpack-merge": "^4.1.2" 70 | }, 71 | "dependencies": { 72 | "cssom": "^0.3.2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import uglify from 'rollup-plugin-uglify' 3 | 4 | export default { 5 | input: 'src/plugin.js', 6 | output: { 7 | file: 'dist/plugin.js', 8 | format: 'cjs' 9 | }, 10 | plugins: [ 11 | babel({ 12 | babelrc: false, 13 | presets: ["es2015-rollup"] 14 | }), 15 | uglify() 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | const CSSOM = require('cssom') 2 | 3 | const SimpleSVG = { 4 | render (createElement) { 5 | return createElement('svg') 6 | }, 7 | name: 'simple-svg', 8 | props: { 9 | src: { 10 | type: String, 11 | required: true 12 | }, 13 | fillClassName: { 14 | type: String, 15 | default: '' 16 | }, 17 | fill: { 18 | type: String, 19 | default: '' 20 | }, 21 | strokeClassName: { 22 | type: String, 23 | default: '' 24 | }, 25 | stroke: { 26 | type: String, 27 | default: '' 28 | }, 29 | width: { 30 | type: String, 31 | default: 'auto' 32 | }, 33 | height: { 34 | type: String, 35 | default: 'auto' 36 | }, 37 | customId: { 38 | type: String, 39 | default: '' 40 | }, 41 | customClassName: { 42 | type: String, 43 | default: '' 44 | } 45 | }, 46 | mounted () { 47 | this.generateInlineSVG() 48 | }, 49 | watch: { 50 | src (val) { 51 | // Re-generate inline SVG if src is updated 52 | this.generateInlineSVG() 53 | }, 54 | fill (val) { 55 | this.updateFillColor(val) 56 | }, 57 | stroke (val) { 58 | this.updateStrokeColor(val) 59 | }, 60 | width (val) { 61 | this.$el.style.width = val 62 | }, 63 | height (val) { 64 | this.$el.style.width = val 65 | } 66 | }, 67 | methods: { 68 | /* Load an SVG file with XHR and generate an inline SVG code */ 69 | generateInlineSVG () { 70 | const context = this 71 | 72 | // Reset first. Remove all the code of the existing inline SVG 73 | this.resetInlineSVG() 74 | 75 | // Get the content of the SVG file 76 | const request = new XMLHttpRequest() 77 | request.open('GET', this.src, true) 78 | request.onload = function () { 79 | if (request.status >= 200 && request.status < 400) { 80 | // Setup a DOM parser to convert the response to text/xml 81 | const domParser = new DOMParser() 82 | const result = domParser.parseFromString(request.responseText, 'text/xml') 83 | const loadedSVG = result.getElementsByTagName('svg')[0] 84 | 85 | if (!loadedSVG) { 86 | console.error('No SVG element found in the given file: ' + context.filepath) 87 | return 88 | } 89 | 90 | // add attributes to the inline SVG 91 | const attributeNames = loadedSVG.getAttributeNames() 92 | for (const name of attributeNames) { 93 | const value = loadedSVG.getAttribute(name) 94 | context.$el.setAttribute(name, value) 95 | } 96 | if (context.customId) context.$el.id = context.customId 97 | if (context.customClassName) context.$el.setAttribute('class', context.customClassName) 98 | context.$el.style.width = context.width 99 | context.$el.style.height = context.height 100 | 101 | // add child nodes to the inline SVG 102 | const domN = loadedSVG.children.length; 103 | for (let i = domN - 1; i >= 0; i--) { 104 | const node = loadedSVG.children.item(i) 105 | context.$el.appendChild(node) 106 | } 107 | 108 | // set colors 109 | context.updateFillColor(context.fill) 110 | context.updateStrokeColor(context.stroke) 111 | 112 | // now the inline SVG is generated 113 | context.$emit('load') 114 | } else { 115 | console.error('There was an error retrieving the source of the SVG.') 116 | } 117 | } 118 | 119 | request.onerror = function () { 120 | console.error('There was on XML Http Request') 121 | } 122 | 123 | request.send() 124 | }, 125 | resetInlineSVG () { 126 | while (this.$el.firstChild) { 127 | this.$el.removeChild(this.$el.firstChild) 128 | } 129 | const attributeNames = this.$el.getAttributeNames() 130 | for (const name of attributeNames) { 131 | this.$el.removeAttribute(name) 132 | } 133 | }, 134 | updateFillColor (fill) { 135 | if (this.fillClassName) { 136 | const matches = this.$el.getElementsByClassName(this.fillClassName) 137 | for (const element of matches) { 138 | element.style.fill = fill 139 | } 140 | } 141 | }, 142 | updateStrokeColor (stroke) { 143 | if (this.strokeClassName) { 144 | const matches = this.$el.getElementsByClassName(this.strokeClassName) 145 | for (const element of matches) { 146 | element.style.stroke = stroke 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | const plugin = { 154 | install (Vue, options) { 155 | Vue.component('simple-svg', SimpleSVG) 156 | } 157 | } 158 | 159 | export {plugin as default, SimpleSVG} 160 | --------------------------------------------------------------------------------