Github:
https://github.com/fenivana/w…webpack 更新到了 4.0,官网还没有更新文档。因而把教程更新一下,轻易人人用起 webpack 4。
写在开首
先说说为何要写这篇文章,最初的原因是组里的小朋友们看了 webpack 文档后,脸色都是如许的:摘自 webpack 一篇文档的批评区)
和如许的:
是的,即使是外国佬也在吐槽这文档不是人能看的。回想起昔时自身啃 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),比方下图:
规划会把 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 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
等。感兴致的同砚可以参照 这里 装置运用。
eslint-loader 用于在 webpack 编译的时刻搜检代码,假如有毛病,webpack 会报错。
项目里装置了 eslint 还没用,我们的 IDE 和编辑器也得要装 eslint 插件支撑它。
Visual Studio Code 须要装置 ESLint 扩大
atom 须要装置 linter 和 linter-eslint 这两个插件,装好后重启见效。
WebStorm 须要在设置中翻开 eslint 开关:
写几个页面
我们写一个最简朴的 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:
// 引入 router
import router from './router'
// 启动 router
router.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:
// 引入 router
import 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:
// 引入 router
import 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()))
}
}
}
走一个
设置 OK 了,接下来我们就运转一下吧。我们先试一下开辟环境用的 webpack-serve
:
./node_modules/.bin/webpack-serve webpack.config.js
实行时须要指定设置文件。
上面的敕令实用于 Mac / Linux 等 * nix 体系,也实用于 Windows 上的 PowerShell 和 bash/zsh 环境(Windows Subsystem for Linux, Git Bash、Babun、MSYS2 等)。安利一下 Windows 同砚运用 Ubuntu on Windows,可以防备许多跨平台的题目,比方设置环境变量。
假如运用 Windows 的 cmd.exe,请实行:
node_modules\.bin\webpack-serve webpack.config.js
npm 会把包的可实行文件装置到 ./node_modules/.bin/
目次下,所以我们要在这个目次下实行敕令。
敕令实行后,掌握台显现:
「wdm」: Compiled successfully。
这就代表编译胜利了,我们可以在阅读器翻开 http://localhost:8080/
看看结果。假如有报错,那多是什么处所没弄对?请自身细致搜检一下~
我们可以随便变动一下 src 目次下的源代码,保存后,阅读器里的页面应当很快会有响应变化。
要退出编译,按 ctrl+c
。
开辟环境编译试过以后,我们碰运气编译临盆环境的代码,敕令是:
./node_modules/.bin/webpack-cli
不须要指定设置文件,默许读取 webpack.config.js
实行剧本的敕令有点贫苦,因而,我们可以运用 npm,把敕令写在 package.json
中:
{
"scripts": {
"dev": "webpack-serve webpack.config.js",
"build": "webpack-cli"
}
}
package.json
中的 scripts
对象,可以用来写一些剧本敕令,敕令不须要前缀目次 ./node_modules/.bin/
,npm 会自动寻觅该目次下的敕令。我们可以实行:
npm run dev
来启动开辟环境。
实行
npm run build
来打包临盆环境的代码。
进阶设置
上面的项目虽然可以跑起来了,但有几个点我们还没有考虑到:
- 设置静态资本的 url 途径前缀
- 各个页面离开打包
- 第三方库和营业代码离开打包
- 输出的 entry 文件加上 hash
- 开辟环境封闭 performance.hints
- 设置 favicon
- 开辟环境许可其他电脑接见
- 打包时自定义部份参数
- webpack-serve 处置惩罚途径带后缀名的文件的特别划定规矩
- 代码中插进去环境变量
- 简化 import 途径
- 优化 babel 编译后的代码机能
- 运用 webpack 自带的 ES6 模块处置惩罚功用
- 运用 autoprefixer 自动建立 css 的 vendor prefixes
那末,让我们在上面的设置的基本上继承完美,下面的代码我们只写出转变的部份。代码在 examples/advanced 目次。
设置静态资本的 url 途径前缀
如今我们的资本文件的 url 直接在根目次,比方 http://127.0.0.1:8080/index.js
, 如许做缓存掌握和 CDN 不是很轻易,因而我们给资本文件的 url 加一个前缀,比方 http://127.0.0.1:8080/assets/index.js
. 我们来修正一下 webpack 设置:
{
output: {
publicPath: '/assets/'
}
}
webpack-serve
也须要修正:
if (dev) {
module.exports.serve = {
port: 8080,
host: '0.0.0.0',
dev: {
/*
指定 webpack-dev-middleware 的 publicpath
平常状况下与 output.publicPath 坚持一致(除非 output.publicPath 运用的是相对途径)
https://github.com/webpack/webpack-dev-middleware#publicpath
*/
publicPath: '/assets/'
},
add: app => {
app.use(convert(history({
index: '/assets/' // index.html 文件在 /assets/ 途径下
})))
}
}
}
各个页面离开打包
如许阅读器只需加载当前页面所需的代码。
webpack 可以运用异步加载文件的体式格局援用模块,我们运用 async/
await 和 dynamic import 来完成:
src/router.js:
// 将 async/await 转换成 ES5 代码后须要这个运转时库来支撑
import 'regenerator-runtime/runtime'
const routes = {
// import() 返回 promise
'/foo': () => import('./views/foo'),
'/bar.do': () => import('./views/bar.do')
}
class Router {
// ...
// 加载 path 途径的页面
// 运用 async/await 语法
async load(path) {
// 首页
if (path === '/') path = '/foo'
// 动态加载页面
const View = (await routes[path]()).default
// 建立页面实例
const view = new View()
// 挪用页面要领,把页面加载到 document.body 中
view.mount(document.body)
}
}
如许我们就不须要在开首把一切页面文件都 import 进来了。
由于 import()
还没有正式进入规范,须要运用 babel-preset-stage-2 来支撑:
npm install babel-preset-stage-2 --save-dev
package.json
改一下:
{
"babel": {
"presets": [
"env",
"stage-2"
]
}
}
然后修正 webpack 设置:
{
output: {
/*
代码中援用的文件(js、css、图片等)会依据设置兼并为一个或多个包,我们称一个包为 chunk。
每一个 chunk 包括多个 modules。不管是不是是 js,webpack 都将引入的文件视为一个 module。
chunkFilename 用来设置这个 chunk 输出的文件名。
[chunkhash]:这个 chunk 的 hash 值,文件发作变化时该值也会变。运用 [chunkhash] 作为文件名可以防备阅读器读取旧的缓存文件。
另有一个占位符 [id],编译时每一个 chunk 会有一个id。
我们在这里不运用它,由于这个 id 是个递增的数字,增添或削减一个chunk,都可以致使其他 chunk 的 id 发作转变,致使缓存失效。
*/
chunkFilename: '[chunkhash].js',
}
}
第三方库和营业代码离开打包
如许更新营业代码时可以借助阅读器缓存,用户不须要从新下载没有发作变化的第三方库。
Webpack 4 最大的革新就是自动拆分 chunk, 假如同时满足以下前提,chunk 就会被拆分:
- 新的 chunk 能被复用,或许模块是来自 node_modules 目次
- 新的 chunk 大于 30Kb(min+gz 紧缩前)
- 按需加载 chunk 的并发要求数目小于即是 5 个
- 页面初始加载时的并发要求数目小于即是 3 个
平常状况只需设置这几个参数即可:
{
plugins: [
// ...
/*
运用文件途径的 hash 作为 moduleId。
虽然我们运用 [chunkhash] 作为 chunk 的输着名,但仍然不够。
由于 chunk 内部的每一个 module 都有一个 id,webpack 默许运用递增的数字作为 moduleId。
假如引入了一个新文件或删掉一个文件,可以会致使其他文件的 moduleId 也发作转变,
那末受影响的 module 地点的 chunk 的 [chunkhash] 就会发作转变,致使缓存失效。
因而运用文件途径的 hash 作为 moduleId 来防备这个题目。
*/
new webpack.HashedModuleIdsPlugin()
],
optimization: {
/*
上面提到 chunkFilename 指定了 chunk 打包输出的名字,那末文件名存在那里了呢?
它就存在援用它的文件中。这意味着一个 chunk 文件名发作转变,会致使援用这个 chunk 文件也发作转变。
runtimeChunk 设置为 true, webpack 就会把 chunk 文件名悉数存到一个零丁的 chunk 中,
如许更新一个文件只会影响到它地点的 chunk 和 runtimeChunk,防备了援用这个 chunk 的文件也发作转变。
*/
runtimeChunk: true,
splitChunks: {
/*
默许 entry 的 chunk 不会被拆分
由于我们运用了 html-webpack-plugin 来动态插进去 <script> 标签,entry 被拆成多个 chunk 也能自动被插进去到 html 中,
所以我们可以设置成 all, 把 entry chunk 也拆分了
*/
chunks: 'all'
}
}
}
webpack 4 支撑更多的手动优化,详见: https://gist.github.com/sokra…
但正如 webpack 文档中所说,默许设置已充足优化,在没有测试的状况下不要自觉手动优化。
输出的 entry 文件加上 hash
上面我们提到了 chunkFilename
运用 [chunkhash]
防备阅读器读取毛病缓存,那末 entry 一样须要加上 hash。
但运用 webpack-serve
启动开辟环境时,entry 文件是没有 [chunkhash]
的,用了会报错。
因而我们只在实行 webpack-cli
时运用 [chunkhash]
。
{
output: {
filename: dev ? '[name].js' : '[chunkhash].js'
}
}
这里我们运用了 [name]
占位符。诠释它之前我们先相识一下 entry
的完整定义:
{
entry: {
NAME: [FILE1, FILE2, ...]
}
}
我们可以定义多个 entry 文件,比方你的项目有多个 html 进口文件,每一个 html 对应一个或多个 entry 文件。
然后每一个 entry 可以定义由多个 module 构成,这些 module 会顺次实行。
在 webpack 4 之前,这是很有效的功用,比方之前提到的第三方库和营业代码离开打包,在之前,我们须要这么设置:
{
entry {
main: './src/index.js',
vendor: ['jquery', 'lodash']
}
}
entry 援用文件的划定规矩和 import
是一样的,会寻觅 node_modules
里的包。然后连系 CommonsChunkPlugin
把 vendor 定义的 module 从营业代码分离出来打包成一个零丁的 chunk。
假如 entry 是一个 module,我们可以不运用数组的情势。
在 simple 项目中,我们设置了 entry: './src/index.js'
,这是最简朴的情势,转换成完整的写法就是:
{
entry: {
main: ['./src/index.js']
}
}
webpack 会给这个 entry 指定名字为 main
。
看到这应当晓得 [name]
的意义了吧?它就是 entry 的名字。
有人可以注重到官网文档中另有一个 [hash]
占位符,这个 hash 是悉数编译历程发生的一个总的 hash 值,而不是单个文件的 hash 值,项目中任何一个文件的修改,都邑形成这个 hash 值的转变。[hash]
占位符是一直存在的,但我们不愿望修正一个文件致使一切输出的文件 hash 都转变,如许就没法运用阅读器缓存了。因而这个 [hash]
意义不大。
开辟环境封闭 performance.hints
我们注重到运转开辟环境是敕令行会报一段 warning:
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (250 kB).
This can impact web performance.
这是说发起每一个输出的 js 文件的大小不要凌驾 250k。但开辟环境由于包括了 sourcemap 而且代码未紧缩所以平常都邑凌驾这个大小,所以我们可以在开辟环境把这个 warning 封闭。
webpack 设置中到场:
{
performance: {
hints: dev ? false : 'warning'
}
}
设置 favicon
在 src 目次中放一张 favicon.png,然后 src/index.html
的 <head>
中插进去:
<link rel="icon" type="image/png" href="favicon.png">
修正 webpack 设置:
{
module: {
rules: [
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
/*
html-loader 接收 attrs 参数,示意什么标签的什么属性须要挪用 webpack 的 loader 举行打包。
比方 <img> 标签的 src 属性,webpack 会把 <img> 援用的图片打包,然后 src 的属性值替代为打包后的途径。
运用什么 loader 代码,一样是在 module.rules 定义中运用婚配的划定规矩。
假如 html-loader 不指定 attrs 参数,默许值是 img:src, 意味着会默许打包 <img> 标签的图片。
这里我们加上 <link> 标签的 href 属性,用来打包进口 index.html 引入的 favicon.png 文件。
*/
attrs: ['img:src', 'link:href']
}
}
]
},
{
/*
婚配 favicon.png
上面的 html-loader 会把进口 index.html 援用的 favicon.png 图标文件剖析出来举行打包
打包划定规矩就根据这里指定的 loader 实行
*/
test: /favicon\.png$/,
use: [
{
// 运用 file-loader
loader: 'file-loader',
options: {
/*
name:指定文件输着名
[hash] 为源文件的hash值,[ext] 为后缀。
*/
name: '[hash].[ext]'
}
}
]
},
// 图片文件的加载设置增添一个 exclude 参数
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
// 消除 favicon.png, 由于它已由上面的 loader 处置惩罚了。假如不消撤除,它会被这个 loader 再处置惩罚一遍
exclude: /favicon\.png$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
]
}
}
实在 html-webpack-plugin 接收一个 favicon
参数,可以指定 favicon 文件途径,会自动打包插进去到 html 文件中。但它有个 bug,打包后的文件名途径不带 hash,就算有 hash,它也是 [hash],而不是 [chunkhash]。致使修正代码也会转变 favicon 打包输出的文件名。issue 中提到的 favicons-webpack-plugin 却是可以用,但它依靠 PhantomJS, 异常大。
开辟环境许可其他电脑接见
const internalIp = require('internal-ip')
module.exports.serve = {
host: '0.0.0.0',
hot: {
host: {
client: internalIp.v4.sync(),
server: '0.0.0.0'
}
},
// ...
}
打包时自定义部份参数
在多人开辟时,每一个人可以须要有自身的设置,比方说 webpack-serve 监听的端口号,假如写死在 webpack 设置里,而谁人端口号在某个同砚的电脑上被其他历程占用了,简朴粗犷的修正 webpack.config.js
会致使提交代码后其他同砚的端口也被改掉。
另有一点就是开辟环境、测试环境、临盆环境的部份 webpack 设置是差别的,比方 publicPath
在临盆环境可以要设置一个 CDN 地点。
我们在根目次竖立一个文件夹 config
,内里建立 3 个设置文件:
- default.js: 临盆环境
module.exports = {
publicPath: 'http://cdn.example.com/assets/'
}
- dev.js: 默许开辟环境
module.exports = {
publicPath: '/assets/',
serve: {
port: 8090
}
}
- local.js: 个人当地环境,在 dev.js 基本上修正部份参数。
const config = require('./dev')
config.serve.port = 8070
module.exports = config
package.json
修正 scripts
:
{
"scripts": {
"local": "npm run webpack-serve --config=local",
"dev": "npm run webpack-serve --config=dev",
"webpack-serve": "webpack-serve webpack.config.js",
"build": "webpack-cli"
}
}
webpack 设置修正:
// ...
const url = require('url')
const config = require('./config/' + (process.env.npm_config_config || 'default'))
module.exports = {
// ...
output: {
// ...
publicPath: config.publicPath
}
// ...
}
if (dev) {
module.exports.serve = {
host: '0.0.0.0',
port: config.serve.port,
dev: {
publicPath: config.publicPath
},
add: app => {
app.use(convert(history({
index: url.parse(config.publicPath).pathname
})))
}
}
}
这里的关键是 npm run
传进来的自定义参数可以经过历程 process.env.npm_config_*
取得。参数中假如有 -
会被转成 _
。
另有一点,我们不须要把自身个人用的设置文件提交到 git,所以我们在 .gitignore
中到场:
config/*
!config/default.js
!config/dev.js
把 config
目次消撤除,然则保存临盆环境和 dev 默许设置文件。
可以有同砚注重到了 webpack-cli
可以经过历程 –env 的体式格局从敕令行传参给剧本,遗憾的是 webpack-cli
不支撑。
webpack-serve 处置惩罚带后缀名的文件的特别划定规矩
当处置惩罚带后缀名的要求时,比方 http://localhost:8080/bar.do ,connect-history-api-fallback
会以为它应当是一个现实存在的文件,就算找不到该文件,也不会 fallback 到 index.html,而是返回 404。但在 SPA 运用中这不是我们愿望的。
幸好有一个设置选项 disableDotRule: true
可以禁用这个划定规矩,使带后缀的文件当不存在时也能 fallback 到 index.html
module.exports.serve = {
// ...
add: app => {
app.use(convert(history({
// ...
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] // 须要合营 disableDotRule 一同运用
})))
}
}
代码中插进去环境变量
在营业代码中,有些变量在开辟环境和临盆环境是差别的,比方域名、背景 API 地点等。另有开辟环境可以须要打印调试信息等。
我们可以运用 DefinePlugin 插件在打包时往代码中插进去须要的环境变量。
// ...
const pkgInfo = require('./package.json')
module.exports = {
// ...
plugins: [
new webpack.DefinePlugin({
DEBUG: dev,
VERSION: JSON.stringify(pkgInfo.version),
CONFIG: JSON.stringify(config.runtimeConfig)
}),
// ...
]
}
DefinePlugin 插件的道理很简朴,假如我们在代码中写:
console.log(DEBUG)
它会做相似如许的处置惩罚:
'console.log(DEBUG)'.replace('DEBUG', true)
末了天生:
console.log(true)
这里有一点须要注重,像这里的 VERSION
, 假如我们不对 pkgInfo.version
做 JSON.stringify()
,
console.log(VERSION)
然后做替代操纵:
'console.log(VERSION)'.replace('VERSION', '1.0.0')
末了天生:
console.log(1.0.0)
如许语法就毛病了。所以,我们须要 JSON.stringify(pkgInfo.version)
转一下变成 '"1.0.0"'
,替代的时刻才会带引号。
另有一点,webpack 打包紧缩的时刻,会把代码举行优化,比方:
if (DEBUG) {
console.log('debug mode')
} else {
console.log('production mode')
}
会被编译成:
if (false) {
console.log('debug mode')
} else {
console.log('production mode')
}
然后紧缩优化为:
console.log('production mode')
简化 import 途径
文件 a 引入文件 b 时,b 的途径是相干于 a 文件地点目次的。假如 a 和 b 在差别的目次,藏得又深,写起来就会很贫苦:
import b from '../../../components/b'
为了轻易,我们可以定义一个途径别号(alias):
resolve: {
alias: {
'~': resolve(__dirname, 'src')
}
}
如许,我们可以以 src
目次为基本途径来 import
文件:
import b from '~/components/b'
html 中的 <img>
标签没法运用这个别号功用,但 html-loader
有一个 root
参数,可以使 /
开首的文件相干于 root
目次剖析。
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: {
root: resolve(__dirname, 'src'),
attrs: ['img:src', 'link:href']
}
}
]
}
那末,<img src="/favicon.png">
就可以顺遂指向到 src 目次下的 favicon.png 文件,不须要体贴当前文件和目的文件的相对途径。
PS: 在调试 <img>
标签的时刻碰到一个坑,html-loader
会剖析 <!-- -->
解释中的内容,之前在解释中写的
<!--
大于 10kb 的图片,图片会被储存到输出目次,src 会被替代为打包后的途径
<img src="/assets/f78661bef717cf2cc2c2e5158f196384.png">
-->
之前由于没有加 root
参数,所以 /
开首的文件名不会被剖析,加了 root
致使编译时报错,找不到该文件。人人记着这一点。
优化 babel 编译后的代码机能
babel 编译后的代码平常会形成机能丧失,babel 供应了一个 loose 选项,使编译后的代码不须要完整遵照 ES6 划定,简化编译后的代码,进步代码实行效力:
package.json:
{
"babel": {
"presets": [
[
"env",
{
"loose": true
}
],
"stage-2"
]
}
}
但这么做会有兼容性的风险,可以会致使 ES6 源码理应的实行结果和编译后的 ES5 代码的现实结果并不一致。假如代码没有碰到现实的效力瓶颈,官方 不发起 运用 loose
形式。
运用 webpack 自带的 ES6 模块处置惩罚功用
我们现在的设置,babel 会把 ES6 模块定义转为 CommonJS 定义,但 webpack 自身可以处置惩罚 import
和 export
, 而且 webpack 处置惩罚 import
时会做代码优化,把没用到的部份代码删撤除。因而我们经过历程 babel 供应的 modules: false
选项把 ES6 模块转为 CommonJS 模块的功用给封闭掉。
package.json:
{
"babel": {
"presets": [
[
"env",
{
"loose": true,
"modules": false
}
],
"stage-2"
]
}
}
运用 autoprefixer 自动建立 css 的 vendor prefixes
css 有一个很贫苦的题目就是比较新的 css 属性在各个阅读器里是要加前缀的,我们可以运用 autoprefixer 东西自动建立这些阅读器划定规矩,那末我们的 css 中只须要写:
:fullscreen a {
display: flex
}
autoprefixer 会编译成:
:-webkit-full-screen a {
display: -webkit-box;
display: flex
}
:-moz-full-screen a {
display: flex
}
:-ms-fullscreen a {
display: -ms-flexbox;
display: flex
}
:fullscreen a {
display: -webkit-box;
display: -ms-flexbox;
display: flex
}
起首,我们用 npm 装置它:
npm install postcss-loader autoprefixer --save-dev
autoprefixer 是 postcss 的一个插件,所以我们也要装置 postcss 的 webpack loader。
修正一下 webpack 的 css rule:
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
然后建立文件 postcss.config.js
:
module.exports = {
plugins: [
require('autoprefixer')()
]
}
运用 webpack 打包多页面运用(Multiple-Page Application)
多页面网站一样可以用 webpack 来打包,以便运用 npm 包,import()
,code splitting
等优点。
MPA 意味着并没不是一个单一的 html 进口和 js 进口,而是每一个页面对应一个 html 和多个 js。那末我们可以把项目构造设计为:
├── dist
├── package.json
├── node_modules
├── src
│ ├── components
│ ├── shared
| ├── favicon.png
│ └── pages 页面放这里
| ├── foo 编译后天生 http://localhost:8080/foo.html
| | ├── index.html
| | ├── index.js
| | ├── style.css
| | └── pic.png
| └── bar http://localhost:8080/bar.html
| ├── index.html
| ├── index.js
| ├── style.css
| └── baz http://localhost:8080/bar/baz.html
| ├── index.html
| ├── index.js
| └── style.css
└── webpack.config.js
这里每一个页面的 index.html
是个完整的从 <!DOCTYPE html>
开首到 </html>
完毕的页面,这些文件都要用 html-webpack-plugin
处置惩罚。index.js
是每一个页面的营业逻辑,作为每一个页面的进口 js 设置到 entry
中。这里我们须要用 glob
库来把这些文件都挑选出来批量操纵。为了运用 webpack 4 的 optimization.splitChunks
和 optimization.runtimeChunk
功用,我写了 html-webpack-include-sibling-chunks-plugin 插件来合营运用。还要装几个插件把 css 紧缩并放到 <head>
中。
npm install glob html-webpack-include-sibling-chunks-plugin uglifyjs-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev
webpack.config.js
修正的处所:
// ...
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackIncludeSiblingChunksPlugin = require('html-webpack-include-sibling-chunks-plugin')
const glob = require('glob')
const dev = Boolean(process.env.WEBPACK_SERVE)
const config = require('./config/' + (process.env.npm_config_config || 'default'))
const entries = glob.sync('./src/**/index.js')
const entry = {}
const htmlPlugins = []
for (const path of entries) {
const template = path.replace('index.js', 'index.html')
const chunkName = path.slice('./src/pages/'.length, -'/index.js'.length)
entry[chunkName] = dev ? [path, template] : path
htmlPlugins.push(new HtmlWebpackPlugin({
template,
filename: chunkName + '.html',
chunksSortMode: 'none',
chunks: [chunkName]
}))
}
module.exports = {
entry,
output: {
path: resolve(__dirname, 'dist'),
// 我们不定义 publicPath,不然接见 html 时须要带上 publicPath 前缀
filename: dev ? '[name].js' : '[chunkhash].js',
chunkFilename: '[chunkhash].js'
},
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all'
},
minimizer: dev ? [] : [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin()
]
},
module: {
rules: [
// ...
{
test: /\.css$/,
use: [dev ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
},
// ...
]
},
plugins: [
// ...
/*
这里不运用 [chunkhash]
由于从同一个 chunk 抽离出来的 css 同享同一个 [chunkhash]
[contenthash] 你可以简朴理解为 moduleId + content 天生的 hash
因而一个 chunk 中的多个 module 有自身的 [contenthash]
*/
new MiniCssExtractPlugin({
filename: '[contenthash].css',
chunkFilename: '[contenthash].css'
}),
// 必需放在html-webpack-plugin前面
new HtmlWebpackIncludeSiblingChunksPlugin(),
...htmlPlugins
],
// ...
}
entry
和 htmlPlugins
会经过历程遍历 pages 目次天生,比方:
entry:
{
'bar/baz': './src/pages/bar/baz/index.js',
bar: './src/pages/bar/index.js',
foo: './src/pages/foo/index.js'
}
在开辟环境中,为了可以修正 html 文件后网页可以自动革新,我们还须要把 html 文件也到场 entry 中,比方:
{
foo: ['./src/pages/foo/index.js', './src/pages/foo/index.html']
}
如许,当 foo 页面的 index.js 或 index.html 文件修改时,都邑触发阅读器革新该页面。虽然把 html 到场 entry 很新鲜,但宁神,不会致使毛病。记得不要在临盆环境这么做,不然致使 chunk 文件包括了无用的 html 片断。
htmlPlugins:
[
new HtmlWebpackPlugin({
template: './src/pages/bar/baz/index.html',
filename: 'bar/baz.html',
chunksSortMode: 'none',
chunks: ['bar/baz']
},
new HtmlWebpackPlugin({
template: './src/pages/bar/index.html',
filename: 'bar.html',
chunksSortMode: 'none',
chunks: ['bar']
},
new HtmlWebpackPlugin({
template: './src/pages/foo/index.html',
filename: 'foo.html',
chunksSortMode: 'none',
chunks: ['foo']
}
]
代码在 examples/mpa 目次。
总结
经过历程这篇文章,我想人人应当学会了 webpack 的准确翻开姿态。虽然我没有说起怎样用 webpack 来编译 React 和 vue.js, 但人人可以想到,无非是装置一些 loader 和 plugin 来处置惩罚 jsx 和 vue 花样的文件,当时难度就不在于 webpack 了,而是代码架构构造的题目了。细致的人人自身去探索一下。