1. webpack核心概念
- 入口(entry)
- 输出(output)
- loader
- 插件(plugins)
官方文档: https://www.webpackjs.com/configuration/output/#output-filename
1.1 入口
指定 webpack 由哪个模块作为项目构建的开始.通过配置 entry
属性,指定一个或多个起点,默认值 ./src
:
用法: entry: string|Array ,可以是一个字符串也可以是一个数组,还可以是一个对象。
module.exports = {
// 字符串入口
entry: './path/leo/file.js'
}
module.exports = {
// 数组,指定多个入口
entry: ['xxx','yyyy']
}
module.exports = {
// 为入口命名
entry: {
file: './path/leo/file.js'
}
}
module.exports = {
entry: {
// 为入口命名
main: ['./path/leo/file.js', './path/leo/index.js', './path/leo/server.js']
}
}
1.2 Chunk 的概念
所谓Chunk,就是在Webpack打包过程中,内部形成的代码块,代码块中的内容可能来源于一个文件中,也可能来源于多个文件中。比如入口文件 index.js ,它依赖于模块A,而模块A又依赖于模块B, 那么webpack 在打包后整个入口文件就会形成一个chunk。
-
如果entry 传入一个字符串或字符串数组,chunk 会被命名为
main
-
如果传入一个对象,则每个键(key)会是 chunk 的名称,该值描述了 chunk 的入口起点。比如
entry: './path/leo/file.js'
chunk的名称为main,entry: ['xxx','yyyy']
chunk的名称也为 main.entry: { file: './path/leo/file.js' }
那么chunk的名称就为 file。
有了chunk 的命名,html在嵌入js文件的时候,就可以指定需要嵌入的 chunk。
1.3 出口
指定 webpack 最终输出的文件输出位置和文件名等信息。默认输出位置为 ./dist
output: {
path: path.resolve(__dirname, 'dist'), //输出的目录绝对路径
filename: 'myjs-webpack.bundle.js' //输出的文件名称
}
// 占位符
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
更多占位符:
[hash] | 模块标识符(module identifier)的 hash |
---|---|
[chunkhash] | chunk 内容的 hash |
[name] | 模块名称 |
[id] | 模块标识符(module identifier) |
[query] | 模块的 query,例如,文件名 ? 后面的字符串 |
1.4 module与 loader
对于webpack来说,要处理的每个文件都是一个module,要想让webpack识别并处理这个module,那就需要使用 loader来进行解析。比如默认情况下 webpack只会处理 js文件,那么要处理 css文件,图片文件这些非js的文件,那就必须使用对应的 loader来解析这些文件。
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
}
所有的文件处理配置都应该放到 module中, 至于如何处理,那就要配置相关的规则 rules 就是指定规则,它是一个复数,表示多个规则,所以其值是一个数组。
- test 用来标识出应该被对应的 loader 进行转换的某个或多个文件,它负责筛选出文件。
- use 表示转换时要用哪个 loader
使用loader的三种方式:
- 配置(推荐):在 webpack.config.js 文件中指定 loader。
- 内联:在每个 import 语句中显式指定 loader。 本教程中会在 html文件中使用这种内联的方式
import Styles from 'style-loader!css-loader?modules!./styles.css';
- CLI:在 shell 命令中指定它们。
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
loader特性:
- 能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
- loader 可以是同步的,也可以是异步的。
- loader 运行在 Node.js 中,并且能够执行任何可能的操作。
- loader 接收查询参数。用于对 loader 传递配置。
- loader 也能够使用 options 对象进行配置。
- 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
- 插件(plugin)可以为 loader 带来更多特性。
- loader 能够产生额外的任意文件。
1.5 插件
让 webpack 能够执行更多任务,从优化和压缩,到重新定义环境中的变量,插件目的在于解决 loader 无法实现的其他事。
webpack本身内置了很多插件,也可以使用第三方插件。
使用时,只需要 require
它,并添加到 plugins
数组,通过 new
实例化即可:
// 通过 npm 安装
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 导入 webpack 是为了访问内置插件,如 webpack.DefinePlugin
const webpack = require('webpack');
const config = {
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new HtmlWebpackPlugin({template: './src/index.html'})
]};
module.exports = config;
1.6 模式
通过配置 mode
参数,指定当前的开发模式,有 development
和 production
两个值:
module.exports = {
mode: 'production'
};
也可以通过 CLI 参数传递:
webpack --mode=production
development | 会将 process.env.NODE_ENV 的值设为development 。启用 NamedChunksPlugin 和 NamedModulesPlugin 。 |
---|---|
production | 会将 process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin ,OccurrenceOrderPlugin , SideEffectsFlagPlugin 和 UglifyJsPlugin 。 |
process 是 NodeJS 应用全局对象,表示进程。 启动webpack的时候其实就启动了nodeJS应用
1.7 关于配置文件
webpack 的配置文件,是导出一个对象的 JavaScript 文件,由 webpack 根据对象定义的属性进行解析。
因为 webpack 配置是标准的 Node.js CommonJS 模块,可以做到以下事情:
- 通过
require(...)
导入其他文件; - 通过
require(...)
使用npm
的工具函数; - 使用 JavaScript 控制流表达式,例如
?:
操作符; - 对常用值使用常量或变量;
- 编写并执行函数来生成部分配置;
1.8 模块
开发中将程序分解成离散功能块,称为模块。而 webpack 模块能够以各种形式表达他们的依赖关系。
- es6:
import
语句; - CommonJS:
require()
语句; - AMD:
define
和require
语句; css/sass/less
文件中的@import
语句;- 样式(
url(...)
)或 HTML 文件(<img src=...>
)中的图片链接(image url
);
模块解析:
使用 resolver
库来找到模块的绝对路径,帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码包含在每个 require
/ import
语句中,在模块打包中,webpack 使用 enhanced-resolve 来解析文件路径
- 绝对路径:
import "/home/me/file";
import "C:\\Users\\me\\file";
- 相对路径
import "../src/file1";
import "./file2";
- 模块路径,将会在 node_modules 文件夹中搜索
import "module";
import "module/lib/file";
2. 项目规划
npm init -y
目录结构
├── public 存放html模板页面
│ ├── include 存放公共的html代码片段
│ │ ├── header.html
│ │ └── tail.html
│ └── index.html 首页
│ ├── about.html 关于
│ ├── favicon.ico 站点图标
├── src 存放源代码(需要编译)
│ ├── assets 存放资源,包含css文件,字体文件,图片文件
│ │ ├── css
│ │ ├── fonts
│ │ └── img
│ ├── common 公共js文件
│ └── index.js
│ ├── about.js
└── vendor 存放第三方提供的组件,src下js文件中需要 import才能使用
├── static 静态文件,这些文件将会原封不动拷贝到发布的assets目录中
├── readme.md
├── package.json 依赖的js文件,启动脚本
注意: public目录下只有html文件和 favicon.ico 文件(图标)。 每个html文件名都对应一个 src下的 同名的js文件。html文件名与js文件名一一对应(目录也要对应)。include 文件夹中是html片段,这些片段使用
<%= require('html-loader!./include/header.html')%>
的方式被包含到其它html页面中。
3. 总目标
- 可以将HTML代码片段提取到独立文件,其它HTML文件需要使用的时候,可以像其它语言一样 include 这些片段到HTML文件中。
- 每个HTML文件都对应一个js文件,这个js文件作为 webpack的入口文件,被webpack处理后自动引入到HTML中。
- 可以使用sass语法编写css,即编写 scss文件,这些文件被webpack处理后,形成独立的css文件,并自动引入到HTML中。
- HTML,CSS中的图片被webpack处理后,放到独立文件夹中。
- 开发的时候启动一个Web服务器方便开发,能够跨域访问后端接口数据。
- JS中可以使用ES6 语法
- 使用 ESLint 统一JS代码风格。
4. HTML页面内容准备
4.1 公共HTML片段文件
这里以简单的页头和页尾为例:
public/include/header.html
<div>
这是头部分
<a href="/production/index.html">产品中心</a>
<a href="/about.html">关于我们</a>
</div>
public/include/tail.html
<div>
这是页脚部分
</div>
4.1 首页
在首页中嵌入页头和页脚, <%= xxxx %>
这种写法浏览器是无法识别的,需要通过webpack处理后才能被浏览器识别
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>这是首页</title>
</head>
<body>
<%= require('html-loader!./include/header.html')%>
<div class="box">
首页内容
</div>
<%= require('html-loader!./include/tail.html')%>
</body>
</html>
public/index.html 对应的入口文件为 src/index.js 。 这里使用了jQuery 库,输出简单的内容
import $ from 'jquery'
$(() => {
alert("这是index.js");
})
4.2 关于页
public/about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>关于我们</title>
</head>
<body>
<%= require('html-loader!./include/header.html')%>
<div>
关于我们
</div>
<%= require('html-loader!./include/tail.html')%>
</body>
</html>
public/about.html 对应的入口文件为 src/about.js :
import $ from 'jquery'
$(() => {
alert("about.js");
})
4.2 二级页面-产品首页
public/production/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>产品中心</title>
</head>
<body>
<%= require('html-loader!../include/header.html')%>
<div>
产品中心
</div>
<%= require('html-loader!../include/tail.html')%>
</body>
</html>
public/production/index.html 对应的入口文件为 src/production/index.js
import $ from 'jquery'
$(() => {
alert("production------>index.js");
})
下面,通过webpack的配置,先让这几个页面能够在浏览器中运行起来。
5. webpack配置
目前没有安装任何依赖,下面安装依赖
- webpack webpack-cli webpack依赖
- html-webpack-plugin 用于将webpack处理后的js文件,引入到HTML文件中,并输出HTML
- html-loader 用于处理 将HTML片段 合并到其它HTML中。
- webpack-dev-server 启动服务器
npm i webpack webpack-cli clean-webpack-plugin html-webpack-plugin html-loader webpack-dev-server -D
- jQuery 页面上用到了jQuery库
npm i jQuery -S
webpack.config.js
const path = require('path')
module.exports = {
entry:{},
output:{},
resolve:{},
module:{
rules:[]
},
plugins:[]
}
5.1 webpack 入口文件
entry:{
'index': './src/index.js',
'about': './src/about.js',
'production/index': './src/production/index.js'
},
入口文件的文件都使用相对路径,那么相对于哪个路径? webpack的配置中,有个 context选项配置,它是一个 绝对路径
,是一个基础目录, entry和 loader 中都是相对于这个基础路径,其默认值是:__dirname
即当前NodeJS运行的 webpack.config.js 文件所在的目录。以上的配置相当于:
context: path.resolve(__dirname),
entry:{
'index': './src/index.js',
'about': './src/about.js',
'production/index': './src/production/index.js'
},
这里的三个HTML文件,每个都对应一个js入口文件,其入口命名(Chunk )就是文件相对路径去掉 ./src
和 .js
后缀 。 之所以要用这样的规则,是因为一个项目中的HTML和入口文件会很多,将来不可能每个JS入口文件都这样来手工配置,到时会使用nodejs 访问磁盘目录,按照这个规则自动生成 entry配置。
5.2 webpack 输出配置
webpack处理完毕后输出到指定的位置
output:{
path: path.resolve('./dist'), //必须是绝对路径
filename: '[name]-[chunkhash].js'
}
5.3 配置 html-webpack-plugin
每个入口文件都对应一个HTML,该如何生成HTML,需要在 html-webpack-plugin 对象中指明输出的 HTML文件名,HTML模板等。详细配置可以参考官方文档
每个HTML文件都应该对应一个 html-webpack-plugin 对象。 HTML文件太多,后面会使用编码的方式来自动生成配置。
...
const HtmlWebpackPlugin = require("html-webpack-plugin")
...
plugins:[
new HtmlWebpackPlugin({
filename: 'about.html',
template: './public/about.html',
favicon: './public/favicon.ico',
chunks: ['about']
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: './public/index.html',
favicon: './public/favicon.ico',
chunks: ['index']
}),
new HtmlWebpackPlugin({
filename: 'production/index.html',
template: './public/production/index.html',
favicon: './public/favicon.ico',
chunks: ['production/index']
}),
]
-
filename: 最终生成的HTML文件名称,可以使用子文件夹,将来会生成到 output 指定的文件夹中。
-
template: HTML模板。 我们将所有的HTML模板都放到了public目录,这个配置项默认是 ‘./src/index.ejs’
-
favicon: 自动向HTML模板中插入指定的 icon文件
-
chunks: 是一个数组,指定要引入的js文件。默认情况下会引入 webpack 输出的 js。比如上面有三个输出,如果不配置chunks,那么 index.js , abount.js 和 production/index.js 都会被引入到 HTML文件中。
5.4 package.json配置启动命令
"scripts": {
"build": "npx webpack --config webpack.config.js --mode production",
"dev": "npx webpack-dev-server --config webpack.config.js --color --inline --hot --progress --mode development --port 8080 --open"
}
- build 构建生产环境输出 npx是执行Node软件包的工具,它从 npm5.2版本开始,就与npm捆绑在一起。它会寻找 node_modules 下可执行包然后执行。如上面要执行的 webpack命令是存放在 node_modules中的。
- –config 指定webpack的配置文件
- – mode 指定是 production 还是 development ,这样可以使用
process.env.NODE_ENV
来判断是哪个环境。 - dev 启动web服务。 它是使用 webpack-dev-server 插件来运行webpack的 --port 指定了启动端口为 8080。–open 会自动打开浏览器。 这些配置参数可以写到 webpack.config.js 中,这里暂时在命令行中配置,后面再移植到 webpack.config.js 中。详细配置,参考官方文档
5.4.1 启动开发环境
npm run dev
启动后,会自动打开浏览器,访问 “http://localhost:8080”
webpack-dev-server 启动后会将 webpack 的输出结果映射到内存中,我们在磁盘上看不到webapck输出的结果。
查看首页 http://localhost:8080/index.html 源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>这是首页</title>
<link rel="icon" href="favicon.ico"></head>
<body>
<div>
这是头部分
<a href="/index.html">首页</a>
<a href="/production/index.html">产品中心</a>
<a href="/about.html">关于我们</a>
</div>
<div class="box">
首页内容
</div>
<div>
这是页脚部分
</div>
<script src="index-be16bdb4c4cb8f5ff984.js"></script></body>
</html>
再对照 public/index.html模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>这是首页</title>
</head>
<body>
<%= require('html-loader!./include/header.html')%>
<div class="box">
首页内容
</div>
<%= require('html-loader!./include/tail.html')%>
</body>
</html>
可以看到, 根据 模板生成的HTML做了如下改变:
- 插入了
<link rel="icon" href="favicon.ico">
<%= xxx %>
的内容被替换成了header.html 和 tail.html 中的HTML 片段- 插入了 webpack 输出的js文件。
5.4.2 启动编译
npm run build
dist 文件夹中的内容
│ about-1c72cf882218d6c9ef0b.js
│ about.html
│ favicon.ico
│ index-0af568ee344bf0082880.js
│ index.html
└─production
index-0d30eb18d3678e852376.js
index.html
6. 优化出口配置
上面输出的html文件和 js文件都在同一个目录中,通过配置,可以将 js输出到指定的 assets/js 文件夹中,只需要修改 output.filename项,filename项上是可以带文件夹的。
output:{
path: path.resolve('./dist'),
filename: 'assets/js/[name]-[hash].js' //存放到path指定的目录下
}
再次 npm run build
后的结果:
│ about.html
│ favicon.ico
│ index.html
├─assets
│ └─js
│ │ about-1c72cf882218d6c9ef0b.js
│ │ index-0af568ee344bf0082880.js
│ │
│ └─production
│ index-0d30eb18d3678e852376.js
│
└─production
index.html
7. 删除上次编译结果
每次 npm run build
的时候,webpack不会清理 dist目录,因为只要文件有改动,那么生成的文件内容的Hash值都不一样,所以上一次编译的结果被保留了下来造成了混乱。配置 clean-webpack-plugin 插件可以每次build之前清理 dist目录
npm i clean-webpack-plugin -D
webpack.config.js
...
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
...
plugins:[
new CleanWebpackPlugin()),
...
]
注意: CleanWebpackPlugin 要写在 HtmlWebpackPlugin 插件的前面
8. 使用scss
下面使用Sass编写一个css样式文件,样式文件写到 src/assets/css文件夹中
├─assets
│ ├─css
│ │ index.scss
│ │
│ └─img
│ bg.png
src/assets/css/index.scss 内容:
$bg-color: #ee3;
.box{
background-color: $bg-color;
display: flex;
height: 500px;
width: 600px;
}
这里定义了一个类样式,如果要在 public/index.html 中使用,那么首先要在 public/index.html 对应的 src/index.js 入口文件中使用 import 导入css文件
src/index.js
...
import './assets/css/index.scss'
...
然后在 public/index.html 中使用类样式:
...
<div class="box"></div>
...
现在执行 npm run dev
会发现报错了。因为 我们在src/index.js 中导入了 index.scss 文件,这个文件 webpack是无法解析的。所以这就需要配置 loader 来解析 scss文件或者css。
8.1 安装loader
npm set SASS_BINARY_SITE http://npm.taobao.org/mirrors/node-sass
npm i style-loader css-loader node-sass sass-loader -D
因为 sass-loader 依赖于 node-sass . 使用之前先设置 node-sass 环境变量,加速安装包下载
8.2 配置loader
module:{
rules:[{
//它会应用到 .css .scss .sass 后缀的文件,
//use数组loader的名字是有顺序的,即先由sass-loader,再由css-loader处理,最后由style-loader处理
test: /\.(sc|c|sa)ss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}]
}
- 首先是 sass-loader 加载 .scss 文件做转换工作,因为.scss 文件中的内容无法直接被浏览器直接识别。
- scss 转换成 css代码后,再由 css-loader 来加载css代码
- 最终加载的css代码被包裹到
<style>...</style>
中,从而插入到 html文件中。
现在再次 npm run dev
,服务启动后发现css生效了。
仔细观察发现css代码最终被放到了 html中的 style标签中。
8.3 css中使用图片文件
src/assets/css/index.scss 中为box 类样式添加一个背景图片:
$bg-color: #ee3;
.box{
...
background: url(../img/bg.png);
...
}
执行 npm run dev
后发现报错。 原因是 css-loader 在加载 css 的时候,遇到了 url所指向的图片,图片这种资源 scss-loader 是无法处理的,因为图片文件需要被拷贝到最后的输出目录中,css中的url指向的路径也发生了改变。
webpack通过file-loader处理资源文件,它会将rules规则命中的资源文件按照配置的信息(路径,名称等)输出到指定目录,并返回其资源定位地址,默认的输出名是以原文件内容计算的MD5 Hash命名的。官方文档:https://www.webpackjs.com/loaders/file-loader/
npm i file-loader -D
webpack.config.js 文件配置 loader 规则:
rules:[
...
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件
loader: 'file-loader',
options: {
outputPath: 'assets/img' //将图片输出到 dist/assets/img文件夹下
}
}]
再次运行npm run dev
在开发者工具中查看:
这说明 file-loader 把 src/assets/img/bg.png 这个文件拷贝到了 dist/assets/img/992ac5d15ba3b012bb054bf80016a636.png ,即文件名已经变成了 992ac5d15ba3b012bb054bf80016a636.png , 然后返回了此时文件的路径。
可以运行 npm run build
来看看最终的编译结果:
├─assets
│ ├─img
│ │ 992ac5d15ba3b012bb054bf80016a636.png
│ │
│ └─js
│ │ about-83083e244833fcde3bfe.js
│ │ index-83083e244833fcde3bfe.js
│ │
│ └─production
│ index-83083e244833fcde3bfe.js
## 8.4 生成css文件
上面虽然能正常使用css了,但是我们发现最终生成的结果并没有css代码,那么这些css代码哪里去了?为什么运行的时候又没有问题?
因为 css代码是在 src/index.js 中 import的, webpack借助 相关的loader (‘style-loader’, ‘css-loader’, ‘sass-loader’) 将这些css代码全部处理成了字符串,编译到了js文件中了。可以打开 index-xxxxxx.js 这个文件,然后搜索 .box
发现那些css确实被处理成了javascript 字符串
js文件被浏览器加载后,js代码开始执行,此时它会在 html文档的head 节点下添加一个 style节点,然后将css代码输出到节点中。
那如何才能将这些css代码"提取" 出来?webpack4中使用 mini-css-extract-plugin 官方文档 https://webpack.js.org/plugins/mini-css-extract-plugin/#minimizing-for-production 来提取css样式, 使用 optimize-css-assets-webpack-plugin 来压缩css
npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
webpack.config.js
...
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//提取css到单独文件的插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件
...
module:{
rules:[{
// 将 style-loader 替换成 MiniCssExtractPlugin.loader
// 因为最后一步不是生成 style标签,而是提取出单独的文件。
test: /\.(sc|c|sa)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}, ...]
},
...
plugins:[
...
new MiniCssExtractPlugin({
filename: "assets/css/[name]_[hash].css"//输出目录与文件
}),
new OptimizeCssAssetsPlugin()
]
需要注意的是: MiniCssExtractPlugin 还需要在 loader上配置一下,MiniCssExtractPlugin 提供了一个loader,在module.rules.use 组数的第一个元素位置上配上这个loader表示文件处理的最后一步交给 这个loader来处理。一定要注意顺序。plugins中配置了插件,并指定了提取的css文件的存储路径和文件名。[name]是入口文件的name。
编译后的结果:
├─assets
│ ├─css
│ │ index_f9c026e959b5b2b1b2d1.css
│ ├─img
│ │ 992ac5d15ba3b012bb054bf80016a636.png
│ └─js
│ │ about-f9c026e959b5b2b1b2d1.js
│ │ index-f9c026e959b5b2b1b2d1.js
│ └─production
│ index-f9c026e959b5b2b1b2d1.js
在 index.html文件中, .css 文件使用了 link标签 引入了 css文件。
现在执行 npm run dev
发现图片无法加载:
Failed to load resource: the server responded with a status of 404 (Not Found)
查看生成的 css文件,发现 .box 类变成:
index_f9c026e959b5b2b1b2d1.css
.box {
background-color: #ee3;
display: flex;
background: url(assets/img/992ac5d15ba3b012bb054bf80016a636.png);
height: 500px;
width: 600px
}
这个css文件 位于 assets/css 文件夹中,而 url中使用的是一个相对地址
在css 文件中 ,url 如果使用的是相对地址,则是基于当前css文件的
也就是说 background实际加载的是 assets/css/assets/img/xxxx.png 这个路径显然是错误的。那怎么解决?
webpack 配置的 output.publicPath 可以解决。 它的意思是在输出的时候,在所有的路径前面都添加一个前缀,比如: http:// xxx.com/ ,如果将其设置为"/" 那输出的时候所欲的路径都会从服务器的根目录开始。
所以在 webpack.config.js 中对 output.publicPath 进行配置:
output:{
path: path.resolve('./dist'),
filename: 'assets/js/[name]-[hash].js',
publicPath: '/'
}
再次 'npm run dev '就发现正常了。此时在查看css文件,发现是这样的:
.box {
background-color: #ee3;
display: flex;
background: url(/assets/img/992ac5d15ba3b012bb054bf80016a636.png);
height: 500px;
width: 600px
}
因为url 是一个绝对路径,是从服务器的根目录开始的,所以这个路径就是正确的。
9. 使用bootstrap
bootstrap 是常用的css样式库,使用之前先安装:
npm i bootstrap -S
不要直接在 html模板文件中引入,而是在 js文件中引入。 webpack 处理是从 js文件入口开始处理的,而不是 HTML文件。
在 src/index.js 中引入 bootstrap样式
import $ from 'jquery'
import './assets/css/index.scss'
import 'bootstrap/dist/css/bootstrap.css'
$(() => {
alert("index.js======");
})
在 public/index.html 模板中使用
...
<button class="btn btn-success">一个按钮</button>
...
现在 npm run dev
,bootstrap样式就生效了
9.1 问题
因为在 js中引入了 自己的 assets/css/index.scss 和 bootstrap.css ,最终自己的css和 bootstrap的css文件被打包到了最终生成的css文件中了。如果其它js中也使用bootstrap,那就会又生成一份css文件。也就是说 boostrap库的css会不断的重复生成,这样会影响最终的性能。
为了减小css文件大小,需要将库css也分离出来,因为库css一般不会发生变化,而自己写的css会变,所以需要将 bootstrap库的css独立出来存放到单独的文件中。
9.2 分离库文件
库文件有 js库和 css库,这些都需要分离出来。webpack 4+ 版本使用内置的 SplitChunksPlugin 插件来进行公共部分的提取。因为 SplitChunksPlugin 是 webpack 4+ 版本内置的插件, 所以无需安装, 只需在 webpack.config.js 中配置:
plugins:[
...
],
// 提取公共模块,包括第三方库和自定义工具库等
optimization: {
// 找到chunk中共享的模块,取出来生成单独的chunk
splitChunks: {
chunks: "all", // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效
cacheGroups: {
vendors: { // 抽离第三方插件
test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包
name: "vendors",
priority: -10 // 抽取优先级
},
utilCommon: { // 抽离自定义的公共库
name: "common",
minSize: 0, // 将引用模块分离成新代码文件的最小体积
minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
priority: -20
}
}
},
// 为 webpack 运行时代码创建单独的chunk
runtimeChunk:{
name:'manifest'
}
}
SplitChunksPlugin 是在 配置文件 optimization 选项来配置的,我们需要抽离的公共部分有如下:
- 库 css或 js
- 本项目的公共样式或js
- webpack 运行时代码
9.2.1 库css或js
本项目中用到了 jQuery 库 和 bootstrap库, 库文件都是在项目的 node_modules 中的,所以分离这部分代码的标准很简单,只要 chunk(代码片段) 是从 node_modules 来的,就是库代码,此时就把这些代码都抽离出来合并在一起,取名为 venders(提供者)
vendors: { // 抽离第三方插件
test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包
name: "vendors",
priority: -10 // 抽取优先级
},
9.2.2 本项目的公共样式或js
webpack在运行的过程中,如果发现某些chunk被引用了超过2次,比如 某个css或者 js被其它文件 import 超过了2次,那就将这些凡是超过2次的代码合并在一起,起名为 common,那么如果是css,那么将来打包后就独立成 common.css ,js 就是 common.js
utilCommon: { // 抽离自定义的公共库
name: "common",
minSize: 0, // 将引用模块分离成新代码文件的最小体积
minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
priority: -20
}
9.2.3 webapck运行时代码
最终生成的js代码中都是有webpack运行时代码的,这部分代码也不会变化,也应该分离出来
// 为 webpack 运行时代码创建单独的chunk
runtimeChunk:{
name:'manifest'
}
打包后的结果,dist/assets/ 目录
├─css
│ index_aafcf62739bb7ef43cdb.css
│ vendors_aafcf62739bb7ef43cdb.css
├─img
│ 992ac5d15ba3b012bb054bf80016a636.png
└─js
│ about-aafcf62739bb7ef43cdb.js
│ index-aafcf62739bb7ef43cdb.js
│ manifest-aafcf62739bb7ef43cdb.js
│ vendors-aafcf62739bb7ef43cdb.js
└─production
index-aafcf62739bb7ef43cdb.js
boostrap 代码被分离到了 vendors_aafcf62739bb7ef43cdb.css 文件中。
jquery 代码被分离到了 vendors-aafcf62739bb7ef43cdb.js 文件中
webpack运行时代码分离到了 vendors-aafcf62739bb7ef43cdb.js 文件中。
index.html文件中引入情况:
...
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>这是首页</title>
<link rel="icon" href="/favicon.ico" />
<link href="/assets/css/vendors_aafcf62739bb7ef43cdb.css" rel="stylesheet"/>
<link href="/assets/css/index_aafcf62739bb7ef43cdb.css" rel="stylesheet" />
</head>
...
<body>
...
<script src="/assets/js/manifest-aafcf62739bb7ef43cdb.js"></script>
<script src="/assets/js/vendors-aafcf62739bb7ef43cdb.js"></script>
<script src="/assets/js/index-aafcf62739bb7ef43cdb.js"></script>
</body>
10. 处理HTML中的 img 图片
现在在 src/assets/img 文件夹中添加一个图片 nodejs.jpg
文件, 然后在 public/index.html中引入这个图片:
├─public
│ index.html
├─src
│ └─assets
│ └─img
│ nodejs.jpg
public/index.html
...
<img src="../src/assets/img/nodejs.jpg" />
...
然后 npm run build
之后发现 nodejs.jpg 这个图片并没有被处理到 dist/assets/img 目录中, 生成的 index.html 文件中 依然还是:
<img src="../src/assets/img/nodejs.jpg" />
没有做任何改变,生成的 img src 明显是错误的。
我们预期的结果应该是这样的:
- src/assets/img/nodejs.jpg 文件被file-loader 处理,计算出文件的md5 Hash值,假设为 123abcde456, 然后将文件拷贝到 dist/assets/img/123abcde456.jpg
- file-loader 返回图片文件的路径为 /assets/img/123abcde456.jpg
- 替换掉 img 标签的src 为 /assets/img/123abcde456.jpg , 即
<img src="/assets/img/123abcde456.jpg" />
如何才能正确处理img标签中的文件?在 html文件中使用 ejs语法
10.1 html中使用ejs语法
HtmlWebpackPlugin 在处理HTML模板的时候,是支持 ejs语法的,如 <%= xxxxxx%>
,前面将公共的头和尾嵌入到页面中就是使用了 ejs语法,借助于 html-loader 完成了html 内容的加载
<%= require('html-loader!./include/header.html')%>
现在需要让webpack能够正确的处理 html中的img,也可以使用 require语句来加载图片,写法如下:
<img src="<%=require('../src/assets/img/nodejs.jpg').default%>" />
注意后面有一个 .default
10.2 进一步优化图片处理
项目中有些时候会使用一些小的图片文件,这些小的图片文件可以直接被处理成 base64编码,放入到 img标签的src中或者 css文件中。 浏览器在渲染图片文件的时候,会向服务器发起请求将图片下载到本地,如果这样的小图片太多,那么浏览器会发起很多次请求,对性能造成影响。如果直接将图片base64文本嵌入到网页中,浏览器就可以直接解析,无需向服务器请求图片。
可以用 url-loader 来做这个优化
npm i url-loader -D
webpack.config.js
module:{
rules:[
...
,{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件
loader: 'url-loader',
options: {
limit:10*1024,//小于limit限制的图片将转为base64嵌入引用位置, 单位为字节(byte)
fallback:'file-loader',//大于limit限制的将转交给指定的file-loader处理
outputPath:'assets/img'//传入file-loader将图片输出到 dist/assets/img文件夹下
}
}]
},
11 .媒体文件/字体文件处理
媒体文件,如音频,视频以及 css样式中的用到的字体文件,同样需要webpack在编译的时候处理这些资源。这些资源统一使用 url-loader来处理
webpack.config.js
module:{
rules:[
...
,{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/media/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/fonts/[name].[hash:7].[ext]'
}
}]
},
12. 动态配置entry 与 HtmlWebpackPlugin
对于多页面的H5 HTML页面很多,每添加一个HMTL文件,就对应有一个js 文件,这样就需要在 webpack.config.js 中不停的添加 entry 与 HtmlWebpackPlugin 对象。我们可以按照 public目录下的HTML模板的组织结构和 src下的 js文件组织结构,动态的生成配置,只需要遍历目录下的文件即可。
完整的 webpack.config.js 文件内容如下:
const path = require('path')
const glob = require("glob")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");//提取css到单独文件的插件
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件
// HTML模板文件所在的文件夹
const htmlDir = path.join(__dirname, 'public/')
// 入口文件所在文件夹
const srcDir = path.join(__dirname, 'src/')
/** 扫描获取入口 */
function scanEntry() {
var entry = {}
glob.sync(srcDir + '/**/*.js').forEach(name => {
name=path.normalize(name)
// 如 index
// about
// production/index
chunkName=name.replace(srcDir, '').replace(/\\/g,'/').replace('.js', '')
entry[chunkName] = name
})
return entry
}
/** 扫描获取所有HTML模板 */
function scnanHtmlTemplate() {
var htmlEntry = {}
// 扫描目录以及子目录下所有html结尾的文件,不包含 include 文件夹
glob.sync(htmlDir + '/**/*.html', {
ignore: '**/include/**'
}).forEach(name => {
name=path.normalize(name)
chunkName=name.replace(htmlDir, '').replace(/\\/g,'/').replace('.html', '')
htmlEntry[chunkName] = name
})
return htmlEntry
}
/** 构建HtmlWebpackPlugin 对象 */
function buildHtmlWebpackPlugins() {
var tpl = scnanHtmlTemplate()
var chunkFilenames = Object.keys(tpl)
return chunkFilenames.map(item => {
var conf = {
// 如 index.html
// about.html
// production/index.html
filename: item + ".html",
template: tpl[item],
inject: true,
favicon: path.resolve('./public/favicon.ico'),
chunks: [item]
}
return new HtmlWebpackPlugin(conf)
})
}
// 所有入口文件
const entry = scanEntry()
// 插件对象
let plugins= [
new CleanWebpackPlugin({
verbose: true,
}),
new MiniCssExtractPlugin({
filename: "assets/css/[name]_[chunkhash].css"//输出目录与文件
}),
new OptimizeCssAssetsPlugin()
]
// 所有 HtmlWebpackPlugin插件
plugins=plugins.concat(buildHtmlWebpackPlugins())
module.exports = {
entry,
output:{
path: path.resolve('./dist'),
filename: process.env.NODE_ENV==='production'? 'assets/js/[name].[chunkhash].js':'assets/js/[name].js',
publicPath: '/'
},
resolve:{},
module:{
rules:[{
//它会应用到普通的 `.css` 文件,
//use数组loader的名字是有顺序的,即先由sass-loader,再由css-loader处理,最后由style-loader处理
test: /\.(sc|c|sa)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
},{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //匹配图片文件
loader: 'url-loader',
options: {
limit:10*1024,//小于limit限制的图片将转为base64嵌入引用位置
fallback:'file-loader',//大于limit限制的将转交给指定的file-loader处理
//outputPath:'assets/img'//传入file-loader将图片输出到 dist/assets/img文件夹下
name: 'assets/img/[name].[hash:7].[ext]'
}
},{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/media/[name].[hash:7].[ext]'
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'assets/fonts/[name].[hash:7].[ext]'
}
}]
},
plugins,
// 提取公共模块,包括第三方库和自定义工具库等
optimization: {
// 找到chunk中共享的模块,取出来生成单独的chunk
splitChunks: {
chunks: "all", // async表示抽取异步模块,all表示对所有模块生效,initial表示对同步模块生效
cacheGroups: {
vendors: { // 抽离第三方插件
test: /[\\/]node_modules[\\/]/, // 指定是node_modules下的第三方包
name: "vendors",
priority: -10 // 抽取优先级
},
utilCommon: { // 抽离自定义的公共库
name: "common",
minSize: 0, // 将引用模块分离成新代码文件的最小体积
minChunks: 2, // 表示将引用模块如不同文件引用了多少次,才能分离生成新chunk
priority: -20
}
}
},
// 为 webpack 运行时代码创建单独的chunk
runtimeChunk:{
name:'manifest'
}
}
}
13 .简化控制台输出
webpack编译或运行的时候,控制台输出内容较多,可以通过一些开关来控制,详细信息参考官方文档
module.exports= {
...
stats: {
assets:false,
modules:false,
entrypoints:false
}
...
}
14 .支持ES6语法
如今 ES6 语法在开发中已经非常普及,甚至也有许多开发人员用上了 ES7 或 ES8 语法。然而,浏览器对这些高级语法的支持性并不是非常好。因此为了让我们的新语法能在浏览器中都能顺利运行,就需要进行转换。Babel 这个工具就可以完成这样的转换。
14.1 安装Babel
因为Babel只是辅助工具,所以安装的时候加入 -D 参数, 这里安装的是 Babel 7 版本
npm i -D babel-loader @babel/core @babel/preset-env
14.2 配置webpack loader
因为webpack编译打包的时候是不知道 Babel存在的,而转换的工作是有Babel完成的,这就需要让webpack 将打包的js文件交给 Babel 完成转换。
在webpack中对于文件的处理,是由 loader来完成的,前面安装Babel的时候,就安装了一个 babel-loader ,将它配置到webpack中就可以做转换了。
webpack.config.js
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}]
},
14.3 Babel的配置文件
虽然在webpack中配置了loader,但是Babel此时还不能发挥它的作用,需要在项目根目录下创建一个 .babelrc 文件(文件名前面有一个 .),内容如下:
.babelrc
{
"presets": [
"@babel/preset-env"
]
}
15 配置别名与默认扩展名
在webpack的 resolve 选项可以配置 require 或者 import 模块时默认的扩展名和别名
@ 表示 src文件夹
resolve:{
extensions: ['.js', '.json','.scss','.css'],
alias: {
'@': path.resolve('./src')
}
},
在js文件中,如果要 import 一个 .scss文件,原来是这样:
import './assets/css/index.scss'
因为配置可扩展名,和别名,现在可以这样用:
import '@/assets/css/index'
16 配置ESLint
ESLint 是个代码检查工具,它可以配置在开发环境中,提供编码规范,帮助我们找出项目中不符合编码规范规则的代码并给出提示,告诉我们哪一个文件哪一行代码不符合哪一条编码规范,方便你去修改代码。在VS Code开发工具中,可以配置在文件保存的时候,自动启用 ESLint 代码检查,并按照规则自动修复代码。
npm i eslint -D
# 初始化ESLint 配置文件
npx eslint --init
npx eslint --init
可以初始化一个 配置文件,它是一个交互式命令,会让你做一些选择:
? How would you like to use ESLint? ...
To check syntax only
> To check syntax and find problems
√ How would you like to use ESLint? · style
√ What type of modules does your project use? · esm
√ Which framework does your project use? · none
√ Does your project use TypeScript? · No / Yes
√ Where does your code run? · browser
√ How would you like to define a style for your project? · guide
√ Which style guide do you want to follow? · standard
√ What format do you want your config file to be in? · JavaScript
运行成功之后,会自动生成 .eslintrc.js 文件,这就是ESLint的配置文件。
webpack 此时并不知道 ESLint的存在 ,需要 安装 ESLint loader, 这样webpack在工作的时候才会启动 ESLint检查。
npm i eslint-loader eslint-friendly-formatter -D
webpack.config.js 中配置 ESLint Loader:
module: {
rules: [
...
, {
test: /\.js$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
}
},
...
]
},
VS Code 中安装 ESLint 插件, Settings.json 建议配置:
"files.autoSave": "off",
"eslint.validate": [
"javascript",
"javascriptreact",
"vue"
],
"eslint.run": "onSave",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}