現代前端庫開髮指南系列(二):使用 webpack 構建一個庫

来源:https://www.cnblogs.com/Array-Huang/archive/2019/12/14/12038684.html
-Advertisement-
Play Games

前言 在前文中,我說過本系列文章的受眾是在現代前端體系下能夠熟練編寫業務代碼的同學,因此本文在介紹 webpack 配置時,僅提及構建一個庫所特有的配置,其餘配置請參考 webpack 官方文檔。 輸出產物 構建一個庫與構建一個一般應用最大的不同點在於 構建完成後輸出的產物 。 一般應用構建完成後會 ...


前言

在前文中,我說過本系列文章的受眾是在現代前端體系下能夠熟練編寫業務代碼的同學,因此本文在介紹 webpack 配置時,僅提及構建一個庫所特有的配置,其餘配置請參考 webpack 官方文檔。

輸出產物

構建一個庫與構建一個一般應用最大的不同點在於構建完成後輸出的產物

一般應用構建完成後會輸出:

  • 一個 html 文件
  • 一個 js 入口 chunk 、若幹子 chunk
  • 若幹 css 文件
  • 若幹其它資源,如圖片、字體文件等

雖然輸出的資源非常多,但實際上所有的依賴、載入關係都已經從 html 文件開始一層一層定下來了,換句話說,這個 html 文件實際上就是整個應用的入口。

一個庫構建完成後會輸出:

  • 一個 CommonJS 格式的 js 文件
  • 一個未壓縮的 UMD 格式的 js 文件
  • 一個已壓縮的 UMD 格式的 js 文件
  • 可能包括若幹的 css 文件
  • 可能包括若幹的其它資源文件

庫的入口分別是上面羅列的 js 文件;你可能會奇怪,一個庫怎麼會有3個入口文件呢?莫急,且聽我一一道來。

CommonJS

CommonJS 是 Node.js 推行的一種模塊化規範,主要語法包括module.exportsrequire()等;而我們在使用 webpack 引入 npm 包時,實際上是處於 Node.js 環境,由此可知,這個 CommonJS 格式的入口 js 文件(<庫名稱>.common.js)是供其它應用在 Node.js 環境下引入 npm 包使用的。由於在引用 npm 包時一般不會過多考慮 npm 包的體積(在構建自己的應用時如有需要可自行壓縮),且為了方便調試,因此該 js 入口文件是沒有經過壓縮的。

UMD

UMD 是一個模塊化規範大雜燴,除了相容 CommonJS 外,它還相容 AMD 模塊化規範,以及最傳統的全局變數模式。

這邊稍微介紹一下 AMD 規範, AMD 全稱 Asyncchronous Module Definition ,一般應用在瀏覽器端(這是與 CommonJS規範最大的不同點),最著名的 AMD 載入器是 RequireJS 。目前由於 webpack 的流行, AMD 這一模塊化方案已逐漸退出市場。

全局變數模式就很好理解了,就是把庫的入口掛載在一個全局變數(如window.xxx)上,頁面上的任何位置都能隨時取用,屬於最傳統的 js 插件載入方案。

由上可知, UMD 格式的入口 js 文件,既可以用於引用 npm 包的場景(未壓縮的版本,即<庫名稱>.umd.js),也可以直接用於瀏覽器端(已壓縮的版本,即<庫名稱>.umd.min.js)。

如何構建不同模塊化規範的庫文件

目前, webpack 不支持同時生成多份入口 js 文件,因此需要分多次來進行構建。

關鍵的 webpack 配置是:

  • CommonJS:output.libraryTarget: "commonjs2"
  • UMD: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

如果庫的用戶是直接通過在瀏覽器裡加載你的庫來使用的話,那麼提供一份 source map 文件是非常有必要的;這是因為你的庫在經過 webpack 構建,甚至壓縮後,與源代碼已經大相徑庭了,用戶將難以在瀏覽器中進行調試;但如果你能為自己的庫附上一份 source map ,瀏覽器開發者工具會調用 source map 來幫助解析,用戶的調試體驗會更接近於調試庫的源碼。

相應的 webpack 配置為:

// webpack 配置
{
    // ...其它配置
    devtool: 'cheap-module-source-map'
}

webpack 支持多種類型的 source map ,不同類型的 source map 在生成速度、支持功能(如 babel )、調試位置偏移等問題上均有不同表現,我這邊只做推薦:

  • 開發環境:cheap-module-eval-source-map
  • 生產環境:cheap-module-source-map

