Packaging Vue Libraries

Hanoi, August 2019

Hien Dao

Hien Dao Vinh

Senior developer @

 

Open-source enthusiast

 

var vm = new Vue({}) since v1

Table of Contents

I. Source control patterns

II. Bundling

III. Testing

IV. Example

V. Alternatives

Source Control Patterns

Monorepo / Multi-repo

Image courtesy of toptal.com

The organizations of your repositories

Source Control Patterns

[  ]    It takes so much effort for you to maintain dependencies & versioning

[  ]    Your packages are sharing the same workflow or environment

[  ]    You are having problems when refactoring global features

Monorepo or Multi-repo?

The checklist

Source Control Patterns

[x]    It takes so much effort for you to maintain dependencies & versioning

[  ]    Your packages are sharing the same workflow or environment

[  ]    You are having problems when refactoring global features

Monorepo or Multi-repo?

The checklist

Source Control Patterns

Monorepo

Maintaining dependencies & versioning

Vue Library

A single package

Source Control Patterns

Monorepo

Maintaining dependencies & versioning

Vue

Core

Utilities

3 separated repositories

Source Control Patterns

Monorepo

Maintaining dependencies & versioning

Vue

Core

Utilities

React

Foo

Bar

v0.1.0

v0.1.0

v0.1.0

v0.1.0

v0.1.0

v0.1.0

vx.y.z

vx.y.z

vx.y.z

vx.y.z

vx.y.z

vx.y.z

6 separated packages

Source Control Patterns

[x]    It takes so much effort for you to maintain dependencies & versioning

[x]    Your packages are sharing the same workflow & environment

[  ]    You are having problems when refactoring global features

Monorepo or Multi-repo?

The checklist

Source Control Patterns

Monorepo

Sharing workflow & environment

Processes

  • Make changes
  • Lint & test
  • Build & test again
  • Release

Environment

  • .editorconfig
  • .eslintrc
  • .babelrc
  • etc

Source Control Patterns

Monorepo

Sharing workflow & environment

Source Control Patterns

[x]    It takes so much effort for you to maintain dependencies & versioning

[x]    Your packages are sharing the same workflow or environment

[x]    You are having problems when refactoring global features

Monorepo or Multi-repo?

The checklist

Source Control Patterns

Monorepo

Coordinate cross-package changes with atomic commits

Vue

Core

Utilities

Source Control Patterns

Monorepo

Coordinate cross-package changes with atomic commits

