webpack学习

webpack

webpack本身

  • 支持CommonJs语法
  • 只能编译js json,
    • js的es语法,这样可以让浏览器识别es语法,但是对于ES6老浏览器不支持,需要用babel转成ES5

Loader

处理css资源

  1. npm i css-loader style-loader -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    2. ```js
    //使用loader
    rules: [{
    //test: 正则匹配 文件
    //use: 按数组顺序从右向左执行
    test: /\.css$/,
    use: ['style-loader','css-loader']
    }
    ],
  2. public/index.html下引入

    1
    2
    <!-- 导入webpack打包的文件 -->
    <script src="../dist/index.js"></script>
  3. src/index.js下导入

    1
    import './css/index.css'
  4. 打包

    1
    npx webpack

style-loader的作用是把 CSS 插入到 DOM 中,就是处理css-loader导出的模块数组,然后将样式通过style标签或者其他形式插入到DOM中。

处理sass

  1. npm i sass-loader sass -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    2. ```js
    //使用loader
    rules: [{
    //test: 正则匹配 文件
    //use: 按数组顺序从右向左执行
    test: /\.less$/,
    use: ['style-loader','css-loader','less-loader']
    }
    ],

less-loader的作用:把less转化成css

sass 的作用:less-loader依赖包

处理stylus

  1. npm i stylus-loader -D
    
    1
    2
    3
    4
    5
    6

    2. ```js
    {
    test: /\.styl$/,
    use: ['style-loader','css-loader','stylus-loader']
    },

处理图片

file-loader url-loader 已内置在webpack5里面

1
2
3
4
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset"
}

打包后:/dist下出现了图片

而样式文件被打包到index.js 文件里了

把小图片转换成base64

优缺:减少请求,文件体积变大

1
2
3
4
5
6
7
8
9
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10*1024 //小于10kb的图片被转成base64
}
}
}

Babel

作用:ES6 => ES5

  1. npm i babel-loader @babel/core @babel/preset-env -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    2. `webpack.config.js `导入babel loader

    ```js
    {
    test: /\.js$/,
    exclude: /node_modules/, // 排除node_modules代码不编译
    loader: "babel-loader",
    },
  2. 在根目录下新建 babel.config.js

    1
    2
    3
    4
    module.exports = {
    //预设
    presets: ['@babel/preset-env'],
    }

    @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
    @babel/preset-react:一个用来编译 React jsx 语法的预设
    @babel/preset-typescript:一个用来编译 TypeScript 语法的预设

Webpack配置

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
//webpack 配置文件使用CommonJs语法
const path = require("path")
module.exports = {
entry: "./src/index.js",
output: {
//打包文件输出目录(绝对路径)
path: path.resolve(__dirname, "dist"),
//打包后的入口文件在dist里的路径
filename: "src/index.js",
//自动清空上次打包的内容
clean: true
},
module: {
//使用loader
rules: [{
//test: 正则匹配 文件
//use: 按数组顺序从右向左执行
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
}, {
test: /\.s[ac]ss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}, {
test: /\.styl$/,
use: ['style-loader', 'css-loader', 'stylus-loader']
},
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 //小于10kb的图片被转成base64
}
},
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
filename: "static/images/[hash:8][ext][query]"
}
},{
test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
type: "asset/resource",
generator: {
filename: "static/media/[hash:8][ext][query]"
}
},
],

},
plugins: [],
//development || production || none
mode: "development",

}
  • type: "asset/resource" 将文件转化成 Webpack 能识别的资源,其他不做处理
  • type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI (base64)形式
  • mode
    • none: 此时webpack不会对入口文件进行任何的优化,webpack直接把模块打包至数组之中
    • development: 它实现了3个插件功能
      • NamedModulesPlugin 对打包出来的模块加上名字
      • NamedChunksPlugin 对每个chunks命名
      • DefinePlugin 对环境变量process.env.NODE_ENV 的值设置为 'development'

[ hash:8 ]: hash值取8位, 文件名长度

[ chunkhash] :根据一个入口文件一个hash

[ contenthash ] : 根据内容生成hash

Plugin

