Because it’s relatively new, Vue.js provides a lot of opportunity to publish your own components for the Vue.js community to use. In this article we are going to build a component from start to finish, from setting up the repo on GitHub through linting, testing, bundling and publishing.

The component we will be creating will simply display the version of Vue being used using Vue.version, which means we get to look at strategies for accessing the Vue object from inside a bundled distributable. So let’s go:

Setup

Let’s start by creating a new Github repo called ‘vue-version’ and because we are publishing this let’s add the MIT license, which gives other developers permission to use the component however they want:

vue-version-setup

Once that’s setup let’s clone the repo on our local machine by opening up a command prompt and doing:

git clone git://github.com/your_github_username/vue-version

Now let’s cd (change directory) to vue-version:

cd vue-version

Setting up our Project for use with NPM

Because we want to publish our project on NPM, we will start by creating a package.json, so let’s do that from the command line:

npm init

And simply follow the steps by choosing all the default options (you can fill in the description, keywords and name if you like), once complete type “yes” and hit enter to confirm the creation of the file.

Next, we want to make sure that git ignores the “node_modules” folder used by npm, so create a new file in the root of the project called “.gitignore” and add the following:

node_modules

Tip
When setting up projects like this it is useful to use the touch command to create files, if your have it available, and the mkdir command to create directories.

Now we need to install a few node modules for our project. We’ll use webpack for our build step and we’ll need vue-loader, vue-template-compiler and css-loader to compile our .vue files and babel, babel-loader and babel-preset-es2015 to transpile the ES2015 code, we’ll also obviously need Vue, so let’s bring them all in:

npm install vue webpack babel-core babel-preset-es2015 babel-loader css-loader vue-template-compiler vue-loader --save-dev

Once those have been installed we need to create a .babelrc file in the root of our project to let babel-loader know we want to apply the es2015 preset we just installed:

{
  "presets": ["es2015"]
}

And now let’s create new folder called “src” and add a new file for our component called “Version.vue”. Then we want to add the code for our component, which simply displays a message letting the user know what version of Vue they are using:

<template>
  <div class="version">
    You are using Vue {{version}}
  </div>
</template>

<script type="text/javascript">
import Vue from 'vue'
export default {
  computed: {
    version() {
      return Vue.version
    }
  }
}
</script>

<style>
.version {
  font-weight: bold;
  font-family: tahoma;
}
</style>

Next we want to add an entry point for our component, so let’s create a file in src called index.js which simply exports our component:

import VueVersion  from './src/Version.vue'

export default VueVersion

Adding ESLint

Now we’ve done that let’s add ESLint to our project to check for common errors. We’ll install everything globally so we can use it in future projects without having to go through the install process each time:

npm install -g eslint eslint-plugin-vue eslint-config-vue

Now we’ll create a new file called .eslintrc.json and extend eslint-config-vue to bring in our defaults. I’m going to override the space-before-peren rule, because I don’t like spaces before parenthesis, so we get:

{
  "extends": "vue",
  "rules": {
    "space-before-function-paren": [2, "never"]
  }
}

And now let’s run our linter over the files in the src folder:

eslint src --ext=js,vue

And we don’t get any errors, so we’re good.

Finally let’s add a script called “lint” to package.json to run that ESLint command, so we don’t have to type it each time:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint src --ext=js,vue || exit 0"
  },

Here we are just telling Eslint to lint all files with the .js or .vue extension. The “|| exit 0” stops npm from throwing an ELIFECYCLE error when ESlint fails.

Note:
You will need to install eslint-plugin-vue & eslint-config-vue in your project if you want to use them via package.json scripts, even if you have installed them globally.

Setting up Webpack

When developing a component for publishing it’s normal to create multiple configs for development and distributables. Our first config will be for development of the component and for that we will want to use webpack-dev-server, so let’s install that by doing:

npm install webpack-dev-server --save-dev

Once that’s installed we can create a new folder for our configs in the root of our project called “build” and add a config file called “webpack.dev.js”:

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './dev/index.js',
  output: {
    path: path.resolve(__dirname, './dev'),
    publicPath: '/dev/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
    ]
  },
  devServer: {
    noInfo: true
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map'
}

