Web 前端这几年进化速度之快让人咂舌。很多前端工程师都不禁吐槽“学不动了”。如今已经不是 HTML、CSS、JS 前端三剑客仗剑走天下的时代了。
以笔者的亲身经历举例。笔者在 2011 年左右进入前端这个行业。当时工作的主要内容是,将设计稿切图转成静态页面,然后用 jQuery 插件实现一些页面的轮播图、跑马灯等交互效果。最后使用后端的模板语言如 smart、velocity 等将静态页面添加页面逻辑,代码交给服务端同学完成上线。整个过程中,JS 框架以 jQuery 为主,CSS 顶多用一下 Less。
再来看一下如今的前端工作方式,以笔者所在的快狗打车前端团队为例进行说明。前端的开发框架以 Vue 为主,使用 Webpack 解决接口 mock、代码检查、代码编译、构建、压缩、添加版本号、部署等全流程的工作。涉及到的技术点有 Vue、Vuex、ESlint、stylelint、mock、Webpack、Sass、PostCSS 等。对前端的要求相比几年前已经从单纯的 JS、CSS 问题变成了更多工程化为主的问题。
前端工程化是一个很大的话题,甚至到现在都没有一个准确的定义。笔者对前端工程化的理解是:一切能提升前端开发效率,提高前端应用质量的手段和工具都是前端工程化。
在前端领域越来越繁荣,越来越复杂的今天,学习前端工程化又能给我们带来哪些好处呢?
极大提升开发效率
前端工程化的演进可以极大地提升开发效率。前端发展到现在,社区涌现出大量的优秀框架和工具,得以将前端工程师从繁重的工作中解脱出来。
举个例子,同样地给一个 dom 元素绑定一个 click 事件,使用纯 JS 可能这样做:
document.getElementById('myDom').addEventListener('click', function(e){ // do some thing})
我们引入 jQuery 的情况下,就简单了许多:
$('#myDom').click(function(){ // do some thing})
如果在 Vue 中,既简单又清晰:
// ...methods: { doSomething: function () { // do some thing }}
如果有大量的事件绑定,没有开发工具的支撑,将有大量重复的代码需要写,想想就头疼。效率之低下可见一斑。
为了减少请求数,前端开发者通常会把大量尺寸较小、细碎的小图片合并成一张大的透明的雪碧图,在 CSS 中通过设置元素的 background-position 来使用图片。如果是完全手动去拼图、测距,将耗费大量的时间。但在雪碧图插件 webpack-spriteSmith 的帮助下,小图可以自动拼成雪碧图,并生成对应的 CSS 样式,插件能帮助我们处理这种毫无技术含量的体力活,效率加倍。
再举一个例子,没有前端脚手架的情况下,如果从零开发一个项目,需要花费大量的时间去初始化项目,比如安装各种 npm 包、配置各种 Webpack 的 loader、配置热加载。如果碰到环境搭建不顺利的情况,还需要花费很长时间去排查问题,单单一个项目初始化的动作就足以耗费一两天的时间。有脚手架工具的情况下,只需要简单的一个初始化命令,2 分钟的时间就可以完成项目的初始化。前端只需要聚焦到业务开发本身,效率大幅提升。
降低大型项目的开发难度
首先前端工程化中提倡模块化、组件化。模块化的思想将大型项目的功能进行分解,分拆成一个个独立的模块。每个模块的开发难度直线下降。同时基于版本控制工具 Git,多个开发者可以并行开发,提升开发效率。项目在后期迭代的时候,由于每个模块相对独立,耦合性极低,一个功能的调整往往只需要修改其中的一个模块就可以,风险可控。不至于出现改动一处代码,引发全局问题的情况。
其次,前端工程化提倡用完善的流程规范和代码规范来保证大型应用的质量和可维护性。比如通过 ESlint、stylelint 对代码进行自动校验,通过评审、详细设计、开发、联调、测试、上线等每个环节的控制,确保项目的高质量和按时交付。向主分支合并代码必须经过 code review。流程规范确保了大型项目质量和可维护性的同时能够如期交付。
更易获得面试官青睐
依稀记得六七年前去面试,遇到面试题大概是这种风格:“如何实现水平垂直居中”,“js 事件委托的原理是什么”,“常见的 css hack 方式有什么?”,“\$(function(){})与 window.onload 有什么区别?”
今天面试遇到的面试题大概是这种风格:“能讲下 Vue 实现双向数据绑定的原理吗?”,“Webpack 中如何配置 Babel?”,“promise 和 await/async 的区别是什么?”
如果想进入大公司工作,前端工程化更是需要具备的基本素质。大公司的业务往往非常复杂,而且对稳定性的要求极高。与之相对应的前端工程化程度很高,各种配套的基础建设很成熟。比如美团点评体系化的工程化方案、移动组件库 Vix、自动化测试工具 Freekite、Hybrid 功能体验的解决方案 Titans 等。想得到这些公司的青睐,候选人需要在前端工程化领域有较深的积累。
前端的岗位技能已经发生深刻的变化。有人甚至戏言成前端工程师为前端配置工程师。
fouber(张云龙)曾经在自己的博文中说:
"前端是一种技术问题较少、工程问题较多的软件开发领域。”
所以前端工程化是每个现代前端人应该必备的技能。
课程适用人群初中级前端工程师
对于初级中级前端工程师来说,由于工作经验较少,技术的宽度和广度都不足,对前端工程化的认知其实也是不够的,一上来整体掌握前端工程化肯定是有困难的。对于这部分同学来讲,首要的事情要学会去"用”,循序渐进地去了解其中的原理。例如在开发之余,自己学一学如何实现一个简单的脚手架工具,了解一下日常开发必备的脚手架是如何实现的,以点带面地学习里面用到的技术点。
期望晋升的前端工程师
前端工程化能力也是一个资深前端的必备技能。工作好多年了,如果连前端工程化都知之甚少,甚至连一个基本的脚手架都不能自己搭建,这是不及格的。如果将来带团队,怎么能指导团队同学,怎么能带领团队进步呢?如果去参加公司的晋升,也是没有说服力的。因为想要晋升高 T,必然需要在效率和性能优化等方面有深厚的积累和贡献。
所以,无论你是处在什么阶段,深入了解一下前端工程化都是极有必要的。
课程介绍前端应用越来越复杂,对前端工程化的要求越来越高。脚手架作为目前前端工程化的一个重要的组成部分,在开发过程中扮演了至关重要的角色。现在几乎所有主流前端几框架都有自己配套的脚手架,无疑给开发者带来了极大的便利。但正如上文所述,很多开发者由于本身入行较短,或者很多工作多年的开发者由于公司业务的原因,没有机会接触到前端工程化领域。
不了解没有关系,重要的是大家看到这篇教程,说明已经意识到前端工程化的重要性了,这就不晚。
本课程以 Vue 为例,结合笔者在团队中的工程化实践,带领大家从零开始搭建一个脚手架,将搭建脚手架用到的技术点逐一拆解,希望大家看完后,每个人都对脚手架和工程化思想有个较深入地理解。
点击了解《透视前端工程化》。
课程特色
- 知识具有系统性。从流程规范、开发、联调、测试、构建、部署各个环节系统地讲解搭建一个脚手架所用到的前端知识。
- 实用性很强。课程源于笔者所在团队的工程化实践,实用性很强,做完教程后可以直接应用到业务开发中。
能学到什么
相信在学完本课程后,大家至少有以下几点收获:
- 对前端工程化有一个系统认知;
- 能独立设计一套前端工程化解决方案;
- 知识广度上有大幅提升;
- 进入更好的平台,获得更好的薪酬。
课程组成
第一部分 模板设计(1~16 课)。该章节的篇幅基本占到了整个教程 90%。主要涉及 Webpack 基础知识的讲解,如何配置 Webpack 以支持模块化开发、如何配置 Webpack 以支持前后端的并行开发、如何配置 webpack 以支持代码的检查、如何配置 Webpack 以支持单元测试和 e2e 测试,如何配置前端本地开发环境、如何配置前端资源的部署功能等。模板设计涉及到开发流程、代码规范、性能优化等工程化的方方面面,知识点很多,大家需要重点学习。
第二部分 命令行设计(17~18 课)。该章节主要是讲解如何开发一个 node 的命令行工具。如何将命令行工具与模板进行结合,帮助开发者快速创建项目模板。
点击了解《透视前端工程化》。
课程寄语希望读者学完本课程后,能理解脚手架背后蕴藏的工程化思想,并且可以亲手完成一个脚手架的搭建。也希望大家在理解了前端工程化思想后,探索更多的方法,赋能自己的团队,共同推动前端工程化的发展。
第 01 课:模板功能设计 1 项目模板我们的项目框架都是基于项目模板生成的。学过 JS 的知道:
function Person(name = 'ant') { this.name = name;}let man = new Person();
这里 Person
是 man
的原型对象。同样项目模板相当于对象 Person
,具体的项目相当于 man
。通过项目模板,我们可以事先将项目所需要的功能点定义好,使用的时候只需要像实例化一个对象一样,简单地 new 一下就搞定了。大大地减少重复劳动,屏蔽项目配置的复杂度。
在动手搭建之前,有必要对我们的项目模板进行一个整体规划。我们后面的课程就是按照规划逐个实现。
2 支持哪些功能 2.1 代码检查JavaScript 是一个动态的弱类型语言,在开发中比较容易出错。同时由于每个人的编码习惯和风格不尽相同,写出的代码风格迥异,时间久了很难维护。因为没有编译程序, JavaScript 代码错误通常需要在执行过程中不断调试,效率很低。
但是,我们不可能人肉进行代码检查,因为当代码量很大的时候,需要耗费很大的精力。所以代码检查我们借助 ESLint 来实现。ESLint 可以让程序员在编码的过程中就发现问题,避免将问题带到执行时。
为什么使用 ESLint?
- ESLint 的所有规则都是可拔插的。ESLint 不限制开发人员的编码风格。因为其设计的初衷就是为了让开发人员创建符合自己编码风格的规则。当然 ESLint 有一套内置的默认规则。但是可以修改甚至自定义自己的规则一切都是可拔插的。
- 每条规则可以自由地开关。开发人员可以根据自己项目的需要决定是否开启某条规则。也可以修改检查结果的告警等级,决定抛错还是只是警告。
一个简单的示例:
let foo = bar;
1:5 - 'foo' is assigned a value but never used. (no-unused-vars)
如果我们在 ESLint 配置文件中启用了 no-unused-vars 规则。那么当代码中出现定义未使用的变量时,就会给出错误提示。
至于 CSS 的检查,这里我们使用 stylelint 来处理。stylelint 功能非常强大,可以支持 scss、sass、less 以及 sugarss 的语法检查。此外,stylelint 和 ESlint 类似,也是提供了插件机制,开发者可以自己来定义规则,根据自己的团队定制一套统一的规范,避免样式错误。
我们看一个简单的示例。
有下面这样一段 css 代码:
/* */a { color: #FFF; }
stylelint 规则如下:
"rules": { "comment-no-empty": true, "color-hex-case": "lower" }
stylelint 提示注释内容不能为空,字体颜色的 16 进制表示字母不允许大写。
1:1 error Unexpected empty comment (comment-no-empty)3:12 error Expected "#FFF" to be "#fff" (color-hex-case)
2.2 本地开发环境
在日常开发中,前端开发经常做的是完成部分功能和页面的开发后,去刷新一下浏览器,看一下效果是否符合预期。所以能有一个本地 Web 服务器加载我们的页面,并且当代码变动的时候自动刷新页面,可以减少开发同学的切换成本,提升开发效率。
我们在模板中使用 webpack-dev-server 作为 一个简单的 Web 服务器,由于 webpack-dev-server 内置了模块热替换(Hot Module Replacement 或 HMR),页面被修改后自动重新加载。
// package.json"scripts": { "dev": "webpack-dev-server --open"}
在 Webpack 配置文件中完成 dev-server 的配置后,我们只需要在 package.json 文件中添加一个 script 命令。在进行开发时,只需要一个简单的 npm run dev
命令就可以启动本地服务器。
前后端分离后,双方有清晰的边界,后端负责业务逻辑的编写,前端负责视图逻辑的开发,双方的数据传输通过 API 实现。前后端共同约定一份接口文档,接口文档中规定了接口名称、入参、出参以及数据结构等。
但仅仅有一份静态的接口文档还远远不够,我们需要的是在开发的阶段就能像在线上一样去调用服务端接口获取数据。
所以,Mock 是前后端分离模式下一项必备的功能。
我们采取的做法是在项目模板中实现一个 mock server,来响应本地请求。同时假数据通过 mockjs 来生成。
由于我们的本地开发环境与 mock server 运行在不同的端口上,因此我们还需要在 Webpack 中提供的 proxy 功能将我们的请求转发到 mock server 上去。
现在的前端开发基本上都是采用了模块化的开发方式,而且在代码中使用了大量的 es6+ 语言特性。但宿主浏览器中对新特性的支持不一。我们的代码无法直接运行在浏览器中,所以需要对代码进行构建打包,将代码编译成浏览器都可运行的代码。
// webpack.config.js...output: { path: path.join(__dirname, './build'), publicPath: opt.publicPath, // 给线上环境的资源添加hash filename: 'js/[name]' + hash(needHash, 'chunk') + '.js', chunkFilename: 'js/[name]' + hash(needHash, 'chunk') + '.js',}...plugins: [ ... new webpack.optimize.UglifyJsPlugin({ compress: { // 删除console和警告 drop_console: true, warnings: false } }) ...]
由于目标代码的应用场景不同,我们需要针对不同的环境来进行构建。比如,针对测试环境运行的代码,由于需要不断进行调试,我们一般不清除代码中的注释和 console。由于代码部署无需考虑备份,不给构建出的文件添加 hash。线上环境构建的时候,我们需要尽可能减小文件的体积,同时需要考虑部署后不覆盖线上资源,所以需要在构建时剔除代码中的注释和调试语句,对代码进行混淆压缩,给文件添加 hash。
现在绝大多数现代浏览器都已经支持了原生的 ES2015,编译后的包通常都比原生的 ES2015+ 代码会更冗长,运行更慢。所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。我们在模板中可以针对现代浏览器打出体积更小的包。
- 现代版的包在被支持的浏览器中通过
进行加载,使用
进行预加载。
- 旧版本的包会通过
加载,支持 ES modules 的浏览器会忽略该引用。
构建完成后,最后一步就是把前端资源发布到服务器了。假定我们的前端项目都是完全的前后端分离的,这意味着前端的资源需要和服务端分开部署。
比如,我们为专门准备一个 OSS 服务器用来部署入口文件,准备另一个 OSS 服务器部署 js、css 和图片等资源。
// package.json"scripts": { // 假定我们的部署逻辑在deploy.js中 "deploy": "node deploy.js"}
部署我们会用到两个 npm 包,一个是 vinyl-ftp
,专门用以登录我们的 OSS 服务器,另一个包是 vinyl-fs
,用来将本地的资源发布到远程服务器。最后我们在 package.json 文件中配置 deploy
命令。部署操作只需要执行 npm run deploy
即可。
相信大家在测试过程中,有一个问题经常会遇到。总是有测试同学把一些本属于服务端的 bug 提给前端。前端同学看到 bug 后,按照复现步骤排查一遍,“咦!这个问题是接口中数据有问题导致的!”赶紧转给服务端同学,并且心中有一丝不爽。
所以,很有必要开发一个小工具辅助测试同学,为他们分析 bug 产生的原因,准确指派 bug 提供事实依据。
因此,我们在项目模板中要实现一个 Qdebug 小工具。
// 初始化 var debug = new QDebug();
Qdebug 基于 vconsole 进行扩展,可以将接口请求中的详细接口数据打印出来,同时会生成一个当前前端代码的版本号。测试同学通过版本号就可以知道前端的资源是不是已经更新,通过查看详细的接口数据就可以准确判断 bug 的归属。
2.7 自动化测试自动化测试在 Web 工程中扮演重要的角色。自动化测试还可以与持续集成进行结合,通过机器来保证工程的质量,提升团队整体的效能。因此在前端工程化的建设过程中,自动化测试也是很重要的一环。
自动化测试根据粒度的不同,基本可以分为:单元测试、接口测试、端到端测试(也叫功能测试)。在前端工程中得到应用的主要是接口测试和端到端测试。
单元测试,是站在开发人员的视角,把代码划分成一个个的代码单元逐个进行测试,看返回的结果是否符合预期。
我们通过 Karma+Mocha+Chai 来实现单元测试功能。执行单元测试很简单也是通过 npm script 来调用,例如:
npm run test:unit
端到端测试是站在用户的视角,把 Web 应用当成一个黑盒,模拟用户的真实使用场景,比如在页面中输入文字,点击搜索。看测试结果是不是符合预期。端到端测试框架有不少,比如 Nightwatch、TestCafe、CasperJS。在本模板中,我们选用 Nightwatch 来实现端到端测试,主要是因为使用 Nigthwatch 编写的代码非常简洁。
执行端到端测试很简单通过 npm script 来调用,例如:
npm run test:e2e
3 目录结构
说完了模板要实现的主要功能后,我们来看一下项目模板的整体目录结构:
boilerplate-vue├── package.json├── README.md├── .gitignore├── .babelrc├── config├── build├── mock├── test| ├── unit| └── e2e├── src| ├── assets│ ├── components│ ├── router│ ├── pages│ ├── app.js│ └── App.vue└── public ├── index.html └── favicon.ico
主要的一些目录和文件功能如下:
- src 中存放业务代码。其中
src/app.js
是项目构建的入口文件,src/App.vue
是视图层的入口文件,src/pages
中放置不同的页面,src/components
中放置 Vue 组件,src/router
中放置 router 相关的文件,src/assets
中放置静态资源如图片、字体等。 - mock 存放 Mock 的假数据。
- test 用于放置测试相关的文件。
- build 用于放置构建相关的文件。
- config 用于放置配置相关的文件。
- public 用于放置公用的静态资源,如 HTML 入口模板文件、站点的小图标。
- package.json npm 模块的配置文件。
- .babelrc babel 的配置文件。
- .gitignore 用于指定哪些文件不提交到 Git 仓库。
- README.md 项目的介绍和使用文档。
点击了解《透视前端工程化》。
总结本节我们对模板的功能进行了系统的规划和目录结构设计。模板功能覆盖了开发阶段、测试阶段、构建阶段、部署阶段。
文中对各个功能的实现所需要用到的技术和工具也进行了简单的介绍,大家对每个部分有个大概的印象即可。在后面的章节中,我们会有更为详尽的讲解。
注意!!! 为了方便学习和技术交流,特意创建了《透视前端工程化》的读者群,入群方式放在 第 03 课 文末,欢迎已购本课程的同学入群交流。
第 02 课:Webpack 基本介绍 1 Webpack 的特点图片来源于网络
Webpack 是一款强大的打包工具。在 Webpack 中一切皆模块。Webpack 官网的 banner 图完美地诠释了这一理念。Webpack 从一个入口文件开始递归地分析模块的依赖关系,根据依赖关系将这些模块打包成一个或多个文件。
目前几乎所有的前端构建和开发都是采用 Webpack 。因为 Webpack 有强大的社区生态,每月 Webpack 的下载量超过百万。通过 loader、plugin 支持 Webpack 与主流的前端框架和语言进行集成,比如 Vue、React、TypeScript。
- 支持所有的模块化 可以对 ES6 模块、commonjs 模块、AMD 模块等所有标准的模块进行打包。
- code splitting 可以将代码打成多个 chunk,按需加载,意味着我们的站点无需等待整个 js 资源下载完成之后才能交互,可以大大提升速度。
- 强大灵活的插件系统 Webpack 提供了很多内置的插件,包括其自身也是架构在插件系统上可以满足所有的打包需求。
- loader 借助 loader 预处理非 js 资源,Webpack 可以打包所有的静态资源。
Webpack 的构建流程是一种事件流机制。整个构建流程可以看成是一个流水线,每个环节负责单一的任务,处理完将进入下一个环节。Webpack 会在每个环节上发布事件,供内置的和自定义的插件有机会干预 Webpack 的构建过程,控制 Webpack 的构建结果。Webpack 的基本的构建流程图如下:
- 初始化 读取 webpack 配置文件和 shell 脚本中的参数,将参数合并后初始化 Webpack ,生成
Compiler
对象。 - 开始编译 执行
Compiler
的 run 方法开始执行编译。 - 编译完成 从入口文件开始,调用配置中的 loader 对模块进行编译,并梳理出模块间的依赖关系,直至所有的模块编译完成。
- 资源输出 根据入口与模块间的依赖关系,将上一步编译完成的内容组装成一个个的
chunk
(代码块),然后把chunk
加入到等待输出的资源列表中。 - 完成 确定好输出资源后,根据指定的输出路径和文件名配置,将资源写入到磁盘的文件系统中,完成整个构建过程。
入口是 Webpack 进行构建的起点,Webpack 在构建过程中从入口文件开始,递归地编译模块,并分析模块间的依赖关系,最终得出依赖图。Webpack 依据该依赖图对模块进行组装,输出到最终的 bundle 文件中。
我们可以在Webpack 的配置文件中配置 entry 属性,来指定入口文件,入口文件可以是一个也可以指定多个。
我们来看一个例子:
// Webpack .config.jsmodule.exports = { entry: './src/app.js'};
配置多个入口的场景常见于多页应用中。如果配置多个入口可以这样:
// Webpack .config.jsmodule.exports = { entry: { pageOne: './src/pageOne/app.js', pageTwo: './src/pageTwo/app.js' }};
输出
配置 output 选项可以指示 Webpack 如何去输出、在哪里输出我们的静态资源文件。
我们通过一个例子来看一下 output 如何使用:
// Webpack .config.jsmodule.exports = { output: { filename: 'bundle.js', path: './dist' }};
上例中,我们指示 Webpack 最终的输出文件名为 bundle.js
,输出的目录为 ./dist
。
loader 的使用
Webpack 本身是不能处理非 js 资源的,但我们却可以在 Webpack 中引入 css、图片、字体等非 js 文件。例如:
// app.jsimport Styles from './styles.css';
那么 Webpack 是如何实现的呢?
Webpack 中使用 loader 对非 js 文件进行转换。loader 可以在我们 import
或者加载模块时,对文件进行预处理,将非 js 的文件内容,最终转换成 js 代码。
loader 有三种使用方式:
- 配置 在 Webpack .config.js 文件中指定
- 内联 在每个
import
语句中线上指定 - CLI 在 shell 命令中指定。
在实际的应用中,绝大数都是采用配置的方式来使用,一方面在配置文件中,可以非常直观地看到某种类型的文件使用了什么 loader,另一方面,在项目复杂的情况下,便于进行维护。
我们通过一个简单的例子来看一下 loader 的使用:
// Webpack .config.jsmodule.exports = { module: { rules: [ { test: /\.css$/, use: 'css-loader' } ] }};
我们需要告诉 Webpack 当遇到 css 文件的时候,使用 css-loader
进行预处理。这里由于 css-loader 是单独的 npm 模块,使用前我们需要先进行安装:
npm install --save-dev css-loader
常用的 loader
Webpack 可以处理任何非 js 语言,得益于社区提供的丰富的 loader,日常开发中所使用到的 loader,都可以在社区找到。这里对一些常用的 loader 进行简要的说明。
- babel-loader 将 ES2015+ 代码转译为 ES5。
- ts-loader 将 TypeScript 代码转译为 ES5。
- css-loader 解析
@import
和url()
,并对引用的依赖进行解析。 - style-loader 在 HTML 中注入
标签将 css 添加到 DOM 中。通常与
css-loader
结合使用。 - sass-loader 加载 sass/scss 文件并编译成 css。
- postcss-loader 使用 PostCSS 加载和转译 CSS文件。
- html-loader 将 HTML 导出为字符串。
- vue-loader 加载和转译 Vue 组件。
- url-loader 和
file-loader
一样,但如果文件小于配置的限制值,可以返回data URL
。 - file-loader 将文件提取到输出目录,并返回相对路径。
插件的使用
插件是 Webpack 的非常重要的功能,Webpack 本身也是建立在插件系统之上的。插件机制极大增强了 Webpack 的功能,为 Webpack 增加了足够的灵活性。通过插件,我们可以在 Webpack 的构建过程中,引入自己的操作,干预构建结果。
我们通过一个示例来看一下插件的使用:
// Webpack .config.jsconst HtmlWebpack Plugin = require('html-Webpack -plugin'); const Webpack = require('Webpack '); const config = { plugins: [ new Webpack .optimize.UglifyJsPlugin(), new HtmlWebpack Plugin({template: './src/index.html'}) ]};module.exports = config;
示例中,我们用到了两个插件,一个是内置的 UglifyJsPlugin
插件,该插件对 js 进行压缩,减小文件的体积。一个是外部插件 HtmlWebpack Plugin
,用来自动生成入口文件,并将最新的资源注入到 HTML 中。
常用的插件
- HtmlWebpack Plugin 自动生成入口文件,并将最新的资源注入到 HTML 中。
- CommonsChunkPlugin 用以创建独立文件,常用来提取多个模块中的公共模块。
- DefinePlugin 用以定义在编译时使用的全局常量。
- DllPlugin 拆分 bundle 减少不必要的构建。
- ExtractTextWebpack Plugin 将文本从 bundle 中提取到单独的文件中。常见的场景是从 bundle 中将 css 提取到独立的 css 文件中。
- HotModuleReplacementPlugin 在运行过程中替换、添加或删除模块,而无需重新加载整个页面。
- UglifyjsWebpack Plugin 对 js 进行压缩,减小文件的体积。
- CopyWebpack Plugin 将单个文件或整个目录复制到构建目录。一个常用的场景是将项目中的静态图片不经构建直接复制到构建后的目录。
下面我们通过一个简单的例子来看一下 Webpack 的使用。这里假定你已经安装了最新版本的 nodejs 和 npm,因为使用旧版本可能会遇到各种问题。
4.1 安装创建 Webpack -demo 目录,初始化 npm,并且在 Webpack -demo 目录中安装 Webpack 和 Webpack -cli:
mkdir Webpack -demo && cd Webpack -demonpm init -ynpm install Webpack Webpack -cli --save-dev
Webpack -cli 用来在命令行中运行 Webpack 。这里建议本地安装 Webpack 和 Webpack -cli,因为全局安装的话,Webpack 的升级会影响到所有的项目。
接下来我们先在项目中新增一些目录和文件:
Webpack -demo├── package.json├── dist├── index.html└── src └── index.js
index.html 内容如下:
Webpack -demo
src/index.js 内容如下:
function createEl() { var element = document.createElement('div') element.innerHTML = 'hello world' return element;}document.body.appendChild(createEl());
4.2 第一次构建
在命令行运行:
./node_modules/.bin/Webpack Hash: 2353b0d3d427eaa8a18aVersion: Webpack 4.29.6Time: 175msBuilt at: 2019-04-03 22:08:36 Asset Size Chunks Chunk Namesmain.js 1 KiB 0 [emitted] mainEntrypoint main = main.js[0] ./src/index.js 175 bytes {0} [built]
大家可以发现,我们并没有在配置文件中指定打包的入口和输出的出口,也没有在命令行中指定配置参数,但可以看到在 ./dist 目录下新增了一个 main.js。这是因为 Webpack 配置中 entry 的默认值为 ./src,出口的默认目录是 ./dist。
Webpack -demo├── package.json├── dist| └── main.js├── index.html└── src └── index.js
构建后的项目目录中新增了 main.js。
Webpack -demo
我们现在将 index.html 中的脚本引用修改为构建后的文件 ./dist/main.js,在浏览器预览,如果一切正常应该可以看到页面上会输出文本 hello world
。
对于简单的构建,Webpack 基本可以做到零配置。但对于复杂的单页应用而言,则需要使用 Webpack 的配置文件来提供个性化的功能。
首先我们在项目根目录下新增 Webpack .config.js
文件:
// Webpack .config.jsconst path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }};
在配置文件中,通过 entry
指定了入口文件为 ./src/index.js
,通过 output 指定了输出的目录为 ./dist
,输出的文件名为 bundle.js
。目录结构更新如下:
Webpack -demo├── package.json├── Webpack .config.js├── index.html├── dist| └── bundle.js└── src └── index.js
同时为了调用简单,我们在 package.json 文件中设置快捷命令来调用 ./node_modules/.bin/Webpack
。
// package.json{ "scripts": { "build": "Webpack " }}
再次执行构建命令:
npm run build> Webpack -demo@1.0.0 build C:\work\tech\Webpack -demo> Webpack Hash: d0fa6b1e011af414e622Version: Webpack 4.29.6Time: 157msBuilt at: 2019-04-03 22:42:50 Asset Size Chunks Chunk Namesbundle.js 1 KiB 0 [emitted] mainEntrypoint main = bundle.js[0] ./src/index.js 175 bytes {0} [built]
将 index.html
中的 script 引用链接修改为 ./dist/bundle.js
,在浏览器中预览页面,不出意外的话会输出文本 hello world
。
我们发现在构建的过程中,如果构建后的资源名称发生了变化,index.html 中对资源的引用会被动地跟着修改,非常不方便,我们引入 HtmlWebpack Plugin 来帮助我们自动生成入口文件,自动将生成的资源文件注入 index.html 中。
安装:
npm install --save-dev html-Webpack -plugin
配置:
const path = require("path");const HtmlWebpack Plugin = require("html-Webpack -plugin");module.exports = { entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, plugins: [new HtmlWebpack Plugin()]};
在配置文件中,我们引入插件,并在 plugins 选项中,将插件实例化后添加到数组中。该插件会自动生成 index.html,因此原目录中的 index.html 文件可以删除。
Webpack -demo├── package.json├── Webpack .config.js├── dist| └── bundle.js└── src └── index.js
再次执行构建命令:
$ npm run build> Webpack -demo@1.0.0 build C:\work\tech\Webpack -demo> Webpack Hash: 39dc7567ef99a69140e7Version: Webpack 4.29.6Time: 1241msBuilt at: 2019-04-03 22:53:44 Asset Size Chunks Chunk Names bundle.js 1 KiB 0 [emitted] mainindex.html 182 bytes [emitted]Entrypoint main = bundle.js[0] ./src/index.js 175 bytes {0} [built]
命令执行后我们发现我们的 ./dist 下多了一个 index.html 文件,并且 index.html 中的资源引用被自动更新为了 。
为了使 Webpack 可以处理 import 进来的 css 文件,我们需要安装并配置 style-loader
和 css-loader
。
npm install --save-dev style-loader css-loader
修改 Webpack 的配置如下:
const path = require("path");const HtmlWebpack Plugin = require("html-Webpack -plugin");module.exports = { entry: "./src/index.js", output: { filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"] } ] }, plugins: [new HtmlWebpack Plugin()]};
如此一来,当 Webpack 匹配到后缀为 .css 的文件都会使用 css-loader 和 style-loader 进行处理。
接下来我们在 ./src 目录下新增一个样式文件 main.css
。在样式中,设置文本的字体颜色为红色。
// main.cssdiv{color: red}
紧接着我们在 ./src/index.js 中引用 main.css:
import "./main.css";function createEl() { var element = document.createElement("div"); element.innerHTML = "hello world"; return element;}document.body.appendChild(createEl());
执行构建命令:
$ npm run build> Webpack -demo@1.0.0 build C:\work\tech\Webpack -demo> Webpack Hash: f9fcb8cfd689f4b96ce6Version: Webpack 4.29.6Time: 2672msBuilt at: 2019-04-03 23:24:40 Asset Size Chunks Chunk Names bundle.js 6.85 KiB 0 [emitted] mainindex.html 182 bytes [emitted]Entrypoint main = bundle.js[0] ./src/index.js 199 bytes {0} [built][1] ./src/main.css 1.05 KiB {0} [built][2] ./node_modules/css-loader/dist/cjs.js!./src/main.css 170 bytes {0} [built] + 3 hidden modules
在浏览器预览,不出意外字体的颜色已经变成了红色,打开浏览器调试工具,可以看到在 标签里插入了一个
标签。
div { color: red; }
通过以上完整的示例,我们演示了 Webpack 的核心的几个配置的使用方式,我们对 Webpack 的使用应该有了一个基本的认识。
Webpack 中还有很多其他有用的配置项,篇幅原因不做详细的介绍。大家可以查阅 官方文档 自行配置和练习。
点击了解《透视前端工程化》。
总结本节我们对 Webpack 进行了总体的介绍。借助 loader、Webpack 可以处理一切资源,JS 的、非 JS 的,都可以。
通过插件,我们可以在 Webpack 的构建过程中的每个事件节点加入自己的行为,来影响 Webpack 的构建。对 Webpack 的使用有了认识后,接下来我们要以之为基础搭建起项目的基本框架。
注意!!! 为了方便学习和技术交流,特意创建了《透视前端工程化》的读者群,入群方式放在第 03 课文末,欢迎已购本课程的同学入群交流。
第 03 课:搭建项目模板框架 第 04 课:前端模块化解决方案 第 05 课:搭建本地开发环境 第 06 课:搭建本地 Mock 服务 第 07 课:引入代码检查工具 第 08 课:自动生成雪碧图 第 09 课:根据浏览器构建 第 10 课:根据环境构建 第 11 课:集成移动端调试工具 第 12 课:引入单元测试 第 13 课:引入 e2e 测试 第 14 课:Webpack 构建性能优化 第 15 课:添加部署功能 第 16 课:聚合项目配置并模板化 第 17 课:cli 功能设计(上) 第 18 课:cli 功能设计(下) 结语:开放的心态才是更高阶的工程化阅读全文: http://gitbook.cn/gitchat/column/5cf77250ce53ed3f49faf0e5