Eslint

  1. npm i eslint-webpack-plugin eslint -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    2. `webpack.config.js `导入eslint 插件

    ```js
    const ESLintWebpackPlugin = require("eslint-webpack-plugin");
    module.export = {
    ...
    plugin: [{new ESLintWebpackPlugin({
    //指定要检查的根目录
    context: path.resolve(__dirname, "src")
    })},]
    }
  2. 在根目录下新建 .eslintrc.js

    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
    module.exports = {
    //1. 解析选项
    parserOptions: {
    ecmaVersion: 6,
    sourceType: "module", //ES模块化
    ecmaFeatures: {
    jsx: true //ES其他特性-jsx
    }
    },
    /* 2. 检查规则
    off / 0 关闭规则
    warn/ 1 开启规则,报警告
    error/2 开启规则,直接报错
    */
    rules: {
    semi: "off", //是否使用分号
    'array-callback-return': 'warn',//数组方法回调函数里有return
    'default-case': [//switch-case 语法必须要有default
    'warn',
    {commentPattern: '^no default$'} //允许在最后注释no default,就不会报警告
    ],
    eqeqeq: [
    'warn', //强制使用=== 和 !==
    'smart', //少数情况不会报警告
    ],
    'no-var': 'error'//禁止使用var定义变量
    },
    /* 3. 继承其他规则
    Eslint官方规则: eslint:recommended
    VueCli : plugin:vue/essential
    ReactCli: react-app
    */
    extends :['eslint:recommended'],
    env: {
    node: true, //启用node中全局变量
    browser: true, //启用浏览器中全局变量
    },
    }

处理HTML资源

生成的index.html有两个特性

  • 内容与public/index.html 一致

  • 自动引入打包好的js等资源

使用

  1. npm i html-webpack-plugin
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    2. ```js
    //webpack.config.js
    const HtmlWebpackPlugin = require("html-webpack-plugin")
    ...
    {
    new HtmlWebpackPlugin({
    template: "./public/index.html",
    filename: "index.html",
    minify: {
    removeAttributeQuotes: true,
    collapseWhitespace: true,
    removeComments: true
    }
    })
    }

处理CSS—高级

提取css

index.js 引入css文件,然后index.html再引入index.js的模式会创建一个style标签来生成样式,可能会有闪屏现象

  1. npm i mini-css-extract-plugin -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    2. ```js
    //webpack.prod.js
    const MiniCssExtractPlugin = require("mini-css-extract-plugin")
    ...
    MiniCssExtractPlugin.loader替换掉所有"style-loader"
    ...
    plugins: [
    new MiniCssExtractPlugin({
    //输出目录和文件名
    filename: "src/index.css"
    })
    ]

兼容性处理

  1. npm i postcss-loader postcss postcss-preset-env -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    2. ```js
    //webpack.prod.js
    rules: [{
    test: /\.css$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader',
    //放在所有css-loader后面
    {
    loader: 'postcss-loader',
    options: {
    postcssOptions: {
    plugins: [
    //可解决大多数兼容问题
    "postcss-preset-env"
    ]
    }
    }
    }]
    },
    ...
    ]

兼容性控制

1
2
3
4
/*  package.json */
{
"browserslist": ["last 2 version", "> 1%", "not dead"]
}

合并配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//webpack.prod.js
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env",
]
}
}
}
preProcessor,
].filter(Boolean)
}

filter 相当于 arr.filter(item=>Boolean(item)) 可以过滤所有Boolean()返回false的值: 例如 0 false null undefined '' ``

开发服务器&自动化

  1. npm i webpack-dev-server -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    2. ```js
    //webpack.config.js
    devServer: {
    host: "localhost",
    port: 8080,
    open: true, //是否自动打开浏览器
    hot: true, //是否开启热更新
    //自动刷新
    // proxy: {
    // '/api': {
    // target: 'http://localhost:3000',
    // changeOrigin: true,
    // pathRewrite: {
    // '^/api': ''
    // }
    // }
    // }
    },
  2. npx webpack server
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11



    ### 开发和生产环境

    在`/config/webpack.prod.js` `/config/webpack.dev.js`

    运行对应配置:

    ```shell
    npx webpack --config ./config/webpack.prod.js

配置脚本

1
2
3
4
5
6
//package.json
{
"dev": "npx webpack serve --config ./config/webpack.dev.js",
"build": "npx webpack --config ./config/webpack.prod.js"
"start": "npm run dev"
}

减少代码体积

SourceMap

构建后的文件内容和开发时的代码的映射文件

1
2
3
4
5
//webpack.config.js
module.exports = {
...
devtool: "cheap-module-source-map"
}
  • cheap-module-source-map: 只包含行映射,打包快
  • source-map:包含行列映射

HotModuleReplacement

webpack默认修改后重新打包所有模块

配置后:让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。

1
2
3
4
5
6
7
8
9
//webpack.config.js
module.exports= {
devServer: {
host: 'localhost',
port: '8081',
open: true,
hot: true,
}
}

此时css以及可以热HMR了,但是js不行,

