Adding Snap.svg to Vue.js Projects Created with vue-cli 3.0

My front end tool chain has finally settled into something I’m getting comfortable with. But these tools don’t stand still, so the learning continues.

One of the higher traffic posts on this blog has been my discussion of how I integrated Snap.svg into my Vue.js and Nuxt.js projects. I wrote that article because while it wasn’t fun, it helped me learn a lot about Webpack configuration.

That article was written at a point in time when where I was maintaining standalone Webpack configuration files for my projects. A big part of the trouble with this approach is that it intrinsically couples a large number of external dependencies together. My Vue.js projects rely on a number of third party libraries, Webpack uses a collection of loaders and the build tool chain itself has a number of dependencies like ESLint and Babel that bring along their own school of remora fish.

So, you can understand why once you’ve got a fairly complicated project and build process configured in Webpack, that the last thing you typically want to do is touch it. It’s even worse when you introduce dependencies like Snap.svg that, because of their lack of module support, require mystical Webpack incantations to get them to work.

I just had to extract this slope intercept calculator from a project that was generated without the CLI, so I was using the older Webpack mysticism to get Snap.svg working. With the need to create this in a new project, startig with the latest Vue.js and the vue-cli was the natural approach… But that meant getting our friend Snap.svg working again. Read on to find out how…

How The vue-cli reduces webpack Configuration hell

Let’s be honest, not many people like working on Webpack configurations.

One of the things the vue-cli sets out to do is to hide away some of the complexity associated with Webpack configurations. This makes building compiled Vue.js projects a bit less intimidating, but it also allows the whole tool chain to be upgraded without breaking what would have been a hand-coded Webpack configuration.

There’s still a Webpack configuration hiding under the hood of course, it’s just that the CLI manages it. And if you upgrade Vue.js and it’s tool chain, in theory it becomes a seamless process because the configuration is “owned” by the CLI and it’s not a visible part of your project that you’ve been tempted to monkey around with internally.

This management of the Webpack configuration by vue-cli is in contrast to many other tools that “eject” a configuration when the project is first setup, or when you might need to make an unsupported modification. At that point, the Webpack configuration file is owned by the developer (lucky you!) and a future upgrade becomes something to potentially be feared.

Fortunately, vue-cli 3.0 introduced a new way to make modifications to the generated Webpack configuration without having to eject a configuration and be responsible for maintaining it forever forward.

Webpack chain: how to modify a vue-cli webpack configuration without ejecting

The magic comes from webpack-chain, a package that came out of the Neutrino.js project. It’s bundled into the vue-cli and the implementation details are a slightly different, but the documentation at the link above is useful for digging into individual options. There’s some examples specific to vue-cli at this link although they fell short of the complete reference at the actual webpack-chain documentation.

Webpack-chain allows you to create little configuration patches that get applied programmatically to the Webpack configuration that is built by a vue-cli project each time you compile. The advantage of this is that you only need to specify the eccentric bits of your project’s build configuration, and if you subsequently upgrade to Webpack 7 or Vue.js 5 all of the stock configuration provided by the rest of the toolchain should hopefully get upgraded appropriately.

An example of this is getting CSS preprocessors like Sass or Less running. Normally this would take some Webpack gymnastics, but the vue-cli does this for you when you configure a project and you never have to touch the actual Webpack configuration. As vue-cli gets updated to use later versions of Webpack or whatever CSS preprocessor you’re using, these upgrades will come along for free in your project without having to go back and touch the Webpack configuration. Woohoo!

Where you do need to go outside the box, you can do it by setting up a “chain” in separate configuration file. You may still have update your custom configuration bits in your chain on undertaking a major version upgrade, but those changes are scoped and isolated, and they’re definitely more succinct. I much prefer this compared to picking through 1000 lines of Webpack boilerplate and finding where I’ve done something outside the box.

The slightly inconvenient part of this is that the syntax of webpack-chain is slightly different from the JSON object structure you may be more familiar with if you’re a Webpack old timer.

As an example, let’s look at our friend Snap.svg.

Getting Snap.svg Working with VUE-CLI

As in manual configurations, we’ll use the imports-loader to load Snap.svg in our project. So once we’ve created a new project with vue-cli, we’ll need to install Snap.svg and imports-loader as appropriate dependencies…

npm install --save snapsvg
npm install --savedev imports-loader

We don’t need to explicitly install webpack-chain as part of our project as vue-cli already bundles this into itself.

To create a chain that gets applied to the vue-cli generated Webpack configuration, you need to create a file named vue.config.js in your project’s root folder. Here’s the entire file I’m using to pull Snap.svg into my project…

module.exports = {
  chainWebpack: config => {
    config.module
      .rule("Snap")
        .test(require.resolve("snapsvg/dist/snap.svg.js"))
        .use("imports-loader")
        .loader("imports-loader?this=>window,fix=>module.exports=0");

    config.resolve.alias.set("snapsvg", "snapsvg/dist/snap.svg.js");
  }
};

That will handle getting Snap.svg resolved and imported when we need to. Again, the webpack-chain documentation illuminate how this is mapped back to traditional JSON Webpack configuration structures, but you can see readily how you might setup other behaviors like plugins or other loaders.

All that’s left is to reference it in the source to one of our components, something in very broad strokes like this…

<template>
  <div >
    <svg id="mySVG"  />
  </div>
</template>
<script>

import Snap from "snapsvg";  // <-- triggers our import resolution

export default {

  data() {
    return { mySVG: undefined };
  },

  mounted() { 
    this.mySVG = window.Snap("#mySVG");
  },

  methods: { 
     // draw amazing stuff here with this.mySVG
  }
}

</script>

Troubleshooting webpack-chain

The vue-cli includes commands to dump a copy of the Webpack configuration with your webpack-chain modifications applied to it.

vue inspect > ejected.js

You can open this file to look and see how the chain was applied to generate corresponding Webpack rules. In our case, this is what we got for the vue.config.js file described above…

  resolve: {
    alias: {
      '@': '/Volumes/Hactar/Users/jscheller/vue/myproject/src',
      vue$: 'vue/dist/vue.runtime.esm.js',
      snapsvg: 'snapsvg/dist/snap.svg.js'
    },
   
    [ ... and about 1000 lines later... ]
 
      /* config.module.rule('Snap') */
      {
        test: '/Volumes/Hactar/Users/jscheller/vue/myproject/node_modules/snapsvg/dist/snap.svg.js',
        use: [
          /* config.module.rule('Snap').use('imports-loader') */
          {
            loader: 'imports-loader?this=>window,fix=>module.exports=0'
          }
        ]
      }

A great thing about running the ‘vue inspect’ command is that it will give you console output if your chain rules have a problem. If you build up a webpack-chain in your vue.config.js file that doesn’t seem to be working right, this is probably one of the first things you should check to see if it’s actually getting applied correctly.