前端工程化


前端工程化

1.前端⼯程化的意义

⼀、本节⼤纲

  • 什么是前端⼯程化
  • 前端的发展史
  • 实现前端⼯程化的要点

⼆、什么是前端⼯程化

⼯程化即系统化、模块化、规范化的⼀个过程。指将具有⼀定规模数量的单个系统或功能部件,按照⼀定的规范,组合成⼀个模块清晰、系统性强的整体。

具体到前端⼯程化就是通过合理的组织、维护、管理 js、css、html、图⽚等前端⽂件,提⾼编码、测试、运维等⼯作的的效率,从⽽完成独⽴前端项⽬的过程。

  • 模块化:通过模块化的开发思维,将功能开发划分。避免代码变量命名冲突,结构清晰,⽅便复⽤与管理。
  • 开发规范:制定统⼀的接⼝规范,编码规范,开发流程等。
  • ⾃动化:⾃动化编译、打包、测试、部署与发布。
  • ⼯具:通过⼯具来辅助模块化、规划化、⾃动化的实现进程,实现有效管理,提⾼效率。

三、前端的发展历史

1 前端⻚⾯在服务端⽣成

这个阶段,⼏乎没有什么前端的概念。⻚⾯代码由服务端⽣成,浏览器只负责将代码接收并展示出来。

代表技术如:Servlet、JSP、PHP、ASP 等。

2 Ajax 带来的前端⾰命

直到 Ajax 技术的出现,才真正的出现了所谓的前端的概念。Ajax 是⼀种在⽆需重新加载整个⽹⻚的情况下,能够更新部分⽹⻚的技术。通过在后台与服务器进⾏少量数据交换,Ajax 可以使⽹⻚实现异步更新。这意味着可以在不重新加载整个⽹⻚的情况下,对⽹⻚的某部分进⾏更新。

Ajax 的出现,将原来只能在服务端完成的⻚⾯渲染动作,移到浏览器端进⾏。在⼀定程度上实现了数据与视图的解耦。⼀定程度上简化了在服务端书写 HTML 代码的复杂性,有效地提⾼了开发效率。

3 NodeJS 带来的前端⾰命

⼤家都知道,NodeJS 的出现,使前端⼈员可以使⽤ JS 代码开发服务端应⽤,会使⽤ JS 的程序员有机会成为全栈⼯程师。

NodeJS 真正带来的⾰命性的核⼼意义在于:它让 Javascript 终于可以脱离浏览器运⾏

这意味着:

  • 前端⼯程可以脱离服务端 Web 应⽤独⽴存在,实现真正意义上的前后端分离,数据与视图的解耦。
  • 基于 NodeJS 创造了 JS 语⾔的独⽴发展的⽣态圈,围绕着 JS 语⾔可以做构建、编译、开发辅助、 集成、脚⼿架等应⽤如⾬后春笋⼀般发展起来,从此为前段⼯程化奠定了基础。

如 VueCLI 项⽬脚⼿架,打包⼯具 Webpack 就是依赖于 NodeJS 环境的。

四、实现前端⼯程化的要点

版本管理、接⼝规范、开发流程规范、测试规范等都是前端⼯程化需要考虑的问题。

我们主要从开发的⻆度去说明前端⼯程化的要点有哪些。

1 组件化开发

  • 代码复⽤及功能复⽤,避免重复造轮⼦,降低程序员的⼯作量。
  • ⽅便代码的组织及管理。
  • 降低代码之间的耦合度,提⾼可扩展性。
  • vue 组件不仅在单功能模块可以多次复⽤,甚⾄可以跨模块、跨项⽬复⽤。

2 模块化开发

模块化就是将⼀个复杂的系统分解成多个独⽴的模块的代码组织⽅式

在很⻓的⼀段时间⾥,前端只能通过⼀系列的 <script> 标签来维护我们的代码关系,但是⼀旦项⽬复杂度提升,这种简陋的代码组织⽅式便会使我们的代码变得混乱不堪。

  • 模块的版本管理。通过别名等配置,配合构建⼯具,可以⽐较轻松地实现模块的版本管理。
  • 提⾼可维护性。模块化可以让每个⽂件的职责单⼀,⾮常有利于代码的维护。

后⾯我们将详细学习前端模块化的实现⽅案,如:CommonJS ,ES6 Module。

3 编译打包⾃动化

优秀的前端技术如⾬后春笋⼀般不断出现,如:Vue,Angular,React,Less,Sass,TypeScrip,然⽽这些技术的编写的代码都⽆法在浏览器直接使⽤,需要我们完成⼀个编译过程,我们希望这个过程是可配置的、⾃动化的。

Webpack 就可以来帮助我们实现⾃动化编译过程。

