Webpack: 构建 NPM Library

概述

虽然 Webpack 多数情况下被用于构建 Web 应用,但与 Rollup、Snowpack 等工具类似,Webpack 同样具有完备的构建 NPM 库的能力。与一般场景相比,构建 NPM 库时需要注意:

  • 正确导出模块内容;
  • 不要将第三方包打包进产物中,以免与业务方环境发生冲突;
  • 将 CSS 抽离为独立文件,以方便用户自行决定实际用法;
  • 始终生成 Sourcemap 文件,方便用户调试。

本文将从最基础的 NPM 库构建需求开始,逐步叠加上述特性,最终搭建出一套能满足多数应用场景、功能完备的 NPM 库构建环境。

开发一个 NPM 库

  • 为方便讲解,假定我们正在开发一个全新的 NPM 库,暂且叫它 test-lib 吧,首先需要创建并初始化项目:
mkdir test-lib && cd test-lib
npm init -y

虽然有很多构建工具能够满足 NPM 库的开发需求,但现在暂且选择 Webpack,所以需要先装好基础依赖:

yarn add -D webpack webpack-cli

接下来,可以开始写一些代码了,首先创建代码文件:

mkdir src
touch src/index.js

之后,在 test-lib/src/index.js 文件中随便实现一些功能,比如:

// test-lib/src/index.js
export const add = (a, b) => a + b

至此,项目搭建完毕,目录如下:

├─ test-lib
│  ├─ package.json
│  ├─ src
│  │  ├─ index.js

使用 Webpack 构建 NPM 库

接下来,我们需要将上例 test-lib 构建为适合分发的产物形态。虽然 NPM 库与普通 Web 应用在形态上有些区别,但大体的编译需求趋同,因此可以复用前面章节介绍过的大多数知识点。例如 test-lib 所需要的基础编译配置如下:

// webpack.config.js
const path = require("path");module.exports = {mode: "development",entry: "./src/index.js",output: {filename: "[name].js",path: path.join(__dirname, "./dist"),}
};
  • 提示:我们还可以在上例基础上叠加任意 Loader、Plugin,例如: babel-loadereslint-loaderts-loader 等。

上述配置会将代码编译成一个 IIFE 函数,但这并不适用于 NPM 库,我们需要修改 output.library 配置,以适当方式导出模块内容:

module.exports = {// ...output: {filename: "[name].js",path: path.join(__dirname, "./dist"),
+   library: {
+     name: "_",
+     type: "umd",
+   },},// ...
};

这里用到了两个新配置项:

  • output.library.name:用于定义模块名称,在浏览器环境下使用 script 加载该库时,可直接使用这个名字调用模块,例如:

    <!DOCTYPE html>
    <html lang="en">
    ...
    <body><script src="https://examples.com/dist/main.js"></script><script>// Webpack 会将模块直接挂载到全局对象上window._.add(1, 2)</script>
    </body></html>
    
  • output.library.type:用于编译产物的模块化方案,可选值有:commonjsumdmodulejsonp 等,通常选用兼容性更强的 umd 方案即可。

  • 提示:JavaScript 最开始并没有模块化方案,这就导致早期 Web 开发需要将许多代码写进同一文件,极度影响开发效率。后来,随着 Web 应用复杂度逐步增高,社区陆陆续续推出了许多适用于不同场景的模块化规范,包括:CommonJS、UMD、CMD、AMD,以及 ES6 推出的 ES Module 方案,不同方案各有侧重点与适用场景,NPM 库作者需要根据预期的使用场景选择适当方案。

修改前后对应的产物内容如下:

请添加图片描述

可以看到,修改前(对应上图左半部分)代码会被包装成一个 IIFE ;而使用 output.library 后,代码被包装成 UMD(Universal Module Definition) 模式:

(function webpackUniversalModuleDefinition(root, factory) {if(typeof exports === 'object' && typeof module === 'object')module.exports = factory();else if(typeof define === 'function' && define.amd)define([], factory);else if(typeof exports === 'object')exports["_"] = factory();elseroot["_"] = factory();
})(self, function() {// ...
});

这种形态会在 NPM 库启动时判断运行环境,自动选择当前适用的模块化方案,此后我们就能在各种场景下使用 test-lib 库,例如:

// ES Module
import {add} from 'test-lib';// CommonJS
const {add} = require('test-lib');// HTML
<script src="https://examples.com/dist/main.js"></script>
<script>// Webpack 会将模块直接挂载到全局对象上window._.add(1, 2)
</script>

