Webpack: 构建 NPM Library

news2024/11/24 8:06:27

概述

虽然 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();
    else
        root["_"] = 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.coloradmin.cn/o/1879745.html

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

相关文章

面了英伟达算法岗,被疯狂拷打。。。

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、算法项目落地经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接如…

Web Based Quiz System v1.0 SQL 注入漏洞(CVE-2022-32991)

前言 CVE-2022-32991 是一个影响 Web Based Quiz System v1.0 的 SQL 注入漏洞。这个漏洞存在于 welcome.php 文件中的 eid 参数处。攻击者可以通过此漏洞在数据库中执行任意 SQL 语句&#xff0c;从而获取、修改或删除数据库中的数据。 具体细节如下&#xff1a; 攻击向量&…

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

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

改机软件有哪些?实现一键新机、改串号、改IMEI和手机参数的需求 硬改手机软件,新机环境模拟 设备伪装,一键改机,一键复原

这次针对可以直接开端口修改参数的机型做一些工具解析 前面接触合作过很多工作室。其中很多工作室对于各自软件的跳验证有各自的需求。 一个机型各项参数一般有IMEI WiFi 蓝牙 sn psb ESN等等。 针对这些参数的修改首先要明白各自软件检测的具体是哪些参数来验证。 对于常用…

解决IDEA的Web项目右键无法创建Servlet问题

右键新建没有servlet? 在pom.xml文件中需要导入servlet依赖&#xff0c;很简单的&#xff0c;别担心&#xff0c;就20秒解决 看我操作&#xff01;&#xff01;&#xff01; 1. 找到自动生成的pom.xml文件 只要你创建了maven项目&#xff0c;就会自动生成pom.xml文件&#xf…

基于Java废物回收机构管理系统详细设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

python多继承的3C算法

python多继承的3C算法 有很多地方都说python多继承的继承顺序&#xff0c;是按照深度遍历的方式&#xff0c;其实python多继承顺序的算法&#xff0c;不是严格意义上的深度遍历&#xff0c;而是基于深度遍历基础上优化出一种叫3C算法 python多继承的深度遍历 class C:def ru…

Solidworke学习(齿轮绘画)

目录 一、齿轮的基础理论 二、齿轮啮合传动的条件 三、传送比 四、绘画齿轮的步骤 五、绘画齿数小的齿轮 学习链接&#xff1a;徒手画齿轮配合&#xff0c;solidworks超简单3D打印齿轮画法_哔哩哔哩_bilibili 一、齿轮的基础理论 齿轮的啮合通过渐开线之间的互相挤压实现…

【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

VRay是什么?有什么特点?渲染100邀请码1a12

Vray是由Chaos Group开发的高性能渲染引擎&#xff0c;能为不同的三维建模软件提供图像和动画渲染服务&#xff0c;它有以下几个特点。 1、Vray采用了先进的光线追踪技术&#xff0c;能够模拟真实世界中光线的传播和反射&#xff0c;生成的图像和动画十分逼真。 2、Vray提供了…

【C++】 ——【模板初阶】——基础详解

目录 1. 泛型编程 1.1 泛型编程的概念 1.2 泛型编程的历史与发展 1.3 泛型编程的优势 1.4 泛型编程的挑战 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6 函数模板的特化 2.7 函数模板的使…

目前常见的几款企业级im即时通讯软件有哪些?

在目前的市场上&#xff0c;有几款常见的企业级即时通讯软件广受企业青睐。以下是其中几款主流企业级即时通讯软件的介绍和特点。 1. 微软Teams 微软Teams是一款集即时通讯、协作和视频会议功能于一体的企业级通讯软件。它提供了实时聊天、语音通话、视频会议、文件共享和团队…

账号和权限的管理1

文章目录 修改用户账号的属性usermod格式常用选项 用户账号的初始化配置文件文件来源主要的用户初始配置文件 组账号文件添加组账号groupadd格式常用选项其他选项 删除组账号groupdel格式 查询账号信息groups格式 id格式 finger格式 W、who、users格式 文件/目录的权限和归属访…

整车功能开发

整车功能开发 站在前人的肩膀上&#xff0c;从系统功能架构集成角度梳理下整车功能开发相关内容 1、整车功能开发相关文件介绍 1.1 配置表 上面的表格&#xff0c;是一种车辆特性的表达方式&#xff0c;其实比较传统&#xff0c;我们称之为配置表&#xff08;Feature list&a…

Python使用defaultdict简化值为list的字典

原始代码&#xff1a; from typing import Dictrelated_objects_for_fetch: Dict[str, list] {}for key, value in [(k1, v1), (k1, v2), (k2, v2), (k3, v3), (k2, v2)]:if key not in related_objects_for_fetch:related_objects_for_fetch[key] []if value not in (value…

AH1117-3.3芯片使用记录

今天在新做好的电路板上测试电源时发现一个问题&#xff0c;那就是散热端不能接地&#xff0c;接地了就好像短路一样&#xff0c;芯片会热的厉害&#xff0c;当我把该引脚与地的所有连接都切断后&#xff0c;短路现象消失&#xff0c;特此记录一下&#xff0c;防止自己下次再犯…

Shell脚本编程 — Shell Script

Shell脚本编程 — Shell Script 基本概念示例脚本示例1&#xff1a;备份文件示例2&#xff1a;监控磁盘使用情况示例3&#xff1a;批量重命名文件 运行脚本提示 Shell脚本是一种编程语言&#xff0c;主要用于在Unix/Linux系统中自动化执行任务。它通过编写一系列的命令来完成特…

49 - 列出指定时间段内所有的下单产品(高频 SQL 50 题基础版)

49 - 列出指定时间段内所有的下单产品 -- 指定2020年2月的方法 -- (1) order_date between 2020-02-01 and 2020-02-29 -- (2) order_date like 2020-02% -- (3) DATE_FORMAT(order_date, "%Y-%m") "2020-02" -- (4) LEFT(order_date, 7) 或 subst…

AI Agent:技术原理与未来趋势

在人工智能的快速发展中&#xff0c;AI Agent作为一项创新技术&#xff0c;正逐渐成为研究和应用的热点。AI Agent不仅仅是执行命令的程序&#xff0c;它们能够感知环境、做出决策并采取行动&#xff0c;展现出类似人类的群体协作能力。本文将探讨AI Agent的技术原理、开源框架…

解决idea中git无法管理项目中所有需要管理的文件

点击文件->设置 选择版本控制—>目录映射 点击加号 设置整个项目被Git管理