浅谈Webpack

发布于 2024-04-23  0 次阅读


一、出现背景

最初,我们会通过文件的形式实现模块化,也就是将每个功能及相关状态数据放到各自的JS文件中,约定每一个文件是一个独立的模块,然后再将这些JS文件引入到页面,一个script标签对应一个模块,然后再调用各自模块的成员。

<script src="modelA.js"></script>
<script src="modelB.js"></script>

但是这种模块化的方式十分明显,模块都在全局中工作,大量的模块成员污染了全局环境,并且模块与模块之间并没有依赖关系,维护困难,没有私有空间。
项目一旦变大,上述问题会尤为明显。

随后,出现了命名空间方式来实现模块化。这种方式规定每个模块只暴露一个全局对象,然后将模块的内容都挂载到这个对象中。

window.modelA = {
    data: 'data',
    methodA: function () {
        /* -- method content -- */
    }
}

但显然,这种方式也并没有解决模块之间相互依赖的问题。

再后来,我们提供立即执行函数为模块提供私有空间,通过参数的形式作为依赖声明。

(function ($) {
    var private = 'private';
    function _methodA () {
        /* -- method content -- */
    }
    window.method = {
        methodA: _methodA
    }
})(jQuery)

上述方式都是早期解决模块的方式,但是仍然存在一些没有解决的问题。例如,我们是通过script标签引入这些模块的,这些模块的加载并不受代码的控制,时间一久维护起来也十分麻烦。

理想的解决方式是,在页面中引入一个JS入口文件,其余用到的模块可以通过代码控制,按需加载进来。

二、问题

从后端渲染的JSP、PHP,到前端原生JavaScript,再到JQuery,再到目前的三大框架Vue、React、Angular,开发语言也从JavaScript到后面的ES5、ES6、7、8、9、10,再到Typescript,包括CSS的预处理器less、sass等,现代前端的开发已经变得十分的复杂,所以我们开发过程中会遇到如下的问题:

  • 一些高级的语言特性在不同的浏览器中存在环境兼容的问题
  • 模块化的方式划分出来的模块文件过多,会导致浏览器频繁的发送网络请求,影响性能
  • 随着应用的日益复杂,不仅JavaScript代码需要模块化,HTML和CSS这些资源文件也会面临需要被模块化的问题
  • 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化

为了解决以上的问题,我们就需要一个前端模块打包工具。

三、Webpack是什么

本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
----Webpack概念

这里的静态模块指的是开发阶段,可以被Website直接引用的资源(可以直接被获取打包进bundle.js的资源)
当Webpack处理应用程序时,他会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不在局限JS文件),并生成一个或多个bundle,使其可让浏览器加载。

Webpack

四、Webpack的核心概念及打包流程

webpack核心概念

1、Entry(入口):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
2、Output(出口):告诉 webpack 在哪里输出它所创建的结果文件,以及如何命名这些文件,默认值为./dist。
3、Loader(模块转换器):将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
4、Plugins(插件):在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。
5、Module(模块):开发者将程序分解成离散功能块,并称之为模块,在webpack里一个模块对应着一个文件,webpack会从配置的 Entry 开始递归找出所有依赖的模块。

Webpack的打包流程

1、初始化: 启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
2、编译: 从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找出该 Module 依赖的 Module,递归地进行编译处理。
3、确定 Chunk: 每个 Module 都编译完成后,根据 Module 之间的依赖关系,生成代码块(Chunk)。
4、生成 Bundle: 每个 Chunk 都转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
5、输出完成: 确定好输出内容后,根据配置确定的路径与文件名,把文件写入到文件系统。