在很久之前的前端开发被人们称为切图仔,还完全没有一种程序员工种叫前端工程师,在 2013 年之前我记得我当时打开一些网站,这些网站还是使用的 Adobe Flash Player 这种技术来实现网站动画效果,现在前端经过这么久的发展已经不像是以前了。在 6 年前如果你只会 JQuery + Bootstrap 框架就可以找到一个很好前端工作,但是今天的前端开发已经不是这样子了,需要掌握很多技术。我写前端也很早,在那个时候只需要会有的 CSS 加 HTML 乃至不需要 JS 这种也能做一个网站,例如 PHP 和 JSP 这种技术可以在 HTML 代码中写各自的逻辑代码控制网站显示部分。但今天前端工程师有很多技术栈的支持,例如 Node.js 运行时的支持,可以让前端工程师写服务端程序 (Server Side Render)技术,前端试图层开发也不是以往复制几行别人写好的 div
代码快和 class
样式名就可以实现一个页面了,今天前端试图层主流的是使用 React 运行时解析和渲染式框架和 Svelte 提前编译式框架。不管哪样框架开发,已经不是以往那种在电脑上安装个 Sublime 编辑器和 Chrome 就可以去开发前端项目的时代了,现在前端开发需要掌握很多 JS 工具软件来完成,这篇文章将完整的介绍现在前端必须掌握的工具链软件,例如 Node.js 、NPM 、MVN 、Babel 、Webpack 、JSDoc 这些工具使用教程。
Node.js 运行时
目前最为流行的 JS 运行环境是 Node.js 运行时,当然还有其他的不同类型的 JS 运行时,但是目前工业界使用的最多还是 Node.js 运行时。运行时有两种含义,第一种是能让传统的 JS 代码脱离浏览器端到服务器端运行,这里的服务器端更多的是只的是个人电脑上,使得 JavaScript 语言与操作系统互动比如:读写文件 、新建子进程 、网络操作等,其次提供很多方便 JS 开发的工具。
npm 包管理
这里首先要介绍的是 npm
(Node Package Manager) 软件包管理工具,使用它可以构建应用程序的代码、库、模块等和管理项目第三方软件包的功能,可以快速安装 、卸载 、共享发布你自己的包的功能。解决的问题就针对于传统前端开发使用的 zip 包或者 cdn 来管理 js 文件问题。如果电脑上安装了 Node.js 运行时 npm 也会一同安装在你对电脑系统之上;下面是一些最为常用的 npm 命令:
命令 | 描述 |
---|---|
npm init | 初始化一个项目 |
npm init --yes | 直接初始化一个项目 |
npm install package_name | 安装第三方包 |
npm install package-name --save-dev | 安装第三方包到开发环境 |
npm i -D nodemon | 安装第三方包到开发环境(简写形式) |
npm i [email protected] | 安装指定版本的第三方包 |
npm remove jquery | 移除指定的第三方包 |
npm uninstall xxxx | 等效于卸载某个包 |
npm i -g xxx | 全局安装某个软件包 |
npm ls | 查看当前项目安装的软件包 |
npm ls -g | 查看全局安装的软件包 |
npm init
命令之后,npm 会自动初始化这个项目的目录,会在目录中自动创建一个 package.json
的文件,接下来篇幅会着重围绕着这个文件做介绍,如下图:
至于 package.json
文件是以 json 的方式进行格式化的,主要的字段保存着本仓库的开发者元数据信息,package.json
包含着开发作者姓名、邮件、主页、开放源代码许可证字段,部分字段看名字也知道是什么意思。
~/.npmrc
文件中,设置操作命令:
npm set init-author-name 'Leon Ding'
npm set init-author-email '[email protected]'
npm set init-author-url 'https://ibyte.me'
npm set init-license 'MIT'
scripts
、 dependencies
、devDependencies
字段进行介绍:
"scripts": {
"start": "node index.js",
"test": "mocha tests",
"build": "webpack",
"lint": "eslint src"
},
scripts
的字段包含着上面键值对信息,左边双引号包含的是命令行别名参数信息,而右边则是对应的真实命令参数,可以使用通过别名运行对应的命令。例如上面的 "start": "node index.js"
可以通过简短的 npm run start
的命令快速启动 node.js 运行时来执行 index.js
程序入口文件,其他 scripts
以此类推进行。
另外一个 package.json 中的 dependencies
字段结构内容会保护当前项目中安装的第三方包依赖信息,当项目被打成 zip 分享出去时,其他人只需要在自己电脑上执行 npm install
命令自动安装,这样就达到项目源代码和项目依赖信息保持一致的目的,默认情况下 npm 命令会主动去 npmjs.com 下载对应的软件包,这些包会被存储在项目的根目录中的 node_modules
中,例如下面内容:
{
"name": "my-nodejs-app",
"version": "1.0.0",
"description": "A simple Node.js application",
"dependencies": {
"express": "^4.17.1",
}
}
express
就为通过 npm 安装的第三方软件包,express 可以用于构建 Web 服务器和 Web 应用程序,后面双引号 ^4.17.1
代表着允许安装续"4.17.1" 以后的更高向后兼容的新版本例如 "4.18.0"、"4.19.0" 等版本的 Express 但不包括 "5.0.0" 及其以上的版本,dependencies
表示的是运行本项目所需要的依赖,如果项目的 node_modules
中没有包含对于的依赖软件包源代码,就会导致不能正常运行项目,常用的命令如下:
# 本地安装一个软件包
npm install <package-name>
# 简写成为 i
npm i <package-name>@<version>
# 也可以从远程 git 仓库安装
npm install git://github.com/<name>/<package-name>.git#<version>
# 全局安装
sudo npm install -g <package-name>
# 强制安装某个库
npm insatll -force <package-name>
# 更新软件包
npm update <package-name>
# 卸载软件包
npm uninsatll <package-name>
devDependencies
字段,该字段保存着开发环境所需要依赖的信息,该字段软件包依赖只能在开发阶段所使用的,例如 js 的 eslint 工具,就为开发环境所使用的,当执行这条命令:
# 安装 eslint 到项目开发依赖
npm i eslint --save-dev
# 等价于 -D 表示开发环境依赖
npm i -D eslint
node_modules
目录中,另外 ESLint 本就为一个可执行命令,它还会在生成一个node_modules/.bin/eslint
可执行脚本,该 .bin
从名称就可以看出对于到可执行文件存放的目录。
在 .bin
不仅可以存放普通的 js 文件,还可以存放 Linux 的下的 bash 文件,这些文件可以在 package.json
中的 scripts
字段属性所引用,例如下面有一个文件较 build.sh
文件内容如下:
#!/bin/bash
echo "execute linux bash script."
给文件添加上可执行权限,就可以在 scripts
进行起别命进行使用,例如的使用如下:
"build-js": "bin/build.sh"
至此就可以使用 npm run build-js
运行引用的 bash 脚本文件中的内容。
nvm 版本管理
对于一个长期从事使用 JS 作为主力开发语言的工程师,如何选择对应版本的的 Node.js 运行时?在不同项目使用着不同运行时,开发环境和线上环境版本如何选择?更为关键的是一个团队人员的运行时怎么统一?不同的版本之间可能存在一些兼容性的问题,如何快速切换 Node.js 版本?这里要介绍的是一款名为 NVM (Node Version Manager) 的工具,使用它可以快速在电脑上安装不同版本的 Node.js 运行时,它的项目地址:
https://github.com/nvm-sh/nvm
使用 nvm
命令来管理依赖版本,要比直接通过官网的方式安装的 Node.js 要方便很多,nvm 可以列出目前 Node.js 官方已经发布共用户选择的版本信息,常用的命令也就那么几个:
命令 | 描述 |
---|---|
nvm use stable | 安装当前可用的稳定版本 |
nvm ls-remote | 列出远程版本信息 |
nvm install x.x.x | 安装一个特定版本(TLS) |
nvm alias default x.x.x | 设置默认版本 |
nvm use default | 使用默认版本 |
nvm use node | 快速切换到当前可靠的版本 |
nvm use x.x.x | 切换到某个特定的版本 |
nvm ls
命令可以快速查看到当前系统 nvm 的软件包管理状态信息,如下如:
有 nvm 来帮助管理基础的 Node.js 环境,使得在开发一些对于运行时环境有要求的项目来说更为方便的切换 Node.js 版本,而不需要手动去设置环境变量和开发环境。
Babel 编译器
写过静态编译型语言的工程师应该知道 Compiler
的作用,可以将我们编写的源代码中人类可读翻译成为机器电脑可读的机器码,在 JS 中也一样有编译器的存在,什么?这里可能有人会问了 JS 不是解释的动态语言吗?怎么还需要进行编译呢?这里要归咎于 JS 发展历史原因,JS 的发展也伴随着它语言规范的发展 ECMAScript 标准,而 JS 除了 Node.js 这样的运行时之外,它的宿主环境为各个浏览器里面的执行引擎。我们工程师使用 JS 编写的程序最后大部分都是运行在浏览器中,导致一个问题用户电脑上安装的浏览器都是不同的版本,而我们编写的源代码采用 ES 版本可能在运行它的浏览器中新的特性语法得不到支持,浏览器 ES 版本可能只支持 ES5 ,而编写程序则使用的 ES6 版本,那么浏览器就不认识高版本 ES6 所编写的代码?怎么解决这个问题?这里就可以使用 Babel 来解决这个问题,按它官网上介绍 Babel 是一个 JavaScript 编译器 Compiler ?对没错就是 Compiler ,和 TypeScript 的 TSC 一样,可以将高版本的 ES6 语法编写的代码通过 Babel 编译器,转换为 ES5 版本的代码,从而使得低版本的浏览器也能运行 ES6 所编写的程序代码。
解析 (parse)-> 转换 (transform)-> 生成 (generate)
,最后得到 AST 为旧版本的 ES 语法树。在 Babel 的官方网站上就给出实际用例,这是一个将 ES6 多 map 函数 arrow function 通过 Babel 转换为 ES5 的语法例子:
// Babel 输入:ES2015 箭头函数
[1, 2, 3].map(n => n + 1);
// Babel 输出:ES5 等价语法
[1, 2, 3].map(function(n) {
return n + 1;
});
要在前端工程中使用 Babel 必须要在项目的目录中创建对于的规则配置文件,它的配置文件分为很多种,这里博文我只会介绍关于 babel.config.json
的方式配置,更多配置可以去查看它们官方文档,官方文档阅读起来有点复杂。推荐从我这篇博文入手,在项目根目录创建一个名为 babel.config.json
的文件,其中包含以下内容:
{
"presets": [...],
"plugins": [...]
}
Presets
和 Plugins
,它们的值是数组类型,可以是多个值组成的。Babel 提供了一些常用的预设,如: @babel/preset-env(用于根据目标环境自动确定需要的转译插件)、@babel/preset-react(用于处理 React JSX 语法)、@babel/preset-typescript(用于处理 TypeScript 语法)等,而 Plugins 是插件单元,用于处理特定的语法或功能,这里日常使用都是有别人已经提供好的插件,也可以自定义插件来使用。最后可以将多个预设和插件组合起来形成一个配置集,以满足特定的转译需求,例如下面例子:
# 安装了 Babel 插件和预设
sudo npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-arrow-functions --save-dev
上面这行命令会安装用于处理 ES6 版本的 babel/preset-env 用于根据目标环境自动确定需要的转译插件,和处理 arrow fucntions 新特性的插件,配置文件则编写:
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
最后为了测试 Babel 的功能,使用 ES6 版本的语法编写一段代码,使其通过 Babel 进行编译为低版本的 ES 语法的代码,代码内容如下:
const numbers = [1, 2, 3, 4, 5];
// 测试 arrow function
const doubled = numbers.map((number) => number * 2);
console.log('Doubled numbers:', doubled);
const sum = numbers.reduce((total, number) => total + number, 0);
console.log('Sum of numbers:', sum);
const person = {
firstName: 'John',
lastName: 'Doe',
};
// 测试解构
const { firstName, lastName } = person;
console.log('First Name:', firstName);
console.log('Last Name:', lastName);
# 执行翻译将结果输出到 dist 目录中
npx babel src --out-dir dist
dist
命令中,而原来采用的 ES6 特性编写的代码存储在 src
目录中,这样就使得开发过程中采用新的 ES6 新特性编写代码,而运行的代码是 ES5 版本,Babel 编译的结果文件会兼容更多浏览器版本,输出结果如下:
"use strict";
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function (number) {
return number * 2;
});
console.log('Doubled numbers:', doubled);
var sum = numbers.reduce(function (total, number) {
return total + number;
}, 0);
console.log('Sum of numbers:', sum);
var person = {
firstName: 'John',
lastName: 'Doe'
};
var firstName = person.firstName,
lastName = person.lastName;
console.log('First Name:', firstName);
console.log('Last Name:', lastName);
babel.config.json
配置文件中使用两个字段 presets 和 plugins ,但是 persets
对象还支持一个 targets
属性选项可以指定被编译器后的代码宿主环境浏览器版本环境要求信息,此属性转译输出针对特定的浏览器版本或环境进行优化的代码,如下配置:
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}]
],
"plugins": [
"@babel/plugin-transform-arrow-functions",
]
}
browsers
属性的值是一个数组,可以是多个条件表达式,下面为具体支持表达式列表。
表达式 | 描述 |
---|---|
last n versions | 最近的 n 个版本 |
x% | 占有率超过 x% 的浏览器 |
not dead | 未停止维护的浏览器 |
IE x | 指定 IE 版本 |
last n major versions | 最近的 n 个主要版本 |
也可以精确指定浏览器的发行版本,要求被编译输出的代码必须能够兼容浏览器的版本,如下配置:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"chrome": "58",
"firefox": "52",
"ie": "11"
}
}
]
]
}
node_modules/.bin
目录中,采用 npx babel 命令相当于直接运行了 node_modules/.bin/babel
程序。另外一种是 babel 提供的 cli 工具 babel-cli 可以方便在系统全局命令行环境中执行 babel 程序,babel-cli 是 Babel 的命令行工具,可以通过全局安装 npm install -g babel-cli
,常用的命令如下:
# 将 src 下的源代码输出到 lib 目录中,也可以是 -d 参数
babel src -out-dir lib
# 转码结果输出到标准输出
babel example.js
# --out-file 或 -o 参数指定输出文件
babel example.js -out-file compiled.js
# 直接运行 ES6+ 代码,无需预先将代码转译为 ES5
babel-node
除此之外,Babel 还提供了基于元编程的方式进行使用,在 @babel/core
包提供了很多的 API 来协助开发人员来进行 AST 解析,使用这些 API 可以达到上面的命令行相同的功能,其实命令行的功能也是基于 @babel/core 包的 API 实现的。