在前文中,我說過本系列文章的受衆是在現代前端體系下可以熟練編寫業務代碼的同窗,所以本文在介紹 webpack 配置時,僅說起構建一個庫所特有的配置,其他配置請參考 webpack 官方文檔。javascript
構建一個庫與構建一個通常應用最大的不一樣點在於構建完成後輸出的產物。css
通常應用構建完成後會輸出:html
雖然輸出的資源很是多,但實際上全部的依賴、加載關係都已經從 html 文件開始一層一層定下來了,換句話說,這個 html 文件實際上就是整個應用的入口。前端
一個庫構建完成後會輸出:vue
庫的入口分別是上面羅列的 js 文件;你可能會奇怪,一個庫怎麼會有3個入口文件呢?莫急,且聽我一一道來。java
CommonJS 是 Node.js 推行的一種模塊化規範,主要語法包括module.exports
、require()
等;而咱們在使用 webpack 引入 npm 包時,其實是處於 Node.js 環境,由此可知,這個 CommonJS 格式的入口 js 文件(<庫名稱>.common.js
)是供其它應用在 Node.js 環境下引入 npm 包使用的。因爲在引用 npm 包時通常不會過多考慮 npm 包的體積(在構建本身的應用時若有須要可自行壓縮),且爲了方便調試,所以該 js 入口文件是沒有通過壓縮的。node
UMD 是一個模塊化規範大雜燴,除了兼容 CommonJS 外,它還兼容 AMD 模塊化規範,以及最傳統的全局變量模式。webpack
這邊稍微介紹一下 AMD 規範, AMD 全稱 Asyncchronous Module Definition ,通常應用在瀏覽器端(這是與 CommonJS規範最大的不一樣點),最著名的 AMD 加載器是 RequireJS 。目前因爲 webpack 的流行, AMD 這一模塊化方案已逐漸退出市場。git
全局變量模式就很好理解了,就是把庫的入口掛載在一個全局變量(如window.xxx
)上,頁面上的任何位置都能隨時取用,屬於最傳統的 js 插件加載方案。github
由上可知, UMD 格式的入口 js 文件,既能夠用於引用 npm 包的場景(未壓縮的版本,即<庫名稱>.umd.js
),也能夠直接用於瀏覽器端(已壓縮的版本,即<庫名稱>.umd.min.js
)。
目前, webpack 不支持同時生成多份入口 js 文件,所以須要分屢次來進行構建。
關鍵的 webpack 配置是:
output.libraryTarget: "commonjs2"
output.libraryTarget: "umd"
對於 UMD ,咱們還須要設置全局變量名稱,即output.library: "LibraryName"
。
爲了壓縮構建出來的文件,最簡單的方法是在 CLI 中調用 webpack 命令時帶上 mode 參數,如webpack --mode=production
;這是由於當 mode 的值爲production
時, webpack 會自動啓用 UglifyJsPlugin 對源碼進行壓縮。
我在某公司工做時,該公司對第三方依賴抓得很緊,全部在項目裏使用的第三方依賴都必須申請且審覈經過後纔可以使用;且申請時是精確到具體版本的,未申請的軟件版本也一律不容許使用。某些第三方依賴不管在文件內容上,仍是在文件名稱上,都沒有體現出版本號,這就對咱們識別這類第三方依賴產生障礙,這是咱們開發本身的庫時須要引覺得戒的。
在構建庫時,咱們徹底能夠利用 webpack 把庫的信息直接輸出到文件內容裏,有了這「身份信息」,用戶使用起來也會格外安心。
輸出庫版本信息的方法是使用 webpack.BannerPlugin ,最簡單的使用方法以下:
const pgk = require('./package.json');
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
/* webpack 配置 */
{
// ...其它配置
plugins: [
// ...其它 plugin 配置
new webpack.BannerPlugin(banner);
]
}
複製代碼
最終生成出來的效果是:
/*! * * vue-directive-window * Vue.js directive that enhance your Modal Window, support drag, resize and maximize. * * @version v0.7.5 * @homepage https://github.com/Array-Huang/vue-directive-window * @repository git+https://github.com/Array-Huang/vue-directive-window.git * * (c) 2019 Array-Huang * Released under the MIT License. * hash: dc6c11a1e50821f4444a * */
複製代碼
若是庫的用戶是直接經過在瀏覽器里加載你的庫來使用的話,那麼提供一份 source map 文件是很是有必要的;這是由於你的庫在通過 webpack 構建,甚至壓縮後,與源代碼已經截然不同了,用戶將難以在瀏覽器中進行調試;但若是你能爲本身的庫附上一份 source map ,瀏覽器開發者工具會調用 source map 來幫助解析,用戶的調試體驗會更接近於調試庫的源碼。
相應的 webpack 配置爲:
// webpack 配置
{
// ...其它配置
devtool: 'cheap-module-source-map'
}
複製代碼
webpack 支持多種類型的 source map ,不一樣類型的 source map 在生成速度、支持功能(如 babel )、調試位置偏移等問題上均有不一樣表現,我這邊只作推薦:
關於其它類型的 source map ,請查看 webpack 官方文檔的 devtool 章節。
與通常應用不同,在開發庫的時候,咱們應儘可能避免引入第三方庫(構建過程當中使用的工具鏈除外),由於這些第三方庫會讓咱們寫的庫的大小猛增;極可能會出現這樣的狀況:咱們本身寫的小功能只有幾百行代碼的邏輯,構建出來的庫卻有幾百k,那這樣的庫意義就不大了。
但咱們的確也很難避免使用第三方庫,那該咋辦呢?
// webpack 配置
{
// ...其它配置
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
}
複製代碼
使用上述配置後,咱們構建出來的庫中就不會包含配置中指定的第三方庫(例子中爲lodash
)了,下面來一一詳解:
commonjs
和commonjs2
項都是指明用戶在 node.js 環境下使用當前庫時,以 CommonJS 的方式來加載名爲lodash
的 npm 包。amd
項表示在瀏覽器中加載運行本庫時,本庫會試圖以 AMD 的方式來加載名爲lodash
的 AMD 模塊。root
項表示在瀏覽器中加載運行本庫時,本庫會試圖取全局變量window._
(經過<script>
標籤加載lodash.js
時, lodash 會把本身注入到全局變量window._
中)。在通常應用中,你或許會看到這樣的 externals 配置:
// webpack 配置
{
// ...其它配置
externals: {
lodash: '_'
}
}
複製代碼
這樣的 externals 配置方式意味着:不管在什麼環境,都要取_
這個全局變量;若是當前是在通常應用且肯定已經使用<script>
來加載指定的第三方庫(好比 jQuery、 Vue 等核心庫,的確很常以這種方式來加載),固然大可直接這樣用;但咱們做爲庫的做者,應提供更寬鬆更靈活的使用方式。
因爲構建不一樣模塊化規範的庫須要不一樣的 webpack 配置(其實也只是稍有不一樣)來進行屢次構建,所以本文只針對構建 UMD 格式且已壓縮這一場景來展現最簡單的 webpack 配置示例;若是想知道如何更有效率地拼接 webpack 配置,請看 micro-schema-validator 項目的 webpack 配置文件。
// webpack.config.js
const webpack = require('webpack');
const pkg = require('./package.json'); // 把 package.json 做爲信息源
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
module.exports = {
entry: `${__dirname}/index.js`,
devtool: 'cheap-module-source-map',
output: {
path: `${__dirname}/dist`, // 定義輸出的目錄
filename: 'micro-schema-validator.min.js', // 定義輸出文件名
library: 'MicroSchemaValidator', // 定義暴露到瀏覽器環境的全局變量名稱
libraryTarget: 'umd', // 指定遵循的模塊化規範
},
/* 排除第三方依賴 */
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/
},
{
test: /(\.jsx|\.js)$/,
loader: 'eslint-loader',
exclude: /(node_modules|bower_components)/
}
]
},
plugins: [
new webpack.BannerPlugin(banner) // 輸出項目信息
]
};
複製代碼
對於 Vue 生態的庫,如 Vue 組件、Vue 自定義指令等,可使用 vue-cli (本文特指 vue-cli 3.0 後的版本)根據你的需求來定製 webpack 配置,可定製內容包括:
定製完成後, vue-cli 將生成一個種子項目,該項目可執行(包括本地開發和構建生產環境的包)但沒有實際內容(實際內容不還得由你來寫嘛哈哈)。與通常的腳手架工具相比, vue-cli 除了能夠生成 webpack 配置外,還將持續對其進行管理和維護,如:
摘自 vue-directive-window 項目的 vue.config.js 文件:
const webpack = require('webpack');
const pkg = require('./package.json');
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
module.exports = {
chainWebpack: config => {
config.output.libraryExport('default');
config.plugin('banner').use(webpack.BannerPlugin, [
{
banner,
entryOnly: true,
},
]);
},
};
複製代碼
看起來是否是比上文中最基礎的 webpack 配置還要簡潔呢?當項目的架構逐漸豐富起來後,這個差距將不斷拉大。
我會以我最近寫的兩個開源庫:javascript-library-boilerplate 和 vue-directive-window 來做爲實例項目代碼來輔助介紹。
javascript-library-boilerplate 是一個現代前端生態下快速構建 javascript 庫的腳手架(或稱種子項目,或稱示例代碼,看你理解了),本庫支持 GitHub 的 repository templates 功能,你能夠直接在項目首頁點擊 Use this template 來直接套用本腳手架的代碼來建立你本身的 javascript 庫。
vue-directive-window 是一個能夠快速讓模態框(modal)支持類窗口操做的加強庫;類窗口操做主要包括三大類:拖拽移動、拖拽調整窗口尺寸、窗口最大化; vue-directive-window 支持以 Vue 自定義指令或是通常 js 類的方式來調用。
vue-directive-window 相對於 javascript-library-boilerplate 來講,更貼近 Vue 生態圈,若是你最近想爲 Vue 生態圈添磚加瓦,不妨參考一下本項目。
想要閱讀更多個人技術文章?請到個人 GitHub 博客 Array-Huang/blog 來,若是對您有幫助的話請 Star&Watch 走一波哈(〃^ω^)