正确使用第三方包

接下来,假设我们需要在 test-lib 中使用其它 NPM 包,例如 lodash

// src/index.js
import _ from "lodash";export const add = (a, b) => a + b;export const max = _.max;

此时执行编译命令 npx webpack,我们会发现产物文件的体积非常大:

请添加图片描述

这是因为 Webpack 默认会将所有第三方依赖都打包进产物中,这种逻辑能满足 Web 应用资源合并需求,但在开发 NPM 库时则很可能导致代码冗余。以 test-lib 为例,若使用者在业务项目中已经安装并使用了 lodash,那么最终产物必然会包含两份 lodash 代码!

为解决这一问题,我们需要使用 externals 配置项,将第三方依赖排除在打包系统之外:

// webpack.config.js
module.exports = {// ...
+  externals: {
+   lodash: {
+     commonjs: "lodash",
+     commonjs2: "lodash",
+     amd: "lodash",
+     root: "_",
+   },
+ },// ...
};
  • 提示: Webpack 编译过程会跳过 externals 所声明的库,并假定消费场景已经安装了相关依赖,常用于 NPM 库开发场景;在 Web 应用场景下则常被用于优化性能。

  • 例如,我们可以将 React 声明为外部依赖,并在页面中通过 <script> 标签方式引入 React 库,之后 Webpack 就可以跳过 React 代码,提升编译性能。

改造后,再次执行 npx webpack,编译结果如下:
请添加图片描述

改造后,主要发生了两个变化:

  1. 产物仅包含 test-lib 库代码,体积相比修改前大幅降低;
  2. UMD 模板通过 requiredefine 函数中引入 lodash 依赖并传递到 factory

至此,Webpack 不再打包 lodash 代码,我们可以顺手将 lodash 声明为 peerDependencies

{"name": "6-1_test-lib",// ...
+ "peerDependencies": {
+   "lodash": "^4.17.21"
+ }
}

实践中,多数第三方框架都可以沿用上例方式处理,包括 React、Vue、Angular、Axios、Lodash 等,方便起见,可以直接使用 webpack-node-externals 排除所有 node_modules 模块,使用方法:

// webpack.config.js
const nodeExternals = require('webpack-node-externals');module.exports = {// ...
+  externals: [nodeExternals()]// ...
};

抽离 CSS 代码

假设我们开发的 NPM 库中包含了 CSS 代码 —— 这在组件库中特别常见,我们通常需要使用 mini-css-extract-plugin 插件将样式抽离成单独文件,由用户自行引入。

这是因为 Webpack 处理 CSS 的方式有很多,例如使用 style-loader 将样式注入页面的 <head> 标签;使用 mini-css-extract-plugin 抽离样式文件。作为 NPM 库开发者,如果我们粗暴地将 CSS 代码打包进产物中,有可能与用户设定的方式冲突。

为此,需要在前文基础上添加如下配置:

module.exports = {  // ...
+ module: {
+   rules: [
+     {
+       test: /\.css$/,
+       use: [MiniCssExtractPlugin.loader, "css-loader"],
+     },
+   ],
+ },
+ plugins: [new MiniCssExtractPlugin()],
};
  • 提示:关于 CSS 构建的更多规则,可参考《如何借助预处理器、PostCSS 等构建现代 CSS 工程环境?》

生成 Sourcemap

Sourcemap 是一种代码映射协议,它能够将经过压缩、混淆、合并的代码还原回未打包状态,帮助开发者在生产环境中精确定位问题发生的行列位置,所以一个成熟的 NPM 库除了提供兼容性足够好的编译包外,通常还需要提供 Sourcemap 文件。

接入方法很简单,只需要添加适当的 devtool 配置:

// webpack.config.js
module.exports = {  // ...
+ devtool: 'source-map'
};

再次执行 npx webpack 就可以看到 .map 后缀的映射文件:

├─ test-lib
│  ├─ package.json
│  ├─ webpack.config.js
│  ├─ src
│  │  ├─ index.css
│  │  ├─ index.js
│  ├─ dist
│  │  ├─ main.js
│  │  ├─ main.js.map
│  │  ├─ main.css
│  │  ├─ main.css.map

此后,业务方只需使用 source-map-loader 就可以将这段 Sourcemap 信息加载到自己的业务系统中,实现框架级别的源码调试能力

