Sunday, December 31, 2017

Creating libraries with Vue components

Vue.js is great. There is no question about it. It's small, versatile, conscious, easy to learn - you name it. With the addition of POI it is probably also one of the most friendly frameworks to work with. Let's see how we can share single-file components between projects!

Method 1: npm link

npm link is probably one of the least known features if your primary occupation is creation of web application and you have never created an NPM package yourself. It has been there in NPM for ages and is the primary tool for library creators.

To use it when you have created your library and want to test it then what you do is you run

npm link

in the library folder and you run

npm link <library-name>

in the project that you want to use the library in. That gives you practically a hard binding between the library and the project using a symlink which means that if you introduce a change in the library then the project sees those changes immediately. It's a fantastic tool when you're in the process of working on the library. It can be further streamlined when we specify a dependency using the file: protocol like so:

"dependencies": {
  "my-library": "file:path-to-library"
}

This is also a great option if the library itself is part of your project and is being used to simply tear the application code from the reusable components. This is a great idea because if the application code changed often and the components change seldom then the size of your client.js gets smaller as all code from node_modules (this includes libraries referenced with the file: protocol) get pushed into the vendor.js block.

"dependencies": {
  "project-components": "file:../project-components"
}

This will automatically setup a link named project-components inside node_modules. What's even more interesting, when you do the npm install to install dependencies then the project-component's dependencies will be installed in ../project-components/node_modules automatically thus each project that uses that library won't need a copy of the shared dependencies.

Method 2: npm publish

When you want to go for a reusable library that you'd like to share between multiple projects then the right way to go is to use a repository. There is a multitude of options but the one I find really useful in an organization are npmjs.com for all things opensource and verdaccio for all the packages that you would like to keep available only for in-house components. The nice thing about verdaccio is that it automatically acts as a proxy to npmjs.com so all you do in your project is you tell npm that the repository is located where verdaccio is

npm set registry http://localhost:4873/

and you're all set.

Example

To show you how it is done I have created a sample project on GitHub. It consists of 3 parts: the library containing 2 components, a host application using those components and a little bit of automation to start the repository server.

Patterns

When creating a component library there are 2 options that one can use:

  • include the modules that build the library
  • include the built version of the library

Which one to use depends on the library itself. If you create a library that is to be used in all sorts of projects but you wrote it in some non-standard way (let's say you used CoffeeScript) then to make it useful for the rest of us you need to point the main to the compiled version. However, if you created a library specifically for use within a specific environment it makes sense to create an index.js and point your main entry in package.json to it and to put all your code in the lib folder:

export { default as componentA } from './lib/componentA.vue'
export { default as componentB } from './lib/componentB.vue'
That way, if someone wants to include all of your library they just include it and all is great:

import myLib from 'myLib'

Or using requirejs:

const myLib = require('myLib')

If for whatever reason they want to include only a portion of it, let's say just one or two components and not the whole shebang, then they are free to do so as follows:

import componentA from 'myLib/lib/componentA.vue'

Or using requirejs:

const componentA = require('myLib/lib/componentA.vue')

The latter form allows for the treeshaking algorithm to exclude everything that is not used by the application in the resulting file thus making the final bundle smaller (sometimes a lot smaller - see the use of lodash).

To create a compiled version of your library with POI all you need is to run the following command:

poi build --library index.js

and in the dist folder you'll get a library.js that you point the main in package.json to and you're done!

That's it! It is really simple to create libraries shared between parts of your project and not all that much more complex to create universal applications shared with everyone else in the NPM ecosystem.

Happy coding in 2018!

Thursday, December 14, 2017

Using webfonts-generator with POI

The problem

This might come as a surprise to you (it did to me at least) that POI is not handling generation of webfonts by default. It does so many wonderful things but webfonts generation is not one of them, unfortunately. You might ask what are webfonts and why do we need to generate one in a frontend project? Aren't there enough fonts to choose from already? To answer that we need to understand how browsers work when downloading multiple resources. First of all there is a limit to how many connections can be made at the same time to one server. Secondly there is a limit to how many connections can there be in total at any given time. So if, for example, you'd like to load 30 small black and white images (for use as icons) then the browser would have a lot of work to do and the page would load very, very slowly. To mitigate that problem you could use a custom font that will contain all the images, compressed, and it will take a fraction of the time to load. This is exactly what webfonts-generator does.

The solution

So how do we use it in a POI project if it is not available by default? It's actually not that simple. We need to extend the already impressive webpack configuration to teach it what to do with the font descriptors. Font descriptors are just JavaScript modules ending with .font.js exporting definition of the font, like the one below:

module.exports = {
  fontName: 'icons',
  files: [
    'phone.svg',
  ]
}

Having that in place we can start extending our configuration. As you already know POI contains webpack rules for all the common types of files. Those rules differ from production to test to development configuration. The rule we need to add for it to work properly needs to be just like the one for CSSes, just with the webfonts-loader added at the end of the use list. So first we need to find the css rule:

const isCssRule = rule => rule.test.toString().indexOf('\.css$') >= 0
const findCssRule = rules => rules.find(isCssRule)

Having that rule in hand let's create another rule for webfonts that contains the definition of a loader:

const constructWebfontRuleFromCssRule = rule => ({
  test: /\.font\.js$/,
  use: [
    ...rule.use, 
    {
      loader: 'webfonts-loader', options: {
        fileName: 'assets/fonts/[fontname].[hash:8].[ext]'
      }
    },
  ]
})

Then we can add the rule to webpack configuration in poi.config.js:

module.exports = {
  webpack (config) {
    const rule = findCssRule(config.module.rules)
    config.module.rules.push(constructWebfontRuleFromCssRule(rule))

    return config
  }
}

And finally we need to install the additional loader like so:

npm install --save-dev webfonts-loader

webfonts-generator is a transitive dependency so it will be installed automatically too.

Final thoughts

I know that doesn't seem like much but it took me way too long to figure it out so I figured I'll post it for posterity. In the meanwhile I have created a ticket in the POI project for it to be implemented as part of the default configuration so that we won't have to deal with it ourselves. Will see if that will catch on or not.

Happy building!

Thursday, December 7, 2017

POI and naming classes in CSS Modules

This will be a quick one. If you're trying to use CSS modules with POI in a Vue.js app and you want to exercise some control over what the names of the generated classes are then there is a configuration option in POI for that. It's a little bit hidden so it took mi like 5 minutes to figure it out - hence the post.

module.exports = {
  vue: {
    cssModules: {
      localIdentName: '[hash:base64:5]'
    }
  }
}

That weirdness stems from the fact that POI configures the css-loader indirectly via the vue when setting up the vue-loader. We can take advantage of the fact that it uses the Object.assign method and sneak in our configuration option.

Happy coding!