Next we want to create a webpack.config.js file to the root of our project which runs any webpack.config file we pass in through the command line using the –env argument:

function buildConfig(env) {
  return require('./build/' + env + '.js');
}

module.exports = buildConfig;

Now we can simply run any of the configs in the “build” folder by doing:

webpack --env=file

Now, if you look at our webpack.dev.js config you will see it is actually looking to output files in a folder called “dev”, which we haven’t yet created, so let’s do that and then add an index.html file with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue Version</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="./dev/build.js"></script>
  </body>
</html>

This now give us a way to run our development build in the browser, but we haven’t actually created anything to bundle, so let’s add a file called App.vue in the “dev” folder which uses our component:

<template>
  <div>
    <vue-version></vue-version>
  </div>
</template>

<script type="text/javascript">
import VueVersion from '../src/index.js'

export default{
  components:{
    VueVersion
  }
}
</script>

And finally we want to create the index.js file our dev webpack config is looking for (see the entry property) that mounts our App:

import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h => h(App)
}).$mount('#app')

We now have everything in place to run our component for development, but let’s add a script to package.json to run our webpack-dev-server with our webpack.dev.js file:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "lint": "eslint src --ext=js,vue || exit 0",
    "dev": "webpack-dev-server --content-base ./dev --env=webpack.dev --open --hot"
  },

This should now fire up the server and show our component in the browser, let’s give it a go:

npm run dev

Any changes you make to the component will automatically be updated in the browser because we have provided the –hot argument, however, for our purposes the component is finished and ready for testing.

Testing the Component with Jasmine and Karma Runner

If you want people to use your component you should make sure you have provided tests. For our component we are going to use Jasmine for the test framework and Karma as our test runner. We will also want to install PhantomJS to give us an environment to test in, and karma-webpack so webpack can compile our component and test files on the fly. I’ll also add karma-spec-reporter to give us pretty results, so let’s pull them :

npm install karma jasmine-core karma-jasmine karma-phantomjs-launcher karma-webpack karma-spec-reporter --save-dev

Once everything is installed we should start by creating a folder for our tests, which we will call “spec”, and then add a new file called “Verson.spec.js”, which we’ll leave empty for now.

With that setup we want to configure Karma. In the root of the project add a file called karma.conf.js and add the following config:

module.exports = function(config) {
    config.set({
        browsers: [
            'PhantomJS',
        ],
        frameworks: ['jasmine'],
        files: ['spec/**/*.js'],
        reporters: ['spec'],
        preprocessors: {
            'spec/**/*.js': ['webpack']
        },
        webpack: {
            module: {
                rules: [{
                    test: /\.vue$/,
                    loader: 'vue-loader'
                }, {
                    test: /\.js$/,
                    loader: 'babel-loader',
                    exclude: /node_modules/
                }]
            }
        },
        singleRun: true
    })
}

As you can see we are using webpack as a pre-processor which applies babel-loader and vue-loader to our tests, which means we can import our components directly, and write our tests using ES2015.

Now we have a test server setup, let’s update the test script in package.json to run karma and also add a test:watch script that watches for any changes to our component and test files and automatically re-runs the tests, which is useful in development:

  "scripts": {
    "test": "karma start",
    "test:watch": "karma start --single-run=false",
    "lint": "eslint src --ext=js,vue || exit 0",
    "dev": "webpack-dev-server --content-base ./dev --env=webpack.dev --open --hot"
  },
  

Now we can run our tests once by doing:

npm run test

And watch for changes and re-run the tests by doing:

npm run test:watch

However, we don’t actually have any tests yet, so let’s create some. For our tiny component we only really want to test two things; that the computed returns the current version of Vue and that it displays the version message on the page, so we can add those tests to Version.spec.js:

import Vue from 'vue'
import VueVersion from '../src/index.js'

Vue.config.productionTip = false; // Turn off production message so it doesn't show in command window.

describe('Vue Version', () => {
  it('returns the current version of Vue', () => {
    let Component = Vue.extend(VueVersion)
    let vv = new Component();
    expect(vv.version).toBe(Vue.version)
  });

  it('displays the current version of Vue', () => {
    let Component = Vue.extend(VueVersion)
    let vm = new Component().$mount()
    expect(vm.$el.textContent.replace(/(^\s+|\n+)/g, '')).toBe('You are using Vue ' + Vue.version)
  });
});

