赞
踩
前端工程化是指遵循一定的标准和规范,通过工具去提高开发效率,降低成本的一种手段。
开发过程中的很多脏活累活,都应该交给自动化工具来完成
*工程*:可以简单理解为一个项目(例如一个网站,一个APP等)
*工程化*:实现一个工程的具体流程、技术、工具、规范等。涉及到从工程立项开发到上线运行的整个过程。
前端工程化就是通过各种工具和技术,提升前端开发效率的过程
目标: 远离刀耕火种的原始时代,全面提升战斗力
结合脚手架、自动化、模块化、规范化搭现代化前端工程
前端工程化包含各种工具和技术,这些工具和技术出现在整个工程生命周期的各个环节。主要可以分为以下几类:
脚手架工具
自动化构建
模块化打包
规范化标准
自动化测试
自动化部署
主要解决的问题
重复的机械式工作(部署上线前,需要手动压缩代码和资源文件)
传统语言或语法的弊端(要是用 ES6+ 和 CSS3 的新特性,兼容性有问题;使用 Less / Sass / PostCSS 增强 CSS 的编程性,但运行环境不支持)
代码风格统一,质量保证(多人协同开发,无法硬性统一大家的代码风格)
依赖后端服务接口支持(部分功能开发时,需要等待后端服务接口提前完成)
JavaScript 可以在浏览器端运行。所以,浏览器是 JavaScript 的一个运行环境。而 Node.js 是除了浏览器之外,另一个可以运行 JavaScript 的环境。
区别在于,Node.js 这个运行环境,是根植于操作系统之上的。提供了一些与操作系统交互的 APIs,例如:文件操作,web 服务发布等。所以,只是 JavaScript 换了一个地方运行而已, Node.js 的语法还是原来 JavaScript 的语法。
浏览器端的 JS 负责与浏览器端的功能交互。Node.js 负责服务器端的功能交互。
具体来说:
运行方式
脚本模式
# 声明 app.js
console.log("Hello Node.js")
# 运行:node 代码路径/文件名.js
node app.js # 返回 Hello Node.js
交互模式
# 在命令行中,进入交互模式
node # 回车,然后进入交互模式
# 运行代码
> console.log('Hello Node'); # 回车
Hello Node
# 退出交互模式
两次 ctrl+c
# 或
输入 .exit
交互模式适合调试代码,相当于浏览器上的控制台。
全局对象
浏览器端 JS 的全局对象是 window,Node.js 端的全局对象是 global。
Node.js 的全局对象是 global
在交互模式下,声明的变量和创建的函数都属于是global下的 var a=1; global.a
在脚本模式下,声明的变量和创建的函数都不属于 global 下的
// 脚本模式下,声明的变量不属于全局对象 global
var a = 'aaa';
console.log(a) // 返回 aaa
console.log(global.a) // 返回 undefined
浏览器端声明的 JS 变量和创建的函数都属于全局 window 下的,var a=1; window.a
但是 window 对象,在 Node.js 下不可用。
注意:DOM 和 BOM 中的对象,在 Node.js 环境下都不能使用
例如:console.log(location);
会报错:location is not defined
全局函数
全局函数在 Node.js 环境下都可用。
JavaScript 语言提供的 APIs
parseInt/parseFloat/isNaN/isFinite/eval…
一次性定时器(当定时时间到了之后,才会执行一次回调函数,单位是毫秒)
开启:var timer = setTimeout(回调函数, 定时时间)
清除:clearTimeout(timer)
周期性定时器(每隔一段时间,执行一次回调函数)
开启: var timer = setInterval(回调函数, 间隔时间)
清除: clearInterval(timer)
Node.js 环境中提供的 APIs
立即执行定时器(在事件队列的开头执行)
开启:var timer = setImmediate( 回调函数 )
清除:clearImmediate(timer)
进程立即执行定时器(在主程序的后边执行)
process.nextTick( 回调函数 )
同步与异步
JS 是单线程模型,代码运行时,先执行主程序中的任务,主程序执行结束后,再执行事件队列。
而 proccess.nextTick 是在主程序结束之后执行,setImmediate 则在事件队列的头部执行
process.nextTick 和 setImmediate 的执行顺序是面试常考的一个知识点。
模块
Node.js 中的模块是具有特定功能的对象。按照模块的作者进行划分,可以分成三类:
内置模块
内置模块(也叫核心模块)是官方提供的,无需下载,可以直接使用的模块。
官网:http://nodejs.cn/api/
console
console
模块提供了一个简单的调试控制台,类似于 Web 浏览器提供的 JavaScript 控制台。
// 不同类型的数据,输出后颜色不同 console.log('1'); console.log(1); var obj = { name: 'Tom', age: 19 } console.log(obj) console.table(obj) // 以表格方式展示数据 console.time('for');//开始计时 for (var i = 1; i <= 100000; i++) { } console.timeEnd('for');//结束计时 console.time('while'); var i = 1; while (i <= 100000) { i++; } console.timeEnd('while');
process
process
对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制。 作为全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()
。 它也可以使用 require()
显式地访问:
// process 是全局变量,可以不写 require 引入 const process = require('process'); // 获取操作系统架构 x64 console.log(process.arch) // 当前系统平台信息 win32 console.log(process.platform) // 获取当前文件所在的目录 D:\cliu\Desktop\node console.log(process.cwd()) // 环境变量 console.log(process.env) // 自定义环境变量 process.env.NODE_ENV = 'develop' console.log(process.env) // 获取进程的编号 console.log(process.pid) // 杀死进程 process.kill(进程编号)
path
path 模块负责文件路径的
const path = require('path') // __dirname 获取当前文件所在的目录 path.join(__dirname, 'dist') console.log('join用于拼接多个路径部分,并转化为正常格式'); const temp = path.join(__dirname, '..', 'lyrics', './友谊之光.lrc'); console.log(temp); console.log('获取路径中的文件名'); console.log(path.basename(temp)); console.log('获取一个路径中的目录部分'); console.log(path.dirname(temp)); console.log('获取一个路径中最后的扩展名'); console.log(path.extname(temp));
fs
fs (file system)模块主要负责文件基本操作
文件操作
// 使用 fs 之前先引入 const fs = require('fs') // 写文件 (清空写入:写入之前会先将文件清空) # fs.writeFile('文件路径','写入内容',回调函数) fs.writeFile('./1.txt', '曾经有一首歌,她感动了我', (err) => { if (err) throw err console.log('写入成功') }) // 读文件 # fs.readFile('文件路径', 回调函数) fs.readFile('./1.txt', (err, data) => { if (err) throw err // data 是二进制数据,默认输出时,以十六进制的方式展示 // 想要看到正常的效果,需要通过 toString() 转换 console.log(data.toString()) }) // 删除文件 # fs.unlink('文件路径', 回调函数) fs.unlink(__dirname+'/1.txt', (err) => { if (err) throw err console.log('删除成功') }) // 追加写入(多次执行,文件中会有多条数据) # fs.appendFile('文件路径','写入内容',回调函数) fs.appendFile(__dirname+'/2.txt', '曾经有一首歌,她是这样唱的\n', (err) => { if (err) throw err console.log('追加写入成功') })
目录操作
// 创建目录 fs.mkdir('./d1', (err) => { if (err) throw err console.log('创建成功') }) // 删除目录 fs.rmdir('./d1', (err) => { if (err) throw err console.log('删除成功') }) // 重命名目录 fs.rename(__dirname+'/d1', __dirname+'/d2', (err) => { if (err) throw err console.log('重命名成功') }) // 读取目录 fs.readdir(__dirname, (err, data) => { if (err) throw err // console.log(data) // data 是数组 data.map((d) => { console.log(d) }) }) // 判断文件是否存在 if (!fs.existsSync('./d2')) { fs.mkdirSync('./d2') }
fs.stat 查看状态
// 查看文件信息
fs.stat(__dirname+"/a.txt", (err, stat) => {
if (err) throw err
if (stat.isDirectory()) {
// 判断当前文件是否是目录
console.log('目录:', d)
} else if (stat.isFile()) {
// 判断当前文件是否是普通文件
console.log('文件:', d)
}
})
http
以前,我们使用 Apache 或 Nginx 来搭建服务器。Node.js 中,也有搭建服务器的模块。就是 http 模块。
const http = require('http') // 1. 创建服务器 /** * req = request 请求 * res = response 响应 */ const server = http.createServer((req, res) => { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain; charset=utf-8') res.end('你好:Node.js') }) // 2. 发布 web 服务 const port = 3000 const host = 'localhost' // 在浏览器中访问 http://localhost:3000 然后能看到效果 server.listen(port, host, () => { console.log(`服务器运行在 http://${host}:${port}`) })
自定义模块
自定义模块就是工程师自己写的一段代码。可以是一个单独的 JS 文件,也可以是一个目录。
在模块中,只有导出(exports)的属性或方法才能被外部使用,没有导出的属性或方法是模块的私有方法,只能在模块内部使用。
模块的声明:
// circle.js 我们声明一个模块 circle,模块中有两个方法,分别求圆的面积和周长。 const PI = 3.14 // 圆的周长 const perimeter = (r) => { return 2 * PI * r } // 圆的面积 function area (r) { return PI * Math.pow(r, 2) } // 模块内容导出后,才能被外部调用 module.exports = { perimeter, area }
module 变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。
模块的使用:
// 在 app.js 中引入模块
// 引入模块时,需要写引入路径,否则 require('circle') 会报错
const circle = require('./circle')
// 调用模块中的属性或方法
const r = 10
const p = circle.perimeter(r)
const a = circle.area(r)
console.log(`直径为 ${r} 的圆的周长是:` + p)
console.log(`直径为 ${r} 的圆的面积是:` + a)
注意:引入自定义模块时,需要带有引入路径,否则,会报错
这里我们可以根据文件的组织方式,将模块分成文件模块和目录模块。
不同类型的模块,引入方式不同,其加载逻辑也不相同,一共有四种情况:
package.json
package.json 是目录模块的描述文件。
目录中可能有多个 js 文件,引入模块时,到底引入哪一个文件呢?
默认是 index.js。如果你希望默认引入的不是 index.js,则可以通过 package.json 中的 main 字段指定
# package.json
{
"main": "app.js"
}
node_modules
如果引入目录模块时,没有指明引入路径。则默认加载当前目录下 node_modules 下的目录模块。
如果当前目录下没有 node_modules, 会到上一级目录(…/)继续寻找,直到顶层目录。
第三方模块
Node.js 中的第三方模块是由社区维护的,有一个公共的平台 http://npmjs.com/。在这个平台上有超过 100 万个第三方模块。这里的第三方模块也叫包。前端工程化中的大部分工具,都以包的形式,存在与 npmjs.com 上
第三方模块使用前,需要单独安装,安装需要借助 npm 命令。
简介
npm(Node Package Manager)是包管理工具。npm 会跟随 Node.js 一起安装。
# 验证 npm 是否已经安装
npm --version
或
npm -v
npm 可以帮我们下载(安装)包和包的依赖
npm官网:https://www.npmjs.com/
在 npm 上,有超过 1000000 (一百万)个软件包。
修改 npm 镜像源
npm 命令下载包的资源地址,成为 npm 的镜像源。
默认 npm 的镜像源是国外的(npmjs.com),下载速度慢,为了提高下载速度,可以将 npm 的镜像源设置为国内的地址(例如:淘宝镜像源)
设置命令为:
修改npm的镜像源
npm config set registry https://registry.npm.taobao.org
# 验证是否更改成功(查看镜像源):
npm config get registry
全局安装(安装全局工具)
通过 npm 安装包时,考虑两种情况:
配置项 | 描述 |
---|---|
全局安装 | 包在多个项目中都能用到;此时将包当作全局工具使用 举例:公交车 |
局部安装 | 包只在当前项目中使用,其他项目不用,我们可以只在当前项目中安装包 举例:私家车 |
如果需要将 npm 包当作工具使用,就需要在命令行中全局安装。通过 --global 参数来指定。
安装命令是:npm install --global
全局安装后的包放在那里了?
Windows下,全局安装的包默认存在 C:\Users\当前用户名\AppData\Roaming\npm\node_modules 下
Mac 下,全局安装的包默认存在 /usr/local/bin/lib/node_modules 下
例如:安装 serve 包,作为web服务器。
# npm install <package-name> --global // 全局安装模块
# 以serve为例,将 https://www.npmjs.com/package/serve 模块全局安装到本地
npm install --global serve
# 或 简写为
npm i -g serve
# 查看是否安装成功
serve -v # 查看serve版本
serve -h # 查看帮助信息
# 启动 web 服务
serve folder_name # 启动指定目录下的项目
# 或
serve . # 启动当前目录
局部安装(安装项目包)
如果你需要的第三方包,只是在某个项目中使用(而不是作为全局工具来用),你可以在项目中进行局部安装。
创建项目目录
mkdir project-name
进入项目目录
cd project-name
初始化项目
init 命令会帮我们创建 package.json 文件
# 初始化项目
npm init # 回车后会进入交互式问答窗口,根据问答结果,帮我们生成 package.json
# --yes 可以略过问答窗口(所有问题的答案都时默认值)
npm init --yes
# 或
npm init -y
项目初始化结束后,我们得到 package.json 文件
{
"name": "01_start", # 项目名称
"version": "1.0.0", # 项目版本
"description": "", # 项目描述
"main": "index.js", # 项目入口
"scripts": { # 脚本命令
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], # 关键字,用于搜索引擎搜索
"author": "", # 作者信息
"license": "ISC" # 开源许可
}
安装包
项目中安装的包,默认存在 当前项目/node_modules 目录下
安装包时,涉及到两个重要的参数:
npm install –save
npm install –save-dev
配置项 | 命令 | 描述 |
---|---|---|
devDependencies | –save-dev 简写 -D | 开发环境,管理的依赖包仅在开发阶段有效 |
dependencies | –save 简写 -S | 生产环境,管理的依赖包在项目上线后依然有效 |
# --save(-S) 安装好后写入 package.json 的 dependencies 中;
# dependencies 依赖的包不仅开发环境能使用,生产环境也能使用,例如 jQuery 开发和线上环境都需要
npm install jquery --save
# 或
npm i jquery -S
# --save-dev(-D) 安装好后写入 package.json 的 devDependencies 中;
# devDependencies是只会在开发环境下依赖的模块,例如 minify 只需要开发环境使用,上线后就不需要了
npm install minify --save-dev
# 或
npm i minify -D
命令行执行
如果包安装在当前项目中(局部安装),则命令的执行路径是当前项目下的 .\node_modules\.bin 目录下。
# 局部安装包的执行路径
.\node_modules\.bin\lessc input.less output.css
如果包是全局安装,则命令执行的路径也是全局的
# 全局安装包的执行路径
lessc input.less output.css
Node.js 是前端工程化的基础,前端工程化的很多工具,都是基于 Node.js 开发的。
脚手架就是帮我们生成能复用的代码(项目基础结构)的工具,有了它,可能成倍地提高开发效率.。 –代码复用,极大地提高我们的开发效率
脚手架是一款辅助开发的工具,它的作用是创建项目基础结构、提供项目规范和约定。
举例:
原来,你想吃饺子,需要自己和面,自己擀皮儿;但有一天,你发现,你家附近的超市里,有人在卖饺子皮儿(饺子的半成品);从那以后,你包饺子的时间就缩短了一半。
脚手架,就是帮我们生产饺子皮儿(这种半成品)的工具。
Yeoman 是一款脚手架工具,其官方网站是 https://yeoman.io/
yo 是 Yeoman 的命令行工具,可以执行一系列的命令。
生成器是 Yeoman 中具体的脚手架。用来应对不同的应用场景(例如:生成网站和生成APP,我们使用不同的生成器)。
yo 是用来管理生成器的命令,生成器是帮我们生成复用代码的具体工具。
举例:yo 是医院院长,生成器是具体医生。医院院长管理医生,不同的医生应对不同的场景(儿科,妇科,眼科…)
如果网站被墙,在官网不能查看生成器
解决方案:https://github.com/justjavac/ReplaceGoogleCDN
Yeoman 最佳实践
仅需要几条安装命令,我们就能获得一个项目的半成品。( 这里以 generator-webapp 这一款生成器为例 )
# 全局安装 yo
npm install --global yo
# 安装对应的生成器( generator )
npm install --global generator-webapp
# 创建项目
mkdir project-name
cd project-name
# 通过 yo 运行 generator ( 时间有点长 )
yo webapp
# 运行看效果
npm run start
什么是构建
所谓构建,是指将源代码转换成生产代码的过程。
为什么需要将源代码转换成生产代码?或者说需要构建哪些内容?
以上这些问题,都是构建的内容。
构建流程
什么是自动化构建
不管是代码压缩还是 less 转换,通过手动方式进行工作量巨大(例如手动压缩2000行代码,估计程序员就疯了)
自动化构建是指将手动构建任务,进行排列组合,然后通过命令(或工具)自动执行的过程。
实现自动化构建最简单的方式是 npm scripts (npm 脚本)。
什么是 npm scripts
npm 允许在 package.json
文件里面,使用 scripts
字段定义脚本命令。
{
"scripts": {
// 命令名称: 任务
"foo": "node bar.js"
}
}
scripts
字段是一个对象。它的每一个属性,对应一段脚本。比如,foo
命令对应的脚本是node bar.js
。
# 命令行下使用 npm run <命令>,就可以执行这段脚本。
$ npm run foo
# 等同于执行
$ node bar.js
通配符
npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符
"style": "lessc *.less"
"style": "lessc **/*.less"
* 表示任意文件名,** 表示任意一层子目录。
执行顺序
如果 npm 脚本里面需要执行多个任务,那么需要明确它们的执行顺序。
如何选择正确的任务执行顺序?
如果是并行执行(即同时的平行执行),可以使用 &
符号。
{
"scripts": {
"parallel": "node task1.js & node task2.js & node task3.js"
}
}
如果是串行执行(前一个任务成功后,才执行下一个任务),可以使用 &&
符号。
{
"scripts": {
"series": "node task1.js && node task2.js && node task3.js"
}
}
**但是,& 符号在 Windows 操作系统下不起作用。**此时,我们可以借助插件,在 Windows 下实现并行操作:
npm-run-all
# 先在项目中安装
npm i npm-run-all -D
# 并行执行:其中 p 是 parallel(并行)的意思
npm-run-all -p 脚本1 脚本2 脚本3
# 或简写为
run-p 脚本1 脚本2 脚本3
# 串行执行:其中 s 是 series(串行)的意思
npm-run-all -s 脚本1 脚本2 脚本3
# 或简写为
run-s 脚本1 脚本2 脚本3
构建样式文件就是将开发环境下的 CSS (包括 Less 或 Sass)源代码,转成线上环境使用的代码。这里的构建任务可能有多个。
# 安装 less 包,编译 less 文件
npm i less -g
# 在 package.json 中,添加 less 解析命令
"scripts": {
"style": "lessc style.less style.css",
}
# 执行命令(自动编译)
npm run style
# 安装 minify 包, 压缩文件
npm i minify -g
# 在 package.json 中,添加 less 解析命令
"scripts": {
# 先编译 && 然后压缩
"style": "lessc style.less style.css && minify style.css > style.min.css",
}
# 执行命令(自动编译)
npm run style
构建样式文件就是将开发环境下的 JavaScript 源代码,转成线上环境使用的代码。这里的构建任务可能有多个。
在开发过程中,经常使用 ES6+ 新特性时,一些旧的浏览器,不支持 JS 的新语法。所以,在项目上线之前,就需要将新的语法特性解析成兼容性更好的 ES5 。最常用的编译工具是 Babel
要是用 ES6+ 新特性,兼容性有问题
# 安装 babel核心,Babel客户端 npm i -g babel-core babel-cli # 安装转码规则 npm i -g babel-preset-env # 在项目根目录下,新建 .babelrc 文件(注意文件名前有一个点),并添加转换规则 { "presets": [ "env" ], } # 通过 babel 编译单个 j s文件 babel input.js --out-file output.js # 或者 babel input.js -o output.js # 通过 babel 编译整个目录 babel js --out-dir scripts # 或者 babel js -d scripts # 在 package.json 中,添加 babel 解析命令 "scripts": { "script": "babel js -d scripts", } # 执行命令(自动编译) npm run script
如果 Babel 是局部安装。则babel 的可执行路径是:./node_modules/.bin/babel
命令需要做相应的调整
babel js -d scripts ===> ./node_modules/.bin/babel js -d scripts
转换前:
转换后:
添加压缩命令
# 在 package.json 中,添加 babel 解析和压缩命令
"scripts": {
"script": "babel js -d scripts && minify scripts/main.js > scripts/script.min.js",
}
压缩后
自动化构建对于现代的前端开发非常关键,这也催生了很多优秀的自动化构建工具。这里推荐Gulp。
编码规范:
每个程序员都有自己的编码习惯,最常见的莫过于:
;
,有的人觉得不加分号 ;
更好看;诸如此类,但是项目开发一般是多人协作,所以,不同的工程师,写的代码风格不同。一般你写自己的代码怎么折腾都没关系,但整个项目需要有一个统一的编码规范。这样别人看源码就能够看得懂。
那么问题来了,总不能一行行去检查代码吧,这是一件很蠢的事情。凡是重复性的工作,都应该通过工具来完成。
这个工具应该做两件事情:
在前端工程化中,不同的代码,使用不同的工具进行检测。
例如:通常我们使用 ESLint 来检测 JavaScript 代码;我们使用 StyleLint 来检测 CSS 代码。
ESLint 是 JSLint 的升级版本,是用来检查 JS 代码质量的插件。通过 ESLint 可以统一不同开发者的代码风格。
ESLint 初体验
先创建项目
mkdir lint-demo
cd lint-demo
# 初始化项目,生成 package.json
npm init --yes
安装 ESLint:
但使用 EsLint 有一个先决条件:Node.js (>=6.14), npm version 3+。
$ npm i eslint -g
初始化一个配置文件:
# 之后进入交互窗口,询问一些问题;根据问题生成配置文件 例如:.eslintrc.json
$ eslint --init
配置规则
rules: {
“规则名”: [规则值, 规则配置]
}
其中规则值有以下 3 种:0 = off, 1 = warn, 2 = error
“off” 或 0 - 关闭规则
“warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
“error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
更多规则详情查看:https://eslint.org/docs/rules/
# 配置检测规则 { "env": { "browser": true, "commonjs": true, "es2021": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 12 }, "rules": { "indent": [ "error", 2 ], # 使用两个空格缩进 "quotes": [ "error", "double" ] # 使用双引号包裹字符串 } }
之后,你可以在任何文件或目录上运行ESLint如下:
$ eslint yourfile.js
拓展阅读:检测 JS 代码风格的工具有多个,除了 ESLint, 另一个常用的是 Standard:
https://github.com/standard/standard
StyleLint 是检测 CSS 代码格式的插件。
官网:https://stylelint.io/
StyleLint 初体验
安装插件
代码风格标准:https://github.com/stylelint/stylelint-config-standard
# stylelint 是运行工具,stylelint-config-standard 是 stylelint 的推荐配置
npm i --g stylelint stylelint-config-standard
在项目的根目录下,创建 .stylelintrc.json 文件,添加如下配置
{
"extends": "stylelint-config-standard",
"rules": {
# 除了使用 stylelint-config-standard,我们还可以在 rules 字段中自定义校验规则
"block-no-empty": true # 代码块不能为空
}
}
运行 stylelint,检测 CSS 代码格式
# 注意:路径是写在双引号中的
# 检测一个文件
stylelint 文件路径/文件名.css
# 检测整个项目下,所有的 CSS 文件
stylelint **/*.css
自动化构建工具
自动化构建工具,可以帮我们又快又好的完成自动化构建任务。相比有 npm scripts,自动化构建工具,功能更为强大。更简单易学。其中比较流行的有三款:
Grunt
是第一款自动化构建工具,对前端工程化的发展具有里程碑意义,其生态完善。但是,它的构建是基于临时文件的,所以构建速度较慢,现在用的人越来越少了。
Gulp
Gulp 的构建是基于内存实现的,其构建速度比 Grunt 快,而且,Gulp 的生态也很完善,插件质量很高。目前最为流行。
FIS
FIS 是百度的前端团队对出的,最开始只在百度内部使用。开源后,逐渐在国内流行起来。但是其更新跟不上,最近的更新都是三年前的,而且其生态主要有国人维护( Grunt 和 Gulp 生态是世界范围的 )。所以,其流行度比不上 Gulp。
接下来,我们以 Gulp 为例,来讲解前端自动化构建工具。
Gulp 是基于 流 的自动化构建系统。
Gulp 的特点
任务化
基于流
使用 Gulp 之前,先在全局安装 gulp-cli ( Gulp 的命令行工具 )
# 全局安装 gulp 客户端
npm i -g gulp-cli
# 验证安装是否成功
gulp -v
Gulp 使用的基本逻辑是:先声明任务,再从命令行中执行任务;具体步骤如下:
使用 Gulp 之前,先在全局安装 gulp-cli
# 安装 gulp 命令行工具
npm i -g gulp-cli
# 验证安装是否成功
gulp -v
初始化项目
# 创建项目目录
mkdir project-name
# 进入项目目录
cd project-name
# 初始化项目
npm init --yes
安装 Gulp 包
# 安装 gulp 包,作为开发时依赖项
npm i gulp -D
创建 gulpfile 文件
gulpfile 文件是项目根目录下的 gulpfile.js
,在运行 gulp
命令时会被自动加载。在这个文件中,你经常会看到类似 src()
、dest()
、series()
或 parallel()
函数之类的 Gulp API,除此之外,纯 JavaScript 代码或 Node.js 模块也会被使用。任何导出( exports )的函数都将注册到 Gulp 的任务(task)系统中。
# 创建任务,任务结束后,需要通过回调函数去标记
exports.foo = () => {
console.log('foo task is running')
}
报错:
The following tasks did not complete: task
Did you forget to signal async completion?解释:在最新的 Gulp 中,取消了同步代码模式。约定每个任务都必须是一个异步任务
解决:再函数参数中,设定回调函数(回调函数是异步操作)
在 gulpfile.js 中注册 Gulp 任务
# 创建任务,并导出任务
exports.foo = cb => {
console.log('foo task is running')
cb()
}
# 旧版 Gulp 注册任务的语法(无需执行导出操作)
gulp.task('foo', function(cb) {
console.log('foo task is running')
cb()
});
运行 Gulp 任务
# 运行 foo 任务
# 如需运行多个任务(task),可以执行 gulp <task> <othertask>
gulp foo
创建默认任务
# 默认任务的名称是 default
exports.default = cb => {
console.log('default task is running')
cb()
}
# 运行默认任务, gulp 后无需指定任务名称
gulp # 效果等同于 gulp default
Gulp 提供了两个强大的组合方法: series()
和 parallel()
如果需要让任务(task)按顺序执行,请使用 series()
方法(相当于 npm scripts 中的 && )。
如果希望任务(tasks)并行执行,可以使用 parallel()
方法将它们组合起来(相当于 npm scripts 中的 & )。
const gulp = require('gulp') const task1 = cb => { setTimeout(() => { console.log('Task 1 is running') cb() }, 1000) } const task2 = cb => { setTimeout(() => { console.log('Task 2 is running') cb() }, 1000) } const task3 = cb => { setTimeout(() => { console.log('Task 3 is running') cb() }, 1000) } # 串行方式执行任务,先执行task1, 然后执行task2, 然后执行task3 exports.foo = gulp.series(task1, task2, task3) # 并行方式执行任务,同时执行task1,task2,task3 exports.bar = gulp.parallel(task1, task2, task3) # 执行命令 gulp foo # 串行执行 gulp bar # 并行执行
series()
和 parallel()
可以被嵌套到任意深度。通过这两个函数,构建任务可以被任意排列组合,从而满足各种复杂的构建需求。
gulp 暴露了 src()
和 dest()
方法用于处理计算机上存放的文件。在代码构建过程中,需要将源文件,写入到目标目录。
# 通过 解构 的方式引入 gulp 中的函数
const { src, dest } = require('gulp')
exports.default = () => {
// 文件操作
// 将 src/styles 目录下的 main.css 文件,复制到 dist/styles 目录下
return src('src/styles/main.less', { base: 'src' }).pipe(dest('dist'))
}
# 执行命令
gulp default
# 或直接执行
gulp
对样式文件进行转换、压缩、重命名。
# 安装相关的 Gulp 插件 npm i gulp-less -D npm i gulp-autoprefixer -D npm i gulp-clean-css -D npm i gulp-rename -D # 在 gulpfile.js 中添加样式编译内容 const gulp = require('gulp') const less = require('gulp-less') // 给 CSS 属性添加前缀的插件(详情请看下方 CSS Hack) const autoprefixer = require('gulp-autoprefixer') // 压缩 CSS 的插件 const cleanCss = require('gulp-clean-css') // 重命名转换文件的插件 const rename = require('gulp-rename') const style = () => { return src('src/styles/*.less', { base: 'src' }) .pipe(less()) .pipe(autoprefixer()) .pipe(cleanCss()) .pipe(rename({extname: '.min.css'})) .pipe(dest('dist')) } module.exports = { style } # 运行命令 npm gulp style
通过样式文件的构建,我们可以更清晰的理解文件操作。
CSS Hack
由于不同浏览器中的渲染引擎不同,这导致了同一段 CSS 代码,在不同的浏览器上解析效果不同(即 CSS 代码具有兼容性问题)。
针对不同浏览器,写不同 CSS 代码的过程称为 CSS Hack。
CSS Hack 有三种形式:CSS 属性 Hack、CSS选择符 Hack 和 IE条件注释 Hack( Hack主要针对IE浏览器 )
属性级 Hack
比如 IE6 能识别下划线“
_
”和星号“*
”,IE7 能识别星号“*
”,但不能识别下划线”_
”选择符级 Hack
IE6 能识别
*html .class{}
IE7 能识别
*+html .class{}
或者*:first-child+html .class{}
IE 条件注释 Hack:
# 针对 IE6 及以下版本: <!--[if lt IE 6]>您的代码<![endif]-->
- 1
- 2
这类 Hack 不仅对 CSS 生效,对写在判断语句里面的所有代码都会生效。
本小节,只讨论属性级 Hack
不同浏览器的 CSS 属性前缀:
例如:use-select 存在兼容性问题。CSS 属性 Hack 的解决方案如下:
上述添加 CSS 属性前缀的操作,之前是通过程序员手动添加的。这类重复性操作,我们可以通过插件完成。
在 Gulp 中 gulp-autoprefixer 插件,可以根据 caniuse.com 上提供的 CSS 兼容性数据,自动地给 CSS 属性加前缀,以保证 CSS 代码的兼容性问题。
对 JS 代码进行 Babel 转换和压缩。
注意:因为不同 babel 版本对应的 gulp-babel 的安装命令不同,所以安装 gulp-babel 之前需要先确定本地 babel 版本 (通过 babel --version 查看)
# Babel 7 $ npm install --save-dev gulp-babel @babel/core @babel/preset-env # Babel 6 $ npm install --save-dev gulp-babel@7 babel-core babel-preset-env
- 1
- 2
- 3
- 4
- 5
# 我本地的 babel 版本是 6,所以,执行 6 的安装命令 npm install --save-dev gulp-babel@7 babel-core babel-preset-env # 安装压缩脚本的插件 npm i gulp-uglify -D # 在 gulpfile.js 中添加脚本编译内容 const rename = require('gulp-rename') const babel = require('gulp-babel') const uglify = require('gulp-uglify') const script = () => { return src('src/scripts/*.js', { base: 'src' }) .pipe(babel({ presets: [ 'babel-preset-env' ] // 不同版本的 babel,其转换规则写法也不同 })) .pipe(uglify()) .pipe(rename({ "extname": ".min.js" })) .pipe(dest('dist')) } module.exports = { style, script } # 运行命令 gulp script
对 html 文件的构建,主要指压缩 html 文件。其中 gulp-htmlmin 插件可以完成压缩任务。
gulp-htmlmin 插件的解析器是:https://github.com/kangax/html-minifier
想要查看 htmlmin 的使用参数,可以查看上述链接。
# 添加 htmlmin 插件 npm i gulp-htmlmin -D # 在 gulpfile.js 中添加页面处理内容 const htmlmin = require('gulp-htmlmin') const html = () => { return src('src/*.html', { base: 'src' }) .pipe(htmlmin({ collapseWhitespace: true, // 去除标签之间多余的空行和空白 minifyCSS: true, // 压缩HTML中的CSS代码 minifyJS: true // 压缩HTML中的JS代码 })) .pipe(dest('temp')) } module.exports = { style, script, html } # 运行命令 gulp html
完成上述三个构建任务后,我们可以将 style,script 和 html 任务组合起来。因为 style,script 和 html 之间没有明确的前后顺序,所以,可以进行并行执行,并行执行可以提升构建效率。
# 引入 parallel 函数
const { src, dest, parallel } = require('gulp')
// 任务的并行执行
const build = parallel(style, script, html)
module.exports = {
build
}
# 运行命令
gulp build
对图片文件的构建,主要是指图片的压缩。通过 gulp-imagemin 插件可以完成图片的压缩任务。
# 安装 imagemin 插件 npm i gulp-imagemin -D # 在 gulpfile.js 中引入图片压缩插件 const imagemin = require('gulp-imagemin') // 图片构建任务 const image = () => { return src('src/images/**', { base: 'src' }) .pipe(imagemin()) .pipe(dest('dist')) } // 图片构建任务,也可以与以上三个任务一起,并行执行 const build = parallel(style, script, html, image) module.exports = { image, build } # 运行命令 gulp build
报错处理:
gulp-imagemin: Couldn’t load default plugin “gifsicle”
gulp-imagemin: Couldn’t load default plugin “optipng”
原因:npm 安装依赖失败
解决:
- 配置 hosts (详情参考本节最后的附录部分)
- 重新安装 npm i gulp-imagemin -D
有时候,我们需要删除一些构建的历史文件,然后再重新构建。删除文件操作,可以通过 del 插件完成。
# 通过del插件来删除指定文件 npm i del -D const del = require('del') // 声明清除任务 const clean = () => { return del(['dist']) } // 编译之前,先执行clean,删除历史文件 const build = parallel(style, script, html, image) const dev = series(clean, build) module.exports = { clean, dev } # 运行命令,查看文件是否删除 gulp clean # 或者 gulp dev
通过web服务器插件,将 dist 下的代码,发布到浏览器查看效果。发布web服务的插件有很多。这里,我们推荐功能强大的 browser-sync。
# 安装 browser-sync 插件 npm i browser-sync -D # 在 gulpfile.js 中添加开发服务器的内容 const browserSync = require('browser-sync') const bs = browserSync.create() // 声明 web 服务构建任务 const serve = () => { bs.init({ server: { baseDir: './dist' // 指定服务启动的目录 routes: { '/node_modules': 'node_modules' // 引入 Bootstrap 是设置路径映射 } } }) } module.exports = { clean, build, serve } # 运行命令,然后在浏览器查看效果 gulp serve
服务发布成功后,之前学习的内容,也可以拿过来使用,例如:Bootstrap
下载插件
# 因为 Bootstrap 和 jQuery 上线之后还要使用,所以,采用 -S 参数(上线依赖) npm i bootstrap@3.4.1 jquery -S
- 1
- 2
引入文件
Bootstrap 和 jQuery 下载后,文件位于当前目录的 node_modules 下
# 在 HTML 中引入 <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css"> ...... <script src="/node_modules/jquery/dist/jquery.min.js"></script> <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
- 1
- 2
- 3
- 4
- 5
引入路径,需要在 browser-sync 中,通过 routes 参数映射后,才能正确引入(详情查看上述代码)
使用 Bootstrap
之前学习的 Bootstrap 的代码,都可以在当前代码中使用。
监视 src 下文件变化的页面更新,代码一旦更新,浏览器上的页面效果也随之更新。
此时,我们需要监视两个目录的变化,一个是 dist 目录,一个是 src 目录。
监视 dist 目录下代码的变化
# 通过 browser-sync 中的 files 字段
files: 'dist/**'
监视 src 目录下代码的变化
# 通过 gulp 中的 watch 函数
watch(被监视的文件,对应的任务)
最终的代码如下:
# 在 gulpfile.js 中添加监视文件变化的代码 const serve = () => { // watch(被监视的文件,对应的任务) watch('src/index.html', html) watch('src/styles/*.less', style) watch('src/js/*.js', script) watch('src/images/**', image) // 初始化服务 bs.init({ notify: false, // 禁用浏览器右上角的 browserSync connected 提示框 files: 'dist/**', // 监视 dist 下 文件的变化,然后在浏览器上实时更新 server: { baseDir: './dist', // 指定服务启动的目录 routes: { '/node_modules': 'node_modules' } } }) } // 组合任务 const build = parallel(style, script, html, image) const dev = series(clean, build, serve) // 导出任务 module.exports = { build, dev, serve } # 运行命令,更新代码文件,查看页面变化 gulp dev
此时,不管是 dist 目录下,还是 src 目录下。只要代码发生变化,我们就可以在浏览器上实时看到效果。
通过vscode 打开 hosts 文件
# hosts 文件的路径
C:\Windows\System32\Drivers\etc
添加 Guthub 相关的内容
将下面的内容复制,然后追加到 hosts 文件的尾部
# GitHub Start (chinaz.com) ================================================= 13.229.188.59 github.com 54.169.195.247 api.github.com 140.82.113.25 live.github.com 8.7.198.45 gist.github.com # 185.199.108.154 github.githubassets.com # 185.199.109.154 github.githubassets.com 185.199.110.154 github.githubassets.com # 185.199.111.154 github.githubassets.com 34.196.247.240 collector.githubapp.com # 52.7.232.208 collector.githubapp.com 52.216.92.163 github-cloud.s3.amazonaws.com 199.232.96.133 raw.githubusercontent.com 151.101.108.133 user-images.githubusercontent.com 151.101.108.133 avatars.githubusercontent.com 151.101.108.133 avatars0.githubusercontent.com 151.101.108.133 avatars1.githubusercontent.com 151.101.108.133 avatars2.githubusercontent.com 151.101.108.133 avatars3.githubusercontent.com 151.101.108.133 avatars4.githubusercontent.com 151.101.108.133 avatars5.githubusercontent.com 151.101.108.133 avatars6.githubusercontent.com 151.101.108.133 avatars7.githubusercontent.com 151.101.108.133 avatars8.githubusercontent.com 151.101.108.133 avatars9.githubusercontent.com 151.101.108.133 avatars10.githubusercontent.com 151.101.108.133 avatars11.githubusercontent.com 151.101.108.133 avatars12.githubusercontent.com 151.101.108.133 avatars13.githubusercontent.com 151.101.108.133 avatars14.githubusercontent.com 151.101.108.133 avatars15.githubusercontent.com 151.101.108.133 avatars16.githubusercontent.com 151.101.108.133 avatars17.githubusercontent.com 151.101.108.133 avatars18.githubusercontent.com 151.101.108.133 avatars19.githubusercontent.com 151.101.108.133 avatars20.githubusercontent.com # GitHub End ===================================================================
保存文件
ctrl+s 保存,此时如果报:没有权限,点击以管理员身份重试
打开终端
输入 sudo vi /etc/hosts
输入密码
进入文件 hosts,然后按 “i”,进入编辑模式
把你的内容添加到最后
# GitHub Start (chinaz.com) ================================================= 13.229.188.59 github.com 54.169.195.247 api.github.com 140.82.113.25 live.github.com 8.7.198.45 gist.github.com # 185.199.108.154 github.githubassets.com # 185.199.109.154 github.githubassets.com 185.199.110.154 github.githubassets.com # 185.199.111.154 github.githubassets.com 34.196.247.240 collector.githubapp.com # 52.7.232.208 collector.githubapp.com 52.216.92.163 github-cloud.s3.amazonaws.com 151.101.108.133 raw.githubusercontent.com 151.101.108.133 user-images.githubusercontent.com 151.101.108.133 avatars.githubusercontent.com 151.101.108.133 avatars0.githubusercontent.com 151.101.108.133 avatars1.githubusercontent.com 151.101.108.133 avatars2.githubusercontent.com 151.101.108.133 avatars3.githubusercontent.com 151.101.108.133 avatars4.githubusercontent.com 151.101.108.133 avatars5.githubusercontent.com 151.101.108.133 avatars6.githubusercontent.com 151.101.108.133 avatars7.githubusercontent.com 151.101.108.133 avatars8.githubusercontent.com 151.101.108.133 avatars9.githubusercontent.com 151.101.108.133 avatars10.githubusercontent.com 151.101.108.133 avatars11.githubusercontent.com 151.101.108.133 avatars12.githubusercontent.com 151.101.108.133 avatars13.githubusercontent.com 151.101.108.133 avatars14.githubusercontent.com 151.101.108.133 avatars15.githubusercontent.com 151.101.108.133 avatars16.githubusercontent.com 151.101.108.133 avatars17.githubusercontent.com 151.101.108.133 avatars18.githubusercontent.com 151.101.108.133 avatars19.githubusercontent.com 151.101.108.133 avatars20.githubusercontent.com # GitHub End ===================================================================
control+c 退出编辑模式
输入 :wq,保存退出
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。