除此之外,打包⼯具还可以帮助我们实现 JS 压缩、图⽚压缩等⼀系列动作。⼤型前端项⽬,必须这样来实施。

2.前端模块化⽅案

⼀、本节⼤纲

  • 变量冲突&难于管理的依赖
  • 解决⽅案之⽴即执⾏函数表达式 IIFE
  • 模块化解决⽅案有哪些?
  • CommonJS 模块导⼊导出

⼆、变量冲突&难于管理的依赖

在传统的开发模式中,有可能程序员 A 开发 module-a 模块,程序员 B 开发 module-b 模块。 我们⽤代码简单模拟⼀下这个场景,注意两个模块代码中都对变量 flag 进⾏定义:

程序员 A 在完成 module-a 模块之后,开发 module-c 模块,并在 c 模块中使⽤ a 模块的变量和⽅法。:

在⽗⻚⾯内,依次分别引⽤ a、b、c 三个模块的 JS ⽂件

运⾏结果:模块 c 中的 doprint()⽅法没有被执⾏。

这个时候程序员 A 就蒙了,我明明定义了 flag=true ,为什么不打印?

于是程序员 A 就尝试修改 bug,怎么改的呢?

他调整了 JS 模块代码的引⽤顺序, doprint() ⽅法就被执⾏了:

这是为什么?

  • 不同的开发⼈员开发的代码,在同⼀个⻚⾯引⽤,有可能发⽣变量冲突。即变量定义的重复及覆盖。
  • JS 代码是按引⽤顺序解释执⾏的,不同的 script 标签引⽤顺序可能导致代码的执⾏结果出现差异。

⼗⼏⾏代码,三个模块还⽐较容易找到问题所在。

如果是上万⾏代码,⼏⼗个模块,怎么处理模块之间的依赖?解决不同开发⼈员,开发不同模块的变量冲突的问题?这就是本节需要学习的内容。

三、解决⽅案之⽴即执⾏函数表达式 IIFE

在很⻓的⼀段时间内,JS 开发语⾔都没有模块化的概念,也没有相关的语法⽀撑。

在这段时间⾥⾯,程序员是如何解决全局变量命名冲突的问题呢?就是使⽤⽴即执⾏函数表达式 IIFE (Immediately-invoked function expression) 名词很⻓,⽤法很简单:

原理在于:JS 语⾔没有块作⽤域,但是有函数作⽤域,我们把定义的内容放在⼀个函数定义(IIFE)⾥ ⾯。在函数内定义的变量与函数,就只能在该函数范围内使⽤。也可以通过将变量与函数定义在对象⾥ ⾯,并将对象返回的⽅式,供给其他模块使⽤。

下图中 module-c 的代码可以正常打印出:“打印 module-a 的代码”

这种⽅法虽然可以解决全局变量命名冲突的问题,但是仍然⽆法处理模块引⼊顺序依赖的问题,当我们 把 module-c.js 挪到 script 标签引⽤第⼀位时候,代码就会报错。

四、模块化解决⽅案有哪些?

  • AMD (Asynchronous Module Definition)提供了异步加载的功能的 JavaScript 模块化规范, RequireJS 是实现了该规范的类库。
  • CMD (Common Module Definition) 最初是由阿⾥的⽟伯提出的,同 AMD 类似,使⽤ CMD 模块也需要使⽤对应的 SeaJS 类库。
  • CommonJS,这是⼀种被⼴泛使⽤的 Javascript 模块化规范,⼤家最熟悉的 Node.js 应⽤中就是采⽤这个规范。
  • UMD,通⽤模块定义 (Universal Module Definition),使⽤该模块化⽅案,可以很好地兼容 AMD, CommonJS 等模块化语法。
  • ES6 Modules,ES6 全称 ECMAScript 6.0 ,是 JavaScript 的版本标准。也就是 javascript 官⽅的亲⼉⼦,也就是说未来会受到众多浏览器的⽀撑!即使⽬前浏览器还⽆法完全⽀持 ES6 modules,我 们还是可以通过 Babel 等转译⼯具进⾏编译使⽤ ES6 Modules。

主流打包⼯具 Webpack 是依赖于 NodeJS 的,所以我们很有必要学习 CommonJS 模块化管理。

另外,ES6 Modules 模块化解决⽅案时⼀定要学的。随着 ES6 Modules 的推出,AMD、CMD、UMD 也逐渐退出了历史舞台。

五、CommonJS 模块导⼊导出

CommonJS 模块导出

CommonJS 模块导⼊

注意:CommonJS 的模块管理⽅案是依赖于 NodeJS 环境的,所以上⾯的代码是⽆法在浏览器端执⾏的。