You may notice I’ve added a regex replace on the textContent, which simply strips whitespace at the beginning of the text and removes any line breaks. Other than that those tests are fairly straightforward, we simply pass our component to Vue.extend and instantiate it. So, if we run the tests we should get green:

testing

Bundling the Component

Our final step is to bundle the component so it can be used by other developers. For this we are going to create a bundle which will work in both modular build environments and for use via CDN.

In order to do this we will create another webpack config in “build” called webpack.dist.js. This config will take our src/index.js file, minify it and bundle it as a UMD module so it can be used in different JavaScript environments.

However, there is one more thing that we need to consider, and that’s our import of Vue itself. To avoid bundling Vue with our component we need to use webpack’s “externals” so it knows that we want to grab Vue externally and not bundle it into our distributable, so our dist config looks like this:

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, '../dist'),
    publicPath: '/dist/',
    filename: 'vue-version.min.js',
    library: 'VueVersion',
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  externals: {
    "vue": "Vue"
  },
  module: {
    rules: [{
      test: /\.vue$/,
      loader: 'vue-loader',
    }, {
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/
    }, ]
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      include: /\.min\.js$/,
      compress: {
        warnings: false
      }
    })
  ],
  performance: {
    hints: "error"
  },
  devtool: 'source-map'
}

And let’s add a script to package.json which will run our dist config:

  "main": "src/index.js",
  "scripts": {
    "test": "karma start",
    "test:watch": "karma start --single-run=false",
    "lint": "eslint src --ext=js,vue || exit 0",
    "dev": "webpack-dev-server --content-base ./dev --env=webpack.dev --open --hot",
    "dist": "webpack --env=webpack.dist"
  },

And now we can build our distributable by running:

npm run dist

What’s really cool about this is that our “dist” file doesn’t require any effort on the developers end, it will just work in any environment they are using, whether that be CommonJS, AMD or with script tags in the browser.

Finally we just want to add the dist file as our “main” entry in package.json:

"main": "./dist/vue-version.min.js"

We’re, now, pretty much ready to publish, however, before we do let’s add some continuous integration to our component using Travis-CI.

Continuous Integration with Travis-CI

If you’re unfamiliar with continuous integration then it’s simply a process that runs our tests whenever we push to GitHub. For this project we will use Travis-CI; we simply need to set up an account on the website and link our GitHub repository by flipping the repository switch to on:

travis-ci

Now we just want to create a “.travis.yml” file in the root of our project and let travis know what environments to run our test in:

language: node_js  
node_js:  
  - "6"
  - "6.1"
  - "5.11"

That’s it! now each time we push to GitHub our tests will automatically run, and hopefully pass. Travis will provide a little “Build:Passing” shield that we can place in our README.md so everybody can see that the current build is passing and that we have written tests for our component.

Publishing on NPM

I’m not actually going to publish this on NPM because I don’t want to clog up the site with a tutorial, but you can actually install your component as if it was published on npm by doing:

npm install git://github.com/username/vue-version

Which will download it into the “node_modules” folder, allowing you to add the component to your project by doing:

import VueVersion from 'vue-version'

However, before we push our completed component to GitHub, we need to add the files we want to publish to NPM in to package.json, although in our case these will be the files that npm pulls from our github repo. So let’s add the “dist” and “src” folders by adding the files array to package.json:

  "files": [
    "dist",
    "src"
  ]

That’s it, we can now commit our files and push them to GitHub. Because we cloned the repo from GitHub we will also have to set the remote so we can push our files to the repo, so let’s do the following:

For the sake of completeness, if you wanted to publish a component on NPM, it is as simple as logging into npm via the command line and running:
npm publish and this will automatically add the component to Yarn as well, if that’s more your thing.

And we’re done! A component built from start to finish. Hopefully you found this useful and I’ll be seeing (and using!) some of your components in the future. You can find the completed component on the vuetiful-blog GitHub page if you want to have a look at the end result. And, as always, please feel free to ask any questions in the comments.

Advertisements