本次我們要做的是編寫一個(gè) vue 的 Tag 組件,使用 parcel 結(jié)合 tree-shaking 進(jìn)行打包浴麻。
本次要講的重點(diǎn)是用 parcel 構(gòu)建 vue,教大家如何去寫一個(gè)可以供別的使用的 vue 組件,并發(fā)布到 npm 上采盒。Tag 組件源碼分析不是這篇文章的重點(diǎn),重點(diǎn)是構(gòu)建思路蔚润。
首先說一下為啥使用 parcel 打包而沒用 webpack磅氨,出發(fā)點(diǎn)很簡單,為了用 parcel 而用 parcel嫡纠,哈哈~~~烦租!其實(shí) parcel 有一個(gè)優(yōu)點(diǎn)就是零配置打包,這次使用 parcel 真的是一行配置代碼沒寫除盏。叉橱。。 而且 parcel-bundler 是安裝到全局的者蠕,所以就是說在整個(gè)項(xiàng)目中都沒有構(gòu)建工具的痕跡(webpack 4也能做到。。夕土。)趟畏,parcel 會(huì)把你使用的工具都幫你配置好,不過得需要自己安裝包抡句。這是官網(wǎng) https://zh.parceljs.org/getting_started.html探膊,不過遺憾的是里面的 API 不全。待榔。逞壁。很多新版本更新的 api 沒有在文檔里說明,需要自己去 Google,比如本次要用到的 tree-shaking 配置:--experimental-scope-hoisting腌闯,在 api 里就沒有袭灯,在 https://medium.com/@devongovett/parcel-v1-9-0-tree-shaking-2x-faster-watcher-and-more-87f2e1a70f79 里面有詳細(xì)說明。
下面是項(xiàng)目目錄結(jié)構(gòu):
project
│ README.md
│ entry.js
│ package.json
│ .babelrc
│ .npmignore
│
└─── src
│ │ index.js
│ │ tag.vue
│ │
│ └─── fonts
│ │ iconfont.ttf
│ │ iconfont.woff
│
└─── demo
│ │ App.vue
│ │ index.html
│ │ main.js
│
└─── es
│ index.js
因?yàn)榻M件比較簡單绑嘹,所以沒有使用 vue-cli 創(chuàng)建稽荧,直接 npm init
,然后安裝需要的包
npm install vue -S
npm install vue-template-compiler babel-preset-env babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props -D
還有一些其他包工腋,parcel 有一個(gè) autoinstall 功能姨丈,是可以自動(dòng)去 install 項(xiàng)目中引用但是沒有下的包。
其中擅腰,vue-template-compiler
的作用是解析 .vue 文件的蟋恬,babel-preset-env
解析 es6 的語法,babel-plugin-syntax-jsx
babel-plugin-transform-vue-jsx
babel-helper-vue-jsx-merge-props
這三個(gè)是解析 vue 中的 jsx 語法用的趁冈。
.babelrc
{
"presets": [
["env", {
"modules": false
}]
],
"plugins": ["transform-vue-jsx"]
}
entry.js:打包的入口文件歼争,暴露出 Tag 組件
import Tag from './src';
export default Tag;
src 文件夾:存放源代碼
src ---> index.js
import Tag from './tag';
function install (Vue) {
if (install.installed) return;
install.installed = true;
Vue.component(Tag.name, Tag);
}
const SftcTag = {
install,
Tag
};
if (typeof window !== undefined && window.Vue) {
window.Vue.use(SftcTag)
}
export default SftcTag
index.js 暴露出一個(gè)對象,包含有兩個(gè)屬性渗勘,Tag 是組件代碼沐绒,install 為了使用 vue.use 注冊 vue 插件。
重點(diǎn):install 中有一個(gè)小技巧旺坠,就是這個(gè)方法接受了 Vue 參數(shù)來注冊全局組件乔遮。沒有在文件中直接使用 import Vue from 'vue'; 的原因是這種引用的 vue 其實(shí)是 node_modules 中的 vue/dist/vue.runtime.esm.js,而我們需要利用 tree-shaking 將 vue 抽離出去的時(shí)候使用的 vue 應(yīng)該是 node_modules/vue/dist/vue.common.js取刃,所以需要使用不同 Vue 進(jìn)行注冊決定了 install 方法需要使用接收的 Vue 對象來注冊蹋肮。細(xì)心的童鞋一定會(huì)對 vue/dist/vue.common.js
有印象,因?yàn)樵谑褂?webpack 的 tree-shaking 的時(shí)候璧疗,使用 alias 配置 'vue$': vue/dist/vue.common.js坯辩,和這個(gè)道理是一樣的,只是 parcel 幫我們做了這件事崩侠。
因?yàn)榻M件源碼不是這篇文章的重點(diǎn)漆魔,所以 tag.vue 的源碼放在最后貼上。
下面是最重要的部分:parcel 打包啦膜!
首先全局安裝 parcel
sudo npm install -g parcel-bundler
不用任何配置文件有送!
在 package.json 中直接寫 scripts:
"scripts": {
"demo": "parcel demo/index.html -p 8090 -d lib",
"build": "rm -rf lib && parcel build entry.js -d lib --out-file sftctag.min.js --experimental-scope-hoisting",
"build:nominify": "rm -rf lib && parcel build entry.js -d lib --out-file sftctag.min.js --no-minify --no-source-maps --experimental-scope-hoisting --no-cache"
},
然后直接運(yùn)行 npm run build
,完事僧家!就是這么快雀摘!
如果不使用 tree-shaking 打包的話就會(huì)把整個(gè)的 vue 源代碼也打進(jìn)去,會(huì)增加 65KB 體積八拱!加載一個(gè)包就會(huì)增加 65 KB阵赠,如果是 10 個(gè)這樣的包涯塔,那性能損失真的是太大了。
下面說一個(gè) build 命令里都干了哪些事:
rm -rf lib
每次打包刪除 之前的打包目錄
parcel build entry.js
執(zhí)行打包命令
-d lib
輸出目錄文件夾名
--out-file sftctag.min.js
打包文件名
--experimental-scope-hoisting
開啟 tree-shaking(parcel 1.9 版本新增特性)
build:nominify 用于查看打包出的源代碼清蚀,新加了幾個(gè)配置匕荸,也說明下:
--no-minify
不壓縮代碼
--no-source-maps
不使用 source-map
--no-cache
不使用 .cache
這樣整個(gè)組件就寫完了,parcel 針對這種很小的組件需求枷邪,確實(shí)是比 webpack 方便很多榛搔,但是對于大型項(xiàng)目,開始建議使用 webpack东揣,原因是 parcel 畢竟是小型大包工具很多定制化構(gòu)建都沒有支持践惑,另外現(xiàn)在parcel的相關(guān)資料確實(shí)不多也不深入。
demo 文件夾:引用 src 中的源碼嘶卧,做一個(gè)小 demo尔觉,一個(gè)是方便看源碼的人通過 demo 理解源碼。一個(gè)是在開發(fā)源碼時(shí)自己做調(diào)試芥吟。
demo ---> index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>sftable</title>
</head>
<body>
<noscript>
<strong>We're sorry but sftable doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
<script src="./main.js"></script>
</html>
demo ---> main.js
import Vue from 'vue';
import App from './App.vue';
import Tag from '../src';
Vue.config.productionTip = false;
Vue.use(Tag);
new Vue({
render: h => h(App),
}).$mount('#app');
demo ---> App.vue
<template>
<div id="app">
<div class="container">
<sftc-tag>標(biāo)簽一</sftc-tag>
<sftc-tag type="success">標(biāo)簽二</sftc-tag>
<sftc-tag type="info">標(biāo)簽三</sftc-tag>
<sftc-tag type="warning">標(biāo)簽四</sftc-tag>
<sftc-tag type="danger">標(biāo)簽五</sftc-tag>
</div>
<div class="container">
<sftc-tag closable>標(biāo)簽一</sftc-tag>
<sftc-tag closable type="success" @close="handleClose">標(biāo)簽二</sftc-tag>
<sftc-tag closable type="info">標(biāo)簽三</sftc-tag>
<sftc-tag closable type="warning">標(biāo)簽四</sftc-tag>
<sftc-tag closable type="danger">標(biāo)簽五</sftc-tag>
</div>
<div class="container">
<sftc-tag closable>默認(rèn)標(biāo)簽</sftc-tag>
<sftc-tag size="medium" closable>中等標(biāo)簽</sftc-tag>
<sftc-tag size="small" closable>小型標(biāo)簽</sftc-tag>
<sftc-tag size="mini" closable>超小標(biāo)簽</sftc-tag>
</div>
<div class="container">
<sftc-tag v-for="item in list" :key="item" closable type="success" @close="handleClose">標(biāo)簽{{ item }}</sftc-tag>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
list: [1, 2, 3, 4, 5]
}
},
methods: {
handleClose() {
this.list.pop();
},
},
}
</script>
<style>
.container {
margin-bottom: 30px;
}
</style>
下一步是發(fā)布 npm 包侦铜,具體發(fā)布 npm 包的方法我就不多介紹了,網(wǎng)上配置文章很多钟鸵,下面主要說一下 package.json 中的 module 字段配置钉稍,Tag 組件的 module 配置是: es/index.js
,下面是 index.js 的代碼:
export { default } from '../lib/sftctag.min.js';
export * from '../lib/sftctag.min.js'
import '../lib/sftctag.min.css'
很簡單就是暴露出了 js 源碼并引入需要的 css 文件
package.json 中引入 module 是為了擁抱 ES2015 中的 ES Module携添,所以也可以享受 tree-shaking 的特性嫁盲,之前 package.json 一直用 main 字段做入口文件篓叶。具體解釋可以參考這篇文章:《package.json 中的 Module 字段是干嘛的》
使用 Tag 組件方法:
import Vue from 'vue'
import SftcTag from 'sftctag'
Vue.use(SftcTag)
<sftc-tag>標(biāo)簽</sftc-tag>
因?yàn)?Tag 組件依賴使用者項(xiàng)目的 vue 烈掠,所以使用 Tag 組件前必須先引入 vue ,否則會(huì)報(bào)錯(cuò)缸托。
使用方法和相關(guān) API左敌,最好都寫到 README.md 中,方便使用者閱讀俐镐。
總結(jié):現(xiàn)在 Google 上 parcel 的資源并不多矫限,更別說度娘了。佩抹。叼风。但是其實(shí) parcel 的構(gòu)建思想和 webpack 很多都是歸一的,所以可以對比著去探索棍苹,寫完 Tag 組件之后對 parcel 有了一個(gè)大致了解无宿,并對如果寫一個(gè) vue 插件需要注意哪些點(diǎn)也有了清晰的思路。學(xué)會(huì)了 Tag 組件的構(gòu)建過程之后枢里,下一步就是寫其他的業(yè)務(wù)組件了孽鸡,比如 Table蹂午、Form 等等。
推薦一個(gè) parcel 的可視化包內(nèi)容占用比的插件:parcel-plugin-bundle-visualiser
和 webpack 的 webpack-bundle-analyzer 很相似
最后貼在 Tag 組件源碼
src ---> tag.vue
<script>
export default {
name: 'sftcTag',
props: {
text: String,
closable: Boolean,
type: String,
hit: Boolean,
disableTransitions: Boolean,
color: Boolean,
size: String
},
methods: {
handleClose(event) {
event.stopPropagation();
this.$emit('close', event);
}
},
computed: {
tagSize() {
return this.size;
}
},
render(h) {
const classes = [ 'sftc-tag', this.type ? `sftc-tag--${this.type}` : '',
this.tagSize ? `sftc-tag--${this.tagSize}` : '',
{'is-hit' : this.hit}
];
const tagEl = (<span class={classes} style={{backgroundColor: this.color}}>
{ this.$slots.default }
{
this.closable && <i class="sftc-tag__close sftc-icon-close" on-click={this.handleClose}></i>
}
</span>);
return this.disableTransitions ? tagEl : <transition name="sftc-zoom-in-center">{ tagEl }</transition>;
},
};
</script>
<style>
/* font */
@font-face {
font-family: sftc-icons;
src: url(fonts/iconfont.woff?t=1545101934831) format("woff"), url(fonts/iconfont.ttf?t=1545101934831) format("truetype");
font-weight: 400;
font-style: normal
}
[class*=" sftc-icon-"],
[class^=sftc-icon-] {
font-family: sftc-icons !important;
speak: none;
font-style: normal;
font-weight: 400;
font-variant: normal;
text-transform: none;
line-height: 1;
vertical-align: baseline;
display: inline-block;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
/* tag */
.sftc-icon-close:before {
content: "\e601"
}
.sftc-tag {
background-color: rgba(64, 158, 255, .1);
display: inline-block;
padding: 0 10px;
height: 32px;
line-height: 30px;
font-size: 12px;
color: #409EFF;
border-radius: 4px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border: 1px solid rgba(64, 158, 255, .2);
white-space: nowrap
}
.sftc-tag .sftc-icon-close {
border-radius: 50%;
text-align: center;
position: relative;
cursor: pointer;
font-size: 12px;
height: 16px;
width: 16px;
line-height: 16px;
vertical-align: middle;
top: -1px;
right: -5px;
color: #409EFF
}
.sftc-tag .sftc-icon-close::before {
display: block
}
.sftc-tag .sftc-icon-close:hover {
background-color: #409EFF;
color: #fff
}
.sftc-tag--info,
.sftc-tag--info .sftc-tag__close {
color: #909399
}
.sftc-tag--info {
background-color: rgba(144, 147, 153, .1);
border-color: rgba(144, 147, 153, .2)
}
.sftc-tag--info.is-hit {
border-color: #909399
}
.sftc-tag--info .sftc-tag__close:hover {
background-color: #909399;
color: #fff
}
.sftc-tag--success {
background-color: rgba(103, 194, 58, .1);
border-color: rgba(103, 194, 58, .2);
color: #67c23a
}
.sftc-tag--success.is-hit {
border-color: #67c23a
}
.sftc-tag--success .sftc-tag__close {
color: #67c23a
}
.sftc-tag--success .sftc-tag__close:hover {
background-color: #67c23a;
color: #fff
}
.sftc-tag--warning {
background-color: rgba(230, 162, 60, .1);
border-color: rgba(230, 162, 60, .2);
color: #e6a23c
}
.sftc-tag--warning.is-hit {
border-color: #e6a23c
}
.sftc-tag--warning .sftc-tag__close {
color: #e6a23c
}
.sftc-tag--warning .sftc-tag__close:hover {
background-color: #e6a23c;
color: #fff
}
.sftc-tag--danger {
background-color: rgba(245, 108, 108, .1);
border-color: rgba(245, 108, 108, .2);
color: #f56c6c
}
.sftc-tag--danger.is-hit {
border-color: #f56c6c
}
.sftc-tag--danger .sftc-tag__close {
color: #f56c6c
}
.sftc-tag--danger .sftc-tag__close:hover {
background-color: #f56c6c;
color: #fff
}
.sftc-tag--medium {
height: 28px;
line-height: 26px
}
.sftc-tag--medium .sftc-icon-close {
-webkit-transform: scale(.8);
transform: scale(.8)
}
.sftc-tag--small {
height: 24px;
padding: 0 8px;
line-height: 22px
}
.sftc-tag--small .sftc-icon-close {
-webkit-transform: scale(.8);
transform: scale(.8)
}
.sftc-tag--mini {
height: 20px;
padding: 0 5px;
line-height: 19px
}
.sftc-tag--mini .sftc-icon-close {
margin-left: -3px;
-webkit-transform: scale(.7);
transform: scale(.7)
}
/* transition */
.sftc-zoom-in-center-enter-active,
.sftc-zoom-in-center-leave-active {
transition: all .3s cubic-bezier(.55,0,.1,1);
}
.sftc-zoom-in-center-enter,
.sftc-zoom-in-center-leave-active {
opacity: 0;
transform: scaleX(0);
}
</style>
這塊就不做多解釋了彬碱,按照 element 的 Tag 寫的豆胸。