浅谈前端工程化的发展以及相关工具介绍

## 什么是工程化?什么是前端工程化?

随着发展的逐步发展,作为工程师除了需要关注需要写的⻚面,样式和逻辑之外,还需要面对日益复杂 的系统性问题,比如模块化文件的组织、ES6 JS 文件的编译、打包压缩所有的 JS 代码、优化和合并图片静态资源等等事情。

我们的项目需要以一种合理的方式进行组合,以应对在团队协作、需求迭代中保持项目 的稳定发展。这种方式就是工程化系统去运行我们的项目。

例如如下图所示就是前端工程化中一次打包的过程,左侧就是我们项目中出现的源文件,例如我们会通 过 sass 这种预处理样式来更好的组织我们的样式代码,使用各类其他语言比如 typescript、coffescript 等来书写我们的逻辑脚本。在最终上线时,我们需要把我们的这类文件,转化为能够在线上直接被浏览 器识别的 css 和 js 。

这种使用工程化思维,以工具的形式来进行上述过程的,就是前端方面的工程化。

一句话总结就是,前端工程化,就是在使用工具处理那些与实际业务无关的内容,比如处理 JS 编译、打包、压缩、图片合并优化等等各个方面的工程性代码。

## 前端工程化具体类目

### 包管理工具

对于一⻔成熟的语言来说,在有语言规范的同时,社区或者制定语言规范的组织也会有模块化的规范和 模块存储到平台,每个人都能将自己写好的模块化代码发布到平台上,同时任何人也可以下载公共平台 上其他人的模块化代码。这种模块化的代码我们一般称之为 包(package),平台我们称之为包管理 平台,这种行为我们称之为 包管理(package manager)。

对于 JS 来说,现阶段大家比较熟悉的就是 node.js 环境自带的 npm 工具,npm 全称是 node package manager,就是 node.js 的包管理工具,对于一个符合规范的包来说,我们可以通过 npm publish 发 布包,同样的,也可以通过 npm install 来下载别人发布的包,实现大家的模块复用。

社区中常⻅的包管理工具有,bower,npm 和 yarn。目前还在经常使用的是 npm 和 yarn。

### bower

bower 最早出现是使用在浏览器项目中。安装 bower 命令后,通过 bower install jquery ,就可 以将 jquery 下载到 bower_components 目录中,我们就可以在 html 文件加上 `<script src="bower_components/jquery/dist/jquery.min.js"></script>` 。免去了我们直接从官网下 载然后挪动到项目中的烦恼。

bower 也支持一些配置,我们只需要在项目根目录下增加 .bowerrc 配置即可:

```

    {

      "directory": "app/components/", "timeout": 120000,

      "registry": {

        "search": ["http://localhost:8000", "https://registry.bower.io"

        ]

      }

    }

```

上面的配置主要配置了我们下载后的模块存储目录,下载超时时间和下载的地址等等。 如果想要发布一个 bower 模块的话,需要我们这个项目下配置 bower.json ,然后通过 bower register 命令发布。

bower 内容的介绍比较简单,现在新项目中使用已经不多,包管理工具这一小节的重点是 npm。

### npm

npm 是伴随着 node.js 下载会一同安装的一个命令,它的作用与 bower 一样,都是下载或发布一些 JS 模块。

我们可以使用 `npm --version` 查看安装的 npm 版本,不同版本带有不同功能。 同时,我们可以通过 npm install 来安装一个模块,例如上面的例子中,我们就可以通过 npm

install jquery 来安装 jquery,npm 会将模块安装到 node_modules 目录中。 一个合格的 npm 包,必须拥有 package.json 这个文件,里面有以下几个常⻅字段:

```

name: 包或者模块的名称

version: 版本

main(重要): 默认加载的入口文件

scripts: 定义一些脚本

dependencies: 运行时需要的模块 devDependencies:本地开发需要运行的模块

optionalDependencies: 可选的模块,即使安装失败安装进程也会正常退出

peerDependencies :必要依赖的版本版本

```

