這幾天基於支持HTML5無感認證的ServerLess平臺開發了一款博客、門戶網站等web平臺常用的PV統計工具:page-counter 。主要用到的技術是js+webpack。
回首看來,解決了以下幾個比較有意思的問題:
- 如何設計代碼,用統一的方式支持多個ServerLess平臺?
- 如何架構項目,使得其支持CDN和npm兩種方式引入?
- 如何精簡源碼,源碼大小控制在4kb?
- 如何藉助webpack分離生產和測試環境?
源碼地址:https://github.com/dongyuanxin/page-counter
npm地址:https://www.npmjs.com/package/page-counter
如果有興趣的同學,歡迎在閱讀完本文後一起接入其他平臺的開發; 覺得不錯的同學,歡迎給個Star哦 。
項目目錄
如上圖所示,bin/backend 目錄是暫時沒用的。幾個比較重要的目錄功能說明:
-
build/
: webpack的配置文件,分別是公共配置、開發模式配置、生產模式配置 -
dist/
- index.template.html: 開發模式下配合webpack的html模板文件
- page-counter.min.js: 打包後的page-counter內容,供CDN引入
- page-counter.bomb-1.6.7.min.js:我手動修改並且打包的Bomb平臺源碼
-
examples/
: gh-pages頁面,請看此頁面 -
deploy.sh
: gh-pages部署腳本,支持ssh和https協議 -
index.js
: npm的入口文件 -
index.build.js
: CDN打包入口文件 -
src/
:-
serverless/
: 暴露不同平臺的統一接口 -
config.js
: 自動讀取全局配置 -
utils.js
: 常用函數方法
-
抽象接口:支持多Serverless平臺
src/serverless/interface.js
中定義了不同平臺的類的公共父類。雖然js不支持抽象接口,但是也可以通過拋出錯誤來實現:
export default class ServerLessInterface {
constructor () {}
ACL () {
throw new Error('Interface method "ACL" must be rewritten')
}
setData () {
throw new Error('Interface method "setData" must be rewritten')
}
count () {
throw new Error('Interface method "count" must be rewritten')
}
}
而 leancloud.js 、bomb.js 等不同平臺的類都要實現這個接口中的這3個方法。然後通過 src/serverless/index.js
統一暴露出去:
import LeanCloud from './leancloud'
import Bomb from './bomb'
class ServerLessFactory {
constructor (name) {
name = name.toLocaleLowerCase()
switch (name) {
case 'leancloud':
return new LeanCloud()
case 'bomb':
return new Bomb()
default:
throw new Error('Serverless must be one of [ leancloud, bomb ]')
}
}
}
export default ServerLessFactory
這兩種設計,既解耦了不同平臺的代碼,而且還約束了實現規則。如果想接入更多平臺,只需要創建新文件,並且暴露一個繼承 ServerLessInterface
接口的指定方法的子類即可。
快速方便:支持CDN和npm的使用
一個成熟的前端小工具需要考慮到多種引入方式,目前主流的就是cdn和npm。例如jquery,cdn引入,jq會被自動掛載在window對象上;npm引入,則作用域只在當前文件模塊有用。
在考察了多種同類工具後,針對cdn和npm做了不同的處理。
npm
對外暴露 PageCounter
對象,其上有3個方法:
-
setData()
:將當前頁面信息發送到雲數據庫 -
countTotal()
: 統計數據庫總記錄數(網站總PV),並且將返回結果自動放入id爲page-counter-total-times的標籤裏 -
countSingle()
: 統計數據庫符合要求的記錄數(當前頁面PV),並且將返回結果自動放入id爲page-counter-single-times的標籤裏
import PageCounter from './src'
export default PageCounter
CDN
不會在全局掛載上述對象方法,會自動執行上面的3種方法。考慮到併發以及pv數允許1以內的誤差,沒有保證串行。
import PageCounter from './src'
PageCounter.setData()
PageCounter.countTotal()
PageCounter.countSingle()
精簡源碼:巧用package.json和第三方SDK
經過精簡,打包後cdn引入的源碼只有4kb。npm引入的話,webpack會自動進行tree shaking。因爲要對接不同的serverless平臺,因此需要使用他們的sdk。
而這些sdk分成2種:
- 類似leancloud:既可以npm引入,也可以cdn引入後自動掛載到window對象
- 類似bomb:無cdn引入,只要npm引入
針對第二種情況,我採取的方案是手動打包編譯。比如對於bomb的sdk,專門創建新的工程,然後配合webpack和以下代碼,進行打包。
import Bomb from 'hydrogen-js-sdk'
window.Bomb = Bomb
打包後的源碼放入版本庫,這樣藉助 https://unpkg.com 等常見的CDN平臺就可以引入了。這麼做的很重要的一點是: 代碼中都是通過window上的對象讀取對應serverless平臺的api,這樣就不會被webpack識別,進而發生重複無用打包 。
關於讀取配置的文件,都放在了 src/config.js
下。考慮到script標籤引入造成的變量掛載時間點不確定,讀取採用了動態讀取。爲了操作起來更方便,而不是像調用函數那樣,藉助了 es6類語法中的 setter和getter。
// 舉個例子:
class Config {
constructor() {}
get serverless() {
if (!window.PAGE_COUNTER_CONFIG) {
throw new Error('Please init variable window.PAGE_COUNTER_CONFIG')
}
return window.PAGE_COUNTER_CONFIG.serverless || 'leancloud'
}
}
const config = new Config()
console.log(config.serverless) // 返回當前最新的window.PAGE_COUNTER_CONFIG.serverless
最後講講package.json的小技巧。雖然代碼中沒有使用import語法讀取sdk的對象,但是我還是把leancloud、bomb平臺的sdk放入了 dependencies
。這樣做有什麼好處呢?
用戶只需要安裝page-counter即可,其他sdk自動安裝(不需要手動再敲命令)。然後用戶就可以使用下面語法美滋滋引入:
import('hydrogen-js-sdk')
.then(res => {
// 將 Bomb 對象掛載在 window 上
window.Bomb = res.default
// 設置應用信息
window.PAGE_COUNTER_CONFIG = {
// ...
}
return import('page-counter')
})
.then(res => {
const PageCounter = res.default
PageCounter.setData() // 發送當前頁面數據
PageCounter.countTotal() // 將總瀏覽量放入 ID 爲 page-counter-total-times 的DOM元素中
PageCounter.countSingle() // 將當前頁面瀏覽量放入 ID 爲 page-counter-single-times 的DOM元素中
})
Webpack:分離生產和開發環境
不得不說,webpack真的好用呀。髒活累活以及常見工具,它都給你承包了。
webpack.base.conf.js
是兩種模式的公共配置,指明入口文件以及代碼環境(web)。並且能夠識別模式,然後自行拼接配置。
webpack.prod.conf.js
:生產模式,主要爲了打包源碼,方便CDN引入。
webpack.dev.conf.js
: 開啓熱更新以及本地服務器方便調試,渲染的前端調試頁面的模板文件就是 dist/index.template.html
。
更多文章
《前端知識體系》
《設計模式手冊》
《Webpack4漸進式教程》