背景
以前项目代码维护困难、管理不易,为了在项目中更高效地管理和维护项目中的每一个资源,程序员们走上了“模块化”的道路
先介绍一下前端方向落实模块化的几个代表阶段。
1.文件划分的形式
最早的时候,我们会通过文件划分的形式实现模块化,也就是将每个功能及其相关状态数据各自单独放到不同的
JS
文件中约定每个文件是一个独立的模块,然后再将这些
js
文件引入到页面,一个script
标签对应一个模块,然后调用模块化的成员。
<script src="module-a.js"></script> <script src="module-b.js"></script>
这种原始“模块化”的实现方式完全依靠约定实现,暴露了很多问题:
- 模块直接在全局工作,大量模块成员污染全局作用域;
- 没有私有空间,所有模块内的成员都可以在模块外部被访问或者修改;
- 一旦模块增多,容易产生命名冲突;
- 无法管理模块与模块之间的依赖关系;
- 在维护的过程中也很难分辨每个成员所属的模块;
模块都是在全局中工作,大量模块成员污染了环境,模块与模块之间并没有依赖关系、维护困难、没有私有空间等问题。项目一旦变大,上述问题会尤其明显。
2. 命名空间方式
后来,我们约定每个模块只暴露一个全局对象,所有模块成员都挂载到这个全局对象中,具体做法是在第一阶段的基础上,通过将每个模块“包裹”为一个全局对象的形式实现,这种方式就好像是为模块内的成员添加了“命名空间”,所以我们又称之为命名空间方式。
只是解决了命名冲突的问题,但是其它问题依旧存在。
3.立即执行函数方式
使用立即执行函数表达式为模块提供私有空间。具体做法是将每个模块成员都放在一个立即执行函数所形成的私有作用域中,对于需要暴露给外部的成员,通过挂到全局对象上的方式实现。
后面又利用立即执行函数的参数作为依赖声明使用,这使得每一个模块之间的依赖关系变得更加明显
这就解决了前面所提到的全局作用域污染和命名冲突的问题。
后来
以上方式确实解决了很多在前端领域实现模块化的问题,但是仍然存在一些没有解决的问题。比如在这几种方式中虽然都解决了模块代码的组织问题,但模块加载的问题却被忽略了
以上都是通过 script 标签的方式直接在页面中引入的这些模块,这意味着模块的加载并不受代码的控制,时间久了维护起来会十分麻烦。试想一下,如果你的代码需要用到某个模块,如果 HTML 中忘记引入这个模块,又或是代码中移除了某个模块的使用,而 HTML 还忘记删除该模块的引用,都会引起很多问题和不必要的麻烦。
理想的解决方式是,在页面中引入一个JS
入口文件,其余用到的模块可以通过代码控制,按需加载进来,除了模块加载的问题以外,还需要规定模块化的规范,如今流行的则是CommonJS
、ES Modules
除了模块代码的组织问题、模块加载的问题以外,不同的开发者在实施的过程中会出现一些细微的差别,因此,为了统一不同开发者、不同项目之间的差异,我们就需要制定一个行业标准去规范模块化的实现方式。
所以现在我们的需求就是:
- 一个构建方案:解决模块代码的组织问题
- 一个可以自动加载模块的基础库:解决模块加载的问题
- 一个统一的模块化标准规范:解决差异问题
模块化规范的诞生
随着技术的发展,JavaScript 的标准逐渐走向完善,可以说,如今的前端模块化已经发展得非常成熟了,而且对前端模块化规范的最佳实践方式也基本实现了统一
-
在 Node.js 环境中,我们遵循 CommonJS 规范来组织模块。
它是 Node.js 中所遵循的模块规范,该规范约定,一个文件就是一个模块,每个模块都有单独的作用域,通过 module.exports 导出成员,再通过 require 函数载入模块。
-
在浏览器环境中,我们遵循 ES Modules 规范。
经过不断的迭代,ES Modules 已发展成为现今最主流的前端模块化标准。相比于 AMD 这种社区提出的开发规范,ES Modules 是在语言层面实现的模块化,因此它的标准更为完善也更为合理。而且目前绝大多数浏览器都已经开始能够原生支持 ES Modules 这个特性了,
MDN 官方的详细资料
ECMAScript 官方详细资料
同期还推出了一个非常出名的库,叫作Require.js,它除了实现了 AMD 模块化规范,本身也是一个非常强大的模块加载器。
模块打包工具的出现
对于开发过程而言,模块化肯定是必要的,所以我们需要在模块化实现的基础之上引入更好的方案或者工具,让我们的应用在开发阶段继续享受模块化带来的优势,又不必担心模块化对生产环境所产生的影响。
所以前端模块打包工具具备了以下能力
-
编译代码能力
它需要具备编译代码的能力,也就是将我们开发阶段编写的那些包含新特性的代码转换为能够兼容大多数环境的代码,解决我们所面临的环境兼容问题。
-
模块整合能力
能够将散落的模块再打包到一起,这样就解决了浏览器频繁请求模块文件的问题。这里需要注意,只是在开发阶段才需要模块化的文件划分,因为它能够帮我们更好地组织代码,到了实际运行阶段,这种划分就没有必要了。
-
万物皆可模块能力
它需要支持不同种类的前端模块类型,也就是说可以将开发过程中涉及的样式、图片、字体等所有资源文件都作为模块使用,这样我们就拥有了一个统一的模块化方案,所有资源文件的加载都可以通过代码控制,与业务代码统一维护,更为合理。
webpack?
Webpack
前端模块打包工具, 最初的目标是实现前端项目的模块化,旨在更高效地管理和维护项目中的每一个资源,
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图,然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
简单的说,webpack
是一个用于现代JavaScript
应用程序的静态模块打包工具。
现代前端开发已经变得十分的复杂
从后端渲染的
JSP
、PHP
,到前端原生JavaScript
,再到jQuery
开发,再到目前的三大框架Vue
、React
、Angular
开发方式,也从
javascript
到后面的es5
、es6、7、8、9、10
,再到typescript
,包括编写CSS
的预处理器less
、scss
等
所以我们开发过程中会遇到如下的问题:
-
需要通过模块化的方式来开发
-
使用一些高级的特性来加快我们的开发效率或者安全性
比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码
-
监听文件的变化来并且反映到浏览器上,提高开发的效率
-
JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题
-
开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化
而webpack
恰巧可以解决以上问题
总的来说,我们可以把 Webpack 看作现代化前端应用的“管家”,这个“管家”所践行的核心理论就是“模块化”,也就是说 Webpack 以模块化思想为核心,帮助开发者更好的管理整个前端工程。
知识支撑
静态模块
这里的静态模块指的是开发阶段,可以被
webpack
直接引用的资源(可以直接被获取打包进bundle.js
的资源)
当 webpack
处理应用程序时,它会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不再局限js
文件),并生成一个或多个 bundle