一、webpack介绍
webpack其实就是javascript应用程序的静态模块的打包器。
核心概念:
入口(entry)、输出(output)、loader、插件(plugins)
作用:
webpack会将项目的资源文件当成一个个模块,模块之间有依赖关系(比如js和png,js会动态的改变引入一个图片,css设置背景图片和png有依赖,webpack会对这些依赖进行处理,让浏览器识别出来),webpack会对这些有依赖关系的文件进行处理,让浏览器识别,最后将应用程序需要的每个模块打包成一个或多个bundle。
目标:
1、切分依赖树,把依赖树切分到不同的代码块里,然后按需加载这些依赖(和懒加载概念像)
2、保持初始化的时间更少
3、任何的资源都应该视为一个模块在项目中被引用
4、整合第三方的类库,把第三方的类库也视为他的模块
5、每个打包部分都可以自定义,做自己的事情
6、适合大型项目
优势:
1、code splitting
2、loaders
3、plugin system 模块热更新提高开发和调试的效率
二、手动配置webpack(0-1)
自己配置完成的源码:https://github.com/qiuzhaofeng/webpackdemo
1、首先安装node,然后咱们建个新文件,在这里面开始我们的webpack之旅。
2、创建package.json文件,里面是项目相关信息,npm init
3、安装webpack
- 本地安装:(推荐)
npm install --save-dev webpack
npm install --save-dev webpack-cli - 全局安装:
npm install --global webpack webpack-cli
这个时候文件夹下就有一个package.json和node_modules两个文件
4、打包
- 默认entry入口 src/index.js
这时候需要再建个src文件夹,里面间隔入口文件index.js - 默认output出口 dist/main.js
- 打包模式:
webpack --mode development
webpack --mode production
我们将package.json里面的scripts更换成
“scripts”: {
“dev”: “webpack --mode development”,
“build”: “webpack --mode production”
}
代表这我们开发和生产两种打包模式。然后我们打包会出现一个dist文件夹,里面是我们打包生成的文件。
5、配置webpack.config.js
-
新建一个webpack.config.js
-
配置入口entry(所需打包的文件路径)
-
单入口
- 单文件,例如: entry:‘./src/index.js’
- 多文件:
在你想要多个依赖文件一起注入,并且将它们的依赖导向到一个“chunk”时,传入数组的方式就很有用。
例如:entry:[‘./src/index.js’,‘./src/index2.js’,…]。
单入口多文件一般是将多个文件整合成一个文件,如果你的页面中引用了多个js文件,用webpack就可以将这些js文件整合成一个文件然后引入到页面,这样就可以减少前端资源的请求数。
-
多入口,例如:
entry:{
pageOne: ‘./src/pageOne/index.js’,
pageTwo: ‘./src/pageTwo/index.js’,
pageThree: ‘./src/pageThree/index.js’
}
-
-
配置出口output:
(1)path指文件打包后的存放路径
(2)path.resolve()方法将路径或路径片段的序列处理成绝对路径
(3)__dirname 表示当前文件所在的目录的绝对路径
(4)filename是打包后文件的名称- 单出口
output: {
path: path.resolve(__dirname, ‘dist’),
filename: ‘bundle.js’
} - 多出口
output:{
path:path.resolve(__dirname,‘dist’),
filename:’[name].js’
}
- 单出口
6、配置webpack-dev-server
webpack-dev-server是webpack官方提供的一个小型Express服务器。使用它可以为webpack打包生成的资源文件提供web服务。webpack-dev-server 主要提供两个功能:
(1)为静态文件提供服务
(2)自动刷新和热替换(HMR)
- 安装: npm install --save-dev webpack-dev-server
- 配置webpack.config.js文件:
devServer:{
contentBase:”./build”, //设置服务器访问的基本目录
host:‘localhost’, //服务器的ip地址
port:8080, //端口
open:true //自动打开页面
} - 配置package.json
“scripts”: {
“start”: “webpack-dev-server --mode development”
}
7、加载css
loader 让 webpack 能够去处理那些非JavaScript文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
npm install style-loader css-loader --save-dev
在webpack.config.js文件里配置module中的rules
在 webpack 的配置中 loader 有两个目标:
test 属性:用于标识出应该被对应的 loader 进行转换的某个或某些文件。
use 属性:表示进行转换时,应该使用哪个 loader。
module: {
rules: [
{
test:/.css$/,
use:[“style-loader”,“css-loader”]
}
]
}
并且style-loader必须再前面。
8、less和sass
- 安装less-loader
npm install less-loader less --save-dev
.less文件
@width:10px;
@height :@width + 10px;
#header {
width: @width;
height: @height;
}
配置rules
{
test:/\.less$/,
use:["style-loader","css-loader","less-loader"]
}
注意这时候如果引入css,请保留css-loader,否则将css文件改成less文件。
- 安装sass-loader
npm install sass-loader node-sass --save-dev
.scss文件
$header-color: #f90;
#header {
$width: 100px;
width: $width;
color: $header;
}
配置rules
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
9、使用PostCSS处理浏览器前缀
npm install --save-dev postcss-loader autoprefixer
$header-color: #f90;
#header {
display: flex;
$width: 100px;
width: $width;
color: $header-color;
}
配置rules
{
test: /\.scss$/,
use: ["style-loader", "css-loader", "sass-loader",{
loader: "postcss-loader",
options:{
plugins:[
require("autoprefixer")({
browsers: [
'ie >= 8', //ie版本大于等于ie8
'Firefox >= 20', //火狐浏览器大于20版本
'Safari >= 5', //safari大于5版本
'Android >= 4', //版本大于Android4
'Ios >= 6', //版本大于ios6
'last 4 version' //浏览器最新的四个版本
]
})
]
}
}]
}
或者在package.json里加上下图设置:来处理css浏览器前缀
"browserslist": [
"ie >= 8",
"Firefox >= 20",
"Safari >= 5",
"Android >= 4",
"Ios >= 6",
"last 4 version"
]
10、文件处理
图片处理(再css中使用图片)
下载安装file-loader
npm install --save-dev file-loader
{
test:/\.png|jpg|gif|jpeg$/,
use: "file-loader"
}
再来看一下选项配置配置options:
name:为你的文件配置自定义文件名模板(默认值[hash].[ext])
context:配置自定义文件的上下文,默认为 webpack.config.js
publicPath:为你的文件配置自定义 public 发布目录
outputPath:为你的文件配置自定义 output 输出目录
[ext]:资源扩展名
[name]:资源的基本名称
[path]:资源相对于 context的路径
[hash]:内容的哈希值
{
test:/\.png|jpg|gif|jpeg$/,
use: [{
loader:"file-loader",
options:{
name:"[path][hash]aaa.jpg",
// context:"../"
// publicPath:"http://www.abc.com/img",
outputPath:"./img"
}
}]
}
11、字体文件处理
以bootstrap字体文件为例子
Boostrap字体文件下载地址:https://v3.bootcss.com/getting-started/
配置config文件
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: "file-loader"
}
打包完成生成的文件有点乱,我们重新配置一下
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: [{
loader: "file-loader",
options: {
outputPath: "./font"
}
}]
}
12、第三方js库处理
以jquery库为例子
(1)本地导入
编写配置文件:
webpack.ProvidePlugin参数是键值对形式,键就是我们项目中使用的变量名,值就是键所指向的库。webpack.ProvidePlugin会先从npm安装的包中查找是否有符合的库。
如果webpack配置了resolve.alias选项(理解成“别名”),那么webpack.ProvidePlugin就会顺着设置的路径一直找下去
使用webpack.ProvidePlugin前需要引入webpack。
const webpack = require("webpack");
resolve: {
alias: {
jQuery:path.resolve(__dirname,"public/js/jquery.min.js")
}
},
plugins: [
new webpack.ProvidePlugin({
$:"jquery"
})
]
(2)npm安装模块
安装jquery库:
npm install jquery --save-dev
直接在js里import引入, import $ from “jquery”即可。
另外webpack.ProvidePlugin会先从npm安装的包中查找是否有符合的库。
我们也可以npm安装jquery,然后不用import,在webpack里面配置
plugins: [
new webpack.ProvidePlugin({
$:"jquery"
})
]
需要注意的是引入第三方插件的时候这几种方式不要混淆,推荐(2)。
13、编译es6
目前,ES6(ES2015)这样的语法已经得到很大规模的应用,它具有更加简洁、功能更加强大的特点,实际项目中很可能会使用采用了ES6语法的模块,但浏览器对于ES6语法的支持并不完善。为了实现兼容,就需要使用转换工具对ES6语法转换为ES5语法,babel就是最常用的一个工具。
babel转化语法所需依赖项:
babel-loader: 负责 es6 语法转化
@babel/core: babel核心包
@babel/preset-env:告诉babel使用哪种转码规则进行文件处理
npm install babel-loader @babel/core @babel/preset-env --save-dev
配置config文件
exclude表示不在指定目录查找相关文件
{
test: /\.js$/,
exclude: /node_modules/,
use: "babel-loader"
}
新建 .babelrc 文件配置转换规则
{
"presets": ["@babel/preset-env"]
}
另一种规则配置,也可以不适用.babelrc文件,直接在use中配置即可。
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader:"babel-loader",
options: {
"presets": ["@babel/preset-env"]
}
}
}
14、生成html(html-webpack-plugin)
HtmlWebpackPlugin会自动为你生成一个HTML文件,根据指定的index.html模板生成对应的 html 文件。
根据src下的index.html自动在打包后的目录下生成html文件,相关引用关系和文件路径都会按照正确的配置被添加进生成的html里
npm install html-webpack-plugin --save-dev
配置config文件
const webpack = require("html-webpack-plugin");
plugins:[
new HtmlWebpackPlugin({
template:"./public/index.html",//模板文件路径
filename:'webpack.html', //生成文件的名称
minify:{
minimise:true, //是否打包为最小值
removeAttributeQuotes:true,//去除引号(不会去除字体图标的引号)
removeComments:true,//去除注释
collapseWhitespace:true,//去除空格
minifyCSS:true,//压缩html内的css
minifyJS:true,//压缩html内的js
removeEmptyElements: true,//清理内容为空的元素(不要加,会把字体图标去了)
},
hash:true //引入产出资源的时候加上哈希避免缓存
})
]
15、提取分离css(2种方式),将css移动到指定文件夹下
将所有的入口 chunk(entry chunks)中引用的 css,移动到独立分离的 CSS 文件
(1)ExtractTextPlugin插件(老插件)
npm install --save-dev extract-text-webpack-plugin@next
不加@next会报错
配置config文件
const ExtractTextPlugin = require("extract-text-webpack-plugin");
Rules设置:
fallback:编译后用什么loader来提取css文件
{
test: /\.css$/,
// use: ["style-loader", "css-loader"]
use: ExtractTextPlugin.extract({
fallback:"style-loader",
use:"css-loader"
})
},
Plugins设置
new ExtractTextPlugin("./css/index.css"),
(2)mini-css-extract-plugin插件
npm install --save-dev mini-css-extract-plugin
配置config文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
Rules设置
{
test: /\.css$/,
// use: ["style-loader", "css-loader"]
use: [MiniCssExtractPlugin.loader,"css-loader"]
},
Plugins设置
new MiniCssExtractPlugin({
filename: "./css/[name].css"
}),
16、压缩css及优化css结构
optimize-css-assets-webpack-plugin插件
npm install --save-dev optimize-css-assets-webpack-plugin
配置config文件
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
Plugins设置
new OptimizeCssAssetsPlugin({
assetNameRegExp:/\.css$/g,
cssProcessor:require("cssnano"),
cssProcessorPluginOptions:{
preset:["default",{"discardComments":{removeAll:true}}]
},
canPrint:true
})
assetNameRegExp: 正则表达式,用于匹配需要优化或者压缩的资源名。默认值是 /.css$/g
cssProcessor: 用于压缩和优化CSS 的处理器,默认是 cssnano.
cssProcessorPluginOptions:传递给cssProcessor的插件选项,默认为{}
canPrint:表示插件能够在console中打印信息,默认值是true
discardComments:去除注释
17、拷贝静态文件
场景:在项目中未被引入到的项目资源,比如图片,开发文档,保留并和项目打包到一起。将开发的静态资源打包到打包过的文件夹下面。
npm install --save-dev copy-webpack-plugin
配置config文件
const CopyWebpackPlugin = require("copy-webpack-plugin");
Plugins设置
new CopyWebpackPlugin([{
from:__dirname+"/public/assets",
to: __dirname + "/build/assets"
}])
[copy-webpack-plugin] patterns must be an array
18、用clean-webpack-plugin来清除文件
当我们修改带hash的文件并进行打包时,每打包一次就会生成一个新的文件,而旧的文件并没有删除。
为了解决这种情况,我们可以使用clean-webpack-plugin在打包之前将文件先清除,之后再打包出最新的文件
npm install --save-dev clean-webpack-plugin
配置config文件
const CleanWebpackPlugin = require("clean-webpack-plugin");
Plugin配置
new CleanWebpackPlugin(["build"])
先删除build,再重新生成新的build
19、HTML中引入图片
HTML中引入图片,使用loader时,会出现路径错误,图片不显示的情况,这时我们需要经过loader处理后,图片能正常显示
cnpm install --save-dev html-loader
配置config文件
Rules中配置
{
test:/\.(html)$/,
use:{
loader:"html-loader",
options:{
attrs:["img:src","img:data-src"]
}
}
}
20、使用sourcemap调试
Sourcemap是为了解决实际运行代码(打包后的)出现问题时无法定位到开发环境中的源代码的问题。
devtool选项
5个基本类型:
(1) eval
每个模块都使用 eval() 执行,每一个模块后会增加sourceURL来关联模块处理前后的对应关系。由于会映射到转换后的代码,而不是映射到原始代码(没有从 loader 中获取 source map),所以不能正确的显示行数。因为不需要生成模块的sourcemap,因此打包的速度很快。
(2) source-map
source-map会为模块生成独立的soucemap文件。
打包后的模块在模块后面会对应引用一个.map文件,同时在打包好的目录下会生成相应的.map文件。
(3) Inline
与source-map不同,增加inline属性后,不会生成独立的.map文件,source map 转换为 DataUrl 后添加到 bundle 中。
(4) cheap
cheap属性在打包后同样会为每一个模块生成.map文件,但是与source-map的区别在于cheap生成的.map文件会忽略原始代码中的列信息,也不包含loader的sourcemap。
(5) module
包含了loader模块之间的sourcemap,将 loader source map 简化为每行一个映射。
5种类型可随机搭配。
使用sourcemap调试
Js调试
devtool:'source-map',
Css调试
{
test: /\.css$/,
// use: ["style-loader", "css-loader"]
use: [MiniCssExtractPlugin.loader, "css-loader", {
loader: "postcss-loader",
options: {
plugins: [
require("autoprefixer")
],
sourceMap:true //在option选项中添加此项
}
}]
},
- 对于开发环境
cheap-module-eval-source-map
打包速度不快,但是重新构建速度会很快,不会忽略列信息,同时会包含loader的source map,调试会很清晰,对应到一个行号,包含了4个基本类型。 - 对于生产环境
source-map 稳健,完整,也可以不适用。
21、模块热替换
模块热替换(HMR - Hot Module Replacement)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面时丢失的应用程序状态。切换tab栏的时候,在修改css或js的时候,tab的切换状态还是停留在那里,我们的tab的状态不会丢失。
- 只更新变更内容,以节省宝贵的开发时间。
- 调整样式更加快速 - 几乎相当于在浏览器调试器中更改样式。
配置config
devServer: {
contentBase: './build', //设置服务器访问的基本目录
host: 'localhost', //服务器的ip地址
port: 8080, //端口
open: true, //自动打开页面
hot:true
},
Plugin设置
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
npm run start 可以查看 http://localhost:8080/webpack.html
但是现在并没有自动替换需要刷新,这是因为我们的热替换只针对有style-loader,应该把我们的提取css的loader更换,修改Js会刷新页面
devServer中加入hotOnly表示只有热更新,不会自动刷新页面
devServer: {
contentBase: './build', //设置服务器访问的基本目录
host: 'localhost', //服务器的ip地址
port: 8080, //端口
open: true, //自动打开页面
hot: true,
hotOnly: true
},
修改js文件时代码不会自动热更新,需加入以下代码可以告诉 webpack 接受更新的模块。
if(module.hot) {
module.hot.accept()
}
22、区分开发环境和生产环境
开发环境和生产环境的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载或热模块替换能力 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,以及资源的优化,以改善加载时间。所以我们通常建议为每个环境编写彼此独立的 webpack 配置。
npm install --save-dev webpack-merge
可以将公共的配置添加到开发或生产环境中
拆分文件
在这里我们可以将webpack.config.js拆分为三个文件,分别是webpack.common.conf.js、webpack.dev.conf.js和webpack.prod.conf.js。
webpack.common.conf.js是放一些我们公用的配置,比如入口entry、出口output、常用loader以及插件等。
webpack.dev.conf.js是在开发环境上的配置,比如devServer配置、模块热替换等方便开发的配置。
webpack.prod.conf.js是在生产环境上的配置,比如提取分离css、压缩css和js等。
需要注意的是,各个环境下,文件的路径以及生成文件的路径配置需正确,详细配置见源码。
修改package.json文件中的script
"scripts": {
"dev": "webpack --mode development --config ./config/webpack.dev.conf.js",
"build": "webpack --mode production --config ./config/webpack.prod.conf.js",
"start": "webpack-dev-server --mode development --config ./config/webpack.dev.conf.js"
},
--config可以指定使用的配置文件
在webpack的开发环境和生产环境配置中分别在plugins配置
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"fz"',
MOCK: 'false',
}
}),
打包的时候就会根据process.env.NODE_ENV去区分是什么环境,然后在不同的环境配置不同的接口baseUrl,在请求接口时将这个变量引入到公共部分。
23、删除生产环境的console
plugins配置
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
},
sourceMap: true
}),
24、webpack打包优化
优化打包速度
影响打包速度的元素:文件多,依赖多
未优化前打包速度是9858ms
Hash: d54dc6c7b8bd740b6853
Version: webpack 4.29.5
Time: 9858ms
(1)减少文件搜索范围
在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在。
在配置 resolve.extensions 时你需要遵守以下几点,以做到尽可能的优化构建性能:
- 后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
- 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
- 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。例如在你确定的情况下把 require(’./data’) 写成 require(’./data.json’) 。
resolve: {
extensions:[".js"]
},
(2)优化 resolve.modules 配置
resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。
resolve.modules 的默认值是 [‘node_modules’],会采用向上递归搜索的方式查找
function resolve(dir) {
return path.join(__dirname,"..",dir)
}
__dirname当前路径的绝对路径
path.join([path1][, path2][, …])
用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是"/",Windows系统是""。
path.resolve([from …], to)
将 to 参数解析为绝对路径,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/foo、/bar、baz,则调用 path.resolve(’/foo’, ‘/bar’, ‘baz’) 会返回 /bar/baz。
resolve: {
modules:[
resolve("public"),
resolve("node_modules")
]
}
(3)优化resolve.alias配置
resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。
resolve: {
alias: {
"assets": resolve("./public/assets")
}
}
(4)缩小文件匹配范围
Include:需要处理的文件的位置
Exclude:排除掉不需要处理的文件的位置
{
test: /\.js$/,
include:[
resolve("src")
],
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
"presets": ["@babel/preset-env"]
}
}
}
这个时候打包
Hash: d54dc6c7b8bd740b6853
Version: webpack 4.29.5
Time: 2977ms
(5)设置noParse
防止 webpack 解析那些任何与给定正则表达式相匹配的文件。忽略的文件中不应该含有 import, require, define 的调用,或任何其他导入机制。忽略大型的 library 可以提高构建性能。比如jquery、elementUI等库。
(6)给babel-loader设置缓存
babel-loader 提供了 cacheDirectory特定选项(默认 false):设置时,给定的目录将用于缓存加载器的结果。
{
test: /\.js$/,
include:[
resolve("public")
],
exclude: /node_modules/,
use: {
loader: "babel-loader?cacheDirectory=true",
options: {
"presets": ["@babel/preset-env"]
}
}
},
设置完成后第一次打包会缓存下来babel-loader,第二次打包会比第一次快很多。
Hash: d54dc6c7b8bd740b6853
Version: webpack 4.29.5
Time: 2811ms
(7)使用happyPack
happyPack的基本原理:在webpack构建过程中,我们需要使用Loader对js,css,图片,字体等文件做转换操作,并且转换的文件数据量也是非常大的,且这些转换操作不能并发处理文件,而是需要一个个文件进行处理,HappyPack的基本原理是将这部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。
cnpm install happypack --save-dev
配置webpack.common.conf.js文件
const happyPack = require("happypack");
Rules设置
{
test: /\.js$/,
include:[
resolve("public")
],
exclude: /node_modules/,
loader:"happypack/loader?id=happypackBabel"
}
Plugins设置
new happyPack({
id:"happyBabel",
loaders:[
{
loader:"babel-loader"
}
]
})