热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Webpack(一)解决了什么问题

模块化的演进过程随着互联网的深入发展,前端技术标准发生了巨大的变化。早期的前端技术标准根本没有预料到前端行业会有今天这个规模,所以在设计上存在很多缺陷

模块化的演进过程

随着互联网的深入发展,前端技术标准发生了巨大的变化。早期的前端技术标准根本没有预料到前端行业会有今天这个规模,所以在设计上存在很多缺陷,导致我们现在去实现前端模块化时会遇到诸多问题。虽然说,如今绝大部分问题都已经被一些标准或者工具解决了,但在这个演进过程中依然有很多东西值得我们思考和学习,所以接下来我想先介绍一下前端方向落实模块化的几个代表阶段。

Stage 1 - 文件划分方式

最早我们会基于文件划分的方式实现模块化,也就是 Web 最原始的模块系统。具体做法是将每个功能及其相关状态数据各自单独放到不同的 JS 文件中,约定每个文件是一个独立的模块。使用某个模块将这个模块引入到页面中,一个 script 标签对应一个模块,然后直接调用模块中的成员(变量 / 函数)。

└─ stage-1├── module-a.js├── module-b.js└── index.html// module-a.js
function foo () {console.log('moduleA#foo')
}// module-b.js
var data &#61; &#39;something&#39;<!DOCTYPE html>
<html>
<head><meta charset&#61;"UTF-8"><title>Stage 1</title>
</head>
<body><script src&#61;"module-a.js"></script><script src&#61;"module-b.js"></script><script>// 直接使用全局成员foo() // 可能存在命名冲突console.log(data)data &#61; &#39;other&#39; // 数据可能会被修改</script>
</body>
</html>

缺点&#xff1a;

模块直接在全局工作&#xff0c;大量模块成员污染全局作用域&#xff1b;
没有私有空间&#xff0c;所有模块内的成员都可以在模块外部被访问或者修改&#xff1b;
一旦模块增多&#xff0c;容易产生命名冲突&#xff1b;
无法管理模块与模块之间的依赖关系&#xff1b;
在维护的过程中也很难分辨每个成员所属的模块。
总之&#xff0c;这种原始“模块化”的实现方式完全依靠约定实现&#xff0c;一旦项目规模变大&#xff0c;这种约定就会暴露出种种问题&#xff0c;非常不可靠&#xff0c;所以我们需要尽可能解决这个过程中暴露出来的问题。

2. Stage 2 – 命名空间方式

后来&#xff0c;我们约定每个模块只暴露一个全局对象&#xff0c;所有模块成员都挂载到这个全局对象中&#xff0c;具体做法是在第一阶段的基础上&#xff0c;通过将每个模块“包裹”为一个全局对象的形式实现&#xff0c;这种方式就好像是为模块内的成员添加了“命名空间”&#xff0c;所以我们又称之为命名空间方式。

// module-a.js
window.moduleA &#61; {method1: function () {console.log(&#39;moduleA#method1&#39;)}
}// module-b.js
window.moduleB &#61; {data: &#39;something&#39;method1: function () {console.log(&#39;moduleB#method1&#39;)}
}<!DOCTYPE html>
<html>
<head><meta charset&#61;"UTF-8"><title>Stage 2</title>
</head>
<body><script src&#61;"module-a.js"></script><script src&#61;"module-b.js"></script><script>moduleA.method1()moduleB.method1()// 模块成员依然可以被修改moduleA.data &#61; &#39;foo&#39;</script>
</body>
</html>

这种命名空间的方式只是解决了命名冲突的问题&#xff0c;但是其它问题依旧存在。

3. Stage 3 – IIFE
使用立即执行函数表达式&#xff08;IIFE&#xff0c;Immediately-Invoked Function Expression&#xff09;为模块提供私有空间。具体做法是将每个模块成员都放在一个立即执行函数所形成的私有作用域中&#xff0c;对于需要暴露给外部的成员&#xff0c;通过挂到全局对象上的方式实现。

// module-a.js
;(function () {var name &#61; &#39;module-a&#39;function method1 () {console.log(name &#43; &#39;#method1&#39;)}window.moduleA &#61; {method1: method1}
})()// module-b.js
;(function () {var name &#61; &#39;module-b&#39;function method1 () {console.log(name &#43; &#39;#method1&#39;)}window.moduleB &#61; {method1: method1}
})()