一般用vue-loader 或者 react-hot-loader 来

OneOf

一个文件有可能被多个rules命中,所以每个文件要和所有规则去test一次

配置后:一个文件只要匹配到一个loader就不继续匹配了

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

Cache

每次打包js要经过Eslint和Babel

配置后:可以缓存之前的Eslint检查和Babel编译结果,打包变快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//webpack.config.js 
//1. babel-loader
{
test: /\.js
include: path.resolve(__dirname, "../src"), // 也可以用包含
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
},

//2. Eslint
new EsLintWebpackPlugin({
context: path.resolve(__dirname, '../src'),
exclude: "node_modules",
cache: true,
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/.eslintcache"),

})

Threads

对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了

略。。。

Tree Shaking

是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。

引用第三方工具函数库或组件库,如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能

依赖ES Module

webpack 默认开启

优化代码运行性能

code split

将所有 js 文件打包到一个文件中,体积太大了

渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

实现了:

  1. 分割文件
  2. 按需加载

1. 多入口

几个entry,就有几个output

  1. npm i webpack webpack-cli html-webpack-plugin -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    2. 修改配置文件

    ```js
    //webpack.config.js
    module.exports= {
    entry: {
    //两个入口文件
    main: './src/main.js',
    index: './src/index.js'
    },
    output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "src/[name].js",
    clean: true
    }
    }

2. 提取重复代码

如果多个入口文件用到同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复

我们需要提取多入口的重复代码生成一个 js 文件,其他文件引用它就好。

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
//webpack.config.js 
optimization: {
// 代码分割配置
splitChunks: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
// 修改配置
cacheGroups: {
// 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
default: {
// 其他没有写的配置会使用上面的默认值
minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},

3. 按需加载,动态导入

动态导入

1
2
3
import("./math.js").then({sum}=>{
console.log(sum(1,2,3))
})

动态导入文件命名

1
2
3
4
5
// webpackChunkName: "math":这是webpack动态导入模块命名的方式
// "math"将来就会作为[name]的值显示。
import(/* webpackChunkName: "math" */ "./math.js").then({sum}=>{
console.log(sum(1,2,3))
})

这种写法会导致eslint报错:webpack.config.js里取消使用Eslint

命名配置

1
2
3
4
5
6
7
8
9
//webpack.config.js
module.exports={
output: {
filename: "src/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
//自动识别type: asset 或 type: asset/resource
assetModuleFilename: "static/media/[name].[hash][ext]"
}
}

打包出来的文件会叫作:math.chunk.js

runtime 文件

如果:a.js 导入了 b.js ,那么b文件修改后,b的hash值发生变化,a导入b时也会发送变化,所以a和b的都发生了更新。这是我们不想看到的。

runtime文件 里面保存 文件地址,这样当某文件改变时,只有该文件和runtime文件会改变,其他文件不会改变hash值

1
2
3
4
5
6
7
//webpack.config.js
module.exports={
...
runtimeChunk: {
name: (entrypoint)=> `runtime~${entrypoint.name}`
}
}

Core.js

  • babel对Es6 的箭头函数,import等编译,但是对于es7的await/async proimise等一些无法解决

  • core-js 是专门用来做 ES6 以及以上 API 的 polyfill

    polyfill 翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。

  1. npm i core-js
    
    1
    2
    3
    4
    5

    2. ```js
    //需要兼容的js文件里
    import "core-js" //全局引入
    import "core-js/es/promise" //局部引入

PWA

离线(offline) 时应用程序能够继续运行功能。

内部通过 Service Workers 技术实现的。

  1. npm i workbox-webpack-plugin -D
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    2. ```js
    //webpack.config.js
    const WorkboxPlugin = require("workbox-webpack-plugin");

    //plugins 中
    new WorkboxPlugin.GenerateSW({
    // 这些选项帮助快速启用 ServiceWorkers
    // 不允许遗留任何“旧的” ServiceWorkers
    clientsClaim: true,
    skipWaiting: true,
    }),
  2. //index.js 中
    if("serviceWorker" in navigator) {
        window.addEventListener("load",()=> {
            navigator.serviceWorker.register("/dist/service-worker.js")
            .then((registration)=> {
                console.log("SW registered", registration)
            }).catch((registrationError)=> {
                console.log("SW registraion failed: ", registrationError);
            })
        })
    }
    

总结

我们从 4 个角度对 webpack 和代码进行了优化:

提升开发体验

  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。

提升 webpack 提升打包构建速度

  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)

减少代码体积

  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)

优化代码运行性能

  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。