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();
    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 库所需的大部分需求

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C : 线性规划例题求解

Submit Page TestData Time Limit: 1 Sec Memory Limit: 128 Mb Submitted: 93 Solved: 49 Description 求解下述线性规划模型的最优值min &#xfffd;1&#xfffd;1&#xfffd;2&#xfffd;2&#xfffd;3&#xfffd;3&#xfffd;.&#xfffd;. &…

Pbootcms留言“提交成功”的提示语怎么修改

我们在用到pbootcms建站时候&#xff0c;其中有个留言功能&#xff0c;提交成功后会提示&#xff1a;提交成功&#xff08;如下图所示&#xff09;&#xff0c;那么我们要修改这个提示语要怎么操作呢&#xff1f; 如果需要修改的话&#xff0c;直接找到文件/apps/home/control…

完美世界|单机版合集(共22个版本)

前言 我是研究单机的老罗&#xff0c;今天给大家带来的是完美世界的单机版合集&#xff0c;一共22个版本。本人亲自测试了一个版本&#xff0c;运行视频如下&#xff1a; 完美世界|单机版合集 先看所有的版本的文件&#xff0c;文件比较大&#xff0c;准备好空间&#xff0c;差…

运行CDN

背景 CDN代码&#xff0c;调试运行 日常 git clone代码配置虚拟环境 puthon3.8,pip install r requirements.txt改项目数据集路径&#xff0c;在hico.py文件里面 # PATHS {# train: (root / images / train2015, root / annotations / trainval_hico.json),# val: …

捕获野生的登录页,暴改Vue3

