組內負責的幾個項目都有一些同樣的公共組件,因此就着手搭建了個公共組件開發腳手架,第一次開發 library,因此是參考着 iview 的配置來搭建的。記錄如何使用webpack4
搭建一個library
的腳手架javascript
使用 webpack4,須要安裝 webpack 和 webpack-clihtml
yarn add webpack webpack-cli -D
而後就是書寫配置文件。vue
我寫的 library 的目錄結構以下,僅供參考,主要是模仿 iview
的結構,其中部分配置參考了 vue-cli
的 webpack 配置文件。java
├─build │ build.js // 用於執行構建 │ check-versions.js // vue-cli 留下的,主要就是檢查npm版本和node版本 │ webpack.base.conf.js // 通用配置 │ webpack.dev.conf.js // 開發環境 │ webpack.dist.prod.conf.js // 用於生成library的代碼 -- hbf.min.js │ webpack.prod.conf.js // 用於生成example文件的打包代碼,這個實際上是沒有必要的. │ ├─dist │ └─example // example生成的打包文件夾,能夠經過githubPage來預覽,或者本地使用anywhere預覽 │ hbf.min.js // library 文件 │ ├─example // example目錄 │ App.vue │ index.html │ main.js │ ├─lib │ │ index.js // 全量引入公共組件,並暴露出來,包含install方法可供vue引入使用該插件 │ │ README.md │ │ │ └─components // 公共組件 │ ├─package.json // 項目包依賴
更爲具體信息的能夠到github倉庫閱覽。node
爲了更好的理解,先來了解下 webpack 編譯後的代碼。webpack
通過webpack處理過的代碼一般都是以下所示git
// webpack編譯後的代碼 /* * @param {Array} modules */ ;(function(modules) { function __webpack_require__(moduleId) { var module = { i: moduleId, // 模塊ID l: false, exports: {}, // 做爲結果返回. } // 調用modules數組的某個元素(類型爲函數) modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ ) return module.exports } return __webpack_require__(0) })([ /** 省略了代碼, 該數組的每一項表明一個模塊,實際是一個函數,接受三個參數,module對象,module.exports對象,__webpack_require__函數 **/ ])
webpack 編譯後的代碼的總體結構就是一個IIFE函數
,接受一個 modules: Array
參數。github
對於模塊處理,不管是 ES Module
的 import
仍是 commonjs
的 require
都轉化爲__webpack_require__
這個函數來引入模塊。web
__webpack_require__
函數,會從 modules
數組的第一個元素開始(moduleId 爲 0,也就是入口文件),執行該模塊(實爲一個函數)的邏輯,利用傳入的module.exports
的數據類型爲引用類型Object
,間接地給module.exports
添加屬性。vue-cli
return __webpack_require__(0)
從入口文件開始,逐個引入依賴模塊,最後返回入口模塊的 module.exports
此時這個編譯後的 js 文件,是沒法被其餘模塊所引用的,只在當前做用域內有效, webpack
就提供了建立 library 的方式,就是在output
裏定義library
和 libraryTarget
。使得構建完的 js 能夠供其餘模塊引入使用。
對於做爲一個 library 使用的項目來講,output 選項須要設置 library
// webpack.dist.pord.conf.js output: { path: path.resolve(__dirname, '../dist'), publicPath: '/dist/', filename: 'hbf.min.js', library: 'hbf', libraryTarget: 'umd' },
library
能夠是字符串,也能夠是對象,(對象僅限於 libraryTarget
的值爲 umd
的狀況下使用)
output: { library: { root:'Hbf', // 暴露給全局變量,window.Hbf進行調用 commonjs: 'hbf-public-components' }, libraryTarget: 'umd' }
commonjs
和 commonjs2
的區別。
commonjs
規範就是定義了一個 exports
對象,而 nodejs
在實現的時候,在 commonjs
規範的前提下作了一些擴展,定義了 module.exports
,從而也叫這種爲 commonjs2
規範。
咱們在引用別人的庫的時候,一般都是能夠經過多種方法引入的,好比 <script>
標籤引入,經過 commonJS
模塊 引入,經過 ES6 Module
引入。
libraryTarget
設置爲umd
(通用模塊規範)的話,則打包後能夠經過多種模塊加載的方法加載 library,具備高兼容性。
若是咱們的 library 是基於某某庫的基礎上開發的,好比說寫一個基於vue
的 UI 組件庫,在開發的這個組件庫的時候,咱們須要引入vue
,若是使用這個組件庫的用戶自己就已經引入了vue
,那麼vue
就會被引入並打包兩次,因此咱們在開發一個library
的時候,對於一些所依賴的模塊,能夠由引入library
的使用者提供。因此咱們須要將依賴的模塊在 library 的打包構建中去除。
externals
的做用,防止將某些 import
的包打包到 bundle 中,而是在運行時再去外部獲取這些擴展依賴。
經過設置 externals
,從輸出的 bundle
中排除 vue
和 iview
。
這些外部依賴多是如下的任何一種形式。
root
全局變量訪問commonjs
做爲一個commonjs
模塊引入commonjs2
與commonjs
相似,不過導出的是 module.exports
amd
使用amd
模塊規範引入// webpack.dist.pord.conf.js externals: { vue: { root: 'Vue', commonjs: 'vue', commonjs2: 'vue', amd: 'vue' }, iview: { root: 'iView', commonjs: 'iview', commonjs2: 'iview', amd: 'iview' } },
另外在 package.json 加多一個peerDependencies
字段,做用是約定library
所依賴的庫的版本號,在使用者下載使用library
的時候,若是所依賴的 iview
和 vue
的版本號不對,就會發出警告。
"peerDependencies": { "iview": ">2.0.0", "vue": ">2.0.0" },
對於這兩個依賴,寫到開發環境依賴中 ,否則安裝時,會在庫的目錄下安裝vue
和 iview
,這也不符合讓 library
的引用者提供 library
的依賴這個想法。
對於 vue 的插件庫 / 組件庫來講,若是想要全局引入的話,須要有一個install
方法。install 內部邏輯就是經過參數傳進來的vue
對象,註冊全部組件。而後最後將全部公共組件連同install
方法組成一個新對象暴露出去。
// 引入公共組件 import publicMenu from './components/public-menu' import tablePage from './components/table-page' import sliderCustom from './components/slider-custom' const components = { publicMenu, tablePage, sliderCustom, } const Hbf = Object.assign({}, components) const install = function(Vue, opts) { if (install.installed) return Object.keys(components).forEach(component => { Vue.component(component, component) }) } // 用於script標籤引入 if (typeof window !== 'undefined' && window.Vue) { install(window.Vue) } // 將install方法賦給Hbf對象 Hbf.install = install // 輸出default變量,用於全量引入,也能夠在引入的時候選擇使用 * 來全量引入 export default Hbf // 輸出各個組件,用於按需引入 export { publicMenu, tablePage, sliderCustom }
在暴露含有 install 方法的對象時,一開始使用的是 module.exports
,引用 library 的時候報錯Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'
。
由於我測試用的項目關閉了 babel
對 ES Module
的編譯,一般狀況下,沒有手動關閉的話,babel
會將 ES6 Module
編譯轉換成 commonjs
規範。因此在關閉了module
的轉換的狀況下,因爲庫的輸出使用的是 commonjs
規範的 module.exports
,而引入庫使用的是 ES6 模塊規範的import
關鍵字,因此產生了報錯,我將庫的導出寫成export
關鍵字,就沒報錯了。
之後書寫某一模塊的導入導出,仍是使用同一種規範的,不要混用。
瞭解到如今大多數庫,都是用的 commonjs
規範,因爲 webpack
的 tree-shaking
只對 ES Module
起做用,webpack
的 tree-shaking
其實是由Uglylify
來實現的。
因此庫的模塊規範可使用兩種,利用 package.json
的 main
和 module
字段分別定義庫的兩種模塊規範的入口文件。 main
使用的是 commonjs
規範語法書寫的文件,而 module
是使用 ES6 module
語法書寫的文件,module
字段目前仍是一個提案。因此採用了 ES2015
模塊語法的庫的,當咱們只使用到 library
的部分代碼,則能夠利用webpack
進行 tree-shaking
,去除未引用的代碼,減小打包文件體積。
首先就是須要註冊一個 npm 帳號。
若是以前使用的是淘寶鏡像的話,須要先切回 npm 官方源。否則是發不了包的。
打開命令行
切換官方源 npm config set registry https://registry.npmjs.org/
執行npm login
,而後輸入你的帳號信息。
能夠配置.npmignore
忽略一些不須要上傳的文件,寫法跟.gitignore
相同。
須要保證項目有正確的package.json
文件和README.md
文件
而後執行npm publish
進行發包。
發完包就能夠切回淘寶鏡像源
npm config set registry https://registry.npm.taobao.org
一開始是將 JS 文件放在本地測試,發如今HTML
文件的第一行就報錯,Unexpected token <
,StackOverflow說的緣由是引入路徑不正確,因此我就把 JS 文件放到 CDN 上了,
<script src="http://osuuzm0m8.bkt.clouddn.com/hbf.min.js"></script> <script> console.log(window.Hbf) // 會看到你導出的對象 </script>
輸出
能夠像使用其餘 vue 插件庫/組件庫同樣使用。
import hbf from 'hbf-public-components' // 使用use方法觸發hbf的intall方法,註冊所有組件 Vue.use(hbf)
若是是沒有導出default
變量,則使用另一種方式全量引入
import * as hbf from 'hbf-public-components'
import { publicMenu } from 'hbf-public-components'
按需引用,若是 library 使用的是ES2015 Module
規範,則不須要安裝任何插件,webpack 會對其進行tree-shaking
,去除未引用的代碼。
前面提過,webpack
的tree-shaking
是由Uglylify
插件實現的,我在開發環境下,沒有啓用Uglylify
來壓縮代碼,因此查看模塊打包圖,會發現整個庫都被引入了,雖然我只引入了一個組件。webpack4
在生產環境下,纔會進行tree-shaking
,設置mode
的值爲production
就會開啓生產環境下的優化。
若是是使用 commonjs
規範的 library 則須要一個插件支持,babel-plugin-import。該插件是ant
官方開發的。許多 UI 組件庫的按需引入也是依賴於這個插件。
安裝
yarn add babel-plugin-import -D
修改.babelrc
文件,
"plugins": [ ["import", { "libraryName": "hbf-public-components", "libraryDirectory": "lib/components" }] ],
其實若是對於一個不是很穩定,須要一直迭代更新
的公用組件庫
來講,使用 npm 包的話,會比較不方便,常常更新的公共組件代碼可使用Git subtree
(教程)來維護,能夠等到必定地步以後,公共組件庫穩定下來以後再考慮發佈一個 npm 包。
而開發一個組件庫,也可使用 rollup.js 來搭腳手架,rollup.js
默認使用的就是 ES2015 Module
,能夠進行靜態分析,去除未引用的代碼,tree-shaking
也是 rollup.js
先提出的。Rollup
相比較於Webpack
,更適合用於構建library
,Vue.js
就是使用 Rollup
構建的。Webpack
在代碼分割這方面比較有優點,因此webpack
相對來講比較適合構建應用程序,不過使用 webpack 構建 library 也是能夠的。
這個項目也能夠用作 webpack 構建 library 的通用腳手架。下次再嘗試Rollup
構建 library
有幫助的話,能夠給個star的話就最好啦。有問題也能夠聯繫我。
項目地址: https://github.com/huya-base-...