其它 NPM 配置

至此,开发 NPM 库所需的 Webpack 配置就算是介绍完毕了,接下来我们还可以用一些小技巧优化 test-lib 的项目配置,提升开发效率,包括:

  • 使用 .npmignore 文件忽略不需要发布到 NPM 的文件;

  • package.json 文件中,使用 prepublishOnly 指令,在发布前自动执行编译命令,例如:

    // package.json
    {"name": "test-lib",// ..."scripts": {"prepublishOnly": "webpack --mode=production"},// ...
    }
    
  • package.json 文件中,使用 main 指定项目入口,同时使用 module 指定 ES Module 模式下的入口,以允许用户直接使用源码版本,例如:

    {"name": "6-1_test-lib",// ..."main": "dist/main.js","module": "src/index.js","scripts": {"prepublishOnly": "webpack --mode=production"},// ...
    }
    

总结

站在 Webpack 角度,构建 Web 应用于构建 NPM 库的差异并不大,开发时注意:

  • 使用 output.library 配置项,正确导出模块内容;
  • 使用 externals 配置项,忽略第三方库;
  • 使用 mini-css-extract-plugin 单独打包 CSS 样式代码;
  • 使用 devtool 配置项生成 Sourcemap 文件,这里推荐使用 devtool = 'source-map'

遵循上述规则,基本上就能满足开发一个 NPM 库所需的大部分需求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/36491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDM(Internet Download Manager)下载器的安装激活与换机方法 IDM怎么用

很多人都知道 Internet Download Manager(以下简称 IDM)是一款非常优秀的下载提速软件。它功能强大&#xff0c;几乎能下载网页中的所有数据&#xff08;包括视频、音频、图片等&#xff09;&#xff0c;且适用于现在市面上几乎所有的浏览器&#xff0c;非常受大家欢迎。IDM 是…

河南企业劳务资质申请:技术负责人角色与职责

河南企业劳务资质申请中&#xff0c;技术负责人的角色与职责至关重要&#xff0c;以下是对其角色与职责的清晰归纳&#xff1a; 一、角色定位 技术核心&#xff1a;技术负责人是企业技术团队的核心&#xff0c;是企业技术实力和专业水平的象征。战略规划者&#xff1a;根据行…

subline设置打开文件重启一个新的窗口

问题 打开文件后&#xff0c;用的是同一个窗口的子tab页面 想要打开一个新的窗口 解决 点解preferences->setting 在右边的配置文件新增一行 “open_files_in_new_window”: “always” 保存 搞定&#xff01;

LeetCode 1667, 36, 199

目录 1667. 修复表中的名字题目链接表要求知识点思路代码 36. 有效的数独题目链接标签思路代码 199. 二叉树的右视图题目链接标签思路代码 1667. 修复表中的名字 题目链接 1667. 修复表中的名字 表 表Users的字段为user_id和name。 要求 编写解决方案&#xff0c;修复名字…

SET加密:电子商务安全的基石

随着电子商务的飞速发展&#xff0c;如何确保在线交易的安全性和可信度已成为消费者、商家和金融机构共同关注的焦点。SET协议&#xff08;Secure Electronic Transaction&#xff09;作为一种安全电子交易的国际标准&#xff0c;凭借其卓越的安全性能和广泛的行业认可&#xf…

食品行业怎么用JSON群发短信

食品作为日常生活不可缺少的元素&#xff0c;市场需求是很稳定的&#xff0c;但是份额就那么多&#xff0c;商家都要来抢占的话&#xff0c;就需要运营推广各凭本事&#xff0c;市场运营中选择合适的推广方式&#xff0c;可以增加店铺销售额&#xff0c;很多实体店或商城都会建…

MATLAB-振动问题:两自由度耦合系统自由振动

一、基本理论 二、MATLAB实现 以下是两自由度耦合系统自由振动质量块振动过程动画显示的MATLAB程序。 clear; clc; close allx0 1; D1 40; D12 8; D2 D1; m1 1; omega0 sqrt(D1/m1); k1 D12 / D1; k2 D12 / D2; k sqrt(k1 * k2); omegazh omega0 * sqrt(1 k); omeg…

pytest中的极其重要固件(request)的理解

pytest 是一个非常流行的Python测试框架&#xff0c;它为开发人员提供了丰寴的测试工具和功能。 在pytest中&#xff0c;固件&#xff08;fixture&#xff09;是一种非常核心的概念&#xff0c;用于设置测试前的预条件&#xff0c;清理测试后的环境&#xff0c;或者提供测试过…