其中 dependencies 和 devDependencies 里面的版本号通过 “大版本.次要版本.小版本” 的格式规定。 我们通过 npm install 安装的模块,最终都会在里面进行记录。同时,我们通过 npm install 时, 还会安装这里记录但是 node_modules 中没有的模块。

如果前面带有波浪号(`~`),则以大版本号和次要版本号为主,例如 "~1.3.2" 的版本,最终安装时就会 安装 1.3.x 的最新版本。

如果前面带有插入号(^),则以大版本号为主,例如 "^1.3.2" 的版本,最终安装就会安装 1.x.x 的最新版本。

高版本的 npm 会将所有依赖进行「打平」操作,这样能保证尽可能少的安装相同的模块。 所有下载的模块,最终都会记录在 package-lock.json 完全锁定版本,下次我们再 npm install 时,就会先下载 package-lock 里面的版本。

### yarn

https://yarnpkg.com/ yarn 是一个新兴的包管理工具,他与 npm 有着相似的功能,最大的优势就是并发和快。

我们可以通过 yarn add 来安装一个模块,通过 yarn xxx 来运行 scripts 中的脚本。

### 包管理工具中常⻅面试问题

1. devDependencies、dependencies、optionalDependencies和 peerDependencies 区别

devDependencies 是指使用本地开发时需要使用的模块,而真正的业务运行时不用的内容。

dependencies 是指业务运行时需要的模块。

optionalDependencies 是可选模块,安不安装均可,即使安装失败,包的安装过程也不会报错。

peerDependencies 一般用在大型框架和库的插件上,例如我们写 webpack--xx-plugin 的时候,对于使用者而言,他一定会先有 webpack 再安装我们的这个模块,这里的peerDependencies 就是约束了这个例子中 webpack 的版本。

2. npm 中 --save-dev 和 --save 之间的区别

对于大型项目来说,他们的界限实际上并不清晰。真正会有差异的地方是究竟我们使用哪种方式来安装所有的依赖。

save-dev 和 save 都会把模块安装到 node_modules 目录下,但 save-dev 会将依赖名称和版本写到 devDependencies 下,而 save 会将依赖名称和版本写到 dependencies 下。如果我们使用 `npm -- production install` 这样的命令安装模块的话,就只会安装 save 安装的包。

## 源代码静态检查和格式化工具

静态检查指的是我们在本地写源代码时,我们使用的编辑器对我们所写代码的 提示,检查 和 格式化。 在大型项目中,提示 这一步因人而异大部分不做共同要求,检查和格式化一般会对团队使用的内容进行 约束,以保证大家能写出「正确」的代码和统一的代码⻛格。

对于代码的检查和格式化,在前端发展中比较经典的是 **jslint**,**jshint**,**eslint** 和 **prettier**。 他们基本上都是一类的工具,再细分的话,jslint,jshint 和 eslint 是一类,他们专⻔处理 JS 格式化和静态语法检查,prettier 是另一类,他能处理多语言的格式化。

### eslint

eslint 的官网:https://eslint.org/ 我们以 eslint 为例,只需要在项目中通过 `npm install --save- dev eslint` 安装他,通过配置 **.eslintrc** (runtime config) 我们就可以使用了。配合 eslint 的编 辑器插件,我们就可以在编辑代码时 eslint 对我们的代码进行提示和修复。

通过配置 **eslint** **index.js** 这样的脚本,就可以对脚本文件进行静态校验。

注意这里是 `--save-dev` 因为我们只需要在项目开发过程中使用它而不是运行过程使用这个模块。

### prettier

prettier 官网:https://prettier.io/ ,同样的我们可以配置 prettier 的配置, **.prettierrc** 里面也可以 进行配置,最终搭配 prettier 的 编辑器插件,我们同样能够实现代码编辑状态下的提示和修复。

## 源代码静态检查和格式化工具

静态检查指的是我们在本地写源代码时,我们使用的编辑器对我们所写代码的 提示,检查 和 格式化。 在大型项目中,提示 这一步因人而异大部分不做共同要求,检查和格式化一般会对团队使用的内容进行 约束,以保证大家能写出「正确」的代码和统一的代码⻛格。