我们后⾯⽤到的打包⼯具 Webpack,是依赖于 NodeJS 使⽤ CommonJS 做模块化管理⽅案的,所以我 们有必要学习⼀下 CommonJS 语法。

3.ES6 模块化⽅案详解

⼀、本节⼤纲

  • ES6 模块数据导出导⼊的基本⽤法
  • VSCode 集成简易本地服务器⽤于调试
  • export default 导出导⼊语法

⼆、ES6 模块数据导出导⼊的基本⽤法

1 module-a 模块内容导出

下图是 module-a.js 的⽂件变量、函数、类的导出的两种⽅式:

  • 第⼀种⽅式,先定义变量及函数、类,然后使⽤ export {……} 统⼀导出
  • 第⼆种⽅式,在定义变量及函数、类的时候就使⽤ export 关键字导出

2 module-c 模块内容导⼊

module-c.js 中使⽤ import {……} 语法导⼊变量、函数、类,from 关键字后⾯跟上导⼊模块⽂件的路径。

如果导出的内容⽐较多,我们可以使⽤ import * as 的语法为导出内容起⼀个别名,如下图中的 moduleA。然后在导⼊之后的使⽤过程都要⽤: moduleA. 的语法引⽤变量、函数、类。

3 在 html 中引⽤模块

<script src="module-c.js" type="module"></script>

尽管我们使⽤到 a 和 c 两个模块,但是我们只引⼊ module-c , js 就可以了。因为 c 模块中 import 了 a 模块的内容,所以 c 依赖于 a ,浏览器会⾃动根据 ES6 规范帮我们处理模块之间的依赖关系。

注意:⽬前⼤部分的现代浏览器都可以⽀持 ES6 的模块化的语法,但是上⾯的代码还不能直接在浏览器运⾏。因为想 chrome 或 firefox 等浏览器出于安全考虑,禁⽌通过 file:// 的⽅式直接在本地⽂件中使⽤ ES6 的模块化代码。需要把上⾯的代码部署到 nginx 等 web 服务上才可以被正确访问。

三、 VSCode 集成简易本地服务器⽤于调试

上⽂中的模块化代码只有部署在 Web 服务容器⾥⾯才能被正确访问,那我们就真的去安装⼀个 nginx 或者 apache 么?然后把⽂件 copy 过去才能运⾏?这样很影响开发效率。为了解决这个问题,我们可以使⽤开发⼯具 VScode 集成⼀个简易的 live-server ,⽅便我们的开发与调试。

  • ⾸先要把 Nodejs 和 npm 包管理⼯具安装好
  • 然后,打开 VS Code ,然后点击“查看” =》“调试控制台”或者直接按快捷键 ctrl+shift+y 调出控制⾯板
  • 在终端输⼊如下命令安装 live-server
npm install -g live-server
  • live-server 安装好之后,在终端输⼊命令 live-server

这时候默认浏览器,会⾃动弹出并显示当前⽬录下所有的⽂件。

和在 VS Code 已经安装的插件 LiveServer ⼀样的效果

我们访问引⽤了模块 c 的 html ⽂件,F12 查看控制台输出,如下:

四、export default 导出导⼊语法

有些时候⼀个模块中包含⼀个默认功能。我们并不想为这个变量、函数、或类起名,我们希望它作为默认的内容导出,由导⼊者给它命名。这个时候就可以使⽤ export default 语法。

注意:export default 在同⼀个模块⾥⾯只能⽤⼀次。也就是说,只能有⼀个默认导出。

导⼊语法如下:

4. webpack 的简介与安装

⼀、本节⼤纲

  • 为什么使⽤ webpack
  • webpack 的安装

⼆、为什么使⽤ webpack

原因⼀:模块化打包

经过前⾯两节的学习,相信⼤家已经知道模块化开发的意义。在实际使⽤模块化的过程中会遇到⼏个问题:

  1. 如 CommonJS 这种模块化⽅案代码,浏览器是⽆法直接执⾏的。
  2. 即使 ES6 的模块化导⼊导出代码可以被⼤部分浏览器所⽀持,但是仍有少部分的浏览器⽆法⽀持。
  3. 模块之间有依赖关系,如何⾃动将有依赖关系的模块打包。

那怎么办呢?Webpack 可以帮我们把 CommonJS 和 ES6 的模块化语法,转换为浏览器可以解释执⾏的语法。也就是说,我们既可以使⽤到模块化⽅案带给我们的好处:避免代码变量命名等冲突,结构清晰, ⽅便复⽤与管理,也可以通过 Webpack 来满⾜浏览器的兼容;Webpack 可以帮我们解析模块间的依赖 关系,并正确打包。

原因⼆:兼容更多的前端技术