高效实现虚拟机(VMware)安装教程(附安装包)

目录 一.下载VMware Wworkstation Pro 二 安装&#xff1a; 注&#xff1a;若是安装完VMware&#xff0c;还想在上面安装Centos、Ubuntu&#xff0c;系统请转到基于VMware的linux操作系统安装&#xff08;附安装包&#xff09;-CSDN博客 一.下载VMware Wworkstation Pro 渠道…

文心一言用户达3亿!文心大模型4.0 Turbo发布,支持API,真GPT-4 Turbo国产来了!

文心一言用户规模达到3亿了&#xff01; 这是笔者在今天的百度Wave Summit 2024大会上的看到的数字。需要强调的是&#xff0c;文心一言的用户规模是在去年12月破亿的。这意味着&#xff0c;仅仅隔了6个月&#xff0c;文心一言用户数量在亿这个级别的数字上竟然直接翻了三倍。…

【C++】线程库

在 C11 问世之前&#xff0c;多线程基本和平台关系密切&#xff0c;不同的平台下多线程各有特点&#xff0c;使得 Windows 和 Linux 下必须拥有各自的系统调用接口&#xff0c;但同时也使得代码的可移植性较差。 C11中最重要的特性&#xff0c;就是支持了多线程编程&#xff0c…

【高性能服务器】服务器概述

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 服务器概述 服…

计算机网络 —— 基本概念

基本概念 1. 通信协议2. 面向连接 v.s. 面向无连接3. 电路交换 v.s. 分组交换4. 单工通信 v.s. 双工通信 1. 通信协议 通信协议就是计算机与计算机之间通过网络实现通信时事先达成的一种“约定”。这种“约定”使那些由不同厂商的设备、不同的CPU 以及不同的操作系统组成的计算…

C# 实现websocket双向通信

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;C# &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff…

人工智能在多模态多组学领域的最新研究进展|顶刊速递·24-06-29

小罗碎碎念 本期推文主题&#xff1a;人工智能在多模态与多组学中的最新研究进展 今天这期推文比较特殊&#xff0c;起来就开始干活&#xff0c;只能跑来会场写了。 小罗观点 今天这期推文覆盖面挺广的&#xff0c;前四篇与肿瘤治疗相关&#xff0c;并且两篇都直接与免疫微环境…

Websocket解析及用法(封装一个通用订阅发布主题的webSocket类)

1、什么是WebSocket? websocket的目标是通过一个长连接实现与服务器全双工&#xff0c;双向的通信。是一种在单个TCP连接上进行全双工通信的协议&#xff0c;使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在 js中创建websocket…

统计信号处理基础 习题解答11-1

题目 观测到的数据具有PDF 在μ给定的条件下&#xff0c;是相互独立的。均值具有先验PDF&#xff1a; 求μ的 MMSE 和 MAP 估计量。另外&#xff0c;当和时将发生什么情况? 解答 和两者都是独立高斯分布&#xff0c;与例题10.1一致&#xff0c;直接套用&#xff08;10.11&am…

Nvidia Jetson/RK3588+AI双目立体相机,适合各种割草机器人、扫地机器人、AGV等应用

双目立体视觉是基于视差原理&#xff0c;依据成像设备从不同位置获取的被测物体的图像&#xff0c;匹配对应点的位置偏移&#xff0c;得到视差数据&#xff0c;进而计算物体的空间三维信息。为您带来高图像质量的双目立体相机&#xff0c;具有高分辨率、低功耗、远距离等优点&a…

ubuntu丢失网络/网卡的一种原因解决方案

现象 开机进入ubuntu后发现没有网络&#xff0c;无论是在桌面顶部状态栏的快捷键 还是 系统设置中&#xff0c;都没有”有线网“和”无线网“的选项&#xff0c;”代理“的选项是有的使用数据线连接电脑和手机&#xff0c;手机开启”通过usb共享网络“&#xff0c;还是没有任何…

Nginx 1.26.1最新版部署笔记

Nginx是一个高性能的 HTTP 和反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。 以下是 Nginx 的一些核心功能和特点&#xff1a; 高性能的 Web 服务器&#xff1a; Nginx 被设计为处理高并发连接&#xff0c;具有非常高的性能和稳定性。反向代理&#xff1a; …