Hanoi, August 2019
Hien Dao
Senior developer @
var vm = new Vue({}) since v1
Image courtesy of toptal.com
The organizations of your repositories
[ ] 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
The checklist
[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
The checklist
Maintaining dependencies & versioning
Vue Library
A single package
Maintaining dependencies & versioning
Vue
Core
Utilities
3 separated repositories
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
[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
The checklist
Sharing workflow & environment
Processes
Environment
Sharing workflow & environment
[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
The checklist
Coordinate cross-package changes with atomic commits
Vue
Core
Utilities
Coordinate cross-package changes with atomic commits
Introducing changes across multiple packages could be a pain in the @ss
[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
The checklist
Leaf devDependencies are supposed to be in the root
/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"
}
}
Hoist your dependencies to save time & space
./node_modules
./packages/*/node_modules
Private git-hosted packages could be a problem
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"
}
Private git-hosted packages could be a problem
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
Private git-hosted packages could be a problem
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
@vue/cli-service
(not official logo)
@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
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
Use rollup-plugin-vue. Modify preprocessOptions for convenience when playing with pre-processors.
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
Jest is a no-brainer for Vue
ESLint is also a de facto linter
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.
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
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
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
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
vue-tabs
tabs
react-tabs
vue-tabs-example
react-tabs-example
Web Component using @vue/web-component-wrapper (vue-cli-service)
Bit using bit.envs/bundlers/vue
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