前端简史之纵横:Node东出
Posted 黑子Kuroko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端简史之纵横:Node东出相关的知识,希望对你有一定的参考价值。
引
💡 Ajax 的出现,带来了 jQuery 时代,而 jQuery 时代也伴随着 Node 风暴淡淡退出了历史舞台。如果说 Ajax给前端带来了从网页静态化到动态化的转变;那么 Node 的出场给前端带来了大步迈向模块化、工程化的转变,同时也给前端开发工程师带来了市场价值的逆袭。
从上一节《前端简史之裂变:Ajax变法》课程中,我们得知在 jQuery 时代前端工程师通常编写一个页面会引入无数个 jQuery 插件,最终导致页面白屏问题严重。于是一些优秀的前端工程师们向后端取经,引入模块机制。直到 Node 之光普及前端领域,再到 Webpack 此类预编译打包工具的广泛应用,前端借着 Node 东出服务端之势,摆脱了对后端服务的强依赖,在IT界占有了一席之地。
课程简介
《前端简史》系列技术分享课程通过讲述前端演变的发展历史,介绍前端重要技术的实现原理与使用方法,带你走进前端,感受前端的“前世今生”,结交前端历史上举足轻重的几位“大佬”,例如:Ajax、Node、Webpack、SPA、PWA、RN、Flutter等等。
该系列课程目前一共分为三节:《前端简史之裂变:Ajax变法》、《 前端简史之纵横:Node东出》、《前端简史之崛起:Router迁鼎》。以及不一定会有的《前端简史之天下:跨端跨平台》。
通过这门课程你将获得什么?
- 宏观上,你将了解前端发展演变的过程,清楚前端岗位的定义与定位,有助于自己做好在前端领域上的职业规划。
- 微观上,你将学到Ajax、Node、前端路由等一系列前端重要技术背后的原理与使用方法,有助于自己在实际项目开发过程中应用更加得心应手。
课程目录
一、Node简介
本文 Node
皆指 Node.js
1、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 逐渐流行于世。
延伸阅读:
2、Node初体验
用 Node 实现一个简单的本地服务
- 首先,创建
http.js
文件:
/**
* http.js
* 使用 http 模块启动本地服务
*/
var http = require('http');
http.createServer(function (request, response)
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.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
3、NVM
NVM:Node Version Manager,NodeJS 版本管理器
NVM 让我们能方便的对 NodeJS 的版 本进行切换。 NVM 的官方版本只支持 Linux 和 Mac。 Windows 用户,可以用 nvm-windows。
延伸阅读:
GitHub - coreybutler/nvm-windows
二、前端模块化
前端模块化的发展先后经历了IIFE
=> AMD
=> CMD
=> UMD
=> ESM
的不断优化与完善,最终在语言层面上实现了模块化编程,未来迎接的是各大平台对于 ESM 的支持。
1、什么是模块化
模块化(modular)编程,是强调将计算机程序的功能分离成独立的、可相互改变的“ 模块”(module)的软件设计技术,它使得每个模块都包含着执行预期功能的一个唯一方面所必需的所有东西。
要做到真正意义上的模块化,我们需要注意以下几点:
- 功能目的明确;
- 避免全局污染;
- 减少命名冲突;
- 向上可复用;
- 向下易扩展。
2、IIFE
IIFE:immediately-invoked function expression,立即调用函数表达式
IIFE
的出现是为了弥补 js 在 scope 方面的缺陷:js 只有全局作用域(global scope)、函数作用域(function scope),从 ES6 开始才有块级作用域(block scope)。早期的模块化都是一堆基于 IIFE 实现的 js 代码库。
举个🌰:
(function($)
// jQuery Code
)(jQuery);
3、CommonJS 与 NodeJS
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
来更好地实现浏览器模块化方案。
延伸阅读:
4、AMD 与 RequireJS
AMD:Asynchronous Module Definition,异步模块规范。
AMD
是 RequireJS
在的推广和普及过程中被创造出来。
AMD 主要是为了解决 CommonJS 规范在浏览器端的不足:
- 缺少模块封装的能力(就是不好用);
- 使用同步的方式加载依赖(浏览器吃不消);
export
只能导出变量,导出函数需要用module.export
(开发体验很不爽)。
延伸阅读:
5、CMD 与 SeaJS
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
);
延伸阅读:
6、UMD
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')
// CommonJS
factory(exports, require('b'));
else
// Browser globals
factory((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)
//AMD
define(['jquery'], factory);
else if (typeof exports === 'object')
//CommonJS
var $ = requie('jquery');
module.exports = factory($);
else
//都不是,浏览器全局定义
root.testModule = factory(root.jQuery);
)(this, ($) =>
//do something... 这里是真正的函数体
);
UMD 成功解决了模块化在服务端与浏览器端的兼容,然而它的存在也只是前端模块化发展历史上的一个过客,没多久 ES6 模块化直接实现了多端的统一。
延伸阅读:
7、ESM
ESM:ES6 Module, ECMAScript 6.0 模块化
2015 年 6 月正式发布,ES6 模块化是欧洲计算机制造联合会 ECMA 提出的 JavaScript 模块化规范,它在语言的层面上实现了模块化。浏览器厂商和 Node.js 都宣布要原生支持该规范。它将逐渐取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
- 2013年5月,Node.js 的包管理器 NPM 的作者 Isaac Z. Schlueter 说过 CommonJS 已经过时,Node.js 的内核开发者已经决定废弃该规范 。原因主要有两个,一个是因为 Node.js 本身也不是完全采用 CommonJS 的规范,譬如在 CommonJS 之 exports 中的提到 exports 属性就是 Node.js 自己加的,Node.js 当时是决定不再跟随 CommonJS 的发展而发展了。二来就是 Node.js 也在逐步用 ES6 Module 替代 CommonJS。
- 2017.9.12 Node.js 发布的 8.5.0 版本开始支持 ES6 Module。只不过是处于实验阶段。需要添加
—experimental-modules
参数。 - 2019.11.21 Node.js 发布的 13.2.0 版本中取消了
—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
三、前端工程化
前端模块化的不断发展与进步,随之而来的就是前端工程量复杂度的递增。如何更好地导入模块、更快地编译模块、更优地压缩模块等一系列需求或问题带来了前端工程化的演变。
1、什么是工程化
模块化是代码层面上的改进,而工程化是项目层面上的优化。
如果要用一句话来概括,在我的理解中前端工程化是把前端开发工作带入到更加系统和规范体系的一系列过程。这个过程会包括源代码的预编译、模块处理、代码压缩等构建方面的工作。工程化会尽可能保证开发者的开发体验更加友好,保证源代码的质量以及依赖的完整性。工程化也会尽可能高效地将构建完成后的代码送达给客户端,来追求更加良好的用户体验。所有这些都属于工程化。
在 Web 技术刚开始的时候,还没有前端工程化这样一个东西。人们只是简单地把 html、CSS 和 JavaScript 直接混在一起丢到用户。而就如人类对于食物的追求在不断进步一样,虽然在最初级的阶段需求只是能填饱肚子,但慢慢地人们开始追求食物的质量。对于前端来说也是一样,用户的需求从最开始简单的页面在向复杂的应用发展。前端需要做的事情更多,同时也要追求更友好的用户体验。
另外,工程化也是为开发者服务的。通过预编译语言、模块热加载等技术可以提升开发效率,而利用自动化测试、lint 工具等可以保证代码的功能和质量。工程化可以有效降低开发成本,谁不想省下埋头 debug 的时间去 摸鱼 做更有意义的事呢。
2、包管理工具
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 的输出信息比较冗长;
- 更好的语义化,
npm run dev
=>yarn dev
延伸阅读:
2.3 PNPM
pnpm:performant npm,在 2017 年正式发布,定义为快速的,节省磁盘空间的包管理工具,开创了一套新的依赖管理机制,成为了包管理的后起之秀。
pnpm 拥有 yarn 超过 npm 的所有附加功能:
- 安全:与 yarn 一样,pnpm 有一个包含所有已安装包校验和的特殊文件,用于在执行代码之前验证每个已安装包的完整性。
- 离线模式:pnpm 将所有下载的包保存在本地注册表镜像中。当包在本地可用时,它从不发出请求。使用
offline
参数可以完全禁止 HTTP 请求。 - 速度快:pnpm 不仅比 npm 快,而且比 yarn 快。无论是冷缓存还是热缓存,它都比 yarn 快。yarn 从缓存中复制文件,而 pnpm 只是从全局存储中链接它们。
延伸阅读:
- Why should we use pnpm? by @ZoltanKochan
- pnpm - Fast, disk space efficient package manager
- GitHub - pnpm/pnpm
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 有两个含义:
- 阿里巴巴及蚂蚁集团 企业私有 NPM 服务。(起源为 taobao npm)
- 腾讯 企业私有 NPM 服务。(tencent npm)
但它们均为企业内部服务,只能通过各自内网进行安装。
对比说明:
cnpm
是淘宝开源的 npm 实现,支持官方 npm registry 的镜像同步,以及私有包能力。npmmirror
是社区基于 cnpm 部署的一个公益项目,为中国前端开发者提供镜像服务。tnpm
是阿里巴巴及蚂蚁集团的企业服务,同样基于 cnpm 之上做了企业级的能力定制。
延伸阅读:
3、打包工具
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 模块,动态导入模块源代码 => 浏览器加载资源 => 完成当前页面渲染。
注:Vite 生成环境仍需打包,流程与本地服务不同。原因:考虑到当前很多平台并未完全支持 ES 模块,以及线上环境网络往返请求效率不如本地服务。所以为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
图示:
延伸阅读:
四、前端组件化
图片来源:atomic-web-design
基于前端模块化与工程化的日趋完善,更多前端开发者的目光转移至前端组件化的「捣腾」,于是越来越多的UI组件库从各大公司内部使用到对外开源。从历史的角度来看,模块化是组件化的前提,组件化是模块化的演进。
1、什么是组件化
模块化(module)是代码层面上的改进,工程化(project)是项目层面上的优化,那么组件化(component)就是业务层面上的优化。
组件化:Web Components
也被叫做 Custom Elements
(自定义HTML元素),这个概念最初于2011年,由 Alex Russell 提出。
大厂目前对前端组件的理解是分层,针对不同的需求将组件分为不同的种类,这种分类是一种规范,而不是规定。基本上我们可以将组件分为四类:
- 基础组件:用于构建页面的原子组件,如 Button、Input、Select、Form 等等,这类组件通常用于取代 HTML 的原生组件,不仅可以实现基本功能,更重要的是可以支持各种样式定制、事件绑定和换肤等等,基础组件库里比较著名的是 AntDesign 和 ElementUI,可以毫不夸张的说现在的前端项目没有不使用基础组件库的。
- 业务组件:基于基础组件构建出的一些更复杂的、可重用的组件,比如二维码组件、搜索框组件等等,这些组件都有很强的业务特征,并且需要通过基础组件进行组装,但是项目中又会频繁出现,对于这类组件,通常可以单独发布一个 npm 包,方便不同项目间进行重复使用。
- 区块:由业务组件构成的更复杂的组件,用于构建页面的某个功能,比如表格区块,会包含:查询条件、表格和翻页器。
- 模块:由区块构成的整个页面,用于快速构建整个页面。
延伸阅读:
2、UI组件库
推荐学习或使用:
Ant Design - The world's second most popular React UI framework
Element - The world's most popular Vue UI framework
iView / View Design 一套企业级 UI 组件库和前端解决方案
Vant 3 - Mobile UI Components built on Vue
2.1 实现一个UI组件库
todo...
五、参考资料
深入浅出 npm & yarn & pnpm 包管理机制 - 知乎
JavaScript 包管理器简史(npm/yarn/pnpm) - 知乎
前端科普系列-CommonJS:不是前端却革命了前端 - 知乎
五分钟带你回顾前端模块化发展史 - SegmentFault 思否
【深度全面】前端JavaScript模块化规范进化论 - SegmentFault 思否
GitHub - doodlewind/jshistory-cn: 🇨🇳 《JavaScript 二十年》中文版
- 完 -
以上是关于前端简史之纵横:Node东出的主要内容,如果未能解决你的问题,请参考以下文章