可以说是我目前看到最详细的 webpack 4 入门文章。
基本看完这个,基本也算一个合格的初级webpack配置工程师了。
webpack 更新到了 4.0,官网还没有更新文档。因此把教程更新一下,方便大家用起 webpack 4。
webpack
写在开头
先说说为什么要写这篇文章,最初的原因是组里的小朋友们看了 webpack 文档后,表情都是这样的:摘自 webpack 一篇文档的评论区)
wtf
是的,即使是外国佬也在吐槽这文档不是人能看的。回想起当年自己啃 webpack 文档的血与泪的往事,觉得有必要整一个教程,可以让大家看完后愉悦地搭建起一个 webpack 打包方案的项目。
官网新的 webpack 文档现在写的很详细了,能看英文的小伙伴可以直接去看官网。
可能会有人问 webpack 到底有什么用,你不能上来就糊我一脸代码让我马上搞,我照着搞了一遍结果根本没什么用,都是骗人的。所以,在说 webpack 之前,我想先谈一下前端打包方案这几年的演进历程,在什么场景下,我们遇到了什么问题,催生出了应对这些问题的工具。了解了需求和目的之后,你就知道什么时候 webpack 可以帮到你。我希望我用完之后很爽,你们用完之后也是。
先说说前端打包方案的黑暗历史
在很长的一段前端历史里,是不存在打包这个说法的。那个时候页面基本是纯静态的或者服务端输出的,没有 AJAX,也没有 jQuery。那个时候的 JavaScript 就像个玩具,用处大概就是在侧栏弄个时钟,用 media player 放个 mp3 之类的脚本,代码量不是很多,直接放在 <script>
标签里或者弄个 js 文件引一下就行,日子过得很轻松愉快。
随后的几年,人们开始尝试在一个页面里做更多的事情。容器的显示,隐藏,切换。用 css 写的弹层,图片轮播等等。但如果一个页面内不能向服务器请求数据,能做的事情毕竟有限的,代码的量也能维持在页面交互逻辑范围内。这时候很多人开始突破一个页面能做的事情的范围,使用隐藏的 iframe 和 flash 等作为和服务器通信的桥梁,新世界的大门慢慢地被打开,在一个页面内和服务器进行数据交互,意味着以前需要跳转多个页面的事情现在可以用一个页面搞定。但由于 iframe 和 flash 技术过于 tricky 和复杂,并没能得到广泛的推广。
直到 Google 推出 Gmail 的时候(2004 年),人们意识到了一个被忽略的接口,XMLHttpRequest, 也就是我们俗称的 AJAX, 这是一个使用方便的,兼容性良好的服务器通信接口。从此开始,我们的页面开始玩出各种花来了,前端一下子出现了各种各样的库,Prototype、Dojo、MooTools、Ext JS、jQuery…… 我们开始往页面里插入各种库和插件,我们的 js 文件也就爆炸了。
随着 js 能做的事情越来越多,引用越来越多,文件越来越大,加上当时大约只有 2Mbps 左右的网速,下载速度还不如 3G 网络,对 js 文件的压缩和合并的需求越来越强烈,当然这里面也有把代码混淆了不容易被盗用等其他因素在里面。JSMin、YUI Compressor、Closure Compiler、UglifyJS 等 js 文件压缩合并工具陆陆续续诞生了。压缩工具是有了,但我们得要执行它,最简单的办法呢,就是 windows 上搞个 bat 脚本,mac / linux 上搞个 bash 脚本,哪几个文件要合并在一块的,哪几个要压缩的,发布的时候运行一下脚本,生成压缩后的文件。
基于合并压缩技术,项目越做越大,问题也越来越多,大概就是以下这些问题:
库和插件为了要给他人调用,肯定要找个地方注册,一般就是在 window 下申明一个全局的函数或对象。难保哪天用的两个库在全局用同样的名字,那就冲突了。
库和插件如果还依赖其他的库和插件,就要告知使用人,需要先引哪些依赖库,那些依赖库也有自己的依赖库的话,就要先引依赖库的依赖库,以此类推。
恰好就在这个时候(2009 年),随着后端 JavaScript 技术的发展,人们提出了 CommonJS 的模块化规范,大概的语法是: 如果 a.js
依赖 b.js
和 c.js
, 那么就在 a.js
的头部,引入这些依赖文件:
var b = require('./b')var c = require('./c')
那么变量 b
和 c
会是什么呢?那就是 b.js 和 c.js 导出的东西,比如 b.js 可以这样导出:
exports.square = function(num) { return num * num }
然后就可以在 a.js 使用这个 square
方法:
var n = b.square(2)
如果 c.js 依赖 d.js, 导出的是一个 Number
, 那么可以这样写:
var d = require('./d')module.exports = d.PI // 假设 d.PI 的值是 3.14159
那么 a.js 中的变量 c
就是数字 3.14159
,具体的语法规范可以查看 Node.js 的 文档。
但是 CommonJS 在浏览器内并不适用。因为 require()
的返回是同步的,意味着有多个依赖的话需要一个一个依次下载,堵塞了 js 脚本的执行。所以人们就在 CommonJS 的基础上定义了 Asynchronous Module Definition (AMD) 规范(2011 年),使用了异步回调的语法来并行下载多个依赖项,比如作为入口的 a.js 可以这样写:
require(['./b', './c'], function(b, c) { var n = b.square(2) console.log(c) })
相应的导出语法也是异步回调方式,比如 c.js
依赖 d.js
, 就写成这样:
define(['./d'], function(d) { return d.PI })
可以看到,定义一个模块是使用 define()
函数,define()
和 require()
的区别是,define()
必须要在回调函数中返回一个值作为导出的东西,require()
不需要导出东西,因此回调函数中不需要返回值,也无法作为被依赖项被其他文件导入,因此一般用于入口文件,比如页面中这样加载 a.js
:
<script src="js/require.js" data-main="js/a"></script>
以上是 AMD 规范的基本用法,更详细的就不多说了(反正也淘汰了~),有兴趣的可以看 这里。
js 模块化问题基本解决了,css 和 html 也没闲着。什么 less,sass,stylus 的 css 预处理器横空出世,说能帮我们简化 css 的写法,自动给你加 vendor prefix。html 在这期间也出现了一堆模板语言,什么 handlebars,ejs,jade,可以把 ajax 拿到的数据插入到模板中,然后用 innerHTML 显示到页面上。
托 AMD 和 CSS 预处理和模板语言的福,我们的编译脚本也洋洋洒洒写了百来行。命令行脚本有个不好的地方,就是 windows 和 mac/linux 是不通用的,如果有跨平台需求的话,windows 要装个可以执行 bash 脚本的命令行工具,比如 msys(目前最新的是 msys2),或者使用 php 或 python 等其他语言的脚本来编写,对于非全栈型的前端程序员来说,写 bash / php / python 还是很生涩的。因此我们需要一个简单的打包工具,可以利用各种编译工具,编译 / 压缩 js、css、html、图片等资源。然后 Grunt 产生了(2012 年),配置文件格式是我们最爱的 js,写法也很简单,社区有非常多的插件支持各种编译、lint、测试工具。一年多后另一个打包工具 gulp 诞生了,扩展性更强,采用流式处理效率更高。
依托 AMD 模块化编程,SPA(Single-page application) 的实现方式更为简单清晰,一个网页不再是传统的类似 word 文档的页面,而是一个完整的应用程序。SPA 应用有一个总的入口页面,我们通常把它命名为 index.html、app.html、main.html,这个 html 的 <body>
一般是空的,或者只有总的布局(layout),比如下图:
image
布局会把 header、nav、footer 的内容填上,但 main 区域是个空的容器。这个作为入口的 html 最主要的工作是加载启动 SPA 的 js 文件,然后由 js 驱动,根据当前浏览器地址进行路由分发,加载对应的 AMD 模块,然后该 AMD 模块执行,渲染对应的 html 到页面指定的容器内(比如图中的 main)。在点击链接等交互时,页面不会跳转,而是由 js 路由加载对应的 AMD 模块,然后该 AMD 模块渲染对应的 html 到容器内。
虽然 AMD 模块让 SPA 更容易地实现,但小问题还是很多的:
不是所有的第三方库都是 AMD 规范的,这时候要配置
shim
,很麻烦。虽然 RequireJS 支持通过插件把 html 作为依赖加载,但 html 里面的
<img>
的路径是个问题,需要使用绝对路径并且保持打包后的图片路径和打包前的路径不变,或者使用 html 模板语言把src
写成变量,在运行时生成。不支持动态加载 css,变通的方法是把所有的 css 文件合并压缩成一个文件,在入口的 html 页面一次性加载。
SPA 项目越做越大,一个应用打包后的 js 文件到了几 MB 的大小。虽然 r.js 支持分模块打包,但配置很麻烦,因为模块之间会互相依赖,在配置的时候需要 exclude 那些通用的依赖项,而依赖项要在文件里一个个检查。
所有的第三方库都要自己一个个的下载,解压,放到某个目录下,更别提更新有多麻烦了。虽然可以用 npm 包管理工具,但 npm 的包都是 CommonJS 规范的,给后端 Node.js 用的,只有部分支持 AMD 规范,而且在 npm 3 之前,这些包有依赖项的话也是不能用的。后来有个 bower 包管理工具是专门的 web 前端仓库,这里的包一般都支持 AMD 规范。
AMD 规范定义和引用模块的语法太麻烦,上面介绍的 AMD 语法仅是最简单通用的语法,API 文档里面还有很多变异的写法,特别是当发生循环引用的时候(a 依赖 b,b 依赖 a),需要使用其他的 语法 解决这个问题。而且 npm 上很多前后端通用的库都是 CommonJS 的语法。后来很多人又开始尝试使用 ES6 模块规范,如何引用 ES6 模块又是一个大问题。
项目的文件结构不合理,因为 grunt/gulp 是按照文件格式批量处理的,所以一般会把 js、html、css、图片分别放在不同的目录下,所以同一个模块的文件会散落在不同的目录下,开发的时候找文件是个麻烦的事情。code review 时想知道一个文件是哪个模块的也很麻烦,解决办法比如又要在 imgs 目录下建立按模块命名的文件夹,里面再放图片。
到了这里,我们的主角 webpack 登场了(2012 年)(此处应有掌声)。
和 webpack 差不多同期登场的还有 Browserify。这里简单介绍一下 Browserify。Browserify 的目的是让前端也能用 CommonJS 的语法 require('module')
来加载 js。它会从入口 js 文件开始,把所有的 require()
调用的文件打包合并到一个文件,这样就解决了异步加载的问题。那么 Browserify 有什么不足之处导致我不推荐使用它呢? 主要原因有下面几点:
最主要的一点,Browserify 不支持把代码打包成多个文件,在有需要的时候加载。这就意味着访问任何一个页面都会全量加载所有文件。
Browserify 对其他非 js 文件的加载不够完善,因为它主要解决的是
require()
js 模块的问题,其他文件不是它关心的部分。比如 html 文件里的 img 标签,它只能转成 Data URI 的形式,而不能替换为打包后的路径。因为上面一点 Browserify 对资源文件的加载支持不够完善,导致打包时一般都要配合 gulp 或 grunt 一块使用,无谓地增加了打包的难度。
Browserify 只支持 CommonJS 模块规范,不支持 AMD 和 ES6 模块规范,这意味旧的 AMD 模块和将来的 ES6 模块不能使用。
基于以上几点,Browserify 并不是一个理想的选择。那么 webpack 是否解决了以上的几个问题呢? 废话,不然介绍它干嘛。那么下面章节我们用实战的方式来说明 webpack 是怎么解决上述的问题的。
上手先搞一个简单的 SPA 应用
一上来步子太大容易扯到蛋,让我们先弄个最简单的 webpack 配置来热一下身。
安装 Node.js
webpack 是基于我大 Node.js 的打包工具,上来第一件事自然是先安装 Node.js 了,传送门 ->。
初始化一个项目
我们先随便找个地方,建一个文件夹叫 simple
, 然后在这里面搭项目。完成品在 examples/simple 目录,大家搞的时候可以参照一下。我们先看一下目录结构:
├── dist 打包输出目录,只需部署这个目录到生产环境 ├── package.json 项目配置信息 ├── node_modules npm 安装的依赖包都在这里面 ├── src 我们的源代码 │ ├── components 可以复用的模块放在这里面 │ ├── index.html 入口 html│ ├── index.js 入口 js│ ├── shared 公共函数库 │ └── views 页面放这里 └── webpack.config.js webpack 配置文件
打开命令行窗口,cd
到刚才建的 simple 目录。然后执行这个命令初始化项目:
npm init
命令行会要你输入一些配置信息,我们这里一路按回车下去,生成一个默认的项目配置文件 package.json
。
给项目加上语法报错和代码规范检查
我们安装 eslint, 用来检查语法报错,当我们书写 js 时,有错误的地方会出现提示。
npm install eslint eslint-config-enough babel-eslint eslint-loader --save-dev
npm install
可以一条命令同时安装多个包,包之间用空格分隔。包会被安装进 node_modules
目录中。
--save-dev
会把安装的包和版本号记录到 package.json
中的 devDependencies
对象中,还有一个 --save
, 会记录到 dependencies
对象中,它们的区别,我们可以先简单的理解为打包工具和测试工具用到的包使用 --save-dev
存到 devDependencies
, 比如 eslint、webpack。浏览器中执行的 js 用到的包存到 dependencies
, 比如 jQuery 等。那么它们用来干嘛的?
因为有些 npm 包安装是需要编译的,那么导致 windows / mac /linux 上编译出的可执行文件是不同的,也就是无法通用,因此我们在提交代码到 git 上去的时候,一般都会在 .gitignore
里指定忽略 node_modules 目录和里面的文件,这样其他人从 git 上拉下来的项目是没有 node_modules 目录的,这时我们需要运行
npm install
它会读取 package.json
中的 devDependencies
和 dependencies
字段,把记录的包的相应版本下载下来。
这里 eslint-config-enough 是配置文件,它规定了代码规范,要使它生效,我们要在 package.json
中添加内容:
{ "eslintConfig": { "extends": "enough", "env": { "browser": true, "node": true } } }
业界最有名的语法规范是 airbnb 出品的,但它规定的太死板了,比如不允许使用 for-of
和 for-in
等。感兴趣的同学可以参照 这里 安装使用。
babel-eslint 是 eslint-config-enough
依赖的语法解析库,替代 eslint 默认的解析库以支持还未标准化的语法。比如 import()。
eslint-loader 用于在 webpack 编译的时候检查代码,如果有错误,webpack 会报错。
项目里安装了 eslint 还没用,我们的 IDE 和编辑器也得要装 eslint 插件支持它。
Visual Studio Code 需要安装 ESLint 扩展
atom 需要安装 linter 和 linter-eslint 这两个插件,装好后重启生效。
WebStorm 需要在设置中打开 eslint 开关:
image
写几个页面
我们写一个最简单的 SPA 应用来介绍 SPA 应用的内部工作原理。首先,建立 src/index.html 文件,内容如下:
<!DOCTYPE html><html> <head> <meta charset="utf-8"> </head> <body> </body></html>
它是一个空白页面,注意这里我们不需要自己写 <script src="index.js"></script>
, 因为打包后的文件名和路径可能会变,所以我们用 webpack 插件帮我们自动加上。
src/index.js:
// 引入 routerimport router from './router'// 启动 routerrouter.start()
src/router.js:
// 引入页面文件import foo from './views/foo'import bar from './views/bar'const routes = { '/foo': foo, '/bar': bar }// Router 类,用来控制页面根据当前 URL 切换class Router { start() { // 点击浏览器后退 / 前进按钮时会触发 window.onpopstate 事件,我们在这时切换到相应页面 // https://developer.mozilla.org/en-US/docs/Web/Events/popstate window.addEventListener('popstate', () => { this.load(location.pathname) }) // 打开页面时加载当前页面 this.load(location.pathname) } // 前往 path,变更地址栏 URL,并加载相应页面 go(path) { // 变更地址栏 URL history.pushState({}, '', path) // 加载页面 this.load(path) } // 加载 path 路径的页面 load(path) { // 首页 if (path === '/') path = '/foo' // 创建页面实例 const view = new routes[path]() // 调用页面方法,把页面加载到 document.body 中 view.mount(document.body) } }// 导出 router 实例export default new Router()
src/views/foo/index.js:
// 引入 routerimport router from '../../router'// 引入 html 模板,会被作为字符串引入import template from './index.html'// 引入 css, 会生成 <style> 块插入到 <head> 头中import './style.css'// 导出类export default class { mount(container) { document.title = 'foo' container.innerHTML = template container.querySelector('.foo__gobar').addEventListener('click', () => { // 调用 router.go 方法加载 /bar 页面 router.go('/bar') }) } }
src/views/bar/index.js:
// 引入 routerimport router from '../../router'// 引入 html 模板,会被作为字符串引入import template from './index.html'// 引入 css, 会生成 <style> 块插入到 <head> 头中import './style.css'// 导出类export default class { mount(container) { document.title = 'bar' container.innerHTML = template container.querySelector('.bar__gofoo').addEventListener('click', () => { // 调用 router.go 方法加载 /foo 页面 router.go('/foo') }) } }
借助 webpack 插件,我们可以 import
html, css 等其他格式的文件,文本类的文件会被储存为变量打包进 js 文件,其他二进制类的文件,比如图片,可以自己配置,小图片作为 Data URI 打包进 js 文件,大文件打包为单独文件,我们稍后再讲这块。
其他的 src 目录下的文件大家自己浏览,拷贝一份到自己的工作目录,等会打包时会用到。
页面代码这样就差不多搞定了,接下来我们进入 webpack 的安装和配置阶段。现在我们还没有讲 webpack 配置所以页面还无法访问,等会弄好 webpack 配置后再看页面实际效果。
安装 webpack 和 Babel
我们把 webpack 和它的插件安装到项目:
npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev
webpack 即 webpack 核心库。它提供了很多 API, 通过 Node.js 脚本中 require('webpack')
的方式来使用 webpack。
webpack-cli 是 webpack 的命令行工具。让我们可以不用写打包脚本,只需配置打包配置文件,然后在命令行输入 webpack-cli --config webpack.config.js
来使用 webpack, 简单很多。webpack 4 之前命令行工具是集成在 webpack 包中的,4.0 开始 webpack 包本身不再集成 cli。
webpack-serve 是 webpack 提供的用来开发调试的服务器,让你可以用 http://127.0.0.1:8080/ 这样的 url 打开页面来调试,有了它就不用配置 nginx 了,方便很多。
html-webpack-plugin, html-loader, css-loader, style-loader 等看名字就知道是打包 html 文件,css 文件的插件,大家在这里可能会有疑问,html-webpack-plugin
和 html-loader
有什么区别,css-loader
和 style-loader
有什么区别,我们等会看配置文件的时候再讲。
file-loader 和 url-loader 是打包二进制文件的插件,具体也在配置文件章节讲解。
接下来,为了能让不支持 ES6 的浏览器 (比如 IE) 也能照常运行,我们需要安装 babel, 它会把我们写的 ES6 源代码转化成 ES5,这样我们源代码写 ES6,打包时生成 ES5。
npm install babel-core babel-preset-env babel-loader --save-dev
这里 babel-core
顾名思义是 babel 的核心编译器。babel-preset-env 是一个配置文件,我们可以使用这个配置文件转换 ES2015/ES2016/ES2017 到 ES5,是的,不只 ES6 哦。babel 还有 其他配置文件。
光安装了 babel-preset-env
,在打包时是不会生效的,需要在 package.json
加入 babel
配置:
{ "babel": { "presets": ["env"] } }
打包时 babel 会读取 package.json
中 babel
字段的内容,然后执行相应的转换。
babel-loader 是 webpack 的插件,我们下面章节再说。
配置 webpack
包都装好了,接下来总算可以进入正题了。我们来创建 webpack 配置文件 webpack.config.js
,注意这个文件是在 node.js 中运行的,因此不支持 ES6 的 import
语法。我们来看文件内容:
const { resolve } = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')const history = require('connect-history-api-fallback')const convert = require('koa-connect')// 使用 WEBPACK_SERVE 环境变量检测当前是否是在 webpack-server 启动的开发环境中const dev = Boolean(process.env.WEBPACK_SERVE)module.exports = { /* webpack 执行模式 development:开发环境,它会在配置文件中插入调试相关的选项,比如 moduleId 使用文件路径方便调试 production:生产环境,webpack 会将代码做压缩等优化 */ mode: dev ? 'development' : 'production', /* 配置 source map 开发模式下使用 cheap-module-eval-source-map, 生成的 source map 能和源码每行对应,方便打断点调试 生产模式下使用 hidden-source-map, 生成独立的 source map 文件,并且不在 js 文件中插入 source map 路径,用于在 error report 工具中查看 (比如 Sentry) */ devtool: dev ? 'cheap-module-eval-source-map' : 'hidden-source-map', // 配置页面入口 js 文件 entry: './src/index.js', // 配置打包输出相关 output: { // 打包输出目录 path: resolve(__dirname, 'dist'), // 入口 js 的打包输出文件名 filename: 'index.js' }, module: { /* 配置各种类型文件的加载器,称之为 loader webpack 当遇到 import ... 时,会调用这里配置的 loader 对引用的文件进行编译 */ rules: [ { /* 使用 babel 编译 ES6 / ES7 / ES8 为 ES5 代码 使用正则表达式匹配后缀名为 .js 的文件 */ test: /\.js$/, // 排除 node_modules 目录下的文件,npm 安装的包不需要编译 exclude: /node_modules/, /* use 指定该文件的 loader, 值可以是字符串或者数组。 这里先使用 eslint-loader 处理,返回的结果交给 babel-loader 处理。loader 的处理顺序是从最后一个到第一个。 eslint-loader 用来检查代码,如果有错误,编译的时候会报错。 babel-loader 用来编译 js 文件。 */ use: ['babel-loader', 'eslint-loader'] }, { // 匹配 html 文件 test: /\.html$/, /* 使用 html-loader, 将 html 内容存为 js 字符串,比如当遇到 import htmlString from './template.html'; template.html 的文件内容会被转成一个 js 字符串,合并到 js 文件里。 */ use: 'html-loader' }, { // 匹配 css 文件 test: /\.css$/, /* 先使用 css-loader 处理,返回的结果交给 style-loader 处理。 css-loader 将 css 内容存为 js 字符串,并且会把 background, @font-face 等引用的图片, 字体文件交给指定的 loader 打包,类似上面的 html-loader, 用什么 loader 同样在 loaders 对象中定义,等会下面就会看到。 */ use: ['style-loader', 'css-loader'] }, { /* 匹配各种格式的图片和字体文件 上面 html-loader 会把 html 中 <img> 标签的图片解析出来,文件名匹配到这里的 test 的正则表达式, css-loader 引用的图片和字体同样会匹配到这里的 test 条件 */ test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/, /* 使用 url-loader, 它接受一个 limit 参数,单位为字节(byte) 当文件体积小于 limit 时,url-loader 把文件转为 Data URI 的格式内联到引用的地方 当文件大于 limit 时,url-loader 会调用 file-loader, 把文件储存到输出目录,并把引用的文件路径改写成输出后的路径 比如 views/foo/index.html 中 <img src="smallpic.png"> 会被编译成 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA..."> 而 <img src="largepic.png"> 会被编译成 <img src="/f78661bef717cf2cc2c2e5158f196384.png"> */ use: [ { loader: 'url-loader', options: { limit: 10000 } } ] } ] }, /* 配置 webpack 插件 plugin 和 loader 的区别是,loader 是在 import 时根据不同的文件名,匹配不同的 loader 对这个文件做处理, 而 plugin, 关注的不是文件的格式,而是在编译的各个阶段,会触发不同的事件,让你可以干预每个编译阶段。 */ plugins: [ /* html-webpack-plugin 用来打包入口 html 文件 entry 配置的入口是 js 文件,webpack 以 js 文件为入口,遇到 import, 用配置的 loader 加载引入文件 但作为浏览器打开的入口 html, 是引用入口 js 的文件,它在整个编译过程的外面, 所以,我们需要 html-webpack-plugin 来打包作为入口的 html 文件 */ new HtmlWebpackPlugin({ /* template 参数指定入口 html 文件路径,插件会把这个文件交给 webpack 去编译, webpack 按照正常流程,找到 loaders 中 test 条件匹配的 loader 来编译,那么这里 html-loader 就是匹配的 loader html-loader 编译后产生的字符串,会由 html-webpack-plugin 储存为 html 文件到输出目录,默认文件名为 index.html 可以通过 filename 参数指定输出的文件名 html-webpack-plugin 也可以不指定 template 参数,它会使用默认的 html 模板。 */ template: './src/index.html', /* 因为和 webpack 4 的兼容性问题,chunksSortMode 参数需要设置为 none https://github.com/jantimon/html-webpack-plugin/issues/870 */ chunksSortMode: 'none' }) ] }/* 配置开发时用的服务器,让你可以用 http://127.0.0.1:8080/ 这样的 url 打开页面来调试 并且带有热更新的功能,打代码时保存一下文件,浏览器会自动刷新。比 nginx 方便很多 如果是修改 css, 甚至不需要刷新页面,直接生效。这让像弹框这种需要点击交互后才会出来的东西调试起来方便很多。 因为 webpack-cli 无法正确识别 serve 选项,使用 webpack-cli 执行打包时会报错。 因此我们在这里判断一下,仅当使用 webpack-serve 时插入 serve 选项。 issue:https://github.com/webpack-contrib/webpack-serve/issues/19 */if (dev) { module.exports.serve = { // 配置监听端口,默认值 8080 port: 8080, // add: 用来给服务器的 koa 实例注入 middleware 增加功能 add: app => { /* 配置 SPA 入口 SPA 的入口是一个统一的 html 文件,比如 http://localhost:8080/foo 我们要返回给它 http://localhost:8080/index.html 这个文件 */ app.use(convert(history())) } } }
作者:冷落i
链接:https://www.jianshu.com/p/991e1067eee0