对于代码的检查和格式化,在前端发展中比较经典的是 **jslint**,**jshint**,**eslint** 和 **prettier**。 他们基本上都是一类的工具,再细分的话,jslint,jshint 和 eslint 是一类,他们专⻔处理 JS 格式化和静态语法检查,prettier 是另一类,他能处理多语言的格式化。

### eslint

eslint 的官网:https://eslint.org/ 我们以 eslint 为例,只需要在项目中通过 `npm install --save- dev eslint` 安装他,通过配置 **.eslintrc** (runtime config) 我们就可以使用了。配合 eslint 的编 辑器插件,我们就可以在编辑代码时 eslint 对我们的代码进行提示和修复。

通过配置 **eslint** **index.js** 这样的脚本,就可以对脚本文件进行静态校验。

注意这里是 `--save-dev` 因为我们只需要在项目开发过程中使用它而不是运行过程使用这个模块。

### prettier

prettier 官网:https://prettier.io/ ,同样的我们可以配置 prettier 的配置, **.prettierrc** 里面也可以 进行配置,最终搭配 prettier 的 编辑器插件,我们同样能够实现代码编辑状态下的提示和修复。

## ES6 及其他泛 JS 语言的编译

大部分时候,我们不能直接在线上使用 ES6 语法规范的 JS 代码,我们就需要通过工具对 JS 进行编译。 同时,有些项目我们可能会使用 `coffeescript`,`typescript`,`flow`,`elm`,`ocaml` 等可以编译为 JS 语言 的泛 JS 语言书写代码,这就需要在调试或发布时,使用编译工具将对应代码编译为 JS 代码才能直接运行。

在编译过程中,JS 比较常⻅的工具是 **babel**,而其他的语言则对应有自己的编译器,例如 coffeescript 使用 coffeescript 编译器进行编译为 js,typescript 使用 typescript 编译器编译为 js。

### babel

babel 官网为 https://babeljs.io/ ,对于一个项目来说,我们可以通过 `npm install --save-dev @babel/core @babel/cli` 来安装 babel 所需要的工具。

**@babel/core** 是 babel 内部核心的编译和生成代码的方法

**@babel/cli** (command line tool) 是 babel 命令行工具内部解析相关方法 安装了这两个包之后,我们就能够使用 babel 相关方法对代码进行操作,接下来我们需要配置,告诉

babel 我们需要将代码变成什么。

增加一个 babel 的 preset,**preset** 代表的是我们希望编译的结果的预设值。在最新的 babel 工具链 中,统一使用了 `@babel/preset-env` 作为环境预设值。我们安装 `npm install --save-dev @babel/preset-env` 之后,新建 .**babelrc** 里面,通过配置

```js

{

  "presets": ["@babel/preset-env"]

}

```

在 scripts 内定义一个脚本执行 `babel index.js -o output.js` ,我们在 index.js 中写的 es6 语法 就会被编译。

这一步只是编译语法层面的内容,如果我们使用了一些新的方法的话,还需要增加一个 **polyfill**

使用 `npm install @babel/polyfill` 安装了所有符合规范的 polyfill 之后,我们需要在入口文件引入这个模块,就能正常的使用规范中定义的方法了。

## JS 打包工具

对于 JS 这⻔语言的不同环境来说,有 **CommonJS**,**AMD** 和 **ESModule** 这几种常⻅的模块化规范,这几种规范都有自身的缺点。

**CommonJS** 不经处理只能运行在 node.js,**AMD** 不经处理无法运用在各个平台,需要搭配符合 AMD 规范的其他库例如 require.js 一起使用。**ESModule** 虽然从语言层面上解决了规范问题,但是即使经过 babel 编译,也会将 import,export 之类的关键词编译为 CommonJS 的 require 和 exports ,我们还是无法直接在浏览器中使用。