關於其它類型的 source map ,請查看 webpack 官方文檔的 devtool 章節。

排除第三方依賴

與一般應用不一樣,在開發庫的時候,我們應儘量避免引入第三方庫(構建過程中使用的工具鏈除外),因為這些第三方庫會讓我們寫的庫的大小猛增;很可能會出現這樣的情況:我們自己寫的小功能只有幾百行代碼的邏輯,構建出來的庫卻有幾百k,那這樣的庫意義就不大了。

但我們的確也很難避免使用第三方庫,那該咋辦呢?

// webpack 配置
{
    // ...其它配置
    externals: {
        lodash: {
            commonjs: 'lodash',
            commonjs2: 'lodash',
            amd: 'lodash',
            root: '_'
        }
    }
}

使用上述配置後,我們構建出來的庫中就不會包含配置中指定的第三方庫(例子中為lodash)了,下麵來一一詳解:

  • commonjscommonjs2項都是指明用戶在 node.js 環境下使用當前庫時,以 CommonJS 的方式來載入名為lodash的 npm 包。
  • amd項表示在瀏覽器中載入運行本庫時,本庫會試圖以 AMD 的方式來載入名為lodash的 AMD 模塊。
  • root項表示在瀏覽器中載入運行本庫時,本庫會試圖取全局變數window._(通過<script>標簽載入lodash.js時, lodash 會把自己註入到全局變數window._中)。

與一般應用不一樣的 externals 配置

在一般應用中,你或許會看到這樣的 externals 配置:

// webpack 配置
{
    // ...其它配置
    externals: {
        lodash: '_'
    }
}

這樣的 externals 配置方式意味著:無論在什麼環境,都要取_這個全局變數;如果當前是在一般應用且確定已經使用<script>來載入指定的第三方庫(比如 jQueryVue 等核心庫,的確很常以這種方式來載入),當然大可直接這樣用;但我們作為庫的作者,應提供更寬鬆更靈活的使用方式。

完整的 webpack 配置示例

由於構建不同模塊化規範的庫需要不同的 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-cli 定製並管理 webpack 配置

對於 Vue 生態的庫,如 Vue 組件、Vue 自定義指令等,可以使用 vue-cli (本文特指 vue-cli 3.0 後的版本)根據你的需求來定製 webpack 配置,可定製內容包括:

  • 是否啟用 Babel
  • 是否接入 TypeScript 語法
  • 是否支持 PWA
  • 是否使用 Vue-Router 和 Vuex
  • 是否使用 CSS 預處理器,並可選擇具體的 CSS 預處理器,包括 Sass / Less / Stylus
  • 是否使用 ESLint 和 Prettier
  • 是否接入單元測試和端對端測試(E2E)

定製完成後, vue-cli 將生成一個種子項目,該項目可執行(包括本地開發和構建生產環境的包)但沒有實際內容(實際內容不還得由你來寫嘛哈哈)。與一般的腳手架工具相比, vue-cli 除了可以生成 webpack 配置外,還將持續對其進行管理和維護,如:

  • 提供一個統一的自定義配置的入口;過往,我們為了達到自定義配置的目的,往往會直接在腳手架工具生成出來的 webpack 配置上直接進行修改,這樣會導致修改點非常分散,難以讓自定義的 webpack 配置在其它項目復用;而使用 vue-cli 後,所有對 webpack 配置的修改點都被集中管理起來了,需要復用的話,直接把這自定義配置文件(vue.config.js)遷移到別的項目即可。
  • 提供持續更新 webpack 配置的機制;假如現在有一個開源庫,我為了達到自己的目的,肆意在庫源碼上修改,那麼當我需要升級該開源庫的時候可就犯難了,因為這會把我之前做的修改都覆蓋掉;同理可得,vue-cli 由於統一了自定義配置的入口,並且是在每次運行項目(運行項目也是通過執行 vue-cli 的命令而非 webpack)時動態渲染 webpack 配置的,因此項目的 webpack 配置可以隨著 vue-cli 的升級而不斷升級了。
  • 提供持續更新 webpack 工具鏈的機制;眾所周知, webpack 工具鏈中包含了大量的第三方開源庫,如 Babel 、ESLint 等,這些開源庫也都是在不斷更新當中,在這個過程中,必然會不斷產生 Breaking Change ,所幸 vue-cli 通過自身升級——不斷修改 webpack 配置來達到適配最新版第三方開源庫的目的,而我們的項目也可以以極小的代價(升級 vue-cli 本身)來獲取 webpack 工具鏈的不斷更新。

