死去的 browserify 开始攻击我,复古前端模块化之 browserify 的解析和使用

1,538 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情

最近看到了一篇讲述前端模块化历史的文章,讲的非常好,其中提到了我的一位老朋友 browserify,在初学前端的时候,一度在 CSDN 的垃圾桶中畅游,其中有一篇文章极力推荐我使用 browserify,但当初我连 Node.js 的环境都不知道怎么装,所以对 browserify 就不了了之了,而今天这篇文章就是来复古学习一下 browserify

实现例子如下,点击实现值的平方(基于初始值),在线查看源代码

browserify 是?

browserify 的作用就是打包工具,又或者说是 require 模块的编译器,为什么这么说呢?这是因为服务端的模块化要早于浏览器的模块化,在 Node.js 诞生的 2009 年,浏览器上运行的 JavaScript 没有自己的模块化方案,而随着浏览器的功能的强大,Ajax 的出现,网站的交互能力越发重要,JavaScript 越来越复杂,传统的前端项目难以维护项目中的 JS

前端暴露问题

比如

  1. 没有包管理器,开源的工具或者框架总是东一个西一个,到处乱找
  2. 没有模块化,所有的文件都写在一个 .js 文件中,导致几千行的脚本文件,维护困难
  3. 使用 <script> 引入 UI 框架或者工具,由于脚本太多导致网络瓶颈,那个时候浏览器限制文件并发 8 个以内,这样就会导致一些静态资源无法得到响应

珠玉在前

对于第一点,在服务端的 Node.js 上先实现了,Node.js 出现前和 npm 的作者达成一致,并采用 CommonJS 的模块标准,先一步实现了包管理和对应的模块化,当时这套方案在服务端反响不错,所以就有开发者想要将这一套搬到浏览器,但是 ComoonJS 只是服务端的模块化标准并不适用于浏览器,会有一些问题,比如

在服务端 require 一个模块,只会有磁盘 I/O,所以同步加载机制没什么问题;但如果是浏览器加载,一是会产生开销更大的网络 I/O,二是天然异步,就会产生时序上的错误。

// Node.js 行
const $ = require("jquery");
const _ = require("lodash");
const { square } = require("./utils/math");

// browser 不行
const $ = require("https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js");
const _ = require("https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js");
const { square } = require("./utils/math");

当时就有了一些讨论和解决方案,最终产生了三种方案,比如 AMD 和文章讲述的 browserify,就是基于 CommonJS 的改进

解决方案

browserify 的方案就是源代码开发阶段采用 Node.jsCommonJS 标准,而在部署,找到对应的模块,打包成一个文件,保证调用顺序

至此前面关于“前端暴露问题”的第二点、第三点已经解决,模块化解决第二点、而打包解决第三点,同时异步顺序也能够解决,一石四鸟呀!

browserify 使用

目录结构

├── node_modules
├── dist           - 打包文件  
├── src            - 项目代码
|  ├── utils       - 工具模块
|    └── math.js
|  └── index.js    - 入口文件
└── index.html

模块导入导出

沿用 Node.js 那一套(有种写 express 的感觉)

// main.js - 导入
const $ = require("jquery");
const { square } = require("./utils/math");

const initialVal = 2;

(function () {
  const countDOM = $(".count");
  const count = $(".count").text();
  console.log(count);
  if (!count) {
    countDOM.text(initialVal);
  }
})();

$(".count-button").on("click", () => {
  const countDOM = $(".count");
  const count = $(".count").text();
  countDOM.text(square(Number(count)));
});
// math.js
/**
 * 计算 x 的平方
 * @param {number} x 
 */
const square = (x) => {
  return x * x;
};

module.exports = {
  square,
};

打包

假设你已经了解过 Node.js 环境和 package.json

npm install --save-dev browserify

执行打包命令

npx browserify src/index.js > dist/boudle.js
# 也可以在 package.json 自定义命令
npm run build

打包文件引用

HTML 中引用打包后的文件

<head>
  <script src="dist/boudle.js" async></script>
</head>
<body>
  <div>
    count: <span class="count"></span>
    <button class="count-button">count = count^2</button>
  </div>
</body>

注意,每调用 browserify 命令时才会更新一次打包文件,对于文件热更新需要自行配置

静态资源服务器的提供就介绍了,用 IDE 或者 Node.jsexpress 都行,此处我使用 VS Code 的插件 Live Server

效果如下

打包文件将会是一个 IIFE -> IIFE,会有相应的层级和依赖关系(实现细节不过多分析)

参考资料

  1. webpack 打包原理浅析及常见优化 - 阿里云开发者社区
  2. 《编程时间简史系列》JavaScript 模块化的历史进程