纵观近些年的软件技术发展趋势,前端的发展可谓是⽇新⽉异,各种新技术及新规范层出不穷。

  • JS 规范:ES6、ES7、ES8
  • CSS 预处理器:LESS、SASS、Stylus、PostCSS
  • 三⼤前端框架:Vue、React、Angular
  • JS 语⾔改进:CoffeScript 和 TypeScript

如果你还不熟悉这些名词,也不要紧。它们实际上还是 JS 和 CSS ,只不过加⼊了⼀些增强语法!这些技术的出现都是为了提⾼前端的开发效率,提升开发体验。

但是有⼀个问题出现了,虽然这些技术都能⼀定程度上规范我们的代码,提升开发效率,改进开发体验,但是它们的语法都⽆法被浏览器直接解释执⾏!这就需要我们使⽤ Webpack,将这些技术的代码做⼀个翻译,将它们翻译成浏览器可以执⾏的代码。

原因三:更多的功能

  • 我们希望⽣产发布的代码是经过压缩的、经过混淆的、难以被识别的。
  • 我们希望 Webpack 帮我们管理⼩素材图标,雪碧图。
  • 我们希望图⽚以 base64 字符串格式存储。

还有很多细节的前端需求,Webpack 都可以帮助我们实现。

三、webpack 的安装

1 安装 NodeJS

需要安装 NodeJS ⾮常容易,NodeJS ⾃带包管理⼯具 npm。

NodeJS、Webpack、npm、Vue 是什么关系?

  • ⾸先我们学习 Vue,实际上是学习 JS 语⾔⼀种框架。这个框架⾥⾯有⼀部分语法是不被浏览器兼容的,⽐如“.vue”单⽂件,我们就需要 Webpack 来帮我们做⼀个翻译,翻译后的代码浏览器可执⾏。

  • 我们在开发前端项⽬的过程中,可能会⽤到各种 JS 类库。npm ⼯具可以帮助我们来管理这些包, 打包⼯具 Webpack 本身就是⼀个 js 的类库,需要使⽤ npm 帮我们管理。

  • Webpack 是⼀个 JS 类库,除了帮我们做翻译,还帮我们打包静态资源。我们都知道 JS 代码是需要执⾏环境的,浏览器环境或者 NodeJS 环境。在服务端运⾏所以 Webpack 依赖于 NodeJS 环境。

2 检查 NodeJS 版本

NodeJS 安装好之后,查看⼀下版本node -v ,要求最低版本 8.10+,低于该版本运⾏ VueCLI 可能会有问题。

3 安装 webpack

  • 全局安装的⽅式(参数-g)
npm install webpack -g
  • 局部安装的⽅式
npm install webpack@5.70.0 --save-dev
  • 全局安装和局部安装的区别在于:全局安装的版本对所有的项⽬⽣效,局部安装的版本只对当前⽬录下项⽬有效。
  • 局部安装的版本的优先级⾼于全局安装,也就是说如果项⽬指定的局部安装的版本,全局安装的版本对该项⽬⽆效。
  • @版本号表示安装指定版本的 Webpack 类库
  • –save-dev 表示该类库只是在编译期间使⽤,打包结果中不包含该类库。也就是说 Webpack 是给开发⼈员⽤的,浏览器运⾏期不需要这部分代码。与此相反,Vue 就是⼀个运⾏时的类库,使⽤ – save 参数安装(没有 dev)。

5.⼿搭脚⼿架-Webpack 初体验

⼀、本节⼤纲

  • 项⽬初始化
  • 开发⼀个 js 模块化 demo
  • 使⽤ webpack 打包 js 模块
  • 总结

⼆、项⽬初始化

使⽤ npm init 命令,初始化前端项⽬。

最后输⼊ yes,回⻋

项⽬初始化之后会在当前⽬录下⽣成⼀个⽂件:package.json (项⽬配置信息管理⽂件),内容如下:

{
  "name": "webpack-template",
  "version": "1.0.0",
  "description": "⼿动搭建脚⼿架",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "syhan",
  "license": "ISC"
}

局部安装 Webpack

npm install webpack@5.70.0 --save-dev

Webpack 安装完成之后,在 package.json ⾥⾯⾃动⽣成⼀段配置(不要⼿动写这段配置)。这段配置表示该项⽬使⽤ Webpack 进⾏模块化静态资源打包,并且 Webpack 是⼀个开发期间的 (dev) 依赖包,不做⽣产上线使⽤。

三、开发⼀个 js 模块化 demo

本节我们来开发⼀个简易的,使⽤模块化思想的 demo,然后使⽤ Webpack 进⾏打包。

1 新建⽬录及⽂件

  • dist ⽬录,⼿动新建,⽤于存放打包的结果
  • src ⽬录,⼿动新建,⽤于存放被打包的资源⽂件:JS、CSS 等
  • index.html,⼿动新建,项⽬⾸⻚
  • node-modules ⽬录,这个⽬录是⾃动⽣成的不需要⼿动新建,npm 包管理⼯具引⼊的包都在这个⽬录下存放