为了能使任何一个模块都能自由的切换所使用的环境,例如在浏览器使用 CommonJS 封装好的模块, 我们就需要经过打包这个步骤。

**browserify** 、 **rollup** 等等工具都是处理诸如此类内容。

### browserify

地址:http://browserify.org/ 我们通过 `npm install --save-dev browserify` 安装 **browserify**, 我们写一个简单的 commonJS 模块,通过 `browserify index.js -o output.js` 命令就可以将 CommonJS 模块化的包转化为通用的任何环境均可以加载的模块化规范。

### rollup

**rollup** 是一个新兴的打包工具,它最先提出一个概念叫 **tree shaking**,他可以移除我们代码中无用其他代码。

通过 ESModule 写的模块,在经过 rollup 处理之后,会对未使用的导出内容进行标记,在压缩过程就 会将这类未使用的内容移除。

## JS 压缩工具

经过编译和打包的 JS 代码,最终要在线上经过压缩处理之后,才能最终在网站上面向用户显示。对于 JS 压缩工具来说,目前有非常多,但用的最多的还是 **uglify** 系列,uglify 最新是版本 3,不同 uglify 的 实现原理和性能都有极大的不同。

### uglify

uglify3 地址 https://github.com/mishoo/UglifyJS2

安装成功之后非常简单,只需要通过 `uglifyjs index.js -o output.js` 就可以输出压缩的结果。 同时我们可以通过添加 **--source-map** 在运行时生成 sitemap 文件,方便我们进行 debug。

## 其他类目工具

### 任务处理工具(gulp/grunt)

上面我们说的所有工具都是针对某一个垂直领域来说的,比如编译、打包、压缩等等,我们需要通过不同的命令去运行和操作我们的 JS 文件。

所谓任务处理工具,就是这一类脚本工具,他们能通过脚本的形式将不同的工具进行组合输出。

流式处理工具比较常说的两个是 **grunt** 和 **gulp**。下面分别介绍一下这两个工具。

### grunt

grunt 官网 https://gruntjs.com/ 。首先通过 `npm install --save-dev grunt` 安装 grunt 工具,新建 gruntfile.js 通过 gruntfile.js 中的配置来让 grunt 做不同的操作。

这里我们安装 `npm --save-dev @babel/core @babel/preset-env grunt-babel grunt- contrib-uglify` 来完整的进行一个项目的构建,通过配置 gruntfile 脚本,我们分别执行了编译、压缩的过程生成最后 js 脚本。

### gulp

gulp 官网 https://gulpjs.com/  同样的我们使用 `npm install --save-dev gulp` 安装 gulp 工具, 新建 gulpfile.js 配置。

我们同样实现相同的功能来重新配置一下 gulp 任务。

gulp 相比于 grunt 来说,配置更加清晰,是一个链式调用的写法。

### 通用处理工具(fis3/webpack)

通用处理工具这里我们的分类是从功能上来讲,具备上面列举类目的多种功能的集合。这里我们列举的几个工具是 fis3 和 webpack。

**fis** 是国内百度公司在早期发布的一款前端通用处理工具(比 webpack 早),fis3 是它的第三代,使用node.js 重写了。

**fis3** 和 **webpack** 他们有个最大的特点就是,他们已经不再是一个普通工具,而是一个具有插件化的系统,有着丰富和完善的社区环境,他们属于前端解决方案 这么一个领域。理论上他们可以做非常多的事情,而不像上面介绍的大部分工具,只能处理某一个垂直分类下的内容。

**webpack** 实际上和 **gulp grunt** 这类的任务处理工具有些类似,但是它本身具有打包的功能,同时也支持通过中间件和插件实现其他领域的功能,最终通过一个命令就能处理完成所有操作。

**webpack** 通过 webpack.config.js 配置,配置 loader 中间件来对不同文件进行操作,同时通过插件化的配置,支持例如压缩等等操作。

使用 fis3 和 webpack,他们更多的是将前面我们讲的所有其他工具融合起来,以一种插件的形式进行加载,从而达到了通用的目的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容