首頁  >  文章  >  web前端  >  使用webpack建立一個js庫

使用webpack建立一個js庫

hzc
hzc轉載
2020-07-04 09:54:082756瀏覽

前言

在前文中,我說過本系列文章的受眾是在現代前端系統下能夠熟練編寫業務代碼的同學,因此本文在介紹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-boilerplate 和  vue-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-boilerplate 和  vue-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 生态圈添砖加瓦,不妨参考一下本项目。

推荐教程:《JS教程

以上是使用webpack建立一個js庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jianshu.com。如有侵權,請聯絡admin@php.cn刪除