(check out my blog)
Hello! This is work-in-progress prototype but actually it already works.
I have this scenario: multiple VueJs Apps which are sharing a component library. I'd like to place all of them in a monorepo managed by Lerna.
The component library is based on Storybook.
Feel free to help me / send me your suggestions.
What I want to achieve
Simplicity and maintainability.
In my scenario one or more teams are working on the components and updating them using semantic versioning.
All the VueJs Apps are using the shared components and the change-log is automatically created based on commit messages and tags.
The commit messages and the tags are automatically managed by Lerna.
The "frame" is already working, but I still have to refine some steps and add features.
This is the GitHub repo: https://github.com/pixari/component-library-monorepo.
And here the "How to":
Getting Started
Install Lerna
Let's start by installing Lerna globally with npm:
$ npm install --global lerna
Next we have to create a new git repository:
$ git init component-library-monorepo && cd component-library-monorepo
And then, following Lerna's official documentation, will turn it into a Lerna repo:
lerna init
The repository should look lihe this:
component-library-monorepo/
packages/
lerna.json
package.json
If you'd like to learn something more about this process, you can check the official Lerna documentation.
Install Storybook
Let's start by installing Lerna globally with npm:
$ npm install @storybook/vue --save-dev
Add peer dependencies
$ npm install vue --save
$ npm install vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue --save-dev
Add a npm script
{
"scripts": {
"storybook": "start-storybook"
}
}
For a basic Storybook configuration, the only thing you need to do is tell Storybook where to find stories.
To do that, create a file at .storybook/config.js with the following content:
import { configure } from '@storybook/vue';
const req = require.context('../packages', true, /.stories.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
Add the first component to the component library
We create in the root a packages/index.stories.js file and write our first story:
import Vue from 'vue';
import { storiesOf } from '@storybook/vue';
import MyButton from './Button/src/Button.vue';
storiesOf('Button', module)
.add('as a component', () => ({
components: { MyButton },
template: '<my-button>with text</my-button>'
}))
.add('with emoji', () => ({
components: { MyButton },
template: '<my-button>๐ ๐ ๐ ๐ฏ</my-button>'
}))
.add('with text', () => ({
components: { MyButton },
template: '<my-button :rounded="true">rounded</my-button>'
}));
Now we create the real "Button" component:
/packages/Button
/src
Button.vue
<template>
<button type="button"><slot /></button>
</template>
<script>
export default {
name: 'MyButton',
}
</script>
The index.js
/packages/Button
src/index.js
import MyButton from './Button.vue';
export default MyButton;
And the package.json:
{
"name": "@mylibrary/my-button",
"version": "0.2.0",
"description": "Just a simple button component",
"main": "dist/index.js",
"module": "src/index.js",
"scripts": {
"transpile": "vue-cli-service build --target lib ./src/index.js"
}
}
Start Storybook
Now you are ready to start Storybook and play with your first component:
$ npm run storybook
And you should see it running here:
http://localhost:51368
Create a VueJs App
Installation
To install the Vue CLI, use this command:
$ npm install -g @vue/cli
$ npm install --save-dev @vue/cli-service
Create a new project
To create a new project, run:
$ cd packages && vue create my-app
And please choose the easiest option:
> default (babel, eslint)
In this tutorial we don't want to build the best VueJs App possible, but just show how to share a component library between VueJs Apps.
Add eslint configuration
Create ./packages/my-app/.eslintrc.js
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:vue/essential"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"vue"
],
"rules": {
}
};
Run the App
Let's run our new app:
$ cd my-app && npm run serve
And now you should see here your app, up&running:
http://localhost:8080/
Using Lerna to link dependencies
Add the following dependency to your packages/my-app/package.json:
{
"dependencies": {
"@mylibrary/my-button": "*"
}
}
Fix eslint
const path = require('path');
module.exports = {
chainWebpack: config => {
config.module
.rule('eslint')
.use('eslint-loader')
.tap(options => {
options.configFile = path.resolve(__dirname, ".eslintrc.js");
return options;
})
},
css: {
loaderOptions: {
postcss: {
config:{
path:__dirname
}
}
}
}
}
And now we can "bootstrap" the packages in the current Lerna repo, install all of their dependencies and links any cross-dependencies:
In the root:
$ lerna bootstrap
Update the Vue App
Change the content of ./packages/my-app/src/main.js:
import Vue from 'vue'
import App from './App.vue'
import MyButton from '@mylibrary/my-button';
Vue.config.productionTip = false
Vue.component('my-button', MyButton);
new Vue({
render: h => h(App),
}).$mount('#app')
and change the content of our HelloWorld component (./packages/my-app/src/components/HelloWorld.vue):
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<my-button>It Works!</my-button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
We now transpile our components:
$ lerna run transpile
run again..
$ cd packages/my-app && npm run serve
Go to http://localhost:8080 and you should se the button in the middle of the HelloWorld page :)
Top comments (3)
The content seems nice, I will give it a try.
I have few questions:
Thank you for helping to try to understand the coupling that lerna create between actual packages and any webapp/storybook ui.
For babel you can put a babel.config.js at the root and then have package specific .babelrcs modify the default. Examples are on the babel documentation for this exact scenario.
Thank you for this post! I'm currently migrating a repository using this tutorial and lerna documentation and I'm wondering if there's a way to properly handle webpack aliases from a
vue.config.js
file inside a source package. Image a scenario where this package is required by the client package. Have you ever tried something similar?