vue-cli 自定義配置示例

摘自 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-boilerplatevue-directive-window 來作為實例項目代碼來輔助介紹。

javascript-library-boilerplate

javascript-library-boilerplate 是一個現代前端生態下快速構建 javascript 庫的腳手架(或稱種子項目,或稱示例代碼,看你理解了),本庫支持 GitHub 的 repository templates 功能,你可以直接在項目首頁點擊 Use this template 來直接套用本腳手架的代碼來創建你自己的 javascript 庫。

vue-directive-window

vue-directive-window 是一個可以快速讓模態框(modal)支持類視窗操作的增強庫;類視窗操作主要包括三大類:拖拽移動、拖拽調整視窗尺寸、視窗最大化; vue-directive-window 支持以 Vue 自定義指令或是一般 js 類的方式來調用。

vue-directive-window 相對於 javascript-library-boilerplate 來說,更貼近 Vue 生態圈,如果你最近想為 Vue 生態圈添磚加瓦,不妨參考一下本項目。

實例項目代碼介紹

我會以我最近寫的兩個開源庫:javascript-library-boilerplatevue-directive-window 來作為實例項目代碼來輔助介紹。

javascript-library-boilerplate

javascript-library-boilerplate 是一個現代前端生態下快速構建 javascript 庫的腳手架(或稱種子項目,或稱示例代碼,看你理解了),本庫支持 GitHub 的 repository templates 功能,你可以直接在項目首頁點擊 Use this template 來直接套用本腳手架的代碼來創建你自己的 javascript 庫。

vue-directive-window

vue-directive-window 是一個可以快速讓模態框(modal)支持類視窗操作的增強庫;類視窗操作主要包括三大類:拖拽移動、拖拽調整視窗尺寸、視窗最大化; vue-directive-window 支持以 Vue 自定義指令或是一般 js 類的方式來調用。

vue-directive-window 相對於 javascript-library-boilerplate 來說,更貼近 Vue 生態圈,如果你最近想為 Vue 生態圈添磚加瓦,不妨參考一下本項目。

系列文章目錄(同步更新)

想要閱讀更多我的技術文章?請到我的 GitHub 博客 Array-Huang/blog 來,如果對您有幫助的話請 Star&Watch 走一波哈(〃^ω^)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 總所周知,Vue新版本3.0 使用 TypeScript 開發,讓本來就很火的 TypeScript 受到更多人的關註。雖然 TypeScript 在近幾年才火,但其實它誕生於2012年10月,正式版本發佈於2013年6月,是由微軟編寫的自由和開源的編程語言。TypeScript 是 Java ...
  • 添加刪除記錄 ~~~javascript Name Email Salary Tom [email protected] 5000 Delete Jerry [email protected] 8000 Delete 添加新員工 name: email: salary: Submit ~~~ ...
  • JQ與JS JQ是JS寫的插件庫,就是一個JS文件 凡是用JQ能實現的,JS都能實現,JS能實現的,JQ不一定能實現 引入 BootCDN:https://www.bootcdn.cn/jquery/ 本地文件引入:`` 線上引入:`` 選擇元素 在JQ中使用選擇器選擇元素和在CSS中使用CSS選擇 ...
  • 前提 其實只有 <body>、<frame>、<iframe>、<img>、<link>、<script>、<style> 這些標簽才有onload事件,而div、p等標簽是沒有的。 但如果我們還是想在div append到DOM時做一些事情該怎麼辦呢?有人會說那就在append到DOM的代碼後面加 ...
  • 前言:eslint很噁心的一個地方:你是否被各種語法報錯一個標點符號,一個空格,一個回車......各種報錯折磨著你! 加上編輯器 VS Code 的自動格式化穩穩的和Eslint衝突報錯。 對此,我們想在 VS Code 中格式化不報錯需要對eslint進行一些設置 錯誤一:方法小括弧前面報錯,E ...
  • Vue ES6箭頭函數使用總結 by:授客 QQ:1033553122 箭頭函數 ES6允許使用“箭頭”(=>)定義函數: 函數不帶參數 定義方法:函數名稱 = () => 函數體 let func = () => 1 等同於 function func() { return 1; } 函數只帶一個 ...
  • 太極圖繪製 ~~~javascript TaiChi ~~~ ...
  • 覆選框全選/全部選 ~~~javascript 你愛好的運動是什麼?全選/全不選 足球 籃球 羽毛球 乒乓球 ~~~ 你愛好的運動是什麼?全選/全不選 足球 籃球 羽毛球 乒乓球 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...