在前文中,我說過本系列文章的受眾是在現代前端系統下能夠熟練編寫業務代碼的同學,因此本文在介紹webpack 配置時,僅提及構建一個庫特有的配置,其餘配置請參考webpack 官方文件。
建構一個函式庫與建構一個一般應用最大的不同點在於建置完成後輸出的產物。
一般應用程式建置完成後會輸出:
雖然輸出的資源非常多,但實際上所有的依賴、載入關係都已經從html 檔案開始一層一層定下來了,換句話說,這個html 檔案其實就是整個應用的入口。
一個函式庫建置完成後會輸出:
庫的入口分別是上面羅列的js 檔案;你可能會奇怪,一個函式庫怎麼會有3個入口檔呢?莫急,且聽我一一道來。
CommonJS 是Node.js 推行的一種模組化規範,主要語法包括module.exports
、require( )
等;而我們在使用webpack 引入npm 套件時,實際上是處於Node.js 環境,由此可知,這個CommonJS 格式的入口js 檔案(<庫名稱>.common.js
)是供其它應用在Node.js 環境下引入npm 套件使用的。由於在引用 npm 套件時一般不會過多考慮 npm 套件的體積(在建立自己的應用時如有需要可自行壓縮),且為了方便調試,因此該 js 入口檔案是沒有經過壓縮的。
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 設定是:
output.library: "LibraryName"。
mode 參數,如webpack --mode=production;這是因為當mode 的值為
production時, webpack 會自動啟用
UglifyJsPlugin 對原始碼進行壓縮。
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 生态圈添砖加瓦,不妨参考一下本项目。
我会以我最近写的两个开源库: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 生态圈添砖加瓦,不妨参考一下本项目。
推荐教程:《JS教程》
以上是使用webpack建立一個js庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!