SVG is amazing, and if you’re building any custom vector graphics from your client code, one of the easiest libraries to use is Snap.svg. I’ve used it in a number of projects, including vanilla JavaScript and various transpiling setups including Transcrypt.
I’m trying to go a little more mainstream after wasted years of time on fringe technologies that fell out of favor.
I’m spending a lot of time these days learning Vue.js and really hoping this is going to be a worthwhile long term investment in my skillset. So it was only a matter of time before I found myself needing to get Snap.svg working in my Vue.js projects, which meant some extra fiddling with WebPack.
Getting Snap.svg Working with Vue.js
Out of the gate, there’s some hurdles because Snap mounts itself on the browser’s window object, so if you’re trying to load Snap through WebPack (as opposed to just including it in a project using a conventional script tag), you need to do some gymnastics to get WebPack’s JavaScript loader to feed the window object into Snap’s initialization logic. You can find an overview of the problem in this GitHub issue which illustrates the obstacles in the context of using React, but the issues as they relate to Vue.js are the same.
I’m assuming you have a Vue.js webpack project that you started with vue-cli or from a template that has everything basically running okay, so you’ve already got Node and webpack and all your other infrastructure in place.
For starters, you’ll want to install Snap.svg and add it to your project dependencies, so from a terminal window open and sitting in the directory where your project’s package.json/package-lock.json sit…
npm install --save snapsvg
That will download and install a copy of the Snap.svg source into your node_modules directory and you’ll have it available for WebPack to grab.
Normally you’d be able to use a package installed like this by using an import statement somewhere, and you’d think you could do this in your Vue project’s main.js file, if you start down this path you’ll get the window undefined issue described in that GitHub link above.
The tricky bit though is getting WebPack to load the Snap properly, and to do that we’ll need a WebPack plugin that lets us load as a JavaScript dependency and pass some bindings to it. So, in that same directory install the WebPack imports-loader plugin…
npm install --savedev imports-loader
To tell the imports-loader when it needs to do its magic, we have to add it to the WebPack configuration. I changed my webpack.base.conf.js file to include the following inside the array of rules inside the module object…
module: { rules: [ ... { test: require.resolve('snapsvg'), use: 'imports-loader?this=>window,fix=>module.exports=0', }, ... ] },
Now we can load Snap.svg in our JavaScript, but imports-loader uses the node require syntax to load the file. So in our main.js, we can attach Snap.svg by telling WebPack to invoke the exports loader like this…
const snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`);
…and then attach it to our root Vue instance, still in main.js, something like this…
const vueInstance = new Vue( { el: '#app', snap, router, axios, store, template: '<App/>', components: { App } } ); export { vueInstance };
There is some redundancy in that require() call and the way we setup the module resolution in the WebPack configuration. I’m fuzzy about why I seemed to need this in both spots, but it works so I’m running with it. If you have insights they’d be appreciated; let me know in the comments.
Getting Snap.svg Working with nuxt.js
Nuxt requires a slightly different twist, because as you’re aware a typical Nuxt project doesn’t have either a main.js file or a native copy of the WebPack configuration. We need to make the same changes, but just in a slightly different spots.
You need to install both snapsvg and imports-loader just like we did above…
npm install --save snapsvg npm install --savedev imports-loader
The way we modify the WebPack configuration in a Nuxt project is to create a function that accepts and extends the WebPack configuration from with your nuxt.config.js file…
/* ** Build configuration */ build: { extend(config, ctx) { config.module.rules.push( { test: require.resolve('snapsvg'), use: 'imports-loader?this=>window,fix=>module.exports=0', } ); } }
Since we don’t have a main.js, we need to use a Vue.js plugin to inject shared objects and code into Vue. In your projects plugins folder, create a file named snap.js that contains code to attach a snap object created again using imports-loader…
export default ({ app }, inject) => { app.snap = require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`); }
…and back in your nuxt.config.js file, include this plugin…
plugins: [ ... {src: '~/plugins/snap'}, ... ],
These approaches seem to work well for me in both a standard Vue.js and Nuxt.js projects, but both of these setups have been cobbled together from reading a lot of other bits and pieces… If you’ve got a better or approach or see a way to clean up what I’ve done, please let me know.
Meanwhile, good luck with your Snap and Vue projects!
I followed the instructions for nuxt configuration. I’m receiving this error. Can you tell me how to solve this.
{ ReferenceError: window is not defined
at Object. (node_modules/snapsvg/dist/snap.svg.js:8635:0)
at __webpack_require__ (webpack:/webpack/bootstrap 5c9de1bbd3fef07362bb:25:0)
at module.exports.__webpack_exports__.a (src/plugins/snap.js:2:0)
at createApp (.nuxt/index.js:159:0)
at
at process._tickCallback (internal/process/next_tick.js:182:7) statusCode: 500, name: ‘ReferenceError’ }
Dkppf, I suspect you’re running into an issue where you’re trying to run some Snap.svg code on a server-side rendered page. Because Snap mounts on the browser’s window object, it won’t be available during the server render process. You’ll want to look at wrapping components in “no-ssr” tags in your templates and checking process.browser in any code that directly references the snap instance.
You can add the “ssr: false” option in your nuxt.config.js
{ src: ‘~/plugins/snap’, ssr: false }
Hi,
I’ve created a Vuejs project with the most recent vue-cli. So I have no webpack.base.conf.js.
Do you now how to get it to work?
Thank you!
I’m just learning vue.js and I followed the instructions for vue.js but I can not figure out how to actually use snap in my components.
For example a simple component like:
export default {
created: function () {
var s = Snap(‘#mysvg’) // <— Snap is undefined
s.circle(150,150,100)
}
}
How is this supposed to work with vue.js? Thanks.
Excellent tip, worked on electron too BTW.
Would you mind posting an example code snippet of how to correctly import & use Snap from within a nuxt vue component? Thanks!
I got it to work by;
nuxt.config.js:
extend (config) {
config.module.rules.push({
test: require.resolve(‘snapsvg’),
use: ‘imports-loader?this=>window,fix=>module.exports=0’
});
},
plugins: [ {src: ‘~/plugins/snap’, ssr: false}]
plugins/snap.js;
export default ({ app }, inject) => {
inject(‘snap’, require(`imports-loader?this=>window,fix=>module.exports=0!snapsvg/dist/snap.svg.js`))
}
component.vue;
created () {
if (process.browser) {
let s = this.$snap(800, 600);
let bigCircle = s.circle(150, 150, 100);
bigCircle.attr({
fill: “#bada55”,
stroke: “#000”,
strokeWidth: 5
});
}
}
Thanks! This worked for me as well!