Introducing changes across multiple packages could be a pain in the @ss

  • we want to group distinct changes into a single commit or a PR                         
  • we want to publish the packages in a correct order
  • we want to test the packages together
  • npm linking is not helping much :(

Source Control Patterns

[x]    It takes so much effort for you to maintain dependencies & versioning

[x]    Your packages are sharing the same workflow or environment

[x]    You are having problems when refactoring global features

Monorepo or Multi-repo?

The checklist

Source Control Patterns

Source Control Patterns

Leaf devDependencies are supposed to be in the root

Lerna

/packages

foo
bar
foo
qux
bar
qux
foo
bar
qux
{
  "devDependencies": {
    "@babel/core": "^7.5.5",
    "@commitlint/cli": "^8.1.0",
    "@commitlint/config-conventional": "^8.1.0",
    "@commitlint/config-lerna-scopes": "^8.1.0",
    "@hiendv/bem-sass": "^0.1.0",
    "@vue/cli-plugin-babel": "^3.10.0",
    "@vue/cli-plugin-eslint": "^3.10.0",
    "@vue/cli-service": "^3.10.0",
    "@vue/eslint-config-standard": "^4.0.0",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.8.0",
    "core-js": "^2.6.9",
    "eslint": "^6.1.0",
    "eslint-plugin-vue": "^5.2.3",
    "flatted": "^2.0.1",
    "husky": "^3.0.3",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^24.8.0",
    "jest-serializer-vue": "^2.0.2",
    "lerna": "^3.16.4",
    "octicons-vue": "^0.18.5",
    "rimraf": "^2.6.3",
    "rollup": "^1.19.4",
    "rollup-plugin-commonjs": "^10.0.2",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-postcss": "^2.0.3",
    "rollup-plugin-vue": "^5.0.1",
    "sass-loader": "^7.2.0",
    "vue": "^2.6.10",
    "vue-jest": "^3.0.4",
    "vue-template-compiler": "^2.6.10"
  }
}

Source Control Patterns

Hoist your dependencies to save time & space

Lerna

  • Common dependencies
  • Common binaries
  • Dependencies with different versions
  • Symlinked binaries

./node_modules   

./packages/*/node_modules   

Source Control Patterns

Private git-hosted packages could be a problem

Lerna

Lerna does support git hosted dependencies with committish versions

(e.g., #v1.0.0 or #semver:^1.0.0) 
// packages/pkg-1/package.json
{
  name: "pkg-1",
  version: "1.0.0",
  dependencies: {
    "pkg-2": "github:example-user/pkg-2#v1.0.0"
  }
}

// packages/pkg-2/package.json
{
  name: "pkg-2",
  version: "1.0.0"
}

Source Control Patterns

Private git-hosted packages could be a problem

Lerna

By using splitsh/lite, you could splitting each packages to their own [READONLY] repository. Read more about the implementation here.

#!/usr/bin/env bash

mirror () {
  echo "Mirroring $1"

  if ! git config remote.$1.url > /dev/null; then
    echo "Adding origin $1"
    git remote add ${1} git@github.com:hiendv/${1}.git
  fi

  sha=$(./splitsh-lite --prefix packages/$1 --quiet)
  echo "Pushing: $1 $sha:refs/heads/master"
  git push -u $1 $sha:refs/heads/master
}

for d in packages/*; do
  if [ -d ${d} ]; then
    mirror ${d##*/}
  fi
done

Source Control Patterns

Private git-hosted packages could be a problem

Lerna

But Lerna does not support self-hosted (non-public) instances of git because of npm/hosted-git-info limitation

E.g. git@github.yourcompany.com:acme/foobar.git

Just run your own registry, forget about git-hosted packages and work happily ever after.

Recommendation: Verdaccio

Bundling

Choose your bundler

@vue/cli-service

(not official logo)

Bundling

@vue/cli-service

@vue/cli-service is "react-scripts" for Vue, it's a wrapper for webpack.

 

It offers all kind of development magics with powerful hooks so you can just forget the boring work with webpack.

 

It's great for applications.

Instant prototyping & easy development

Bundling

@vue/cli-service

The ESM problem

Webpack does not support ESM output, which means your awesome library cannot be distributed in ESM. See @chrisvfritz/1060282363031289865.

Does it matter? Yes, because of tree-shaking

  • Smaller bundle
  • Less bloat, messy & dead code
  • Less unused dependencies

Bundling

Rule of thumb

  • If you are building a library, go with rollup.
  • If you are building an application, go with @vue/cli-service.
  • If you are building a library & example without Storybook, use both.

Bundling

Wait, Parcel?

  • Aiming to be a webpack opponent.
  • It's too much magic with the zero-configuration motto but in order to do what we really want, we have to get our hands dirty anyway.
  • Not supported widely.
  • No tree-shaking.

Bundling

Use rollup-plugin-vue. Modify preprocessOptions for convenience when playing with pre-processors.

Rollup

import importer from 'node-sass-tilde-importer'
vue({
  style: {
    preprocessOptions: {
      scss: {
        importer,
        includePaths: [reslv('src')],
        data: `@import "assets/variables.scss";`
      }
    }
  }
})

You might not need babel. buble is a lite version of babel: faster transpilation, smaller output & less configuration but it's limited to ES6 only.