这种方式带来了私有成员的概念&#xff0c;私有成员只能在模块成员内通过闭包的形式访问&#xff0c;这就解决了前面所提到的全局作用域污染和命名冲突的问题。

4. Stage 4 - IIFE 依赖参数
在 IIFE 的基础之上&#xff0c;我们还可以利用 IIFE 参数作为依赖声明使用&#xff0c;这使得每一个模块之间的依赖关系变得更加明显。

// module-a.js
;(function ($) { // 通过参数明显表明这个模块的依赖var name &#61; &#39;module-a&#39;function method1 () {console.log(name &#43; &#39;#method1&#39;)$(&#39;body&#39;).animate({ margin: &#39;200px&#39; })}window.moduleA &#61; {method1: method1}
})(jQuery)

模块加载的问题
以上 4 个阶段是早期的开发者在没有工具和规范的情况下对模块化的落地方式&#xff0c;这些方式确实解决了很多在前端领域实现模块化的问题&#xff0c;但是仍然存在一些没有解决的问题。

<!DOCTYPE html>
<html>
<head><title>Evolution</title>
</head>
<body><script src&#61;"https://unpkg.com/jquery"></script><script src&#61;"module-a.js"></script><script src&#61;"module-b.js"></script><script>moduleA.method1()moduleB.method1()</script>
</body>
</html>

最明显的问题就是&#xff1a;模块的加载。在这几种方式中虽然都解决了模块代码的组织问题&#xff0c;但模块加载的问题却被忽略了&#xff0c;我们都是通过 script 标签的方式直接在页面中引入的这些模块&#xff0c;这意味着模块的加载并不受代码的控制&#xff0c;时间久了维护起来会十分麻烦。试想一下&#xff0c;如果你的代码需要用到某个模块&#xff0c;如果 HTML 中忘记引入这个模块&#xff0c;又或是代码中移除了某个模块的使用&#xff0c;而 HTML 还忘记删除该模块的引用&#xff0c;都会引起很多问题和不必要的麻烦。

更为理想的方式应该是在页面中引入一个 JS 入口文件&#xff0c;其余用到的模块可以通过代码控制&#xff0c;按需加载进来。


模块化规范的出现

除了模块加载的问题以外&#xff0c;目前这几种通过约定实现模块化的方式&#xff0c;不同的开发者在实施的过程中会出现一些细微的差别&#xff0c;因此&#xff0c;为了统一不同开发者、不同项目之间的差异&#xff0c;我们就需要制定一个行业标准去规范模块化的实现方式。

再接合我们刚刚提到的模块加载的问题&#xff0c;我们现在的需求就是两点&#xff1a;

一个统一的模块化标准规范
一个可以自动加载模块的基础库
提到模块化规范&#xff0c;你可能会想到 CommonJS 规范&#xff0c;它是 Node.js 中所遵循的模块规范&#xff0c;该规范约定&#xff0c;一个文件就是一个模块&#xff0c;每个模块都有单独的作用域&#xff0c;通过 module.exports 导出成员&#xff0c;再通过 require 函数载入模块。现如今的前端开发者应该对其有所了解&#xff0c;但是如果我们想要在浏览器端直接使用这个规范&#xff0c;那就会出现一些新的问题。

如果你对 Node.js 的模块加载机制有所了解&#xff0c;那么你应该知道&#xff0c;CommonJS 约定的是以同步的方式加载模块&#xff0c;因为 Node.js 执行机制是在启动时加载模块&#xff0c;执行过程中只是使用模块&#xff0c;所以这种方式不会有问题。但是如果要在浏览器端使用同步的加载模式&#xff0c;就会引起大量的同步模式请求&#xff0c;导致应用运行效率低下。

所以在早期制定前端模块化标准时&#xff0c;并没有直接选择 CommonJS 规范&#xff0c;而是专门为浏览器端重新设计了一个规范&#xff0c;叫做 AMD &#xff08; Asynchronous Module Definition&#xff09; 规范&#xff0c;即异步模块定义规范。同期还推出了一个非常出名的库&#xff0c;叫做 Require.js&#xff0c;它除了实现了 AMD 模块化规范&#xff0c;本身也是一个非常强大的模块加载器。