2 简单的模块化依赖 demo

src ⽬录新建 player.js

src ⽬录新建 main.js,引⼊ player.js

如果模块化知识掌握了,上⾯的代码很容易理解。有⼏个关键点:

  • play.js 模块通过 CommonJS 模块化语法导出了⼀个对象 players

  • main.js 项⽬作为⼊⼝ js ⽂件,通过 CommonJS 模块化语法引⼊了 player 模块,并将 players 变量打印

四、使⽤ webpack 打包 js 模块

node_modules/.bin/webpack src/main.js dist/bundle.js

我们使⽤了如下命令进⾏⽂件打包

node_modules/.bin/webpack src/main.js dist/bundle.js

  • 为什么要使⽤ node_modules/.bin 下⾯的 Webpack ?因为这⼀个 Webpack 是我们局部安装的 3.6.0 版本的 Webpack 。如果不加前缀路径,直接使⽤ Webpack 命令,使⽤的是全局安装的 Webpack 版本。
  • 为什么只对 main.js 打包,player.js 不需要打包么?因为 main 模块依赖于 player 模块,Webpack 会根据依赖关系,⾃动将 player 模块进⾏打包,不需要我们⼿⼯指定。
  • dist/bundle.js 是最终的打包⽂件结果,我们可以在 index.html ⾥⾯通过 script 标签引⽤这个 js。

访问 index.html 结果如下

五、总结

在上⾯的打包过程中,Webpack 帮我们做了两件关键的事:

  • 翻译:将 CommonJS 模块化导⼊导出语法(浏览器不认识),转换成浏览器可以执⾏的代码。
  • 处理打包依赖:存在依赖的 JS 模块,都会⾃动被打包到⽬标⽂件中,不需要显式声明。

也就是说,我们以后想使⽤什么样的模块化语法都可以,不论你是 CommonJS 还是 ES6,或者 AMD、 CMD,虽然这些模块化的语法浏览器可能不认识或不全认识,但是有了 Webpack 它会帮我们做翻译, 我们使⽤翻译之后的 bundle.js 就可以了。

现在讲的内容好⽐是教你如何组装电脑,这个过程是有些复杂的。如果你想简单,就去买组装好的品牌 电脑,但是如果你不了解组装电脑的过程,品牌电脑坏了你不会修。

如果你只是想快速搭建 Vue 项⽬,直接学 VueCLI 就好了;如果你想把 Webpack 结合 Vue 打包的原理搞清 楚,以后能够游刃有余地调整配置,这些就是必学内容。

6.⼿搭脚⼿架-Webpack 基础配置

⼀、本节⼤纲

通过 npm run build 打包命令,学习 Webpack 基础配置

⼆、Webpack 基础配置

上⼀节我们在项⽬⽬录下,使⽤如下命令对项⽬进⾏打包。

node_modules/.bin/webpack src/main.js dist/bundle.js

来看这条命令有⼏个关键点 :

  • ⼀是 Webpack 命令的相对路径
  • ⼆是⼊⼝⽂件 main.js
  • 三是打包出⼝⽂件 bundle.js

这种使⽤⽅式是通过命令⾏的⽅式传递打包参数,我们还可以使⽤配置⽂件。

Webpack 配置⽂件的默认名称是:webpack.config.js,我们⼿动在项⽬根⽬录下⾯创建这个⽂件,内容如下:

出⼝⽂件路径 output.path 必须是绝对路径。

上⽂中的路径有⼀个问题:我们把绝对路径写死了,这样⼀旦我们移动项⽬所在⽬录或者其他⼈使⽤我们的项⽬,就⽆法正确识别路径打包。

做如下修改:

  • ⾸先我们引⼊ NodeJS 提供的模块 path
  • path.resolve ⽅法作⽤是完成路径拼接,__dirname 是全局变量,代表当前⽂件所在路径
  • __dirname + “dist” 就是 bundle.js 出⼝⽂件的完整的绝对路径

到此,我们可以使⽤如下命令打包:

也就是说不⽤再使⽤命令⾏参数了,把⼊⼝、出⼝⽂件参数写在了配置⽂件⾥⾯。

现在,离我们使⽤ npm run build 命令来实现打包操作还差⼀步。

在项⽬配置管理⽂件 package.json ⾥⾯加⼊如下⼀⾏代码:

加⼊这⼀⾏代码的意义在于:当我们输⼊ npm run build 命令的时候,会⾃动查找 package.jso n ⽂件 script.build 脚本,从⽽执⾏ webpack 命令。并且这个 webpack 命令不是全局的,默认就是 node_modules/.bin 下⾯局部安装的 webpack 版本,所以我们就不⽤再加上前缀路径。

