工程化基础、自动化工具


一、前端工程化,Node.js

1. 前端工程化

是什么

前端工程化是指遵循一定的标准和规范,通过工具去提高开发效率降低成本的一种手段。

开发过程中的很多脏活累活,都应该交给自动化工具来完成

*工程*:可以简单理解为一个项目(例如一个网站,一个APP等)

*工程化*:实现一个工程的具体流程、技术、工具、规范等。涉及到从工程立项开发到上线运行的整个过程。

前端工程化就是通过各种工具和技术,提升前端开发效率的过程

目标: 远离刀耕火种的原始时代,全面提升战斗力

结合脚手架、自动化、模块化、规范化搭现代化前端工程

做什么

前端工程化包含各种工具和技术,这些工具和技术出现在整个工程生命周期的各个环节。主要可以分为以下几类:

脚手架工具
自动化构建
模块化打包
规范化标准
自动化测试
自动化部署

主要解决的问题

  1. 重复的机械式工作(部署上线前,需要手动压缩代码和资源文件)

  2. 传统语言或语法的弊端(要是用 ES6+ 和 CSS3 的新特性,兼容性有问题;使用 Less / Sass / PostCSS 增强 CSS 的编程性,但运行环境不支持)

  3. 代码风格统一,质量保证(多人协同开发,无法硬性统一大家的代码风格)

  4. 依赖后端服务接口支持(部分功能开发时,需要等待后端服务接口提前完成)

2.Node.js 基础

是什么

JavaScript 可以在浏览器端运行。所以,浏览器是 JavaScript 的一个运行环境。而 Node.js 是除了浏览器之外,另一个可以运行 JavaScript 的环境

区别在于,Node.js 这个运行环境,是根植于操作系统之上的。提供了一些与操作系统交互的 APIs,例如:文件操作,web 服务发布等。所以,只是 JavaScript 换了一个地方运行而已, Node.js 的语法还是原来 JavaScript 的语法。

官网https://nodejs.org/

中文网http://nodejs.cn/

做什么

浏览器端的 JS 负责与浏览器端的功能交互。Node.js 负责服务器端的功能交互。

具体来说:

  • Node.js 适合用于开发前端方向的各种工具
    • 各种前端工程化的工具
  • Node.js 适合开发服务器端的应用层(BFF)
    • 为网站,APP,小程序等提供数据服务
  • Node.js 可以用来做桌面应用开发
    • 各种跨平台的桌面应用
      各种跨平台的桌面应用

使用

运行方式

  1. 脚本模式

    # 声明 app.js
    console.log("Hello Node.js")
    
    # 运行:node 代码路径/文件名.js
    node app.js # 返回 Hello Node.js
    
  2. 交互模式

    # 在命令行中,进入交互模式
    node # 回车,然后进入交互模式
    
    # 运行代码
    > console.log('Hello Node'); # 回车
    Hello Node
    
    # 退出交互模式
    两次 ctrl+c
    # 或
    输入 .exit
    

    交互模式适合调试代码,相当于浏览器上的控制台。

全局对象

浏览器端 JS 的全局对象是 window,Node.js 端的全局对象是 global。

  1. Node.js 的全局对象是 global

    • 在交互模式下,声明的变量和创建的函数都属于是global下的 var a=1; global.a

    • 在脚本模式下,声明的变量和创建的函数都不属于 global 下的

    // 脚本模式下,声明的变量不属于全局对象 global
    var a = 'aaa';
    
    console.log(a)        // 返回 aaa
    console.log(global.a) // 返回 undefined
    
  2. 浏览器端声明的 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 中的模块是具有特定功能的对象。按照模块的作者进行划分,可以分成三类:

  • 内置模块:也叫核心模块,
    • 对应 Web 端 JS 的宿主对象,例如:window,localtion,history 等;
    • 无需单独安装 - 会随着Node.js一起安装;
    • 可以在官方API文档中查看:http://nodejs.cn/api/
  • 自定义模块:程序员自己写的,具有一定功能的代码块。
    • 文件模块:单独的一个 JS 文件组成的模块
    • 目录模块:多个 JS 文件组合在一起,放在一个目录中形成的模块
  • 第三方模块:既不是内置模块,也不是自定义模块。
    • 对应 Web 端 JS 的第三方库,例如:jQuery,Bootstrap 等。
    • 想要使用,必须先安装( 如同使用 jQuery 之前,必须先通过 script 标签引入一样 )
    • 在Node.js中,大量的第三方模块通过 npm 来管理。

