简介

webpack 是前端一款高效打包工具,它能把一个庞大的工程打包压缩,甚至体积能达到仅仅几kb,webpack是前端或不可缺的一环

环境搭建

既然是前端项目,那么就npm就必不可少
运行 npm install webpack-cli -g 来全局安装脚手架
随后使用 npm install webpack-cli -D 安装到工程下

在工程目录下创建 webpack.config.js (文件名固定) 来作为webpack的配置文件,所有有关webpack的配置都写在此文件中,其中的内容通过es5语法暴露出去

五个核心配置

webpack 五个核心配置是

  1. entry 入口文件
  2. output 输出
  3. module loader配置
  4. plugins 插件
  5. mode 打包模式
    其结构如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module.exports = {
    //入口起点
    entry:

    //输出
    output:{

    },
    module:{

    },
    plugins:[

    ],//插件配置
    mode:'development',
    //mode:'production'
    }
    下面详细聊聊这五个配置

entry
入口文件,指示webpack 从哪个文件开始打包,这个入口文件所引用导入的文件都会参与打包

output
输出文件,指示webpack将打包好的文件输出到哪里
其中需要在顶部导入js的内置模块path

webpack 默认只能打包js/json资源,所以当我们想要打包其他资源是,则需要借助loader

module
module主要作用是配置loader,而每一个loader都作为一个对象,存在rules数组中。 loader是webpack最重要的一环,只有配置了各种loader webpack才能打包各种各样的资源,其中各种loader通过npm 下载即可

  1. 打包资源

    1. 打包css资源(需要下载css-loader,style-loader)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      {
      //正则表达式 表示匹配哪些文件
      test:/\.css$/,

      use:[//use为一个数组,将loader按从下至上顺序执行

      //创建一个style标签 将js中的样式添加到页面中生效
      'style-loader',

      //将css文件 以字符串的形式变成一个commjs模块加载到js
      'css-loader',
      ]
      }
    2. 打包less资源(需要下载style-loader,css-loader,less-loader)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      {
      test:/\.less$/,//每一种匹配规则 只适用与一种文件

      use:[
      //创建style标签 将js中的样式添加到页面中
      'style-loader',

      //将css文件 编译成commjs模块加载到js
      'css-loader',

      //将less文件编译成css文件
      'less-loader'
      ]
      },
    3. 将css单独打包成一个文件(单独下载mini-css-extract-plugin)

      首先new一个对象
      const MiniCssExtracetPlugin = requir('mini-css-extract-plugin')

      调用插件对象

      1
      2
      3
      new MiniCssExtracetPlugin({
      filename:'css/built.css'//对输出文件进行重命名
      })

      修改loader信息

      1
      2
      3
      4
      5
      6
      7
      8
      {
      test:/\.css/,
      use:[
      //'style-loader',//创建style标签,将样式放上去
      MiniCssExtracetPlugin.loader,//取代style-loader 提取js中的css为单独文件
      'css-loader'//将css文件整合到js中
      ]
      }
  2. 打包img资源(1)(需要下载url-loader,file-loader)
    由于图片数量在实际开发中会很多,在打包过程中就会拖慢打包速度,所以我们需要使用 options来修改loader的一些参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //当只使用了一个loader时,则可以不使用use数组
    loader:'url-loader',
    options:{
    limit:8 * 1024,
    //图片大小小于8kb(项目中较小的图片) 就会被base64编码方式处理
    //优点:减少请求数量 (减轻服务器压力)
    //缺点:图片体积会更大(请求速度更慢)

    name: '[hash:10].[ext]'
    //文件名取哈希值前十位加上文件原拓展名
    }
    },

  3. 打包img资源(2)(需要下载html-loader)
    上面那种处理方式只能处理 html引用的css文件中引入的图片,不能处理html页面中引入的图片。解决方法是使用html-loader
    但是 url-loader是使用es6model去解析,而html-loader是使用commjs去解析, 为了避免冲突我们需要关闭url-loader的es6Module 改用commjs解析(在option中加入esModule:false)

     
    1
    2
    3
    4
    5
    {
    //使用html-loader
    test:/\.html$/,
    loader:'html-loader'
    }
    1
    2
    3
    4
    5
    6
    7
    options:{
    limit:8 * 1024,
    name: '[hash:10].[ext]'

    //在url-loader中关闭es6Module
    esModule:false
    }
  4. 打包html资源(需要下载适用于html的plugin插件,html-webpack-plugin -D

    在顶部引入
    const HtmlWbpackPlugin = requi('html-webpack-plugin');

    随后在plugins列表中new一个对象,其中的template属性,将作为结构模板。

    1
    2
    3
    4
    5
    6
    plugins:[
    new HtmlWbpackPlugin({
    template:'./src/index.html'
    })

    ],
  5. 兼容性处理

    1. 兼容css(需要下载postcss-loader,postcss-preset-env)
      随着css版本的更新迭代,相对于应的兼容性问题也层出不穷,webpack也提供了css兼容性的解决方案

      修改loader

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      {
      test:/\.css/,
      use:[
      MiniCssExtracetPlugin.loader,
      'css-loader',

      //第一种 使用loader的默认配置 修改loader配置
      //帮postcss找到package.json中browserslist的配置,通过指定配置加载兼容性样式
      {
      loader:'postcss-loader',
      options:{
      ident:'postcss',
      plugins:()=>{
      require('postcss-preset-env')
      }
      }
      }
      ]
      }

      在package.json 中添加支持的browserlist

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      "browserslist": {
      "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
      //兼容最近一个版本的谷歌火狐索菲亚浏览器
      ],
      "production": [
      ">0.2%",//兼容99.8%的浏览器
      "not dead",//不兼容已经死掉的浏览器
      "not op_mini all"//不兼容op_mini
      ]
      },

