Webpack打包一解
一. 简述
万水千山总是情,稍缓一缓行不行......。
做为一个程序猿,感觉身体被掏空,需要不断学习很多很多,因为项目中用到,所以就找了好多资料了解了下,现在总结下方便后面更好的学习。
image.png
1. 为什么使用webpack
现在是网络时代,在我们的生活中网络成为了必不可少的,我们在网络上可以看到很多漂亮的功能丰富的页面,这些页面都是由复杂的JavaScript代码和各种依赖包组合形成的,那么这些都是怎么*组合在一起的呢,组合在一起需要花费多少精力呢,经过漫长发展时间现前端涌现出了很多实践方法来处理复杂的工作流程,让开发变得更加简便。
- 模块化 可以使复杂的程序细化成为各个小的文件
-
预处理器 可以对Scss,less等CSS预先进行处理
......
2. 什么是webpack
模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
二. weback使用流程
1. 安装
因为安装webpack要用npm,所以安装之前我们首先要安装node
首先我们要新创建一个文件夹,这里名称就叫做webpackDemo
// 创建文件夹
mkdir webpackDemo
// 进入文件夹
cd webpackDemo
第一步 安装全局的webpack
npm install webpack -g
webpack-dev-server 介绍
npm install webpack-dev-server -g
- 是一个小型node.js express服务器
- 新建一个开发服务器,可以serve我们pack以后的代码,并且当代码更新的时候自动刷新浏览器
- 启动webpack-dev-server后,你在目标文件夹中是看不到编译后的文件的,实时编译后的文件都保存到了内存当中
注:如果要全局安装着个命令,那么在全局安装完webpack之后就可以执行这个命令,如果只是要安装到项目中,在项目根目录下安装(不加-g,在项目根目录出现node_modules文件夹,内含webpack-dev-server及其依赖包)
此练习是在根目录下安装,后面会提到。
第二步 要用npm init
初始化,生成package.json文件
npm init
初始化过程中会有好多提示,如果非正式项目下可以直接回车调过,括号里的都是默认的,正式项目下可以根据情况填写每一步
name: (webpackDemo) // 项目名称
version: (1.0.0) // 版本号
description: // 项目的描述
entry point: (index.js) // 入口文件
test command: // 测试命令
git repository: // git仓库
keywords: // 关键字
author: // 作者创始人
license: (ISC) //许可:(ISC)
About to write to C:\Users\Administrator\Desktop\webpackDemo\package.json:
{
"name": "webpackdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this ok? (yes) // 这里直接输入yes就可以了
第三步 安装webpack-dev-server
npm install --save-dev webpack
注:由于webpack版本号升级,4.0之后的版本安装配置有差异,所以本次安装的是3.5.6的版本,运行的是以下命令
npm install webpack@3.5 --save-dev
接下来看下我们创建的package.json文件,里面的都是我们刚配置的
{
"name": "webpackdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.5.6" // 本次安装的webpack-dev-server
}
}
注意:package.json
文件中不能有注释,在运行的时候请将注释删除
第四步 创建需要用到的文件
- 为了好区分先创建两个文件夹app和common
app 文件夹用来存放原始数据和我们将写的JavaScript模块
common 文件夹用来存放之后供浏览器读取的文件(包括使用webpack打包生成的js文件以及一个index.html
文件)
mkdir app
mkdir common
- 在app内创建两个文件
cd app // 进入app文件夹
touch app.js // 创建app.js文件
touch main.js // 创建main.js文件
- 在common文件夹创建一个文件
cd .. //返回到webpackDemo文件
cd common // 进入common文件
touch index.html // 创建index.html文件
下面看下项目的目录结构
第五步 基础代码
- 主入口index.html
index.html是主入口,需要设置根目录并且将打包后的文件导入
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
- app.js
app.js是我们写的模块,并依据CommonJS规范导出这个模块,这里我们以输出welcome to use webpack
为例
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "welcome to use webpack!";
return greet;
}
- main.js
main.js其实是一个组件,它的目的是将我们写的一些代码模块返回并插入到页面中
const greeter = require('./app.js');
document.querySelector("#root").appendChild(greeter());
第六步 使用Webpack打包
webpack可以在终端中使用,基本的使用方法如下:
node_modules/.bin/webpack app/main.js common/index.js // 注意是非全局安装情况
app/main.js // 是入口文件的路径,本文中就是上述main.js的路径
common/index.js // 是打包文件的存放路径
现在我们看下终端打包命令执行后
image.png
可以看出webpack同时编译了main.js 和app,js,现在打开编辑器,可以看到如下结果
image.png
image.png
从编辑器可以看出打包后的index.js已经生成,接下来我们在看下页面
image.png
是不是很激动我们已近将welcome to use webpack!
在页面打包生成,但是这种方式需要在终端运行复杂的命令而且容易出错很不方便,如果能更方便点就好了,那么接下来我们在看下它的升级版打包。
第七步 通过配置文件webpack.config.js
来使用webpack
Webpack拥有很多其它的比较高级的功能,这些功能其实都可以通过命令行模式实现,但是在终端中进行复杂的操作,这样不太方便且容易出错的,更好的办法是定义一个配置文件,这个配置文件其实也是一个简单的JavaScript模块,我们可以把所有的与打包相关的信息放在里面。
在当前项目webpackDemo文件夹下新创建一个文件webpack.config.js
,写入简单的配置代码,目前的配置主要涉及到的内容是入口文件路径和打包后文件的存放路径
module.exports = {
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
}
}
注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。
有了这个配置之后,再打包文件,只需在终端里运行webpack(全局情况下)或node_modules/.bin/webpack(非全局安装需使用)
命令就可以了,不需要再命令行打入主入口和打包文件的路径了,这条命令会自动引用webpack.config.js
文件中的配置选项。
示例如下:
是不是很简单这样我们就不用再终端输入那么多烦人的配置文件的路径了,那么如果
node_modules/.bin/webpack
这条命令都不用在终端输入,是不是更简单呢?,接下来接着往下看。更加方便的打包操作
根据上面的方式来看我们只要配置了
webpack.config.js
就可以将打包的路径命令省去,那么我们想是否可以以这种方式将node_modules/.bin/webpack
命令省去呢? 答案是可以,只不过不是在这个文件内配置,也不用去新建文件配置。npm可以引导任务执行,对npm进行配置后可以在命令行中使用简单的npm start命令来替代上面略微繁琐的命令。在
package.json
中对scripts对象进行相关设置即可,设置方法如下。
{
"name": "webpackdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack" // 修改的是这里,JSON文件不支持注释,引用时请清除
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.5.6"
}
}
注:package.json中的script会安装一定顺序寻找命令对应位置,本地的node_modules/.bin
路径就在这个寻找清单中,所以无论是全局还是局部安装的Webpack,你都不需要写前面那指明详细的路径了。
npm的start命令是一个特殊的脚本名称,其特殊性表现在,在命令行中使用npm start就可以执行其对于的命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}
如npm run build
,我们在命令行中输入npm start
,看看输出结果是什么,输出结果如下:
现在只需要使用
npm start
就可以打包文件了,有没有觉得webpack也不过如此嘛,不过不要太小瞧webpack,要充分发挥其强大的功能我们需要修改配置文件的其它选项,一项项来看。
三. webpack配置之Source Maps
1. 什么是Source Maps
devtool配置 | 描述 |
---|---|
source-map |
在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map ,但是它会减慢打包速度 |
cheap-module-source-map |
在一个单独的文件中生成一个不带列映射的map ,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便 |
eval-source-map |
使用eval 打包源文件模块,在同一个文件中生成干净的完整的source map 。这个选项可以在不影响构建速度的前提下生成完整的sourcemap ,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项 |
cheap-module-eval-source-map |
这是在打包文件时最快的生成source map 的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map 选项具有相似的缺点 |
在开发过程中总是需要调试的,当打包压缩后调试打包文件的时候,我们往往都感到很头疼,因为打包后的文件都是压缩到一起的,我们很难找到问题所在,这样严重影响调试效率。为了我们开发过程中调试更方便快捷,我们就要用到Source Maps。
Source map就是一个信息文件,里面储存着位置信息,也就是说,转换后的代码的每一个位置,所对应的转换前的位置。当我们在开发中出错的时候,除错工具将直接显示原始代码,而不是转换后的代码,这样使得编译后的代码可读性更高,也更方便快捷调试。
在webpack
配置中配置source maps
时我们需要先配置devtool
devtool配置有四种不同的方法,具体如下:
devtool配置 | 描述 |
---|---|
source-map |
在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map ,但是它会减慢打包速度 |
cheap-module-source-map |
在一个单独的文件中生成一个不带列映射的map ,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便 |
eval-source-map |
使用eval 打包源文件模块,在同一个文件中生成干净的完整的source map 。这个选项可以在不影响构建速度的前提下生成完整的sourcemap ,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项 |
cheap-module-eval-source-map |
这是在打包文件时最快的生成source map 的方法,生成的Source Map 会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map 选项具有相似的缺点 |
注意:cheap-module-eval-source-map
方法构建速度更快,但是不利于调试,推荐在大型项目考虑时间成本时使用。
从以上的四个位置方法来看,自上而下打包速度越来越快,但是在打包过程中也出现了各种隐患,这也验证了一句话:“心急吃不了热豆腐”。对于我们现在来说我们就可以用到eval-source-map
。
接下来我们基于上诉例子进行下一步操作:
在配置文件webpack.config.js
中配置如下代码
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
}
}
四. 使用webpack构建本地服务器
devserver的配置选项 | 描述 |
---|---|
contentBase | 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“common"目录) |
port | 设置默认监听端口,如果省略,默认为”8080“ |
inline | 设置为true,当源文件改变时会自动刷新页面 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html |
如果想让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果,那你就需要单独安装它作为项目依赖。
npm install --save-dev webpack-dev-server
devserver作为webpack配置选项中的一项,以下是它的一些配置选项
devserver的配置选项 | 描述 |
---|---|
contentBase | 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“common"目录) |
port | 设置默认监听端口,如果省略,默认为”8080“ |
inline | 设置为true,当源文件改变时会自动刷新页面 |
historyApiFallback | 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html |
如果想看更多配置选项请点击查看更多
接下来在webpack.config.js
配置文件中添加如下代码:
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true // 实时刷新
}
}
在package.json
中的scripts
对象中添加如下命令,用以开启本地服务器:
{
"name": "webpackdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^3.5.6",
"webpack-dev-server": "^2.5.1"
}
}
注意:webpack
版本与webpack-dev-server
版本有兼容,本文用的版本是2.5.1的版本
在终端中输入npm run server
即可在本地的8080
端口查看结果如下:
页面运行结果:
image.png
五. webpack之Loaders
因为我下面讲的Babel需要基于loaders,所以这里简单介绍下loaders及它的配置。
loaders中文翻译是装载机,从字面意思看它是用来安装某些东西的,它可以调用框架以外的脚本和工具来处理各种格式的文件,比如将Scss、Less分析转换成为CSS,将ES6、ES7转换为浏览器兼容的ES5,以及react中的JSX文件转换为JS文件。
loaders需要手动去安装(具体安装在下文Babel安装),配置同样要在webpack.config.js
文件中modules
关键字下配置。
配置项如下:
-
test
:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须) -
loader
:loader的名称(必须) -
include/exclude
:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选) -
query
:为loaders提供额外的设置选项(可选)
接下来我们在配置之前先要将我们的文件改动下,将app.js
文件中的问候语单独拎出来放到一个JSON文件中,然后通过配置来读取这个JSON文件中的问候语。
第一步 在app文件夹中创建一个JSON文件(此处创建一个文件名为config.json
的JSON文件)
在
config.json
文件中加入问候语:“Hello, welcome to loaders.”
[{
"text": "Hello, welcome to loaders."
}]
第二步 将app.js
的文件中的问候语去掉并将config.json
中的问候语导入。
var config = require('./config.json');
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = config[0].text;
return greet;
}
接下来我们运行下npm run server
看看效果
注意:由于webpack3./webpack2.已经内置可处理JSON文件,这里我们无需再添加webpack1.*需要的json-loader。
六. webpack之Babel
Babel是一个广泛使用的转码器,它可以帮你实现以下操作:
- 它可以帮你将(ES6、ES7...)转换为现浏览器支持的ES5,这样你就不用考虑新标准是否被浏览器识别了。
- 让你能使用基于JavaScript进行了拓展的语言,比如React的JSX。
1. 安装
Babel其实是几个模块化的包,其核心功能位于称为babel-core
的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset
包和解析JSX的babel-preset-react
包)。
npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-env
npm install --save-dev babel-preset-react
安装的依赖包是不是有点多,需要npm安装好多遍,当然也不是必须要分开安装,你完全可以多个依赖包一起安装,这里要注意的是:“如果多个依赖包一起安装那么每个依赖包之间需要用空格隔开”,如下:
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
2. 配置
先来看下在webpack.config.js
文件中配置如下:
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true // 实时刷新
},
module: { // 这里配置Bobal
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
}
当然这样配置是完全没错的,但是在正式项目中需要在webpack.config.js
文件中配置很多项配置,这样就使得这个文件有特别多配置,从而使得这个配置文件会特别复杂,因此为了使这个文件配置变得稍微简单点,一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中,webpack会自动调用.babelrc里的babel配置选项。
接下来我们在来修改配置文件
第一步 在项目根目录创建.babelrc
如下:
第二步 在
.babelrc
中配置babel
{
"presets": ["react", "env"]
}
第三步 修改webpack.config.js
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}
]
}
}
在配置Babel过程中大家有没有注意到react
,大家想到了什么,没错就是安装react,我们要先安装react才能用ES6以及JSX语法。
npm install --save react react-dom
接下来我们要用ES6语法
第四步 修改app.js
,让他以组件形式返回
这个时候要注意了,我们之前app.js文件名称的开头字母是小写,这里我们要改为大写,因为react组件名称是需要大写的。
import React, {Component} from 'react'
import config from './config.json';
class App extends Component{
render(){
return (
<div>
{config[0].text}
</div>
)
}
}
export default App
修改main.js如下,使用ES6的模块定义和渲染App模块
import React from 'react';
import {render} from 'react-dom';
import App from './App';
render(<App />, document.getElementById('root'));
第五步 重新使用npm start
打包,并且使用npm run server
启动本地服务,如果之前打开的本地服务器没有关闭,你应该可以在localhost:8080下看到与之前一样的内容,这说明react和es6被正常打包了。
image.png
七. 模块化处理
webpack可以将不同的文件都按照模块的方式进行处理,比如js,css等都可以通过不同的loader进行打包处理。
CSS
webpack为我们提供css-loader
和 style-loader
两个工具来处理样式表,他们二者处理的任务是不同的。
css-loader
使你能够使用类似@import 和 url(...)的方法实现 require()的功能。
style-loader
将所有的计算后的样式加入页面中。
这二者组合在一起可以使你能够把样式表嵌入webpack打包后的JS文件中。
我们接着上面的例子来配置这两个工具:
安装
npm install --save-dev css-loader
npm install --save-dev style-loader
配置
在webpack.config.js
中配置这两个工具
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true // 实时刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, { // 这里配置这两个工具
test: /\.css$/,
use: [ // 请注意这里对同一个文件引入多个loader的方法。
{
loader: "style-loader"
}, {
loader: "css-loader"
}
]
}
]
}
}
接下来,在app文件夹里创建一个名字的main.css
的文件,对一些元素设置样式
在
main.css
文件内填入样式
body{
background-color: red;
border: 5px solid yellow;
}
ul, li{
list-sytle:none;
}
然后我们要怎么用这个样式呢?,我们知道我们的例子中有一个webpack的入口,没错那就是main.js
,为了让webpack能找main.css
文件,我们要将这个样式导入到main.js
中,其它的模块需要通过 import, require, url等与入口文件建立其关联。
main.js
文件修改如下:
import React from 'react';
import {render} from 'react-dom';
import App from './App';
import './main.css'; // 使用require导入css文件
render(<App />, document.getElementById('root'));
通常情况下,css会和js打包到同一个文件中,并不会打包为一个单独的css文件,不过通过合适的配置webpack也可以把css打包为单独的文件的。
CSS module
CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效,那么如果我们设置的相同class名称较多的时候,那么可想而知页面会从在声明现象,用一个比较官方的词就是它会造成全局污染。
CSS module
功能就是将JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块,这样做有效避免了全局污染。
接下来我们在来改动下webpack.config.js
配置文件
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定启用css modules
// 指定css的嘻哈类名格式
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}
]
}
]
}
}
我们在app文件夹下创建一个App.css
文件,并填入以下代码来进行一下测试
.root {
height: 30px;
background-color: green;
margin-top: 20px;
border: 2px solid blue;
}
将App.css
导入App.js
import React, {Component} from 'react'
import config from './config.json';
import styles from './App.css'; // 导入
class App extends Component{
render(){
return (
<div className={styles.root}>
{config[0].text}
</div>
)
}
}
export default App
我们看下页面运行结果
image.png
image.png
CSS预处理器
Sass
和 Less
之类的预处理器是对原生CSS的拓展,它们允许你使用类似于variables
,nesting
,mixins
,inheritance
等不存在于CSS中的特性来写CSS,CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句。
以下是常用的CSS 处理loaders
Less Loader
Sass Loader
Stylus Loader
不过其实也存在一个CSS的处理平台-PostCSS
,它可以帮助你的CSS实现更多的功能,在其官方文档可了解更多相关知识。
举例来说如何使用PostCSS,我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀
第一步 安装postcss-loader
和 autoprefixer
(自动添加前缀的插件)
npm install --save-dev postcss-loader
npm install --save-dev autoprefixer
第二步 在根目录新建postcss.config.js
并添加如下代码
module.exports = {
plugins: [
require('autoprefixer')
]
}
第三步 在webpack.config.js
中配置babel-loader
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定启用css modules
// 指定css的嘻哈类名格式
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}, {
loader: "postcss-loader"
}
]
}
]
}
}
最后 重新使用npm start
打包,你写的css会自动根据Can i use里的数据添加不同前缀了。
八. 插件(Plugins)
Webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成很多丰富的功能。
如果我们需要在webpack中引入插件时,首先我们需要npm/cnpm
安装需要的插件(内置插件不用安装),其次要在webpack配置文件webpack.config.js
中的plugins关键字部分添加该插件的一个实例去配置这个插件,需要注意的是:“plugins是一个数组”。
这里就以添加一个版本声明的插件为例,继续使用上面讲的例子,我们要在webpack.config.js
中配置版本说明。
const webpack = require('webpack'); // 引入webpack
module.exports = {
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定启用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
}
}, {
loader: "postcss-loader"
}
]
}
]
},
// 配置插件
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究')
]
}
这里要注意的是,如果我们需要用到插件时,在配置文件中的头部必须要引入webpack,然后下面才可以用这个插件
如果不在头部引入webpack那么打包会报下列错:
接下来我们来看看正确打包后的结果:
image.png
打包后的js文件中会出现我们添加的版权字样。
HtmlWebpackPlugin
这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html
。这在每次生成的js文件名称不同时非常有用(比如添加了hash
值)。
安装
npm install --save-dev html-webpack-plugin
这个插件会自动完成我们之前手动做的一些事情。
接下来我们要修改我们的项目让插件自动帮我们生成文件(为了保留上面demo,这里重新拷贝上面的demo并改名为webpackDemo3,也可以不拷贝直接进行下面的操作)
- 移除common文件夹中的所有文件,利用此插件,index.html文件会自动生成,此外CSS已经通过前面的操作打包到JS中了。
-
在app目录下,创建一个index.tmpl.html文件模板,这个模板包含html、head、body等必须元素,在编译过程中,插件会依据此模板生成最终的html页面,会自动添加所依赖的 css, js,favicon等文件。
修改后的项目目录和index.tmpl.html中的模板源代码如下:
image.png
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpackDemo3</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
- 更新webpack的配置文件
webpack.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入插件
module.exports = {
...
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究'),
new HtmlWebpackPlugin({
//new 一个这个插件的实例,并传入相关的参数
template: __dirname + "/app/index.tmpl.html"
})
]
}
注意别忘记引入插件
- 再次执行
npm start
你会发现,common文件夹下面生成了index.js
和index.html
。
image.png
Hot Module Replacement
Hot Module Replacement
(HMR)也是webpack里很有用的一个插件,它允许你在修改组件代码后,自动刷新实时预览修改后的效果。
在webpack中实现HMR也很简单,只需要做如下两项配置:
- 在webpack配置文件中添加HMR插件;
- 在webpack配置文件的
devServer
字段中添加“hot”参数;
不过配置完这些后,JS模块其实还是不能自动热加载的,还需要在你的JS模块中执行一个Webpack提供的API才能实现热加载,虽然这个API不难使用,但是如果是React模块,使用我们已经熟悉的Babel可以更方便的实现功能热加载。
具体实现方法如下
Babel和Webpack是独立的工具
- 二者可以一起工作
- 二者都可以通过插件拓展功能
- HMR是一个webpack插件,它让你能浏览器中实时观察模块修改后的效果,但是如果你想让它工作,需要对模块进行额外的配额;
- Babel有一个叫做
react-transform-hrm
的插件,可以在不对React模块进行额外的配置的前提下让HMR正常工作;
下面我们在配置文件中添加如下代码:
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
hot: true // 在这里配置hot
},
...
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究'),
new HtmlWebpackPlugin({
//new 一个这个插件的实例,并传入相关的参数
template: __dirname + "/app/index.tmpl.html"
}),
new webpack.HotModuleReplacementPlugin() // 热加载插件
]
}
接下来安装react-transform-hmr
npm install --save-dev babel-plugin-react-transform
npm install --save-dev react-transform-hmr
配置Babel
在.babelrc
文件中修改如下代码:
{
"presets": ["react", "env"],
"env": {
"development": {
"plugins": [["react-transform", {
"transforms": [{
"transform": "react-transform-hmr",
"imports": ["react"],
"locals": ["module"]
}]
}]]
}
}
}
现在去使用npm run server
运行demo,之后当你使用React时,可以热加载模块了,每次保存就能在浏览器上看到更新内容。
产品阶段的构建
目前为止,我们已经使用webpack构建了一个完整的开发环境。但是在产品阶段,可能还需要对打包的文件进行额外的处理,比如说优化,压缩,缓存以及分离CSS和JS。
对于复杂的项目来说,需要复杂的配置,这时候分解配置文件为多个小的文件可以使得事情井井有条,以上面的例子来说,我们创建一个webpack.production.config.js
的文件,在里面加上基本的配置,它和原始的webpack.config.js
很像,如下
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
hot: true // 在这里配置hot
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定启用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究'),
new HtmlWebpackPlugin({
//new 一个这个插件的实例,并传入相关的参数
template: __dirname + "/app/index.tmpl.html"
}),
new webpack.HotModuleReplacementPlugin() // 热加载插件
]
}
在package.json
中的scripts
字段下配置build
{
"name": "webpackdemo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open",
// 这里配置,注意此注释在运行项目是删除
"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress"
},
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^8.2.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-plugin-react-transform": "^3.0.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.11",
"html-webpack-plugin": "^3.2.0",
"postcss-loader": "^2.1.3",
"react-transform-hmr": "^1.0.4",
"style-loader": "^0.20.3",
"webpack": "^3.5.6",
"webpack-dev-server": "^2.5.1"
},
"dependencies": {
"react": "^16.3.0",
"react-dom": "^16.3.0"
}
}
注意:
- 如果是window电脑,build需要配置为
"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress"
。 - 如果是Mac电脑,build可以直接配置为
"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
。
优化插件
webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能
-
OccurenceOrderPlugin
:为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID -
UglifyJsPlugin
:压缩JS代码; -
ExtractTextPlugin
:分离CSS和JS文件
我们继续上面的demo,OccurenceOrder
和UglifyJS plugins
都是内置插件,你需要做的只是安装其它非内置插件
npm install --save-dev extract-text-webpack-plugin
在配置文件webpack.production.config.js
中的plugins
字段下配置如下:
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index.js" // 打包后输出文件的文件名
},
devServer: {
contentBase: "./common", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true, // 实时刷新
hot: true // 在这里配置hot
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: {
modules: true, // 指定启用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
}
}, {
loader: "postcss-loader"
}
]
}
]
},
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究'),
new HtmlWebpackPlugin({
//new 一个这个插件的实例,并传入相关的参数
template: __dirname + "/app/index.tmpl.html"
}),
new webpack.HotModuleReplacementPlugin(), // 热加载插件
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css")
]
}
在说最后一遍注意:需要导入
此时执行npm run build
可以看见被压缩后的代码如下:
缓存
缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)
webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前
我们来修改webpack.production.config.js
文件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'eval-source-map', // 在这里配置devtool
entry: __dirname + "/app/main.js", // 之前提到的唯一入口文件
output: {
path: __dirname + "/common", // 打包后的文件存放的地方
filename: "index-[hash].js" // 打包后输出文件的文件名
},
...
}
下面我们来运行下npm run build
看下结果
clean-webpack-plugin
添加了hash之后,会导致改变文件内容后重新打包时,文件名不同而内容越来越多,因此我们可以用clean-webpack-plugin
。
安装
npm install --save-dev clean-webpack-plugin
同样需要在webpack.production.config.js
文件中的plugins
字段下配置
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require("clean-webpack-plugin");
module.exports = {
...
plugins: [
new webpack.BannerPlugin('版权所有,翻版必究'),
new HtmlWebpackPlugin({
//new 一个这个插件的实例,并传入相关的参数
template: __dirname + "/app/index.tmpl.html"
}),
new webpack.HotModuleReplacementPlugin(), // 热加载插件
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css"),
new CleanWebpackPlugin('common/*.*', {
root: __dirname,
verbose: true,
dry: false
})
]
}
然后去除common文件中的残余文件,并运行 npm run build
每次打包都会清除之前的打包文件然后重新去生成打包文件
关于
clean-webpack-plugin
的详细使用可参考这里
结尾
文章到这里就要和大家告一段落了,通过这篇文章大家可以初步的了解webpack的打包流程,以及webpack的一些工具和插件,并且可以简单的去打包一些demo。
其实webpack还有很多需要我们去学习和深入了解去探索的东西。
最后祝愿大家能够早日将webpack掌握并熟练的去运用,也祝大家事业有成,最重要的一点是:“一定要注意身体吆!”