内置模块

内置模块(也叫核心模块)是官方提供的,无需下载,可以直接使用的模块。

官网:http://nodejs.cn/api/

  1. 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');
    
  2. 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(进程编号)
    
  3. path

    path 模块负责文件路径的

    • ./ 表示当前目录
    • …/ 表示上一级目录
    • __dirname 返回当前文件所在的目录
    • __filename 返回当前文件的完整路径(目录+文件)
    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));
    
  4. fs

    fs (file system)模块主要负责文件基本操作

    1. 文件操作

      // 使用 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('追加写入成功') 
      })
      
    2. 目录操作

      // 创建目录
      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')
      }
      
    3. 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)
        }
      })
      
  5. 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)

注意:引入自定义模块时,需要带有引入路径,否则,会报错

这里我们可以根据文件的组织方式,将模块分成文件模块目录模块

不同类型的模块,引入方式不同,其加载逻辑也不相同,一共有四种情况:

  1. 以路径开头的文件模块
  2. 不以路径开头的文件模块
  3. 以路径开头的目录模块
  4. 不以路径开头的目录模块

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

简介

npm(Node Package Manager)是包管理工具。npm 会跟随 Node.js 一起安装。

# 验证 npm 是否已经安装
npm --version
或
npm -v

npm 可以帮我们下载(安装)包和包的依赖

  • 包:就是一坨代码,就是 Node.js 的第三方模块
  • 包的依赖:是指包的辅助代码(例如:下载 Bootstrap 时,必须先下载 jQuery,因为 Bootstrap 是基于 jQuery 开发的,没有 jQuery,Bootstrap 就运行不起来。此时,我们说:Bootstrap 依赖 jQuery )

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 . # 启动当前目录

局部安装(安装项目包)

如果你需要的第三方包,只是在某个项目中使用(而不是作为全局工具来用),你可以在项目中进行局部安装。

  1. 创建项目目录

    mkdir project-name
    
  2. 进入项目目录

    cd project-name
    
  3. 初始化项目

    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"        # 开源许可
    }
    
    
  4. 安装包

    项目中安装的包,默认存在 当前项目/node_modules 目录下

    安装包时,涉及到两个重要的参数:

    • npm install –save

      • 安装好后,包的信息会写入 package.json 的 dependencies 中;
      • dependencies 中的包,在开发和生产环境都使用,例如:jQuery。
    • npm install –save-dev

      • 安装好后,包的信息会写入 package.json 的 devDependencies 中
      • devDependencies 中的包只用于开发环境,生产环境不需要。例如 minify,项目上线后,就不需要压缩文件了。
    配置项命令描述
    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
    
  5. 命令行执行

    如果包安装在当前项目中(局部安装),则命令的执行路径是当前项目下的 .\node_modules\.bin 目录下。

    # 局部安装包的执行路径
    .\node_modules\.bin\lessc input.less output.css
    

    如果包是全局安装,则命令执行的路径也是全局的

    # 全局安装包的执行路径
    lessc input.less output.css
    

3.前端工程化是由 Node.js 强力驱动的。

Node.js 是前端工程化的基础,前端工程化的很多工具,都是基于 Node.js 开发的。

二、脚手架

简介

脚手架就是帮我们生成能复用的代码(项目基础结构)的工具,有了它,可能成倍地提高开发效率.。 –代码复用,极大地提高我们的开发效率

脚手架是一款辅助开发的工具,它的作用是创建项目基础结构、提供项目规范和约定。

举例:

原来,你想吃饺子,需要自己和面,自己擀皮儿;但有一天,你发现,你家附近的超市里,有人在卖饺子皮儿(饺子的半成品);从那以后,你包饺子的时间就缩短了一半。