1.实现效果 2.Vue组件 <script setup> import {onMounted} from "vue";onMounted(()>{// getAllData() }) </script><template><div class"login"><div class"form-cont"><div class"form-top"&…

音视频开发34 FFmpeg 编码- 将h264和acc文件打包成flv文件

FFmpeg合成流程 示例本程序会⽣成⼀个合成的⾳频和视频流&#xff0c;并将它们编码和封装输出到输出⽂件&#xff0c;输出格式是根据⽂件 扩展名⾃动猜测的。 示例的流程图如下所示。 ffmpeg 的 Mux 主要分为 三步操作&#xff1a; avformat_write_header &#xff1a; 写⽂…

ASP.Net.WebAPI和工具PostMan

1.WebAPI概述 1.1 WebAPI WebAPI 是一种传统的方式&#xff0c;用于构建和暴露 RESTUI风格的Web服务。它提供了丰富的功能和灵活性&#xff0c;可以处理各种HTTP请求&#xff0c;并支持各种数据格式&#xff0c;如JSON、XML等。 WebAPI使用控制器(Controllers)和动作方法(Ac…

25 防火墙基础操作

1 防火墙进入WEB页面操作 华三防火墙的默认用户:admin/密码:admin 将IP地址改在同一网段的信息 在防火墙的管理地址 GE/0/0/1&#xff1a;192.168.0.1 主机的地址是:192.168.0.101 思考一下为什么Ping不通 security-zone name Management import interface GigabitEthernet1/…

python、pytorch、cuda安装及常见pip命令

1、查看本地安装的CUDA版本 命令&#xff1a;nvidia-smi 2、安装CUDA 下载地址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive?spm5176.28103460.0.0.49e33da2H7ktW7 进入后选择指定版本进入详情页&#xff0c;选择linux和windows版本&#xff0c;下载然后…

Qt开发报错:Q_INTERFACES Error: Undefined interface

1、背景 VS2019qt5.12.10 从svn拉下来的项目&#xff0c;结果报错&#xff1a; Q_INTERFACES Error: Undefined interface 之前在VS的扩展中在线安装了qt插件&#xff0c; 安装了一半&#xff0c;比较慢&#xff0c;直接强行退出了。。 后来安装了qt官网的插件。。。。 2、报…

【计算机网络】HTTP——基于HTTP的功能追加协议(个人笔记)

学习日期&#xff1a;2024.6.29 内容摘要&#xff1a;基于HTTP的功能追加协议和HTTP/2.0 HTTP的瓶颈与各功能追加协议 需求的产生 在Facebook、推特、微博等平台&#xff0c;每分每秒都会有人更新内容&#xff0c;我们作为用户当然希望时刻都能收到最新的消息&#xff0c;为…

Keepalive技术

文章目录 一、Keepalive基础vrrp技术Keepalived介绍Keepalived架构 二、 Keepalived 相关文件配置文件组成全局配置虚拟路由器配置 三、配置lvs和keepalive联动服务器架构抢占模式配置配置单播、组播配置通知模块日志功能脑裂现象 四、keepalived和nginx联动keepalive和其他应用…

探索MySQL核心技术:理解索引和主键的关系

在数据密集型应用中&#xff0c;数据库的性能往往是决定一个应用成败的重要因素之一。其中&#xff0c;MySQL作为一种开源关系型数据库管理系统&#xff0c;以其卓越的性能和丰富的功能被广泛应用。而在MySQL数据库优化的众多技巧中&#xff0c;索引和主键扮演着极其重要的角色…

专题一: Spring生态初探

咱们先从整体脉络上看下Spring有哪些模块&#xff0c;重要的概念有个直观印象。 从Spring框架的整体架构和组成对整体框架有个认知。 Spring框架基础概念 Spring基础 - Spring和Spring框架组成 上图是从官网4.2.x获取的原图&#xff0c;目前我们使用最广法的版本应该都是5.x&am…

svn怎么新建分支,切换分支

在当前分支下&#xff0c;点svn右键&#xff0c;选择分支/标记 在选择远端地址时&#xff0c;点右边更多选项&#xff0c;打开远端版本库。找到对应的分支上级位置&#xff0c;点击确定 填写新分支名称&#xff0c;我这儿是将分支建在了branches下&#xff0c;分支名称为V1.1 填…

WEB攻防【4】——JavaWeb项目/JWT身份攻击/组件安全/访问控制

一、知识点 1、Javaweb常见安全及代码逻辑 Javaweb的架构&#xff1a; 如何通过包查找到文件&#xff0c;通过URL对应源码的文件&#xff0c;或者通过源码文件对应URL地址。 2、目录遍历&身份验证&逻辑&JWT Javaweb里面有身份认证的JWT的技术&#xff0c;pyth…

二级建造师(建筑工程专业)考试题库,高效备考!!!

16.在施工合同履行期间发生的变更事项中&#xff0c;属于工程变更的是&#xff08;&#xff09;。 A.质量要求变更 B.分包单位变更 C.合同价款变更 D.相关法规变更 答案&#xff1a;A 解析&#xff1a;工程变更一般是指在工程施工过程中&#xff0c;根据合同约定对施工的…

SSM学习2:依赖注入、依赖自动装配、集合注入、加载properties文件

依赖注入 依赖注入方式 setter注入——引用类型 setter注入——简单类型 public class BookDaoImpl implements BookDao {public void setDatabaseName(String databaseName) {this.databaseName databaseName;}public void setNum(int num) {this.num num;}private Stri…

Spark学习3.0

目录 10.3.4 Spark运行原理 1.设计背景 2.RDD概念 3.RDD特性 4.RDD之间的依赖关系 窄依赖和宽依赖 5.Stage的划分 Stage的类型包括两种&#xff1a;ShuffleMapStage和ResultStage 6.RDD运行过程 10.3.4 Spark运行原理 1.设计背景 许多 迭代式算法&#xff08;比如机器学习、图…

【C++】————string基础用法及部分函数底层实现

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;C 创作时间 &#xff1a;2024年6月30日 前言&#xff1a; 本文主要介绍STL容器之一 ---- string&#xff0c;在学习C的过程中&#xff0c;我们要将C视为一个语言联邦&#xff08;摘录于Effective C 条款一&#x…