引言
最近專案中需要提供一個封裝的JS SDK,雖然目前是一個很簡單的功能,但是為了日後便於維護,擴充套件,因此還是選用了 webpack 作為打包工具,作為參考,推薦一篇上好的文章,感興趣的朋友可以先閱讀一下
目標
作為一個SDK,我想達到如下的目的
- 提供一個載入方案
- 暴露一個公共變數,最好能支援多種載入方式
- 提供未壓縮版與壓縮版
- 可以為不同的合作商提供定製版本
- 內部實現通過模組引用,方便擴充套件
接下來一步步講一下如何通過 webpack 實現
準備
假如我們最後需要提供的 SDK 如下
// 引用
<script type="text/javascript" src="http://xxx.com/sdk.js"></script>
// 使用
window.SDK.Shop.getList() // 獲取門店資訊列表
window.SDK.Store.getById() // 通過ID獲取商品資訊
複製程式碼
那麼檔案列表應該大致如下
|
| - package.json
| - webpack.config.js
| - node_modules
| - src
| - index.js
| - lib
| - shop.js
| - store.js
| - dist
| - build.js
複製程式碼
webpack 通過 index.js 入口打包好檔案,放到 dist 資料夾,一些關鍵檔案的程式碼應該如下
webpack.config.js
let path = require('path')
let webpack = require('webpack')
module.exports = {
entry: {
'sdk': ['./src/index.js']
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
// 壓縮混淆 js
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
})
]
}
複製程式碼
shop.js
module.exports = {
getList: function () {
.....
}
}
複製程式碼
store.js
module.exports = {
getById: function (id) {
.....
}
}
複製程式碼
index.js
var Shop = require('./lib/shop.js')
var Store = require('./lib/store.js')
module.exports = {
Shop: Shop,
Store: Store
}
複製程式碼
build.js
// 這裡簡單的 copy 了 vue-cli 提供的 build 程式碼
let webpackConfig = require('./webpack.config')
let rm = require('rimraf')
let path = require('path')
let webpack = require('webpack')
let util = require('util')
const compileCallback = (er, stats) => {
if (er) throw er
stats = util.isArray(stats.stats) ? stats.stats : [stats]
stats.forEach((item) => {
process.stdout.write(item.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
})
console.log('Build complete.\n')
}
rm(path.resolve(__dirname, './dist'), err => {
if (err) throw err
let compiler = webpack(webpackConfig)
compiler.run(compileCallback)
})
複製程式碼
方案
1. 載入引用
這部分很好實現
- 提供一個靜態檔案地址(或者一個CDN地址)來簡單的通過 html 載入
提供一個npm包,在伺服器端載入(因為暫時沒這種需求,所以先掛起)
2. 暴露一個公共變數
最簡單的做法是在 index.js 里加一句 window.SDK = ...
不過 webpack 有更好的解決方案 output.library
output
選項主要用於配置檔案輸出規則,而 output.library
選項可以用於輸出時將檔案暴露為一個變數,可以說是為了打包 SDK 檔案而生的一個配置項
這裡還有一個 webpack 的教程來幫你如何使用
library
建立 Library
另一個選項 output.libraryTarget
則可以配置如何輸出變數,預設值是 var
簡單的說明一下這些值的含義
- var:在當前作用域匯出一個變數
- assign: 匯出一個變數作為全域性變數
- this: 匯出作為
this
的一個屬性,這個this
不一定是window
,要看引用 SDK 的位置- window: 匯出為
window
的一個屬性,基本上就算全域性變數了- global:匯出為
global
的一個屬性,估計是這個變數名比較常用吧。。- commonjs:匯出為
exports
的一個屬性,匯出的格式可以在 CommonJS 環境裡引用- commonjs2:賦值給
module.exports
,同樣可以用在 CommonJS 環境裡- amd:暴露給 AMD 模組
- umd:暴露為所有模組都可用的格式
- jsonp:包裹到一個 jsonp 包裝容器中,也就是一個 Function
因此,我們稍微修改一下 webpack.config.js 的程式碼
module.exports = {
...
output: {
path: './dist',
filename: '[name].js',
library: 'SDK',
libraryTarget: "umd"
}
...
}
複製程式碼
3. 提供兩個版本
簡單的可以寫兩個 build 指令碼,分別打包為壓縮程式碼與未壓縮程式碼,不過 webpack 本身也可以匯出為多個配置(這也摸清了 webpack 如何為多個 output
配置不同的引數)
於是,我們的 webpack.config.js 程式碼修改為
module.exports = [
// 未壓縮版
{
entry: {
'sdk': './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
library: 'SDK',
libraryTarget: "umd"
}
},
// 壓縮版
{
entry: {
'sdk.min': './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
library: 'SDK',
libraryTarget: "umd"
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
})
]
}
]
複製程式碼
打包後的結果如下圖所示
4. 提供定製版本
這個相對容易點了,可以通過上一條的方式做多個配置,也可以簡單的在 entry
中寫多個入口
webpack.config.js 程式碼修改為
module.exports = [
{
entry: {
'sdk': './src/index.js',
'custom': './src/custom.js'
}
...
},
...
]
複製程式碼
5. 內部實現通過模組引用
這個就不再囉嗦了...經過了一系列配置,別說模組了,ES6都能給你加進去,不過要注意的是,如果引入了 babel 或者其他的庫,打包出來的 SDK 檔案就很大了,甚至是簡單的引用一個 webpack-merge 都會增加50K的容量,所以最好還是以原生的方式去寫,如果需要 ajax
等功能就簡單的封裝一下,能不引用別的庫就不引用,如果覺得檔案體積太大,可以用 webpack-bundle-analyzer 分析一下檔案大小的分佈,以及是否有重複引用
結語
雖然是個小專案,不過在初期也應該考慮的全面,目前專案雖小,但是不見得以後會發展成什麼樣,可能有些人會說這麼簡單的專案用閉包封裝一下,暴露兩個介面即可,何必搞那麼複雜,但是假如之後需要新增新的介面呢,假如需要提供兩個 sdk ,分別提供不同的介面,同時又有部分相同的介面呢,如果這時候再進行重構,會不會對線上有很大的影響?需要進行多少測試?這無形之中給我們加大了很多成本與不確定性
希望這篇比較初級的文章能對大家在建立一個SDK專案上有所幫助~