If you are using typescript, you may have problems because tools don't play nice

  • ​Use rollup-plugin-typescript2
  • TSLint must exclude .vue files (palantir/tslint#2099)
  • ESLint should include .vue files with the help from @typescript-eslint/parser & @vue/typescript rules

Testing

Jest & ESLint

Jest is a no-brainer for Vue

  • vue-jest: Transformer
  • jest-serializer-vue: Snapshot serializer

ESLint is also a de facto linter

  • ​eslint-plugin-vue: Official plugin                       

Testing

Jest

Check out Testing Vue Apps - Edd Yerburgh - VueConf US 2018 if you are new with testing Vue components.

​You can test the output when needed (e.g. tree-shaking).

Storybook is a helpful toy with states & visual explanation & testing.

 

You can test components naturally with template string and createLocalVue

ModuleNameMapper may come in handy when working with stylesheets or symlinked packages exporting module only.

Testing

Jest

You can easily do this

<!-- template -->
<Parent>
  <slot/>
</Parent>

<!-- expectation -->
<Parent>
  <Children/>
</Parent>
const wrapper = mount(Parent, {
  slots: {
    default: [Children] // it works
  }
})

Slot mounting with template

Testing

Jest

But there are scenarios where you want more from the slot component

<!-- template -->
<Parent>
  <slot/>
</Parent>

<!-- expectation -->
<Parent>
  <Child foo="bar"><div>Lorem ipsum</div></Child>
  <Child foo="qux"><div>Dolor sit amet</div></Child>
</Parent>
const wrapper = mount(Parent, {
  slots: {
    // ehhh, where to pass attrs
    default: [Children]
  }
})
//
const wrapper = mount(Parent, {
  slots: {
    default: {
      // this wont work, too
      render (h) {
        return h(Child, {
          props: { foo: 'bar' }
        })
      }
    }
  }
})

Slot mounting with template

Testing

Jest

Solution ?

<!-- template -->
<Parent>
  <slot/>
</Parent>

<!-- expectation -->
<Parent>
  <Child foo="bar"><div>Lorem ipsum</div></Child>
  <Child foo="qux"><div>Dolor sit amet</div></Child>
</Parent>
const localVue = createLocalVue()
localVue.component('child', Child)

const wrapper = mount(Parent, {
  slots: {
    default: `
      <child foo="bar"><div>Lorem ipsum</div></child>
      <child foo="qux"><div>Dolor sit amet</div></child>
    `
  },
  localVue
})

Slot mounting with template

Testing

Jest

ModuleNameMapper

{
  "jest": {
    "moduleNameMapper": {
      "\\.(css|less|scss|sass)$": "<rootDir>/__mocks__/styleMock.js"
    }
  }
}

Mocking static assets imports & CSS modules

{
  "jest": {
    "moduleNameMapper": {
      "\\.(css|less|scss|sass)$": "identity-obj-proxy"
    }
  }
}

// CSS Modules are now a map[key] => value where value === key

Example

vue-tabs

tabs

react-tabs

vue-tabs-example

react-tabs-example

Tabs

Example

Tabs

Example

Tabs

  • Monorepo with Lerna
  • Core component for utilities & themes
  • Vue & React components with Rollup
  • Examples with @vue/cli-serivice & react-scripts
  • Linting with ESLint
  • Testing & coverage with Jest
  • Demonstration with GitHub Pages
  • Travis CI

Alternatives

Web Component using @vue/web-component-wrapper (vue-cli-service)

  • ​Leverages Custom Elements, Shadow DOM, ES Modules and HTML Templates
  • Requires ES2015 classes. IE11 and below not supported.

Bit using bit.envs/bundlers/vue

  • ​Manages & publish components
  • Compiler for Vue is basically webpack under the hood

More on this later!

Sharing & reusing your components

Q & A

Any questions, guys?

You can reach me anytime at <hien.dv.neo@gmail.com>

If you have any questions regarding the talk, please create an issue at https://github.com/hiendv/vue-meetup-vietnam-2019-talk

Thank You!

Let's start building your new Vue.js library

Packaging Vue Libraries

By hiendv

Packaging Vue Libraries

Gotchas & traps when packaging vue libraries

  • 1,418