package.json 配置⽂件和 webpack.config.js 配置⽂件区别?

package.json 是项⽬配置管理⽂件,⽤于记录当前项⽬的基本信息、依赖类库、项⽬脚本等。⽽ webpack.config.js 是模块化资源打包配置⽂件,关于静态资源打包操作的细节配置都在这个⽂件⾥⾯ 配置。

现在我们就可以使⽤ npm run build 命令来实现项⽬模块化资源⽂件打包,这和 Vue 脚⼿架实现的原理是⼀致。

或者直接点击如图按钮

7.⼿搭脚⼿架-CSS ⽂件处理打包

⼀、本节⼤纲

  • 什么是 Webpack 的 Loader
  • CSS ⽂件处理
  • CSS 预处理器⽂件处理

⼆、什么是 Webpack 的 loader

Webpack 可以帮我们做翻译⼯作,也就是说⼀些新技术或新规范的语法,浏览器⽆法直接解析。以⽬前浏览器的兼容标准,我们通常可以认为符合 ES5 规范的 javascript 是我们前端语⾔届的“普通话”。其他所有的⽅⾔都需要被翻译为“普通话”,才能被浏览器识别。

这时我们就需要 Webpack 来帮我们把这些新技术或新规范语法,翻译成浏览器可以识别的语法。这 个“翻译官”在 Webpack 中就叫做 loader

⼀些常⻅的翻译⼯作如下:

  • CSS ⽂件模块打包到 JS ⽂件
  • SCSS、LESS 翻译成 CSS 代码
  • ES6 代码或 TypeScript 翻译成 ES5 代码
  • JSX 或者 Vue ⽂件翻译成 JS 代码
  • 图⽚⽂件转 base64 代码打包到 JS ⽂件

不同的翻译⼯作,需要使⽤不同的 loader,使⽤过程需要两步:

  • 通过 npm 安装对应的 loader
  • 在 webpack.config.js 的 module 中进⾏配置

三、CSS ⽂件处理

符合 ES5 规范的 Javascript 是我们前端语⾔届的“普通话” ,所以 CSS ⽂件也要打包到 JS ⽂件⾥⾯才能被正确的使⽤。

下⾯我们来学习如何处理 CSS ⽂件。

⾸先调整 src ⽬录,新建 2 个⽂件夹 css 和 js 。

将 player.js 移⼊ js ⽂件夹,新建⼀个 common.css 放⼊ css ⽂件夹。

common.css ⽂件内容如下,设置⼀个背景⾊

body {
  background-color: rgb(133, 166, 216);
}

main.js 中将 common.css 当做⼀个模块引⼊。

require("./css/common.css");

这时,我们调⽤ npm run build 打包,就会出现如下错误:**需要合适的 loader 去处理 css ⽂件类型**

所以需要我们安装 css-loader,实际上是安装 css-loaderstyle-loader

css-loader 帮助我们加载 css ⽂件,style-loader 帮我们把样式嵌⼊到⽂档中。

npm install --save-dev css-loader style-loader

然后在 webpack.config.js 中加⼊如下配置:

注意:⼀定是先写 ‘style-loader’ ,后写 ‘css-loader’ 。

原因是这些 loader 的执⾏顺序是从右到左(数组尾部向数组头部)依次执⾏的。css-loader 负责加载 css 所以先执⾏,style-loader 负责嵌⼊ css 到⽂档⾥⾯所以需要后执⾏。test 的配置使⽤正则表达式, 表示当⽂件名匹配 .css 的时候使⽤ use 配置的 loader 进⾏处理。

按照以上步骤处理之后的 css ⽂件,再调⽤打包命令就可以正确的打包到 bundle.js ,并且应⽤了该 JS 的 html 的 body 区域被渲染为蓝⾊背景。

三、CSS 预处理器⽂件处理

CSS 预处理器是⼀种专⻔的编程语⾔,⽤来为 CSS 增加⼀些编程特性( CSS 本身不是编程语⾔)。它能让你的 CSS 具备更加简洁、可读性更加、层级关系更加明显、更易于代码的维护等诸多好处。

它的缺点在于:浏览器⽆法直接解析,所以需要我们使⽤ Webpack 来做翻译⼯作。

常⽤的 CSS 预处理器有:Sass、Less、stylus 等。

我们以 less 为例,在 src/css ⽬录下新建⼀个 less ⽂件:lessdemo.less

上⾯代码的含义是定义变量 fontColor ,在 body 标签样式中引⽤这个变量。

然后在 main.js 中引⼊ less 模块:require('./css/lessdemo.less')