在 AMD 规范中约定每个模块通过 define() 函数定义&#xff0c;这个函数默认可以接收两个参数&#xff0c;第一个参数是一个数组&#xff0c;用于声明此模块的依赖项&#xff1b;第二个参数是一个函数&#xff0c;参数与前面的依赖项一一对应&#xff0c;每一项分别对应依赖项模块的导出成员&#xff0c;这个函数的作用就是为当前模块提供一个私有空间。如果在当前模块中需要向外部导出成员&#xff0c;可以通过 return 的方式实现。
在这里插入图片描述
除此之外&#xff0c;Require.js 还提供了一个 require() 函数用于自动加载模块&#xff0c;用法与 define() 函数类似&#xff0c;区别在于 require() 只能用来载入模块&#xff0c;而 define() 还可以定义模块。当 Require.js 需要加载一个模块时&#xff0c;内部就会自动创建 script 标签去请求并执行相应模块的代码。
在这里插入图片描述
目前绝大多数第三方库都支持 AMD 规范&#xff0c;但是它使用起来相对复杂&#xff0c;而且当项目中模块划分过于细致时&#xff0c;就会出现同一个页面对 js 文件的请求次数过多的情况&#xff0c;从而导致效率降低。在当时的环境背景下&#xff0c;AMD 规范为前端模块化提供了一个标准&#xff0c;但这只是一种妥协的实现方式&#xff0c;并不能成为最终的解决方案。

同期出现的规范还有淘宝的 Sea.js&#xff0c;只不过它实现的是另外一个标准&#xff0c;叫作 CMD&#xff0c;这个标准类似于 CommonJS&#xff0c;在使用上基本和 Require.js 相同&#xff0c;可以算上是重复的轮子。但随着前端技术的发展&#xff0c;Sea.js 后来也被 Require.js 兼容了。如果你感兴趣可以课后了解一下 Seajs官网。
在这里插入图片描述


模块化的标准规范

尽管上面介绍的这些方式和标准都已经实现了模块化&#xff0c;但是都仍然存在一些让开发者难以接受的问题。

随着技术的发展&#xff0c;Javascript 的标准逐渐走向完善&#xff0c;可以说&#xff0c;如今的前端模块化已经发展得非常成熟了&#xff0c;而且对前端模块化规范的最佳实践方式也基本实现了统一。


  • 在 Node.js 环境中&#xff0c;我们遵循 CommonJS 规范来组织模块。
  • 在浏览器环境中&#xff0c;我们遵循 ES Modules 规范。

而且在最新的 Node.js 提案中表示&#xff0c;Node 环境也会逐渐趋向于 ES Modules 规范&#xff0c;也就是说作为现阶段的前端开发者&#xff0c;应该重点掌握 ES Modules 规范。

因为 CommonJS 属于内置模块系统&#xff0c;所以在 Node.js 环境中使用时不存在环境支持问题&#xff0c;只需要直接遵循标准使用 require 和 module 即可。

但是对于 ES Modules 规范来说&#xff0c;情况会相对复杂一些。我们知道 ES Modules 是 ECMAScript 2015&#xff08;ES6&#xff09;中才定义的模块系统&#xff0c;也就是说它是近几年才制定的标准&#xff0c;所以肯定会存在环境兼容的问题。在这个标准刚推出的时候&#xff0c;几乎所有主流的浏览器都不支持。但是随着 Webpack 等一系列打包工具的流行&#xff0c;这一规范才开始逐渐被普及。

经过 5 年的迭代&#xff0c; ES Modules 已发展成为现今最主流的前端模块化标准。相比于 AMD 这种社区提出的开发规范&#xff0c;ES Modules 是在语言层面实现的模块化&#xff0c;因此它的标准更为完善也更为合理。而且目前绝大多数浏览器都已经开始能够原生支持 ES Modules 这个特性了&#xff0c;所以说在未来几年&#xff0c;它还会有更好的发展&#xff0c;短期内应该不会有新的轮子出现了。

综上所述&#xff0c;如何在不同的环境中去更好的使用 ES Modules 将是你重点考虑的问题。