脚手架,就是帮我们生产饺子皮儿(这种半成品)的工具。

Yeoman简介

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

三、自动化构建

是什么

什么是构建

所谓构建,是指将源代码转换成生产代码的过程。

为什么需要将源代码转换成生产代码?或者说需要构建哪些内容?

  • 代码需要编译( CSS3,JS6+ ), 保证浏览器的兼容性
  • 代码需要压缩( CSS,JS,HTML,图片等 )。节省带宽,提高加载速度
  • 代码需要做格式化校验,统一代码风格。

以上这些问题,都是构建的内容。

构建流程

什么是自动化构建

不管是代码压缩还是 less 转换,通过手动方式进行工作量巨大(例如手动压缩2000行代码,估计程序员就疯了)

自动化构建是指将手动构建任务,进行排列组合,然后通过命令(或工具)自动执行的过程。

在这里插入图片描述

实现自动化构建最简单的方式是 npm scripts (npm 脚本)。

npm scripts

  1. 什么是 npm scripts

    npm 允许在 package.json 文件里面,使用 scripts 字段定义脚本命令。

    {
      "scripts": {
        // 命令名称: 任务
        "foo": "node bar.js"
      }
    }
    

    scripts 字段是一个对象。它的每一个属性,对应一段脚本。比如,foo 命令对应的脚本是node bar.js

    # 命令行下使用 npm run <命令>,就可以执行这段脚本。
    $ npm run foo
    # 等同于执行
    $ node bar.js
    
  2. 通配符

    npm 脚本就是 Shell 脚本,因为可以使用 Shell 通配符

    "style": "lessc *.less"
    "style": "lessc **/*.less"
    

    * 表示任意文件名,** 表示任意一层子目录。

  3. 执行顺序

    如果 npm 脚本里面需要执行多个任务,那么需要明确它们的执行顺序。
    在这里插入图片描述

    如何选择正确的任务执行顺序?

    • 串行执行:任务之间有明确的先后顺序,必须先执行前一个任务,然后才能执行下一个任务
      • 相当于 4X100 接力,拿到上一个队员的接力棒后,下一个队员才能继续跑
    • 并行执行:任务之间没有明确的先后顺序,同时执行,可以提高效率
      • 相当于 100 米短跑,8个人同时起跑

    如果是并行执行(即同时的平行执行),可以使用 & 符号。

    {
      "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)源代码,转成线上环境使用的代码。这里的构建任务可能有多个。

  1. 编译 less 文件
# 安装 less 包,编译 less 文件
npm i less -g

# 在 package.json 中,添加 less 解析命令
"scripts": {
    "style": "lessc style.less style.css",
}

# 执行命令(自动编译)
npm run style
  1. 压缩 css 文件
# 安装 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。

代码格式校验

编码规范

每个程序员都有自己的编码习惯,最常见的莫过于:

  • 有的人代码结尾必须加分号 ;,有的人觉得不加分号 ; 更好看;
  • 有的人写字符串时,喜欢用双引号,而有的人喜欢用单引号;
  • 有的人代码缩进喜欢用 4 个空格,有的人喜欢用 2 个空格;

诸如此类,但是项目开发一般是多人协作,所以,不同的工程师,写的代码风格不同。一般你写自己的代码怎么折腾都没关系,但整个项目需要有一个统一的编码规范。这样别人看源码就能够看得懂。

那么问题来了,总不能一行行去检查代码吧,这是一件很蠢的事情。凡是重复性的工作,都应该通过工具来完成。

这个工具应该做两件事情:

  1. 提供编码规范
  2. 根据编码规范,自动检查代码。

在前端工程化中,不同的代码,使用不同的工具进行检测。

例如:通常我们使用 ESLint 来检测 JavaScript 代码;我们使用 StyleLint 来检测 CSS 代码。

ESLint

ESLint 是 JSLint 的升级版本,是用来检查 JS 代码质量的插件。通过 ESLint 可以统一不同开发者的代码风格。

官网:https://eslint.org/

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

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

四、Gulp

是什么

自动化构建工具

自动化构建工具,可以帮我们又快又好的完成自动化构建任务。相比有 npm scripts,自动化构建工具,功能更为强大。更简单易学。其中比较流行的有三款:

  • Grunt

    是第一款自动化构建工具,对前端工程化的发展具有里程碑意义,其生态完善。但是,它的构建是基于临时文件的,所以构建速度较慢,现在用的人越来越少了。

  • Gulp

    Gulp 的构建是基于内存实现的,其构建速度比 Grunt 快,而且,Gulp 的生态也很完善,插件质量很高。目前最为流行。

  • FIS

    FIS 是百度的前端团队对出的,最开始只在百度内部使用。开源后,逐渐在国内流行起来。但是其更新跟不上,最近的更新都是三年前的,而且其生态主要有国人维护( Grunt 和 Gulp 生态是世界范围的 )。所以,其流行度比不上 Gulp。

接下来,我们以 Gulp 为例,来讲解前端自动化构建工具。

在这里插入图片描述

Gulp

Gulp 是基于 的自动化构建系统。

Gulp 的特点

  • 任务化

    • 所有的构建操作,在 gulp 中都称之为任务
  • 基于流

    • gulp 中所有的文件操作,都是基于 方式进行 ( Gulp有一个自己的内存,通过指定 API 将源文件流到内存中,完成相应的操作后再通过相应的 API 流出去)

基础

使用 Gulp 之前,先在全局安装 gulp-cli ( Gulp 的命令行工具 )

# 全局安装 gulp 客户端
npm i -g gulp-cli 

# 验证安装是否成功
gulp -v

官网:https://gulpjs.com/

基本用法

Gulp 使用的基本逻辑是:先声明任务,再从命令行中执行任务;具体步骤如下:

  1. 使用 Gulp 之前,先在全局安装 gulp-cli

    # 安装 gulp 命令行工具
    npm i -g gulp-cli 
    
    # 验证安装是否成功
    gulp -v
    
  2. 初始化项目

    # 创建项目目录
    mkdir project-name
    
    # 进入项目目录
    cd project-name
    
    # 初始化项目
    npm init --yes
    
  3. 安装 Gulp 包

    # 安装 gulp 包,作为开发时依赖项
    npm i gulp -D
    
  4. 创建 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 中,取消了同步代码模式。约定每个任务都必须是一个异步任务

    解决:再函数参数中,设定回调函数(回调函数是异步操作)

  5. 在 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()
    });
    
  6. 运行 Gulp 任务

    # 运行 foo 任务
    # 如需运行多个任务(task),可以执行 gulp <task> <othertask>
    gulp foo
    
  7. 创建默认任务

    # 默认任务的名称是 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]-->
    

    这类 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
# 我本地的 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 安装依赖失败

解决:

  1. 配置 hosts (详情参考本节最后的附录部分)
  2. 重新安装 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

  1. 下载插件

    # 因为 Bootstrap 和 jQuery 上线之后还要使用,所以,采用 -S 参数(上线依赖)
    npm i bootstrap@3.4.1 jquery -S
    
  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>
    

    引入路径,需要在 browser-sync 中,通过 routes 参数映射后,才能正确引入(详情查看上述代码)

  3. 使用 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 目录下。只要代码发生变化,我们就可以在浏览器上实时看到效果。

附录

1. Win10 配置 hosts

  1. 通过vscode 打开 hosts 文件

    # hosts 文件的路径
    C:\Windows\System32\Drivers\etc
    
  2. 添加 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 ===================================================================
    
    
  3. 保存文件

    ctrl+s 保存,此时如果报:没有权限,点击以管理员身份重试

2. Mac 配置 hosts

  1. 打开终端

  2. 输入 sudo vi /etc/hosts

  3. 输入密码

  4. 进入文件 hosts,然后按 “i”,进入编辑模式

  5. 把你的内容添加到最后

    # 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 ===================================================================
    
    
  6. control+c 退出编辑模式

  7. 输入 :wq,保存退出

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值