下⾯来聘请翻译官:less-loader。

npm install --save-dev less-loader less

然后修改 webpack.config.js 配置⽂件。在 module.rules 段加⼊如下配置:

同样注意:loader 的配置顺序(数组尾部->数组头部),先执⾏ less-loader 将 less 代码转换为 css 代码。然后再使⽤ css-loader 和 style-loader。

注释掉 main.js 中引⼊ css 的那⼀⾏,重新执⾏ npm run build ,index.html 效果

8.⼿搭脚⼿架-图⽚资源打包

⼀、本节⼤纲

  • CSS 中图⽚⽂件处理 url-loader
  • 独⽴图⽚⽂件处理 file-loader
  • 图⽚⽂件名称格式化
  • JS ⽂件以模块化⽅式引⽤图⽚资源

⼆、CSS 中图⽚⽂件处理

上⼀节我们学习了如何将 CSS 及 CSS 预处理器代码“翻译”并打包。

其实我们还忽略了⼀种情况,如果 CSS ⽂件中包含有图⽚,该如何打包?

我们来准备 2 张图⽚:less10.jpg 和 more10.png(⼀个⼩于 10 kb,⼀个⼤于 10 kb),然后将这两张图⽚放⼊ src/image ⽂件夹。

然后修改 common.css ⽂件,加⼊背景图⽚

这时候如果直接执⾏打包命令会报错,我们需要安装处理图⽚的 url-loader ,使⽤如下命令:

npm install --save-dev url-loader

然后配置 webpack.config.js ,添加对 css 中图⽚的处理策略:

  • test 配置⽤于匹配处理图⽚的⽂件后缀名
  • use.options.limit:10240 表示如果图⽚⽂件⼩于 10 kb,就把它打包到结果⽂件 bundle.js.

由于我们在 css ⾥⾯引⽤的是 less10kb.jpg ,所以执⾏打包命令 npm run build 之后,图⽚⽂件被以 BASE64 编码的形式打包到最终的结果⽂件 bundle.js ⾥⾯。如下:

通过浏览器访问,能够正确的把背景图渲染为 less10kb.jpg。

三、独⽴图⽚⽂件处理 file-loader

上⾯⼀⼩节,我们处理了 CSS ⽂件中的图⽚,假如图⽚⼩于 10 kb ,我们就把图⽚转换为 Base64 编码打包到 bundle.js ⽂件中。如果图⽚⽐较⼤,打包到 JS ⽂件会⽐较臃肿,我们希望图⽚⽂件独⽴于 JS ⽂件存在并可以被使⽤,该怎么办呢?

我们就需要使⽤到 file-loader ,⾸先来安装它

npm install --save-dev file-loader

然后将上⽂中 common.css ⽂件中的 less10kb.jpg 换成 more10kb.png 。然后执⾏打包命令,得到如下结果:

dist ⽬录下多出来⼀个图⽚⽂件,打开看⼀看和 more10kb.png ⼀样。也就是说,当⽂件⼤于 10 kb 的时候,webpack 在打包的时候通过 file-loader 帮我们处理。处理完成之后,图⽚可以脱离于 bundle.js 存 在,并且可以被 bundle.js 正确调⽤。

但是,我们发现这时访问浏览器,more10kb.png 并未被正确渲染为背景图⽚。需要我们在 webpack 配置⽂件加上如下的配置才可以:

  • publicPath 表示可以被公开访问的独⽴资源⽂件存放的⽬录。

四、图⽚⽂件名称格式化

在处理图⽚⽂件过程中,file-loader 帮我们把图⽚进⾏了重命名。⽂件名实际上是⼀个 32 位的 hash 值, Webpack 为什么要这么做呢?因为这样可以避免浏览器图⽚缓存,也就是每次打包结果的图⽚名称 hash 都是不⼀致的。这样做⽤户通过浏览器访问服务端,就可以避免同名⽂件缓存问题造成⽤户看到的还是 更新之前的图⽚。

但是这样也造成了⼀个问题:我们通过⽂件名⽆法准确的查找图⽚,那我们怎么解决这个问题呢?我们 可以⾃定义,让图⽚⽂件名既有⼀定的随机性,⼜有⼀定的规律可寻。我们可以在 options 中添加上如下 选项:

  • img: ⽂件要打包到⽬标⽂件夹
  • name:图⽚原始⽂件名
  • hash:8:8 位的 hash 值,保证⼀定的随机性
  • ext:使⽤图⽚原来的扩展名

五、JS ⽂件以模块化⽅式引⽤图⽚资源

我们将 CSS 中的图⽚⽂件进⾏了资源打包。那么我们如何在 JS ⽂件中,以模块化的⽅式使⽤图⽚⽂件呢?

