原因
网页开发原来越复杂,迫切需要一个团队分工协作、并行开发等
理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块,代码复用,提高开发效率
复杂业务变量冲突难以避免
如果你没有使用模块化系统,那么你只能用这种方式来处理你的模块化代码了。
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>
每个模块向外暴露一个接口给全局对象,即window对象。模块就可以通过全局对象访问依赖项向外暴露的接口。
通常存在的问题
- 全局对象中的变量冲突。
- 按需加载的问题。
- 开发者需要手动解析模块或者库的依赖项。
- 在特别大的项目中,这个现象会变得越来越严重,越来越难以管理。
模块化中比较有代表性的就是AMD、CMD、CommonJS和es6模块化这几种方案。
而随着nodejs的发展,我们有了对代码做编译的能力。这样原本不能在浏览器上运行的模块化方案,通过编译处理以后,也能正常在浏览器上跑了。这种模块化方案最具代表性的就是CommonJs的模块化方案。node.js的模块系统,就是参照CommonJS规范实现的。由于我们是要编译的,所以大部分处理模块化的代码都可以通过编译的过程追加进去,这样我们只用关心业务逻辑部分就可以了。当然这种方案也不是完美的,这种提前编译打包的方案会把所有引用的代码都打包进去,如果想按需加载就需要再做进一步的处理。总体来说,我还是比较倾向这种模块化方案。
浏览器加载 CommonJS 模块的原理与实现 require() 源码解读
CommonJS模块化方案同步加载,并不适用于浏览器端,服务器端所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。
因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)
AMD CMD
AMD 是 RequireJS 在推广过程中的规范化产出,而CMD 是 SeaJS 在推广过程中的规范化产出,这两个模块化方案有些相似,主要的区别是加载和运行的时机不太一样。这两种模块化方案是可以直接在浏览器上运行的,但也有个缺点,就是会将模块化的代码和业务代码掺杂在一起,对于强迫症的同学来说,这并不算一个很好的方案。+
在模块化方案中,还有一种被纳入标准的ES6模块化方案,理论上这种模块化方案也是可以直接运行在浏览器上的,但对浏览器的版本要求比较高。现在也有一种方案,就是通过babel编译,把ES6的语法转换成大部分浏览器可以兼容的旧版本js的语法。但这样的话,ES6的模块化相对CommonJs也就没有多大的优势了。
ES6 模块与 CommonJS 模块的差异
JavaScript 模块化历程
模块化与打包工具
由于模块化方案多样, 且浏览器支持不一, 再加上上述模块化方案仅仅支持JavaScript本身, 对 于复杂的前端应用来说远远不够用, 因此出现了各种打包工具来解决这些问题
早期打包工具
- r.js -- RequireJS提供的打包工具,仅仅支持RequireJS
- SPM -- SeaJS提供的打包工具,仅仅支持SeaJS
- browserify -- 让浏览器使用Node.js的NPM模块
- gulp/grunt/fis -- 前端自动化构建, 用来测试,压缩,检错,合并前端代码, 不支持模块化(类似Maven/Gradle)
webpack -- 高度可配置的静态资源打包器, 有着强大的插件和生态
rollup -- 小巧高效的前端资源打包器, 适合用来编写库或框架
parcel -- 后起之秀, 极速零配置Web应用打包工具
webpack 简介
webpack 优点
- 可以兼容多模块风格,无痛迁移老项目
- 一切皆模块,js/css/图片/字体/音视频 等都是模块, 都可被打包
- 配合插件/加载器可以进行各种操作: 转译, 代码检查, 压缩等等
- 静态解析,按需打包,动态加载,
- 支持抽离公共模块
- 支持进行代码分隔, 按需下载
- 扩展性强,插件机制强大, 生态完善
- 强大的webpack-dev-server: 检测代码改变, 进行代码热重载, 无需浏览器刷新