ES Modules 特性
那对于 ES Modules 的学习&#xff0c;可以从两个维度入手。首先&#xff0c;你需要了解它作为一个规范或者说标准&#xff0c;到底约定了哪些特性和语法&#xff1b;其次&#xff0c;你需要学习如何通过一些工具和方案去解决运行环境兼容带来的问题。


模块打包工具的出现

模块化可以帮助我们更好地解决复杂应用开发过程中的代码组织问题&#xff0c;但是随着模块化思想的引入&#xff0c;我们的前端应用又会产生了一些新的问题&#xff0c;比如&#xff1a;

首先&#xff0c;我们所使用的 ES Modules 模块系统本身就存在环境兼容问题。尽管现如今主流浏览器的最新版本都支持这一特性&#xff0c;但是目前还无法保证用户的浏览器使用情况。所以我们还需要解决兼容问题。

其次&#xff0c;模块化的方式划分出来的模块文件过多&#xff0c;而前端应用又运行在浏览器中&#xff0c;每一个文件都需要单独从服务器请求回来。零散的模块文件必然会导致浏览器的频繁发送网络请求&#xff0c;影响应用的工作效率。

最后&#xff0c;谈一下在实现 JS 模块化的基础上的发散。随着应用日益复杂&#xff0c;在前端应用开发过程中不仅仅只有 Javascript 代码需要模块化&#xff0c;HTML 和 CSS 这些资源文件也会面临需要被模块化的问题。而且从宏观角度来看&#xff0c;这些文件也都应该看作前端应用中的一个模块&#xff0c;只不过这些模块的种类和用途跟 Javascript 不同。

对于开发过程而言&#xff0c;模块化肯定是必要的&#xff0c;所以我们需要在前面所说的模块化实现的基础之上引入更好的方案或者工具&#xff0c;去解决上面提出的 3 个问题&#xff0c;让我们的应用在开发阶段继续享受模块化带来的优势&#xff0c;又不必担心模块化对生产环境所产生的影响。

接下来我们先对这个更好的方案或者工具提出一些设想&#xff1a;

第一&#xff0c;它需要具备编译代码的能力&#xff0c;也就是将我们开发阶段编写的那些包含新特性的代码转换为能够兼容大多数环境的代码&#xff0c;解决我们所面临的环境兼容问题。

第二&#xff0c;能够将散落的模块再打包到一起&#xff0c;这样就解决了浏览器频繁请求模块文件的问题。这里需要注意&#xff0c;只是在开发阶段才需要模块化的文件划分&#xff0c;因为它能够帮我们更好地组织代码&#xff0c;到了实际运行阶段&#xff0c;这种划分就没有必要了。

第三&#xff0c;它需要支持不同种类的前端模块类型&#xff0c;也就是说可以将开发过程中涉及的样式、图片、字体等所有资源文件都作为模块使用&#xff0c;这样我们就拥有了一个统一的模块化方案&#xff0c;所有资源文件的加载都可以通过代码控制&#xff0c;与业务代码统一维护&#xff0c;更为合理。

针对上面第一、第二个设想&#xff0c;我们可以借助 Gulp 之类的构建系统配合一些编译工具和插件去实现&#xff0c;但是对于第三个可以对不同种类资源进行模块化的设想&#xff0c;就很难通过这种方式去解决了&#xff0c;所以就有了我们接下来要介绍的主题&#xff1a;前端模块打包工具。

写在最后
本课时重点介绍了前端模块化的发展过程和最终的统一的 ES Modules 标准&#xff0c;这些都是我们深入学习 Webpack 前必须要掌握的内容&#xff0c;同时也是现代前端开发者必不可少的基础储备&#xff0c;请你务必要掌握。

学到这里&#xff0c;你可能会有这样的疑问&#xff0c;本课时的内容是否偏离了主题&#xff1f;但其实我想传达的思想是&#xff0c;虽然 Webpack 发展到今天&#xff0c;它的功能已经非常强大了&#xff0c;但依然改变不了它是一个模块化解决方案的初衷。你可以看到&#xff0c; Webpack 官方的 Slogan 仍然是&#xff1a;A bundler for Javascript and friends&#xff08;一个 Javascript 和周边的打包工具&#xff09;。