postcss 默认使用生产环境 如需使用开发环境则需要手动配置nodejs 中的临时环境变量,在webpack.config.js 顶部添加
process.env.NODE_ENV = 'development'

如抛错 ValidationError: Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 则说明 此插件版本不支持在webpack.config.js中进行配合
需要在项目根目录下 创建 postcss.config.js 添加如下代码

module.exports = { plugins:[ require('postcss-preset-env')() ] }

然后在webpack.config.js 中删除post-loader的额外配置

1
2
3
4
5
6
7
8
9
{
loader:'postcss-loader',
//options:{
// ident:'postcss',
// plugins:()=>{
// require//('postcss-preset-env')
// }
//}
}


2. 兼容js(1)(需要下载babel-loader,@babel/core,@babel/preset-env)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 预设:指示babel 做怎样的兼容处理
presets: [
[
'@babel/preset-env',
{
//指定兼容性做到哪个版本的浏览器
targets: {
chrome: '60',
firefox: '50',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}
}

这种方法只能转换基本语法,promise等不能转换

如需要兼容所有js(需要下载@babel/polyfill),在入口文件中引入
import '@babel/polyfill',这种方法会将所有js兼容性都包括,所以打包后会非常大
3. 压缩处理

  1. 压缩js html
    在修改mode属性为 production生成模式

    删除html中无用的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    new HtmlWbpackPlugin({
    template:'./index.html',
    minify:{
    //移除空格
    collapseWhitespace:true,

    //移除注释
    removeComments:true
    }
    })
  2. 压缩css(需要下载optimize-css-assets-webpack-plugin插件),并在插件列表中使用

    1
    2
    3
    plugins:[
    new OptimizeCssAssetsWebpackPlugin()
    ],

    性能调优

HMR

HMR:hot module replacement 热模块替换
当某一个模块发生变化时 只会重新加载变化的模块 其他模块不会被加载

在webpack.config.js 结尾处添加

1
2
3
4
5
6
7
devServer:{
contentBase:resolve(__dirname,'build'),
compress:true,
port:3000,
open:true,
hot: true
}

js默认不能使用hmr,如需要,在入口文件添加监听即可

1
2
3
4
5
6
if(module.hot){
module.hot.accept('./print.js',()=>{
dosomething()
//监听print.js 文件,如果发生变化,会执行回调函数
})
}

html默认不能使用hmr 而且会导致html文件不能热更新
在入口文件 加上html文件即可(通常不需要hmr功能)

source-map映射

在源代码和构建后代码之间形成映射,当构建后代码出错时,可以映射到源代码
在webpack.config.js 末端添加devtool属性即可属性值及功能如下

1
2
3
开发环境下使用:eval-source-map(调试好) || eval-cheap-module-source-map(速度快)
生成环境下使用:source-map(调试好) || cheap-module-source-map(速度好) ||
nosources-source-map(隐藏全部代码)||hidden-source-map(隐藏源代码)

oneOfLoader

当使用oneOf后 当文件命中loader就会停止 不会接着继续判断 提高效率
将所有loader对象放入oneOf数组即可

1
2
3
4
5
6
7
8
9
10
11
module: {
rules: [
{
oneOf: [
{},
{},
{}
]
}
]
},

treeShaking

去除无用代码 减少打包体积
前提:es6module productionEnv
问题:会把一些文件干掉 如css babel(直接引用,没有使用)

在packge.json 中 添加配置sideEffects 以排除相应文件

1
2
3
4
"sideEffects": [
"*.css",
"*.less"
]

codeSplit

将打包后的文件拆分成多个文件 可以实现并行加载/按需加载

  1. 多个入口 多个出口 每一个出口文件都对应一个入口文件
1
2
3
4
5
6
7
8
9
10
entry:{
main:'./js/index.js',
test:'./js/test.js'
},

output:{
// 生成的构建后文件将根据入口文件进行命名
filename:'js/[name].js',
path:resolve(__dirname,'build')
},
  1. 添加 属性 将node_modules 中的文件 单独打包成一个chunk,如果是多入口 会将里面公用的modules 单独打包,在配置文件中添加配置
1
2
3
4
5
optimization:{
splitChunks:{
chunks:'all'
}
},

lazyLoading

只有当需要模块的时候 才去加载,加载完成之后 再次调用会使用缓存

pwa

渐进式网络开发应用程序(离线访问) (需要下载 workbox-webpack-plugin 并导入其GenerateSW方法)

在配置文件plugins中使用插件

1
2
3
4
new GenerateSW({
clientsClaim:true,
skipWaiting:true
})

随后在入口文件中 注册service-worker

1
2
3
4
5
6
7
8
9
10
11
12
//注册serciceworker
if('serviceWorker' in navigator){
window.addEventListener('load',()=>{
navigator.serviceWorker.register('/service-worker.js')
.then(()=>{
console.log('serviceworkder 注册成功');
})
.catch(()=>{
console.log('serviceworkder 注册成功');
})
})
}

externals

忽略打包
如果有包通过标签引入了 可以设置不被打包
在配置文件中添加

1
2
3
4
//忽略包名 不被打包
externals:{
jquery:'jQuery'
}

完整通用版本

生产版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
正常来讲,一个文件只能被一个loader处理。
当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
先执行eslint 在执行babel
*/
{
// 在package.json中eslintConfig --> airbnb
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};

开发版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
// 关闭es6模块化
esModule: false,
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'
},
{
// 处理其他资源
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true
}
};