目录
概念
安装
webpack.config配置文件
入口和输出
模式
loader
插件(plugins)
tml-webpack-plugin
extract-text-webpack-plugin
webpack内置方法
开发
webapck-dev-server
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack
//或者
yarn add webpack --dev
webpack4还需要安装webpack-cli
npm install --save-dev webpack-cli
现在从零开始搭建环境,运行
npm init
会生成一个package.json文件,有以下内容
{
"name": "init",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
然后本地安装webpack,记得-dev,代表开发环境需要的依赖。接着我们在本地新建两个文件夹,js和views,js下存放main.js,views下存放index.html。
现在我们通过webpack命令行来把main.js文件打包到views目录下,运行
webpack js/main.js views/index.js
提示我们需要安装webpack-cli,webpack3中webpack-cli是在webpack包里面的,webpack4改成了单独的两个包,可以选择全局-g安装也可以选择本地安装。
这里我ctrl+c退出运行以下命令
npx webpack js/main.js -o views/index.js
npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装
-o 表示指定的输出文件位置
这里有个黄色部分的内容提示,是提示我们没有设置模式mode,mode在后面会说到,这个提示可以不理也可以在命令后面加--mode=“development”。
运行完成我们打开views目录就能看到index.js文件,里面就是打包好的js代码。但是这样一个个打包非常的麻烦,接下来引出webpack的配置文件,默认命名webpack.config.js。
从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。在开始前你需要先理解四个核心概念:
知道这四个东西,你就可以说懂webpack4了,但是还没到熟的地步。
入口(entry)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
出口(output)告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
。
在根目录新建webpack.config.js
module.exports={
entry:"./js/main.js"
}
有了入口,还需要出口,指定打包好的文件输出位置。这里我们需要node的path模块
const path = require('path');
module.exports={
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),//当前目录下的dist文件夹
filename: 'index.js'//输出的文件名
}
}
path
模块提供用于处理文件路径和目录路径的实用工具。resolve方法用于将绝对路径转为相对路径。
运行npx webpack,当前目录就会生成dist文件夹,文件夹里面是我们的js文件。
有时候开发需要我们要生成不唯一的js文件,为了防止页面缓存,上图中有个Hash值,每打包一次,值都是不同的,我们可以用这个值来为js文件加个版本。
const path = require('path');
module.exports={
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.[hash].js'
}
}
我们发现,dist目录已经有我们想要的文件,但是每次打包,之前打包好的文件就不想要了,不然文件越积越多。这里就先留着,等到了插件再处理。
我们发现,运行的结果最后还是报mode未设置的警告。后面我们会通过模式mode解决这个警告。
入口entry还有另一种写法,多文件入口,配合输出一起使用。entry接收一个对象,每个属性对应文件路径。我们在js文件夹下面新建app.js。
const path = require('path');
module.exports={
mode: 'development',
entry: {
index: './js/main.js',
app: './js/app.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash].js'
}
}
[name]中的name对应的则是entry的属性名。
mode分为开发模式development和生产模式production,如下配置
const path = require('path');
module.exports={
mode: 'development',
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
}
}
支持以下字符串值
选项 | 描述 |
development | 会将 process.env.NODE_ENV 的值设为 development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。 |
production | 会将 process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin 和 UglifyJsPlugin . |
记住,只设置
NODE_ENV
,则不会自动设置mode
。
// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
- new webpack.NamedModulesPlugin(),
- new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}
再次打包文件
现在警告已经没有了。
loader 用于对模块的源代码进行转换,包括图片,字体文件,html文件,当然还有一些处理js语法的loader。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
首先我们来说一下css的loader。
css的loader有很多,包括css-loader、stylus-loader、sass-loader等,不同的loader对应处理不同的css文件。
我们在根目录新建一个css文件夹并新建index.css文件。
在main.js引入css文件
接着下载css-loader和style-loader。
npm i --save-dev css-loader style-loader
style-loader
是将css-loader
打包好的css代码以标签的形式插入到html文件中。
const path = require('path');
module.exports={
mode: 'development',
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
},
module:{
rules: [
{
test: /\.css$/,
use: ["style-loader","css-loader"],
include: path.resolve(__dirname, 'css'),
exclude: path.resolve(__dirname, 'node_modules')
},
]
}
}
rules允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览。
test
属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件,用正则匹配。
use
属性,表示进行转换时,应该使用哪个 loader,可以存放多个loader,执行循序是从右到左。include 只命中css目录里的css文件,加快webpack搜索速度
exclude 排除node_modules目录下的文件
在webpack官网中有另一种写法
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
loader 指定loader
options 参数配置
浏览器打开我们发现css文件已经内嵌到页面,但是实际开发我们不想使用内嵌的css,我们想使用外联的css,通过link标签引入。这块也在后面的插件里一起讲。
我们还可以使用一些css的预处理语法,如sass,scss,stylus等等,下面就拿sass举例,在css文件夹下面新建一个index.sass文件。
接着在main.js导入,在webpack里配置处理。
const path = require('path');
module.exports={
mode: 'development',
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
},
module:{
rules: [
{
test: /\.(c|sa|sc)ss$/,
use: ["style-loader","css-loader","sass-loader"],
include: path.resolve(__dirname, 'css'),
// exclude: path.resolve(__dirname, 'node_modules')
},
]
}
}
再打包运行我们就会发现字体变大了。
接下来我们讲图片的处理。新建img文件夹,放入bg.png。在index.css里引入。
安装处理图片的file-loader。
npm i file-loader --save-dev
配置处理的规则。
const path = require('path');
module.exports={
mode: 'development',
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
},
module:{
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ["file-loader"],
},
]
}
}
我们会发现在dist目录下生成了一个.png结尾的图片。但是这不是我们想要的结果,如果是一百张图片都和其他静态文件一起就会很乱,现在我们想要的是把打包好的图片文件放在dist下的img文件夹里面。
这些,我们在file-loader的npm网站上就能看到。所以图片打包的模块我们可以改成这样。
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader:"file-loader",
options:{
publicPath:"img",
name:"[name].[ext]",
outputPath: './img',
}
}],
},
publicPath 图片打包前的目录
name 图片打包好后的名字,其中[name]表示文件/资源的基名,默认是图片打包前.前面的字段,[ext]表示目标文件/资源的文件扩展名,默认是图片打包前的.后面的字段
outputPath 图片打包好后输出的文件目录
再次运行我们发现dist文件夹下面多出了一个img文件夹,里面正好是我们想要的图片。现在看感觉图片处理的好像可以了。但是,对于优化来说还是有不足的地方。图片多了表示请求的次数增加,不管是什么图片,都会去请求。有些很小的图片我们就可以把它转成base64格式。
在这里我们就需要安装另一个图片的处理loader,url-loader。url-loader
类似于 file-loader
,但如果文件小于字节限制,则可以返回DataURL。
npm install --save-dev url-loader
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader:"url-loader",
options:{
publicPath:"img",
name:"[name].[ext]",
outputPath: './img',
limit: 8192
}
}],
},
limit 指图片的大小,小于这个大小的都会打包成base64。大于的都会打包存在img文件夹下。
插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!
插件目的在于解决 loader 无法实现的其他事。
现在我们可以慢慢填前面留下来的坑了。首先,我们发现之前打包的一直都是js,css,图片等资源,html文件并没有打包。现在我们就来先说一下打包html文件的插件。
npm install --save-dev html-webpack-plugin
我们需要导入这个包,然后构造出处理html文件的函数。
+const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports={
mode: 'development',
entry:"./js/main.js",
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
},
module:{
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader:"url-loader",
options:{
publicPath:"img",
name:"[name].[ext]",
outputPath: './img',
limit: 8192
}
}],
},
]
},
+ plugins: [
+ new HtmlWebpackPlugin(),
+ ]
}
运行的结果是dist目录下存在index.html文件,并且用浏览器打开就能看到我们想要的页面效果。不过这个简单的配置只对index.html文件打包,它默认打包index.html文件。所以如果是多页面的打包我们还需要用到插件的参数。
名称 | 类型 | 默认 | 描述 |
title |
{String} | Webpack App | 用于生成的HTML文档的标题 |
filename |
{String} | 'index.html' | 要将HTML写入的文件。默认为index.html 。您可以在这里指定一个子目录太(如:assets/admin.html ) |
template |
{String} | `` | webpack 模板的相对或绝对路径。默认情况下,src/index.ejs 如果它存在,它将使用。 |
templateParameters |
{Boolean|Object|Function} | `` | 允许覆盖模板中使用的参数 |
inject |
{Boolean|String} | true | true || 'head' || 'body' || false 将所有资产注入给定template 或templateContent 。传递true 或'body' 所有javascript资源将被放置在body元素的底部。'head' 将脚本放在head元素中 |
favicon |
{String} | `` | 将给定的favicon路径添加到输出HTML |
meta |
{Object} | {} | 允许注入meta -tags。例如meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'} |
base |
{Object|String|false} | false | 注入base 标签。例如base: "https://example.com/path/page.html |
minify |
{Boolean|Object} | true 如果mode 是'production' ,否则false |
控制是否以及以何种方式缩小输出。 |
hash |
{Boolean} | false | true ,webpack 为所有包含的脚本和CSS文件附加唯一的编译哈希。这对缓存清除很有用 |
cache |
{Boolean} | true | 仅在文件被更改时才发出文件 |
showErrors |
{Boolean} | true | 错误详细信息将写入HTML页面 |
chunks |
{?} | ? | 允许您仅添加一些块(例如,仅添加单元测试块) |
chunksSortMode |
{String|Function} | auto | 允许控制在将块包含到HTML之前应如何对块进行排序。允许的值是'none' | 'auto' | 'dependency' | 'manual' | {Function} |
excludeChunks |
{Array. |
`` | 允许您跳过一些块(例如,不添加单元测试块) |
xhtml |
{Boolean} | false | 如果true 将link 标记渲染为自动关闭(符合XHTML) |
所以我们可以这样配置。
plugins: [
new HtmlWebpackPlugin({
template:"./views/index.html",
title: 'index',
filename: 'views/index.html',
inject:true,
hash:true
})
]
这样写,我要是有三十个页面,那不是要复制三十份,代码就会冗余。我们可以封装成方法调用,即使是三十个页面也不过增加了三十多行代码。
const getHtmlConfig=(name,title)=>
new HtmlWebpackPlugin({
template:`./views/${name}.html`,
title: title,
filename: `views/${name}.html`,
inject:true,
hash:true
})
title虽然可以动态传入,但是在html页面里面还需要插入指定代码,不然title还是默认打包前的title。
<%= htmlWebpackPlugin.options.title%>
然后还有让webpack知道应该引入哪个js,css先不讲,因为css的单独打包还没有讲。
这里需要输入输出配合一起。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const getHtmlConfig=(name,title)=>
new HtmlWebpackPlugin({
template:`./views/${name}.html`,
title: title,
filename: `views/${name}.html`,
inject:true,
hash:true,
+ chunks: [name]
})
module.exports={
mode: 'development',
entry:{
+ "index":"./js/main.js",
+ "news":"./js/main.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js'
},
module:{
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader:"url-loader",
options:{
publicPath:"img",
name:"[name].[ext]",
outputPath: './img',
limit: 8192
}
}],
},
]
},
plugins: [
getHtmlConfig("index","首页1"),
getHtmlConfig("news","news")
]
}
chunks 用来识别导入的js文件
打开打包好后的news.html我们就会发现,body的尾部引入了news.js。
现在来填css打包的坑,之前我们css都是内嵌在页面里的,现在我们需要单独打包css文件。
npm install --save-dev extract-text-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const path = require('path');
const getHtmlConfig=(name,title)=>
new HtmlWebpackPlugin({
template:`./views/${name}.html`,
title: title,
filename: `views/${name}.html`,
inject:true,
hash:true,
chunks: [name]
})
module.exports={
mode: 'development',
entry:{
"index":"./js/main.js",
"news":"./js/main.js"
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js'
},
module:{
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader:"url-loader",
options:{
publicPath:"img",
name:"[name].[ext]",
outputPath: './img',
limit: 8192
}
}],
},
]
},
plugins: [
+ new ExtractTextPlugin("css/[name].css"),
getHtmlConfig("index","首页1"),
getHtmlConfig("news","news")
]
}
这样就配置好了,然后就可以很愉快的看到想要的效果了?这样想的人那就是太天真了。我一直烦webpack的地方也是在这里。
意思是这个版本的这个方法已经被弃用,现在摆在你面前的就只有两种方法,要么你把插件升级到最新版,要么你换插件。但是我用的安装方法已经是最新的啊,不,还有可能有更新的,那就是别人的测试版。
npm i extract-text-webpack-plugin@next --save-dev
在package.json里面看到的版本是4.0.0-beta.0,再次打包就不会报错了,而且也能看到在head里面引入了我们想要的css。
第二种方法是换插件,mini-css-extract-plugin可以解决之前的问题。这里就不再演示,npm官网上会有你所有疑惑的答案。
那么多js代码,总会有一些公用的,那这些公用的方法在每个js文件都写一遍就会冗余。现在我们来把这些代码独立出来。
webpack4以前,打包独立js的办法是在插件引用。
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name : 'common',
filename : 'js/base.js'
}),
]
但是再webpack4,这个方法已经移除,用的人就会很烦,又看到命令行运行报错,又必须去啃又臭又长的英文文档。
现在内置的方法不用再构造出来,直接和Plugins同级。
optimization:{
splitChunks:{
cacheGroups: {
commons: {
name : 'common',
chunks: "initial",
minChunks: 2
}
}
}
}
name 打包好后的名字
chunks 这表示将选择哪些块进行优化。当提供一个字符串,有效值为all,async和initial。提供all可以特别强大,因为这意味着即使在异步和非异步块之间也可以共享块。
minChunks 分割前必须共享模块的最小块数。
这样配置了还不行,你必须要让html页面也引用公共模块的js,在打包html的函数里面添加即可。
const getHtmlConfig=(name,title)=>
new HtmlWebpackPlugin({
template:`./views/${name}.html`,
title: title,
filename: `views/${name}.html`,
inject:true,
hash:true,
- chunks: [name]
+ chunks: ["common",name]
})
打包完在html的body尾部就能看到引入的代码,js文件夹下也存在了common.js文件。
前面留了一个文件打包使用hash生成不同文件的坑,现在来填。文件打包后上次打包的文件会留下来,这样会叠加占用空间。这里我们用到一个插件clean-webpack-plugin。
npm install --save-dev clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins:
new CleanWebpackPlugin(),
这样每次打包都会清dist文件夹。
我们来会看一下前面说的那么多,在开发的时候这样做是不是有哪些可以改进的(在不增加其他需求的情况下)。
好像我们页面的头部是一样的,略有不同也是引入的css文件的不同,基本像之后加的mate都会差不多。那这样我们就可以把同样的部分提取出来。
这里我们使用到html-loader。
npm i html-loader --save-dev
在views文件夹下新建template文件夹,在template下新建head.string,这个后缀名随你喜欢,只要不要html结尾就行,因为用html结尾会和html打包的插件冲突。
然后我们把相同的头部提取到head,string。
是不是觉得很奇怪,title也相同,为什么不提取出来,我试了,动态设置title那一块并没有成功,也就是说打包好的页面title标签里的还是<%= htmlWebpackPlugin.options.title%>。
然后我们怎么导入到index.html呢。
webpack4以前我是用<%= require('./template/head.string')%>,然后我看来一下文档es6的方法也是可以的
${require('./template/head.string')}。所以两种方法都能用。
而如果我们要在页面中使用图片就不能直接在img的src里这样写../img/bg.png,我们需要像引用页面那样
接下来我们开始提需求,如果我有两个显示屏,一个看我写的代码,一个看页面的效果,当我代码改变了,就要用鼠标移到展示网页的那块显示屏,点击刷新。这样我觉得好麻烦,有没有什么办法,我代码一改页面就帮我自动刷新呢。这就用到了一个很好用的webpack热更新方法。还要配合webapck-dev-server一起使用。
npm install webpack-dev-server --save-dev
所以我们开启文件的package文件也要修改。
"dev": "webpack-dev-server",
然后我们要在配置文件配置端口,在输入输出同级。
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000
},
contentBa 告诉服务器从哪里提供内容。只有在您想要提供静态文件时才需要这样做。
compress 为所服务的一切启用gzip压缩。
port 指定用于侦听请求的端口号。
当然devSerner有个很牛逼的功能,类似Nginx的映射。可以通过配置proxy,把当前请求映射到指定的端口,并且可以映射多个。
devServer: {
proxy: {
'/api': 'http://localhost:3000'
}
}
配置好我们就可以运行看效果,直接运行dev。代码还是原先配置图片的代码,但是我们发现,背景图片并没有显示出来,打开调试看到的是和我们预期不同的地址。原本图片的地址是http://localhost:9000/img/bg.png,然而实际显示的是http://localhost:9000/css/img/bg.png。多了点东西。
我去网上查看了别人的解决方法都说这样行那样行,但是我自己来试就是不行。想了很久会不会是css打包的beat版本有问题。所以我考虑换成另一种打包css的方法mini-css-extract-plugin。
npm install --save-dev mini-css-extract-plugin
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../',
hmr: process.env.NODE_ENV === 'development',
},
},
'css-loader',
],
},
插件换成这样。
new MiniCssExtractPlugin({
filename: 'css/[name].css',
chunkFilename: '[id].css',
}),
然后再运行,用http://localhost:9000/打开页面发现图片显示正常,地址也正常。
我们再试一下热刷新功能。把字体变大变色。
不用刷新,效果已经出来了,至此基本的webpack4的功能配置就说的差不多了。不过还有一个是开发环境和生产环境的webpack配置,留着下次讲。
demo已放GitHub,可以clone使用。webpack-demo。