⾸先我们要明确⼀点,在 JS 模块化编程⽅案中,每⼀个资源⽂件都是⼀个模块。

明⽩了这点,相信我们就清楚该怎么做了:

Webpack 可以正确的帮我们处理图⽚⽂件模块的编译、打包。

9.⼿搭脚⼿架-JS 规范兼容 babel

⼀、本节⼤纲

  • ECMAScript 是什么
  • babel 是什么
  • babel-loader
  • 深度兼容

⼆、ECMAScript 是什么

ECMAScript,简称 ES,是由 Ecma 国际(前身为欧洲计算机制造商协会,英⽂名称是 European Computer Manufacturers Association)按照 ECMA-262 和 ISO/IEC 16262 标准制定的⼀种脚本语⾔规 范。Javascript 是 ECMAScript 规范的实现,ECMAScript 是 javascript 的标准。那么都有哪些常⽤的标 准呢?

⼤名 ⼩名 备注
ECMAScript 5 ES5 兼容绝⼤部分浏览器
ECMAScript 2015 ES6 兼容绝⼤部分现代浏览器
ECMAScript 2016 ES7 浏览器兼容性⼀般
ECMAScript 2017 ES8 浏览器兼容性⼀般

我们说过 ES5 是我们⽬前前端 JS 语⾔届的“普通话”,也就是说 ES5 可以被⼤部分浏览器兼容。但是 ES5 ⾯向开发⼈员不够友好,语法繁琐并且难于理解,特别是原型、对象、集成;也没有⼀些新的 JS 特性,如 Promise 的⽀持。

三、babel 是什么

Babel 是⼀个 javascript 编译器,主要⽤于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运⾏在当前和旧版本的浏览器或其他环境中。

中⽂⽹站地址:https://www.babeljs.cn/

注意看下⾯的这个动图,左侧是转换前的 ES6+ 语法,右侧为转换之后 ES5 的语法。

四、babel-loader

之前学到,css 打包转换我们使⽤ css-loader,图⽚打包转码使⽤ url-loader。

那么我们将 ES6+ 的语法转换为“普通话” ES5 使⽤的就是 babel-loader。

⾸先来安装它:

npm install --save-dev babel-loader babel-core babel-preset-env

可以看到除了 babel-loader,还安装了 babel-core,因为前者对后者有依赖关系。

babel-preset ⽤于识别语法规范,babel-preset 有多项选择安装,⽐如 es2015, es2016, es2017, env。

⼀般⽤的⽐较多的是 babel-preset-env ,env 表示兼容 ES2015+ 的语法。

配置 webpack.config.js 中的 module.rules 代码段:

  • test 中正则表达式表示只针对 js ⽂件做处理
  • exclude 表示不对 node_modules ⽬录下的⽂件做处理,这下⾯ JS ⽂件不是我们写的
  • 使⽤ env 设置:表示兼容 ES2015+ 的语法
  • targets.browers 表示兼容使⽤率⼤于 1%,并且兼容浏览器最新 2 个版本,并且不兼容 IE8 及以下浏览器

然后使⽤ npm run build 打包,打包之后结果如下。我们原始代码中定义变量使⽤的是 let 关键字, 被转换为 ES5 规范的变量定义关键字 var。

五、深度兼容

虽然使⽤ babel-loader 帮助我们⼀定程度上实现了⾼级别 ECMAScript 规范的浏览器兼容。但是⼀些 JS 新特性仍然⽆法被⽀持,⽐如 Promise、新数据结构 Set 等语法。还需要安装如下插件:

npm install babel-runtime --save npm install babel-plugin-transform-runtime --save-dev

通常,为了在编译过程中做到深度兼容,我们将 babel 配置独⽴出来⼀个⽂件叫做:.babelrc (注意⽂件 名前⾯带⼀个点),配置内容如下:

{ "presets": ["babel-preset-env"], "plugins": ["transform-runtime"] }

有了 .babelrc ⽂件,就不需要在 webpack.config.js ⾥⾯配置 loader 的 options 选项了,全都配置在 .babelrc ⽂件夹⾥⾯。

我们在 main.js ⾥⾯加⼊如下的代码(新特性语法 Set ):

let baz = new Set([1, 2, 3]);
console.log(baz);

可以被正确编译及打包,执⾏也正确,Set 可以被正确打印。

在这⾥只讲解了 90%以上的代码兼容性场景,在实际的项⽬开发中,如果你使⽤的语法⽐较新,还有可能遇到新的 ECMAScript 规范兼容问题,可以查找 babel ⽂档解决。也建议⼤家不要花太多时间深度学习 新语法,⽬前前端开发者掌握 ES6 差不多也就够⽤了。


文章作者: Syhan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Syhan !
评论
  目录