包管理工具
有的工程师发现,自己抽象出来的模块其实也可以放在社区让别人使用,比如发异步请求的 axios,工具库 lodash 等。这就需要一个中心仓库来存放这些库了,同时也需要一个包管理工具来管理包的发布、安装、升级等。
目前 npm 就是使用最多的包管理工具,当电脑里装了 Node.js 后,npm 也会一并装上。不过使用 npm 在国内下载时会很慢,一般推荐使用 yarn 这个包管理工具,速度更快。
工程化
模块拆分使得写代码时候爽了,但是如果把这些 JS 文件都引入到一个 HTML 上是不是太恐怖了?一个 HTML 里有 1000 个 script 标签,比内容还多也有点反人类了吧。CSS 文件同理。
为了解决这个问题,前端工程师提出了 bundle 这个概念——不管你的模块多乱,多分散,最终通过一个工具,直接转换为1 个 .js 文件。这样的工具就叫做打包工具。
在 2016 年,Grunt,一个 JavaScript Task Runner 被制作出来了,开发者可以编写自己的任务,然后流水线地执行。这已经有了工程化的雏形了。
但是 Grunt 的打包速度太慢了。工程师们受不了了,又造了一个 Glup 的打包工具,功能差不多,但是一个字快!
与此同时,另一只巨兽也在悄然进化——Webpack。
Webpack 以其强大的功能、高灵活配置度的特性直接抢夺了 Glup 的市场,很多人都纷纷用起了 Webpack。
随着 Webpack 的功能不断增强,开发者的要求也不断提高,市面上充斥着大量的 Loader 和插件:
- 热加载
- 代码混淆
- 代码压缩
- 精简代码、TreeShaking
- loader: file-loader, css-loader, vue-loader
- ...
Webpack 的另一个问题是,不同环境需要不同的 Webpack 打包配置,导致 Webpack 的配置越来越繁琐,前端工程师除了平常写代码之外,还要负责维护 webpack.config.js
的配置项目。
而 Parcel 的出现正好打破了这一局面,使用 Parcel 就像使用 iPhone 一样,不用太多配置可以马上跑出小网页。但是,治本不治根,在大型项目面前,还是没办法解决繁琐配置的问题。所以,Webpack 依旧是占领市场的巨头。
使用 Webpack 的另一个问题是本地开发打包很慢,Webpack 一般先打包构建再启动开发服务器。而 Evan You 则想到另一个方法:先启动开发服务器,当代码执行到模块加载时再请求对应模块的文件。加快本地开发时的打包编译速度,然后造出了 Vite。目前 Vite 还是个新生儿,可以关注一波,看看以它以后会迸发出什么好玩的东西。
CSS 预处理器
CSS 让人诟病的一点是不够简洁,很多东西不能复用。比如:
.container { background: red; /*背景为红色*/ } .container .title { color: red; /*标题字体为红色*/ } 复制代码
为什么不写成这样呢:
.container { backtround: red; /*背景为红色*/ .title { color: red; /*标题字体为红色*/ } } 复制代码
遗憾的是,浏览器只认识 CSS,不认识上面这样的写法。程序员又开始思考了:其实我不用浏览器认识第二种写法,我只要把第二种写法在打包的时候转换成 CSS 不就行了嘛。有了打包工具的加成,这件事我觉得能成!所以,第二种高级写法被称为 CSS 预处理器,即这些写法要先被处理成 CSS,然后再以普通 CSS 使用。
于是,在 2007 年,Sass 诞生了,借鉴了 Ruby 社区的 Sass 语法,但是前端程序员比较傲娇:凭啥要跟你叫一个名,就叫为 Scss,不过一般叫法还是叫 Sass,因为 Scss 不会读,哈哈。
后来在 2009 年,Less 被创造出来了,语法 Scss 差不多,没多大差别。
在 2010 年,又一个预处理器 Stylus 诞生了。
目前 Scss 和 Less 用的比较多,Stylus 名气比较小。新手不用担心,这三玩意语法都差不多,大同小异,会一个相当于3个都会了。
进击的 JavaScript
虽然 JavaScript 本来是一个设计非常糟糕的编程语言,但是 JS 它不是咸鱼,它也要努力成为世界上最好的编程语言!
JavaScript 总不能一成不变吧?但是也不能一下子就变天了吧?所以,需要有一个起草方案,审核方案,同意方案,变成规范的过程,这需要大量的人力和专家,那不如做一个 “JS爱好者协会”?European Computer Manufacturers Association (ECMA) 就是类似这样的组织,不过这个组织格局比“JS爱好者协会”的格局大多了,主要任务是是将 Computer System 标准化。
ECMA International 在 ECMA-262 里规范了 JavaScript,可以认为 ECMAScript 就是标准的 JavaScript,所有浏览器都要支持标准的 JavaScript。这个 262 的标准规范从 1997 年开始提出了第 1 版。
从上面可以看到,往后几年就是第 N 代的 ES 标准对应标准的 JavaScript 就是 ECMAScript 201(N-1),比如我们最熟悉的 ES6 其实就是 ECMAScript 2015。而 ES6 对 JavaScript 一个大变革,后面的 ES7,ES8 新增的东西就很少了,所以现在 ES6 其实是 ES6+ 的一个泛指。
但是你有没有想过一个问题:虽然我用最新的语法做开发,但是用户用的可能还是老版本的浏览器呀,这要怎么办呢?
再次得益于自动化打包工具的兴起,我们可以在开发的时候用最新的语法,在打包生产代码时将新语法都转成旧语法就好了嘛。这种语法的转换听起来就很麻烦,不过,聪明的前端工程师已经帮各位大哥大嫂做好,那就是 Babel。
Babel 发展到了现在,除了做新旧语法的转换,还支持 JSX 语法的转换。
TypeScript
虽然 ES6 新增的语法和 API 已经大幅提升前端程序员的幸福感了,但是 JavaScript 依然是个弱类型的语言:
类型不规范,同事两行泪。当不正确使用类型时:
那能不能强行给 JavaScript 加上类型呢?微软说:可以!由微软牵头,开发了 TypeScript 编程语言和 TypeScript 的编译器,前者其实是 JavaScript 的超集,只是多加了很多料;后者则是负责将 TypeScript 编译成 JavaScript。
注意:这里的 TypeScript -> JavaScript 是不能用 Babel 实现的,因为这一步是编译,而不是新旧语法的替换。TypeScript 不是新语法,是一门正经的编程语言,只不过可以被编译成 JavaScript。
有了 TypeScript,开发者终于享受到了强类型约束的福利了:
const x: string = '123' 复制代码
再再再一次得益于自动化打包工具人 Webpack,可以在输出生产代码时将 TypeScript 编译成 JavaScript,如果再加上 Babel,则能进一步转为某个时代的 ECMAScript。
单页应用
在打包工具不断厮杀的同时,单页应用框架也一并发展。
在以前,大部分都是一直在用 jQuery 直接操作 DOM 来更新页面。
每次操作 DOM 时就不得不写一些面条代码。但是这样很麻烦啊,操作 DOM 这一步能不能封装一下,数据更新时自动操作 DOM 去更新页面?
2010 年,Google 研发的 Angular.js 率先实现了 MVVM 想法,即开发者不再需要操作 DOM,可以直接拿数据渲染页面 Modal-View,而页面的变化,比如输入值改变,可以反过来改变数据内容。后来又研发了 Angular2,但是无论是 Angular.js 还是 Angular 本身都太复杂了,借鉴了非常多的后端设计,前端工程师上手难度非常巨大,最终并没有形成大潮流。
注意 Angular.js 和 Angular 是两个不同的东西!
2013 年,一个新的前端框架诞生了——Facebook 的 React.js。React 可以说是一个非常纯净的 JS 框架,没有 Angular 繁琐的内容,开发者只需要关注单向数据流就可以上手撸页面了。最后 React.js 在前端社区流行了起来。
但是 React.js 也有自己的问题:由于 React.js 内容太过于纯净了,本身没有太多的功能,导致开发 React 应用时出现非常多的解决方案,但没有一个方案是最优的,各有各的优缺点。这也导致了 React 社区总是骂战不断、帮派林立、出现各式各样的鄙视链的局面。
比如,在写样式时你可以直接 import 样式文件:
import 'xxx.css' const xxx = <button className="xxx">xxx</button> 复制代码
也可以用 CSS module
import styles from 'xxx.css' const xxx = <button className={styles.xxx}>xxx</button> 复制代码
也可以用 styled-component
const Button = styled.a` color: white; ` const xxx = <Button>xxx</Button> 复制代码
但是偏偏有人说 styled-components 是高级人,别的都是垃圾,使用 CSS Module 的人就受不了了,说你天天引那么多库来干嘛?
另一个麻烦点是,单向数据流并不是所有人都喜欢的,人们开始怀念 Angular 的数据双向绑定了。
到了 2014 年,那个男人来了,他带着 Vue.js 来了!
Evan You 以前在 Google 和 Meteor 工作过。Vue.js 取了 Angular 和 React 的中间位置,以一种优雅、轻便的姿态登陆前端社区。保留了 Angular 的数据双向绑定,但是摒弃了 Angular 很多复杂的设计和 API,同时不像 React.js 那么纯净,开放很多方便的 API 给使用者爽爽。而且还基于 Webpack 开发了 vue-loader 用来解析 .vue 模板文件。这种 .vue 模板文件和 .html 非常相象,新人上手十分简单。
同时,得益于 Vue 简洁好看的中文官方文档,Vue.js 在中国迅速抢占了小公司的市场。
但是由于 Vue.js 太容易上手了,所以经常被 React.js 社区的一些人觉得写 Vue.js 的人都是新手。而 Vue.js 的人又觉得写 React.js 的人天天折腾这么多“最佳实践”,简直是在浪费生命,而且 JSX 的语法太丑了,不如我的 template 语法简洁好懂。直到现在,Vue 和 React 社区时不时就会爆发小规模的骂战。
另一个问题出现了:原来的 UI 库仅仅提供了简单的 CSS 和原生的 JS,这些 JS 放到单页应用里显得有点冗余了,有的还会报错,因为大多数都是 DOM 的操作。所以 UI 库必须要配合对应的 SPA 框架进行升级。在别的 UI 库升级的同时,饿了么针对 Vue.js 开发 Element UI,而蚂蚁金服则针对 React 开发了 Antd。随后,更多的 UI 库再次涌现,比如 iView、Ant Design for Vue, Ant Design for Angular 等。
总得来说,Angular, React.js, Vue.js 都开发了自己的一套单页应用框架,这套框架最后要做的就是 SPA(Single Page Application) 单页应用。即所有的逻辑都打包在 JavaScript 文件里了,对外,只会看到一个 .html,一个 .css 和一个 .js 文件。
等一下?一个 .html 文件?那不同页面怎么做跳转呢?这就是前端路由的由来了。
前端路由
不妨想想以前是怎么做路由的:用户页是 user.html,首页是 index.html,一个 url 对应着一个文件,也就说我们每次键入 url 时,实际上是访问某个 .html。
而浏览器里有一个监听浏览器地址改变的功能,单页应用的开发者就想了:我只要监听地址 url 的变化,再用 JS 渲染对应的页面组件,不就可以实现前端控制路由了么?这就是前端路由的基本思想。
上面的三大单页应用框架都有自己的前端路由框架:@angular/router, react-router, vue-router。
数据管理
单页应用框架另一个问题就是数据的管理,子组件访问的数据都只能靠父组件传过来,如果一个在很深的子组件想要最外层组件的数据时,就不得不把数据从头一路传到尾。
为了应对这种数据很难共享的问题,程序员就想:我把数据都存到一个公共的地方不就行了嘛?要的时候随便拿。
那公共地方是哪里呢?存全局变量?不行啊,会被别人覆盖啊,而且数据改了之后视图不能随之改变呀。所以,工程师又开发一些全局数据管理库:mobx, vuex, redux。
同构渲染
用多了单页框架之后,程序员们又发现问题了,单页最后生成的 HTML 是这样的:
<body> <div id="root"></div> <script src="bundle.js"></script> </body> 复制代码
要知道搜索引擎每天都会用网络爬虫抓取成千上万个网页,分析 HTML 里的内容,以此来提高搜索准确性,这种做法做叫 SEO(Search engine optimization) 搜索引擎优化。但是你看上面这样的结构,搜索引擎根本不知道你这个网页是用来干嘛的。另一个问题是,如果用户网络环境很差,那只要 JS 还没被加载出来,网页永远是一片白色,这非常影响用户体验。这要怎么解决呢?
大家开始怀念当时 JSP、PHP 服务端渲染 HTML 的时候了,因为服务端渲染 HTML 可以马上返回 HTML 结构,页面会先展示一些内容,不至于白屏,而且有了大概的 HTML 结构,搜索引擎更容易做 SEO。服务端的渲染也被叫做 SSR(Server Side Rendering)。
前端工程师想到了:对于一些静态内容,比如商品种类、导航栏内容,其实可以在生成 HTML 的时候就加上,不需要再通过 API 获取了。这样的技术就叫做 SSG(Static Site Generation)。那动态的内容,比如朋友圈列表怎么做呢?初始展示的数据可以先通过服务端先渲染,等用户与页面发生交互,比如点击按钮后再发请求获取数据。这就是 同构渲染。
与传统的服务端渲染不同,同构渲染的服务端也使用 JavaScript 来编写,这样一来前后端都使用上了 JavaScript 了。
同构渲染简单来说就是一份代码,服务端先通过服务端渲染(server-side rendering),生成html以及初始化数据,客户端拿到代码和初始化数据后,通过对 html 的 dom 进行 patch 和事件绑定对 Dom 进行客户端激活(client-side hydration)。
这个整体的过程叫同构渲染。既可以吸取服务端的优点,比如加速首屏渲染,又保留了 SPA 应用的特点,比如前端控制路由跳转,使得跳转时不需要再渲染新 HTML。
Single-SPA
当越来越多项目用上了 SPA 框架后,当公司要把多个项目合并成一个项目的时候,这就很麻烦了,不同项目可能用的框架都不一样,比如用户详情用的是 Vue.js,而首页用的是 React.js,怎么把这两者结合呢?
single-spa 和国内阿里开发的乾坤就是解决这种问题的。本质上相当于造一个更大的“单页的应用”,这个“单页应用”里又会有多个单页的应用。
这种将多个 SPA 整合成一个大 Project 的技术就是 微前端。
自动化测试
随着前端越来越工程化,自动化测试是比不可少的一件事。当造好一些轮子后,就需要接入自动化测试,不然每次修改都要做点点点的人工测试。
最简单的莫过于单元测试,目前单测常用的库有 *ava, jest, moch, sinon, chai 等。而前端又是一个非常依赖环境的工种,经常要用到不同的环境,比如 JSX 环境、浏览器环境,Vue 环境。又催生出很多提供 Mock 环境的库,比如 enzyme 就是用来提供 React 环境的。
同时,为了模拟一些特定的场景,前端还要 Mock 一些东西,比如 localStorage, indexedDB, cookie 等。这些都有不同的库和工具来实现,比如 mock-axios, mock-redux, mock-cookie 等。
上面的只是属于白盒测试的做法,前端能不能直接模拟人工做点点点的操作呢?可以的,这就叫端对端测试、或者叫 e2e 测试,或者叫集成测试。现在比较火的工具是 cypress 和 nightwatch。
不过对于业务经常频繁改动的项目,自动化测试并不是一件好事,改动频繁的业务带来的是变化无常的测试用例。写太多的测试代码,有时候变得事倍功半。
手机H5
随着智能手机发展得越来越快,微信等一些应用里都不得不内嵌一些前端的 H5 页面,比如微信公众号文章,其实就是一个小型网页。
另一个应用场景是,工程师们发现 App 里也可以嵌入 H5 来做简单的展示和交互,这样一来移动端就可以少开发一些内容了。这种内嵌H5页面的应用开发也称为混合开发,开发出来的 App 就是 Hybird App.
现在我们手机上的支付宝、微信、QQ 等都是采用了混合开发的模式,既能使用原生的代码保证流畅度,同时又可以内嵌H5提升开发速度(注意,速度不等于效率)。
低代码
手机上的 H5 写多了之后,前端工程师们又发现很多 H5 都千篇一律,很多都是模板一套,再改个颜色就OK了。
因此,聪明的程序员就想到了能不能用拖拽就生成网页呢?其实拖拽生成网页并不是什么新鲜事,也不难实现,早在 wordpress 的时代已经出现拖拽生成个人博客的工具了。只不过现在用到手机上了。
但是拖拽的问题在于不灵活,有时候非常死板。有些运营人员还是懂一点代码的。所以拖拽工具再度进化成了:可以给别人留个入口做一些简单的自定义事情。这种模板+简单代码做开发的就称为**“低代码”开发**,通过简单的配置就能自动生成页面。
需求生成代码
除了拖拽,有没有更高级的生成代码工具呢?有!比如,用文字描述场景来生成前端代码,这也是阿里正在做的事情,它们正在研究 P2C (PRD to Code)。个人觉得这是一件好事,对于简单需求来说可以直接生成比什么都快。
有人会担心:这会不会取代前端工程师呢?答案是不可能,也不科学。再厉害的人工智能最终也无法实现设计复杂、灵活多变、千奇百怪、疯狂迭代的产品需求。最终取代的是只会写简单 html、css 的低级工程师。
最后
不知不觉就写好了多东西,可见前端东西真的很多很杂。其实再下写去还能再细写下去,但是文章实在太长了,到此结束了。
这篇文章除了带大家了解前端常见的“名词”之外,还希望大家学前端时不要上来就我要精通XXX。你看前端的发展多么坎坷、都是通过解决一个一个问题才有今天的前端,学习也是同样的道理,先实现一个最 Low 的版本,再慢慢升级自己。