对于前端仔来说,相信大家对 webpack 都再熟悉不过了,但是你对 webpack 的了解程度又有多深呢,笔者花了几天时间看了一下《深入浅出 webpack》,虽然说书中大部分介绍的是配置和使用相关的,但是如果你对 webpack 的配置、使用、原理和构建流程更加熟悉的话,对于你的开发可以说是百里无一害!本文不会局限于介绍配置,也不会详细介绍打包原理(后面打算写一篇有关 webpack 打包原理的~),更多着重于 webpack 打包的思想介绍。
没有打包构建的日子
nodejs 的出现对于构建工具具有重要的意义,在没有 nodejs 之前,js 只能执行在浏览器环境下,所以意味着对发布前的 js 文件要进行处理,十分局限,没有打包工具,只能用 PHP 脚本来处理文件,甚至还需要借助一些在线压缩网站,开发体验十分差劲,在史前时代存在以下几个痛点:
1、缺乏文件处理工具,对文件进行编译或其他预处理,进行打包压缩等工作;
2、缺乏文件的模块化,引入第三方库直接用 cdn 引入,需要处理依赖管理,人为控制脚本的加载顺序,并且存在全局变量命名冲突的问题;
3、缺乏代码校验和自动化测试,在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
有了打包构建工具的日子
随着 nodejs 的诞生,我们可以在开发环境下书写 nodejs 代码脚本,对我们的前端代码做预处理,编译压缩等工作,最初诞生的是 grunt 和 gulp,Grunt 和 Gulp 都属于任务流工具 Tast Runner,两者都是通过配置好配置文件,但是相比之下,gulp 通过函数式编写配置文件,以及前端人员所熟悉的链式调用,让大家觉得更易懂更易上手,gulp 本身借鉴了 grunt 的经验进行升级和加入一些新特性。正因为流管理多任务配置输出方式的提高,人们逐渐选择使用 Gulp 而放弃 grunt。
有了 grunt 和 gulp,文件压缩处理的工作解决了,代码校验和测试也可以处理了,但是模块化仍没有结果?
其实前端的痛点还远不止模块化那么简单,频繁的 DOM 节点处理,JS 里杂糅了交互逻辑、请求逻辑、数据处理和校验逻辑、DOM 操作逻辑,导致 JQ 书写的代码就更意大利炒大便,呸!意大利炒面一样。在团队开发中,可能你的代码要给别人维护,这就非常痛苦了。
webpack 诞生记
1、模块化思想
隔离不同的 js 文件,模块化开发,仅暴露当前模块所需要的其他模块,这是模块化思想想要传递给我们的。nodejs 诞生后,后端所采取的模块化思想是 commonjs,然而,不同于后端,前端的代码运行在浏览器端,有两点不同之处:
1、没有 nodejs 执行环境,不支持 module.exports 的书写格式;
2、后端 require 一个文件,是读取本地文件的形式,速度极快,而对于前端而言,需要去动态加载一个 js 文件,存在额外耗时。
于是 AMD 思想应运而生,对此的相应实现是 requireJS,允许你定义好模块名称、模块依赖以及当前的模块代码(function),通过广度优先遍历的方式,递归加载父模块所依赖的子模块,但是这也暴露出了一些问题:
1、通过 js 加载执行后再去加载其依赖的子模块,这个递归加载过程本身是耗时的;
2、模块化思想提倡我们分隔逻辑,管理好各个 js 文件内的逻辑,一旦分割的 JS 文件过多,最终造成前端资源加载压力。
不过不用担心,requireJS 提供了 r.js 来处理发布前的模块合成,帮助你把多个 JS 文件打包成一个文件。
再到后来,国内出现了 CMD 的思想,不同于 AMD 的声明依赖的形式,允许你动态加载依赖,但是其实现以及具体的运行结果被大家诟病。
再到后后来,为了解决这种不同库的模块实现不一致的问题,提出了 UMD,其实很简单,只是写一段 hack,让你的模块能够兼容不同的模块加载场景,无论是 commonjs 还是 AMD,如果都没有的话就直接声明为一个全局变量的形式。
下面引用一段 UMD 的代码:
(function (global, factory) {typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.Vue = factory());
}(this, function () {
'use strict';
//your code here
}
再到后后后来,未来的大一统,es6 中所提出的 import 和 export 的形式来统一前后端的模块化加载方式。相比以往的实现,import/export 的形式,在模块化加载 JS 文件的时候,保留动态的执行引用,其次,不允许动态控制加载依赖,使得 tree-shaking 成为可能。
2、组件化思想
组件化的思想并非前端所特有,在客户端也会面临相同的问题。想象一下,A 跟 B 被同时分配一起开发完成一个首页页面,包括导航栏、轮播图、网站列表数据,登录框等,两人需要如何分工协作?导航栏、列表这种需要在多个页面复用的 HTML 怎么办?假如在没有组件化处理的情况下:
1、A 和 B 分工困难麻烦,代码提交时会处理大量的冲突;
2、导航栏、列表等多处复用的地方,需要 cv 大法直接复制粘贴到另一个页面中去使用。
组件化思想,让我们把页面划分为一个个组件,组件内部维护自己的 UI 展示、交互逻辑,组件间可以进行数据通信,实现一种变相的相互隔离,便不会出现 A 和 B 两人一起编辑一段 html 的难受场景,同时,提高了代码的可维护性和复用性,这是其解决的关键痛点。
引用 vue 官网的一张有关组件化思想的图:
3、MVC 框架、MVVM 框架的流行
在模块化和组件化的基础上,实现了页面组件之间的隔离和各自维护,但是面临的最大的一个痛点问题,仍然是前面所说的,前端的 JS 逻辑中杂糅了各种处理逻辑,交互逻辑、请求逻辑、数据处理和校验逻辑、DOM 操作逻辑;而其实这一切可以划分为两个层次,一个是数据层,一个是视图层,如何避免重复的书写操作 DOM 的逻辑,如果说早期的各种模板引擎给了我们初期的解决方案,那么 vue、react 以及 angular 就是在模板引擎的基础上的上层建筑。
为了让我们更加专注于数据的处理,MVC 框架和 MVVM 框架帮我们做了以下两件事:
1、监听页面操作事件,触发相应的事件钩子,执行代码逻辑,即 V 层到 M 层的过程;
2、执行代码逻辑后,数据层发生修改,帮我们更新渲染页面,即 M 层到 V 层的过程;vue 中通过 vm 实现,react 中通过触发 setState 通知。
如此,我们只需要书写一次 html,在 html 中写明绑定或展示的数据,同时绑定好事件监听器,后续便不需要再处理视图层相关的操作,只需要关注于自己的业务逻辑代码、数据层的处理等。
MVVM 架构流程图:
4、代码打包构建
前面介绍了 grunt、gulp 打包构建工具,其实 webpack 本质也是打包构建工具,但是 webpack 呈现出来的功能更为强大和成熟。对于代码的预处理、模块化加载、代码分割等,webpack 具有更大的优势。
webpack 诞生!
读者读到这里,可能仍有些许疑惑,前面讲了这么多,为啥还是没有介绍到 webpack 相关的,其实不然,仔细回想一下前面所介绍的思想,以及你平时使用 webpack 来打包构建的时候,其实 webpack 正是帮你处理了这些繁杂琐碎的事情。
如果代码预处理压缩足以,那么 grunt 和 gulp 已经满足了;
如果说模块化开发足以,那么 requireJS 和 Browserify 已经满足了;
如果说组件化开发、MV* 框架足以,那么只需要在页面内引入相应的 vue 或 react 框架,足矣。
笔者写这边文章,更多是想让大家能够 思考工具或者框架背后,所呈现出来的思想,webpack 就像是一个巨无霸,集大成者,它解决了打包构建,它处理了模块化开发,它帮助你和其他框架完美融合实现组件化开发;而这几年来 MV* 框架的流行对于 webpack 市场的迅速扩展有着不小的贡献。
webpack 为我们做了以下这些事:(引自《深入浅出 webpack》)
代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
其实以小见大,你能窥见的不仅是 webpack 的思想,更多的是前端的发展,从最初的土法炼钢,不规范,到如今的模块化、组件化、MV* 框架,是前端思想的进步。作为一个前端仔,我们应该探索和研究的是如何磨刀、磨好刀,而不是砍柴而已。
谢谢观看~