从另外一个角度来看&#xff0c;Webpack 从一个“打包工具”&#xff0c;发展成现在开发者眼中对整个前端项目的“构建系统”&#xff0c;表面上似乎只是称呼发生了变化&#xff0c;但是这背后却透露出来一个信号&#xff1a;模块化思想是非常伟大的&#xff0c;伟大到可以帮你“统治”前端整个项目。这也足以见得模块化思想背后还有很多值得我们思考的内容。

总的来说&#xff0c;我们可以把 Webpack 看作现代化前端应用的“管家”&#xff0c;这个“管家”所践行的核心理论就是“模块化”&#xff0c;也就是说 Webpack 以模块化思想为核心&#xff0c;帮助开发者更好的管理整个前端工程。


推荐阅读
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文详细介绍了在Linux系统上编译安装MySQL 5.5源码的步骤。首先,通过Yum安装必要的依赖软件包,如GCC、GCC-C++等,确保编译环境的完备。接着,下载并解压MySQL 5.5的源码包,配置编译选项,进行编译和安装。最后,完成安装后,进行基本的配置和启动测试,确保MySQL服务正常运行。 ... [详细]
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • PHP预处理常量详解:如何定义与使用常量 ... [详细]
  • 为开发者提供了一系列实用的参考网站和资源链接,包括HTML速查手册( 和 ),帮助开发者快速查找和学习相关技术知识。此外,还涵盖了其他重要的开发工具和文档,为编程工作提供全面支持。 ... [详细]
  • 通过纯CSS技术,可以轻松创建精致的小圆点和三角形图形。本文详细介绍了如何利用CSS的伪元素、边框和背景属性,实现这些图形的高效绘制,并提供了多种应用场景和示例代码,帮助开发者在网页设计中增添更多视觉效果。 ... [详细]
  • Vue应用预渲染技术详解与实践 ... [详细]
  • 在本文中,我们将为 HelloWorld 项目添加视图组件,以确保控制器返回的视图路径能够正确映射到指定页面。这一步骤将为后续的测试和开发奠定基础。首先,我们将介绍如何配置视图解析器,以便 SpringMVC 能够识别并渲染相应的视图文件。 ... [详细]
  • 本文探讨了 Kafka 集群的高效部署与优化策略。首先介绍了 Kafka 的下载与安装步骤,包括从官方网站获取最新版本的压缩包并进行解压。随后详细讨论了集群配置的最佳实践,涵盖节点选择、网络优化和性能调优等方面,旨在提升系统的稳定性和处理能力。此外,还提供了常见的故障排查方法和监控方案,帮助运维人员更好地管理和维护 Kafka 集群。 ... [详细]
  • iOS 设备唯一标识获取的高效解决方案与实践
    在iOS 7中,苹果公司再次禁止了对MAC地址的访问,使得开发者无法直接获取设备的物理地址。为了在开发过程中实现设备的唯一标识,苹果推荐使用Keychain服务来存储和管理唯一的标识符。此外,还可以结合其他技术手段,如UUID和广告标识符(IDFA),以确保设备的唯一性和安全性。这些方法不仅能够满足应用的需求,还能保护用户的隐私。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • MySQL数据库安装图文教程
    本文详细介绍了MySQL数据库的安装步骤。首先,用户需要打开已下载的MySQL安装文件,例如 `mysql-5.5.40-win32.msi`,并双击运行。接下来,在安装向导中选择安装类型,通常推荐选择“典型”安装选项,以确保大多数常用功能都能被正确安装。此外,文章还提供了详细的图文说明,帮助用户顺利完成整个安装过程,确保数据库系统能够稳定运行。 ... [详细]
  • 利用树莓派畅享落网电台音乐体验
    最近重新拾起了闲置已久的树莓派,这台小巧的开发板已经沉寂了半年多。上个月闲暇时间较多,我决定将其重新启用。恰逢落网电台进行了改版,回忆起之前在树莓派论坛上看到有人用它来播放豆瓣音乐,便萌生了同样的想法。通过一番调试,终于实现了在树莓派上流畅播放落网电台音乐的功能,带来了全新的音乐享受体验。 ... [详细]
author-avatar
zjjj-jz政
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有