理解部分
为什么要使用WebPack?
现在的网页就像功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,我们开始使用了模块化、各种拓展语言(TypeScript)、css预处理器等。
这些进步提高我们开发效率的同时,也存在着它们所编写的文件不能直接被浏览器识别的问题,这就需要我们再进行处理,这就为WebPack类的工具的出现提供了需求。
什么是Webpack?
WebPack就像一个打包传送带:它按照模块 一 一 打包好之后传送给浏览器。具体为:分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
插一句:WebPack和上篇提到的Grunt以及Gulp相比有什么特性呢?
Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。
Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务(是逐个流水线型加工的)。
Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件(是由冰山一角及整个冰山的)。
使用部分
先安装webpack
步骤:
- 新建一个空的练习文件夹,后面的指令都在终端输入
- 先要保证在node的环境下(因为webpack要使用npm,npm是nodejs的包管理器,能解决NodeJS代码部署上的很多问题)
- npm install -g webpack (-g表示全局安装)
可以通过输入 “npm -v” 来测试是否成功安装 - npm install webpack-cli -g
webpack本来就带webpack-cli,但是到webpack4版本之后,要单独安装一次 - npm install cnpm
因为npm是从国外服务器下载,速度太慢,所以我们通过npm安装一个cnpm,之后就都用cnpm来代替npm,cnpm:是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读)
之后就这样写:cnpm install +需要的模块
简单使用
这里介绍三个方法来使用:
通过命令行来使用Webpack:
步骤:
-
npm init
在终端中使用npm init命令可以自动在上述练习文件夹中创建一个package.json文件,这是一个npm说明文件,会加入版本管理。每个项目的根目录下面,一般都有一个package.json文件,定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install 命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。 -
回车跳过
输入上一步的命令后,终端会问你一系列诸如项目名称,项目描述,作者等信息,如果你不准备在npm中发布你的模块,回车默认即可。 -
npm install --save-dev webpack(本地安装webpack)
虽然我们前面在全局安装过了webpack,但在本项目中还要再安装一次。
为什么要再安装一次到本地呢?
首先,有时不同程序依赖不同版本的包,这时如果只有一个全局的包就会产生冲突。
其次,对每个项目的包的个性化设计还减轻了API兼容性压力。
再次,默认下node.js会在NODE_PATH(npm的默认全局安装路径)和目前js所在项目下的node_modules文件夹下去寻找模块,因此,如果只是全局安装,不能直接通过require()的方式去引用模块,需要手动解决包路径的配置问题。
或者也可以复制全局安装的node_modules文件夹到项目下,还有办法可以选择将环境变量的NODE_PATH设置为C:\Program Files\nodejs。总之是相当于在本地再创立一个npm包环境,这些都可以行得通但是比起上面一步就显得太繁琐了。 -
回到之前的空文件夹,并在里面创建两个文件夹,app文件夹和public文件夹,app文件夹存放原始数据和源代码,public文件夹用来存放之后供浏览器读取的文件(包括webpack打包后的bundle.js文件和一个index.html文件)。
再在app文件夹中创建一个js文件 main.js。 -
webpack app/main.js -o public/bundle.js
这样就打包完成了。
有的人可能有疑问,命令不是 webpack app/main.js public/bundle.js 吗?这个命令是3.x.x的老版本webpack的命令,会报错。有-o的是现在通用的,可以正常使用。
可以输入webpack -v 查询webpack版本号。
通过配置文件来使用Webpack:
通过命令行来使用webpack是不太方便且容易出错的,更好的办法是定义一个配置文件,这个配置文件其实也是一个简单的js模块,我们可以把所有的与打包相关的信息放在里面。
步骤:
- 在当前练习文件夹的根目录下新建一个webpack.config.js(固定的)。
- 写入配置代码,目前的配置主要涉及到的内容是入口文件路径和打包后文件的存放路径。
module.exports = {
entry: __dirname + "/app/main.js",//入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
}
注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。
有了这个配置之后,再打包文件,只需在终端里运行webpack命令就可以了,这条命令会自动引用webpack.config.js文件中的配置选项,自动打包。
这样就成功了。
优化配置文件来使用Webpack:
有些情况下没有全局安装webpack,只进行了非全局安装,这时候在终端输入路径仍然是不方便且容易出错的。这时候,对npm的文件配置增添新的设置后,在终端输入npm start命令就可以完成打包了。
步骤:
- 在package.json中对scripts对象进行添加一对键值即可。
{
"name": "test",
"version": "1.0.0",
"description": "Sample webpack project",
"scripts": {
"start": "webpack" // 修改的是这里,JSON文件不支持注释,引用时请清除
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "4.28.4"
}
}
- npm start
在终端输入就可以完成打包了。
上面我们通过入口文件main.js打包了js文件,后面两种方法做出了改进和优化,但实质上仍然是对js的打包,如果我们还想打包css、fonts字库、图片等等呢?
这时就要想想我们一开始就说到的理解部分的内容了:Webpack把所有的文件都当做模块处理。
那直接用webpack也来打包其他的这些不就好了吗?理论上说是可以的,但是随着发展,各种语言都推陈出新,为了协调不同人使用新版和旧版但都需要用webpack来打包这个问题,又有了一个新的插件——Loaders。
理解:
loader一般和webpack配合使用:在使用不同的loader的环境下,webpack就有能力对不同格式的文件进行处理了,比如说分析转换scss为css,把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件,把JSX文件转换为JS文件。
使用:
打包css:
- 先需要安装 Loaders
npm install --save-dev style-loader css-loader
webpack提供两个工具处理样式表,css-loader 和 style-loader。
css-loader:使我们能够使用类似import 和 url的方法实现 require()的功能。
style-loader:将所有的计算后的样式加入页面中。
两个处理表一起使用让我们能够把样式表嵌入webpack打包后的JS文件中。
- 再在webpack.config.js中的modules下进行配置。
module.exports = {
...
module: {
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader"
}
]
}
}
};
这是对同一个文件引入多个loader的写法。
use: [ 'style-loader', 'css-loader' ]
这是对同一个文件引入单个loader的写法。
- 最后引入css文件到main.js里
import './main.css';//使用require导入css文件
到这里就完成了css的所有打包步骤。
但是关于css的打包仍是可以进一步优化的——不仅可以通过类(js、css)来模块化,还可以进行css内部的模块化。
css内部的模块化——CSS module。
将css模块化,进行模块化编程,(上一篇中我们已经讲过这样做的好处,不明白的可以再回去查看)。
Webpack对CSS模块化提供了非常好的支持,只需要在CSS loader中进行简单配置即可:
module.exports = {
...
module: {
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
options: { //添加了这个属性
modules: true, // 指定启用css modules
localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式
}
}
]
}
};
css的打包就全部完成了。
打包图片:
- 先安装 Loaders
npm install file-loader url-loader --save-dev
对图片打包是需要对文件同时打包的。
- 再在webpack.config.js中的modules下进行配置。
rules: [{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
publicPath: "./images/",
outputPath: "images/"
}
}]
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: ['img:src', 'img:data-src', 'audio:src'],
minimize: true
}
}
}
]
以上所有webpack的基本安装和使用已经全部介绍完毕了。
但是除此之外webpack可以实现很多别的功能,现在我们来看看通过添加一些组件和插件实现在实际开发中方便我们的功能。这里我们先介绍两个非常有用的。
使用webpack构建本地服务器:
可以让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果。
步骤:
- 安装组件
npm install --save-dev webpack-dev-server
- 配置文件webpack.config.js
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
},
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
historyApiFallback: true,//不跳转
inline: true//实时刷新
}
}
- 在package.json中的scripts对象中添加如下代码,用以开启本地服务器:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack",
"server": "webpack-dev-server --open"
},
- 在终端中输入npm run server即可在本地的8080端口查看结果
插件HtmlWebpackPlugin:
这个插件的作用是依据一个简单的html模板,生成一个自动引用了打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。
步骤:
- 安装插件
npm install --save-dev html-webpack-plugin
- 在app目录下,创建一个index.html文件模板,插件会依据此模板生成最终的html页面,会index.html中的模板源代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<div id='root'>
</div>
</body>
</html>
- 配置文件webpack.config.js
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/pubic",
filename: "bundle.js"
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html', //加上template属性,指向我们app里的html文件
相关参数,eg: title:‘test’
})
],
};
- 执行npm start,public文件夹下生成了自动创建的bundle.js和index.html,这就完成了。
解决一些问题
在开发中可能一个文件中需要打包多个不同的js和html出来,在默认情况下,后一个打包的js会覆盖前一个打包的js,后一个html会覆盖前面的html,这样后面一个覆盖前面一个下来,dist文件里就始终只有一个js和html,是最新的打包的。
但是这样我们前面的打包就变成无用功了,我们还是想要不同模块的文件有不同的打包产物,那如何解决这个问题呢?
- 其实只要修改配置文件就可以了
解决html冲突:
plugins: [new HtmlWebpackPlugin({
template:'./user/user_mobile.html',
filename:'user.html'
})
]
template:模版
filename:导出名称
此时生成的是user.html。
解决js冲突:
entry:{
user:'./user/user_mobile.js'
}
本身生成的是main.js,
将leader改成user后,生成的就是user.js 。