💡 Ajax 的出现,带来了 jQuery 时代,而 jQuery 时代也伴随着 Node 风暴淡淡退出了历史舞台。如果说 Ajax给前端带来了从网页静态化到动态化的转变;那么 Node 的出场给前端带来了大步迈向模块化、工程化的转变,同时也给前端开发工程师带来了市场价值的逆袭。
从上一节《前端简史之裂变:Ajax变法》课程中,我们得知在 jQuery 时代前端工程师通常编写一个页面会引入无数个 jQuery 插件,最终导致页面白屏问题严重。于是一些优秀的前端工程师们向后端取经,引入模块机制。直到 Node 之光普及前端领域,再到 Webpack 此类预编译打包工具的广泛应用,前端借着 Node 东出服务端之势,摆脱了对后端服务的强依赖,在IT界占有了一席之地。
《前端简史》系列技术分享课程通过讲述前端演变的发展历史,介绍前端重要技术的实现原理与使用方法,带你走进前端,感受前端的“前世今生”,结交前端历史上举足轻重的几位“大佬”,例如:Ajax、Node、Webpack、SPA、PWA、RN、Flutter等等。
该系列课程目前一共分为三节:《前端简史之裂变:Ajax变法》、《 前端简史之纵横:Node东出》、《前端简史之崛起:Router迁鼎》。以及不一定会有的《前端简史之天下:跨端跨平台》。
本文 Node
皆指 Node.js
英文:Node.js® is a Javascript runtime built on Chrome’s V8 Javascript engine.
中文:Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行时
来源:Node.js
2009年5月,Ryan Dahl 在 GitHub 上发布了最初版本的部分 Node 包。
2009 年底,Ryan Dahl 在柏林举行的 JSConf EU 会议上发表关于 Node.js 的演讲,之后 Node.js 逐渐流行于世。
延伸阅读:
Node.js 中文网
用 Node 实现一个简单的本地服务
http.js
文件:/**
* http.js
* 使用 http 模块启动本地服务
*/
var http = require('http');http.createServer(function (request, response) {// 发送 HTTP 头部 // HTTP 状态值: 200 : OK// 内容类型: text/plainresponse.writeHead(200, {'Content-Type': 'text/plain'});// 发送响应数据 "Hello World !"response.end('Hello World !');
}).listen(8888);// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/ or http://localhost:8888/');
node http.js
http://localhost:8888
查看效果。如果想进一步实现一个本地 api-mock-server
应用,可以参考 👉 share-demos/node-demo · GitHub
NVM:Node Version Manager,NodeJS 版本管理器
NVM 让我们能方便的对 NodeJS 的版 本进行切换。 NVM 的官方版本只支持 Linux 和 Mac。 Windows 用户,可以用 nvm-windows。
延伸阅读:
GitHub - nvm-sh/nvm
GitHub - coreybutler/nvm-windows
nvm安装与使用 - 简书
前端模块化的发展先后经历了IIFE
=> AMD
=> CMD
=> UMD
=> ESM
的不断优化与完善,最终在语言层面上实现了模块化编程,未来迎接的是各大平台对于 ESM 的支持。
模块化(modular)编程,是强调将计算机程序的功能分离成独立的、可相互改变的“ 模块”(module)的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面所必需的所有东西。
来源:模块化编程 - 维基百科,自由的百科全书
要做到真正意义上的模块化,我们需要注意以下几点:
IIFE:immediately-invoked function expression,立即调用函数表达式
IIFE
的出现是为了弥补 js 在 scope 方面的缺陷:js 只有全局作用域(global scope)、函数作用域(function scope),从 ES6 开始才有块级作用域(block scope)。早期的模块化都是一堆基于 IIFE 实现的 js 代码库。
举个🌰:
(function($) {// jQuery Code
})(jQuery);
CommonJS:同步模块规范
CommonJS 项目由 Mozilla 工程师 Kevin Dangoor 于2009年1月发起,最初名为 ServerJS
。在2009年8月,这个项目被改名为 CommonJS
来展示其API的广泛的应用性。早期 NodeJS
模块化的实现就是采用了CommonJS 规范,同时带来了 NPM
(全球最大的模块仓库) 。
CommonJS 规定每个模块内部有两个变量可以使用,require
和 module
:
require
用来加载某个模块;module
代表当前模块,是一个对象,保存了当前模块的信息;exports
是 module
上的一个属性,保存了当前模块要导出的接口或者变量,使用 require 加载的某个模块获取到的值就是那个模块使用 exports 导出的值。举个🌰:
// a.js
var name = 'Jack'
var age = 18
module.exports.name = name
module.exports.getAge = function(){return age
}//b.js
var a = require('a.js')
console.log(a.name) // 'Jack'
console.log(a.getAge()) // 18
由于 CommonJS
的模块加载是同步的,服务器端加载的模块从内存或磁盘中加载,耗时基本可忽略。但是在浏览器端却会造成阻塞,白屏时间过长,用户体验不够友好。于是有了之后 AMD
、CMD
、UMD
来更好地实现浏览器模块化方案。
延伸阅读:
AMD:Asynchronous Module Definition,异步模块规范。
AMD
是 RequireJS
在的推广和普及过程中被创造出来。
AMD 主要是为了解决 CommonJS 规范在浏览器端的不足:
export
只能导出变量,导出函数需要用 module.export
(开发体验很不爽)。延伸阅读:
CMD:Common Module Definition,普通模块规范
CMD
是 SeaJS
在的推广和普及过程中被创造出来。
CMD 是另一种 js 模块化方案,它与 AMD 很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD 推崇依赖就近、延迟执行,CMD 规范与 CommonJS 更贴近。
举个🌰:
// AMD
// 依赖必须一开始就写好
define(['a.js'], function(a) {console.log(a.name) // 'Jack'console.log(a.getAge()) // 18
});// CMD
define(function(require, exports, module) {// 依赖可以就近书写var a = require('a.js');console.log(a.name) // 'Jack'console.log(a.getAge()) // 18
});
延伸阅读:
UMD:Universal Module Definition,通用模块规范
UMD
是 AMD
和 CommonJS
的一个糅合。
AMD 是浏览器优先,异步加载;CommonJS 是服务器优先,同步加载。
UMD 在定义模块的时候会检测当前使用环境和模块的定义方式,将各种模块化定义方式转化为同样一种写法。
举个🌰:
(function (root, factory) {if (typeof define === 'function' && define.amd) {// AMD. Register as an anonymous module.define(['exports', 'b'], factory);} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {// CommonJSfactory(exports, require('b'));} else {// Browser globalsfactory((root.commonJsStrict = {}), root.b);}
}(this, function (exports, b) {//use b in some fashion.// attach properties to the exports object to define// the exported module properties.exports.action = function () {};
}));
在 jQuery 中的应用:
((root, factory) => {if (typeof define === 'function' && define.amd) {//AMDdefine(['jquery'], factory);} else if (typeof exports === 'object') {//CommonJSvar $ = requie('jquery');module.exports = factory($);} else {//都不是,浏览器全局定义root.testModule = factory(root.jQuery);}
})(this, ($) => {//do something... 这里是真正的函数体
});
UMD 成功解决了模块化在服务端与浏览器端的兼容,然而它的存在也只是前端模块化发展历史上的一个过客,没多久 ES6 模块化直接实现了多端的统一。
延伸阅读:
ESM:ES6 Module, ECMAScript 6.0 模块化
2015 年 6 月正式发布,ES6 模块化是欧洲计算机制造联合会 ECMA 提出的 Javascript 模块化规范,它在语言的层面上实现了模块化。浏览器厂商和 Node.js 都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
—experimental-modules
参数。—experimental-modules
参数 ,也就是说从 v13.2 版本开始,Node.js 已经默认打开了 ES6 Module 的支持。举个🌰:
// a.js
var name = 'Jack'
var age = 18
const getAge = () => age
export {name,getAge
}// b.js
import * as a from 'a.js'
console.log(a.name) // 'Jack'
console.log(a.getAge()) // 18
这也是我们现在项目代码中最常见的模块化编程。
模块化规范 | 代码风格 | 调用时间 | 本质 |
CommonJS | require / exports | require 是运行时调用 | require 是赋值过程 |
ESM | import / export | import 是编译时调用 | import 是解构过程 |
最后放一个彩蛋 👉【问题记录】Uncaught TypeError: Cannot assign to read only property ‘exports’ of object
图片来源:Toolchains - Timeline
前端模块化的不断发展与进步,随之而来的就是前端工程量复杂度的递增。如何更好地导入模块、更快地编译模块、更优地压缩模块等一系列需求或问题带来了前端工程化的演变。
模块化是代码层面上的改进,而工程化是项目层面上的优化。
如果要用一句话来概括,在我的理解中前端工程化是把前端开发工作带入到更加系统和规范体系的一系列过程。这个过程会包括源代码的预编译、模块处理、代码压缩等构建方面的工作。工程化会尽可能保证开发者的开发体验更加友好,保证源代码的质量以及依赖的完整性。工程化也会尽可能高效地将构建完成后的代码送达给客户端,来追求更加良好的用户体验。所有这些都属于工程化。
在 Web 技术刚开始的时候,还没有前端工程化这样一个东西。人们只是简单地把 HTML、CSS 和 Javascript 直接混在一起丢到用户。而就如人类对于食物的追求在不断进步一样,虽然在最初级的阶段需求只是能填饱肚子,但慢慢地人们开始追求食物的质量。对于前端来说也是一样,用户的需求从最开始简单的页面在向复杂的应用发展。前端需要做的事情更多,同时也要追求更友好的用户体验。
另外,工程化也是为开发者服务的。通过预编译语言、模块热加载等技术可以提升开发效率,而利用自动化测试、lint 工具等可以保证代码的功能和质量。工程化可以有效降低开发成本,谁不想省下埋头 debug 的时间去 摸鱼 做更有意义的事呢。
2.1 NPM
Node Package Manager,是一个 NodeJS 包管理和分发工具,已经成为了非官方的发布 Node 模块(包)的标准。
延伸阅读:
2.2 YARN
2016 年 6 月 18 日,yarn 正式在 github 上提交代码,初始版本为 0.2.0 ,当时名字叫 kpm(fbkpm)。
2016 年 10 月 11 日 正式公开发行 。Facebook 官方在 10 月 11 日发布的这篇文章(此时 yarn 已经稳定),介绍了 yarn 出现的缘由和特点: Yarn: A new package manager for Javascript
yarn 的优势:
npm run dev
=> yarn dev
延伸阅读:
2.3 PNPM
pnpm:performant npm,在 2017 年正式发布,定义为快速的,节省磁盘空间的包管理工具,开创了一套新的依赖管理机制,成为了包管理的后起之秀。
pnpm 拥有 yarn 超过 npm 的所有附加功能:
offline
参数可以完全禁止 HTTP 请求。延伸阅读:
2.4 CNPM
2013年上线并开源。
淘宝搭建的一个国内的 npm 服务器,它目前是每隔10分钟将国外 npm 仓库的所有内容搬运回国内的服务器上,这样我们直接访问淘宝的国内服务器就可以了。
设置淘宝镜像:
# npm 默认仓库地址
npm config get registry
http://registry.npmjs.org
# 旧版(已废弃)
npm config set registry http://registry.npm.taobao.org/
# 新版
npm config set registry http://registry.npmmirror.com/
延伸阅读:
2.5 TNPM
目前在国内前端领域,tnpm 有两个含义:
对比说明:
cnpm
是淘宝开源的 npm 实现,支持官方 npm registry 的镜像同步,以及私有包能力。npmmirror
是社区基于 cnpm 部署的一个公益项目,为中国前端开发者提供镜像服务。tnpm
是阿里巴巴及蚂蚁集团的企业服务,同样基于 cnpm 之上做了企业级的能力定制。延伸阅读:
Grunt / Gulp 是一种工具,能够优化前端工作流程。比如自动刷新页面、combo、压缩css、js、编译less等等。
Browserify / Webpack 是一个预编译模块的方案,不管是 AMD / CMD / ES6 风格的模块化,最终都会被编译成浏览器认识的JS。
3.1 Grunt
2012年,Grunt 发布首版( npm 包首次发版)。
Grunt 是一个基于任务的 Javascript 工程命令行构建工具。
延伸阅读:
3.2 Gulp
2013年,Gulp 发布首版( npm 包首次发版)。
Gulp 是一个基于流的自动化构建工具。
延伸阅读:
3.3 Browserify
2011 年,Browserify 发布首版( npm 包首次发版)。
Browserify 可以让你使用类似于 node 的 require () 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。
延伸阅读:
3.4 Webpack
2012 年 Webpack 发布首版,并于 2014 年发布 1.0 正式版。
Webpack是一个用于现代 Javascript 应用程序的静态模块打包工具。
延伸阅读:
3.5 Vite
2020 年,Vite 发布首版,下一代前端开发与构建工具。
Vite 诞生背景:浏览器开始原生支持 ES 模块,且越来越多 Javascript 工具使用编译型语言编写。
Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
对比传统构建工具(本地dev模式):
Webpack
为例:分析依赖,从 entry 入口 => 编译打包,生成 bundle => 启动服务,加载资源 => 完成页面渲染。Vite
:启动服务 => 基于浏览器支持 ES 模块,动态导入模块源代码 => 浏览器加载资源 => 完成当前页面渲染。图示:
延伸阅读:
图片来源:atomic-web-design
基于前端模块化与工程化的日趋完善,更多前端开发者的目光转移至前端组件化的「捣腾」,于是越来越多的UI组件库从各大公司内部使用到对外开源。从历史的角度来看,模块化是组件化的前提,组件化是模块化的演进。
模块化(module)是代码层面上的改进,工程化(project)是项目层面上的优化,那么组件化(component)就是业务层面上的优化。
组件化:Web Components
也被叫做 Custom Elements
(自定义HTML元素),这个概念最初于2011年,由 Alex Russell 提出。
大厂目前对前端组件的理解是分层,针对不同的需求将组件分为不同的种类,这种分类是一种规范,而不是规定。基本上我们可以将组件分为四类:
延伸阅读:
推荐学习或使用:
Ant Design - The world's second most popular React UI framework
Element - The world's most popular Vue UI framework
iView / View Design 一套企业级 UI 组件库和前端解决方案
cube-ui Document
Vant 3 - Mobile UI Components built on Vue
Naive UI
Bootstrap中文网
2.1 实现一个UI组件库
todo...
前端科普系列-Node.js:换个角度看世界 - 知乎
深入浅出 npm & yarn & pnpm 包管理机制 - 知乎
Javascript 包管理器简史(npm/yarn/pnpm) - 知乎
前端科普系列-CommonJS:不是前端却革命了前端 - 知乎
五分钟带你回顾前端模块化发展史 - SegmentFault 思否
【深度全面】前端Javascript模块化规范进化论 - SegmentFault 思否
GitHub - doodlewind/jshistory-cn: 🇨🇳 《Javascript 二十年》中文版