使用Webpack5搭建项目

webpack主要配置的5个模块:

 

 

1.entry:入口,指示webpack从哪个文件开始打包:2.output:输出,webpack打包的文件输出到哪里去,如何命名等

3.loader:加载器,webpack本身只能处理js,json等资源,其他的资源需要借助loader,webpack才可以解析

4.plugins:插件

5.mode:模式,主要两种,生产和开发模式


在本地创建项目文件夹

初始化package.json文件

npm init -y

并创建以下文件:

main.js为入口文件

public下放置静态资源

d7fe96ca9c774b2391ab642b54a3e78e.png

 下载webpack以及webpack-cli 

npm i webpack webpack-cli -D

可以使用指令检查是否可以打包编译

npx webpack ./src/main.js --mode=development

npx webpack ./src/main.js --mode=production

 打包成功后,在index.html中引入 dist下的main.js


在根目录下创建webpack.config.js并进行基本配置:

const path = require("path")
module.exports = {
    //入口
    entry:"./src/main.js",//取相对路径
    //输出
    output:{
        //__dirname nodejs的变量,代表当前文件夹的目录
        path:path.resolve(__dirname,"dist"),//文件输出的路径,取绝对路径
        filename:"main.js",//文件名称,
    },
    //加载器
    module:{
        rules:[]
    },
    //插件
    plugins:[],

    //模式
    mode:"development"
}

引入需要的js,css等资源到main.js,启用命令 npx webpack,即可看打包是否成功,成功后在index.html中使用script标签引入dist下的main.js即可查看效果。

自动清空上次打包,在output中添加clean属性为true:

output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
        filename: "main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容
        clean:true,
    },

开发模式:

处理Css资源,webpack不能识别css资源,下载包:

npm install --save-dev css-loader 

npm install style-loader -D

配置rules:

rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                     "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
        ]

处理less资源,下载less-loader:

npm install less less-loader --save-dev

 配置rules:

rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
        ]

配置sass,下载loader以及配置rules:

npm install sass-loader sass --save-dev
rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
        ]

配置stylus-loader,下载loader,配置rules:

npm install stylus stylus-loader --save-dev
rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
        ]

处理图片资源

webpack4时,我们处理图片资源通过file-loader和url-loader处理,现在webpack5已经将两个Loader功能内置到webpack里了,我们只需要简单配置即可:

rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                }
            }
        ]

至此,打包后的文件比较乱,我们需要处理一下,js修改output,图片要修改生成的路径在对应的loader里面

output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容
        clean:true,
    },
{
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            }

处理字体图标资源:

{
                test: /\.(ttf|woff2?)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            }

处理其他资源,在原先字体的基础上直接加就好了:

{
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },

处理Js资源,针对兼容性,使用babel来完成,针对代码格式,使用eslint来完成,先使用eslint,检测代码后,再由babel做代码兼容性处理。

使用Eslint,主要是写eslint配置文件,里面写上各种rules,将来运行eslint时就会以写的规则 对代码进行检查。

配置文件有很多种写法:

.eslint.* : 新建文件位于根目录

.eslintrc / .eslintrc.js / .eslintrc.json 区别在于配置格式不同,

package.jason中eslintConfig :不需要创建文件,在原有文件基础上写。

Eslint会查找和自动读取他们,所以以上配置文件存在一个即可,这里以.eslintrc.js文件为示例(以下为示例)在根目录创建.eslintrc.js文件:

module.exports={

        //解析选项

        parserOptions:{},

        //具体检查规则

        rules:{},

        //继承其他规则

        extends:[ ],

        ...其他详情见Configuring ESLint - ESLint中文文档

}

parserOptions解析选项:

parserOptions:{

        ecmaVersion:6, //ES 语法版本

        sourceType:'module', //es 模块化

        ecmaFeatures:{

                jsx:true //如果是react项目就需要开启jsx语法

        }

}

rules具体规则:

'off' 或 0 -关闭规则,

'warn' 或 1 -开启规则,使用警告级别的错误:warn(不会导致程序退出) 

'error' 或 2 -开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

rules:{

        semi:'error',//禁止使用分号

        'array-callback-return':'warn',//强制数组方法的毁掉中有return语句,否则警告

        'default-case':[

                'warn', //要求switch语句有default分支,否则警告

                {componentPattern : '^no default$'} //允许在最后注释no default,就不会有警告了

        ],

        eqeqeq:[

                'warn' , //强制使用 === 和 !== ,否则警告

                'smart', //除了少数情况下不会有警告

        ]

extends继承

开发中一点点写rules规则太费劲,所以有更好的办法,继承现有的规则。

现有以下较为有名的规则:

Eslint 官方规则:eslint : recommended,

Vue Cli 官方的规则:plugin : vue/essential

React Cli 官方规则 : react-app  

module.exports = {

        extends :[ 'reacr-app'],

        rules :{

                //在这里我们的规则会覆盖react-app中对应的规则,可以在这里修改规则

                eqeqeq:[

                        'warn' , //强制使用 === 和 !== ,否则警告

                        'smart', //除了少数情况下不会有警告

                ]

        }

}


讲了这么多,下面看看使用 

在webpack5中,eslint是一个插件,4中是一个loader,所以我们安装eslint-webpack-plugin:

npm install eslint-webpack-plugin eslint --save-dev

需要引入Eslint并在plugins中使用:

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容
        //原理:在打包前,将path整个目录清空,在进行打包
        clean:true,
    },
    //加载器
    module: {
        rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },

        ]
    },
    //插件
    plugins: [new ESLintPlugin({
        //检测哪些文件
        context:path.resolve(__dirname,"src")
    })],

    //模式
    mode: "development"
}

在根目录下创建.eslintrc.js文件,并配置:

module.exports = {
    //继承eslint规则
    extends:["eslint:recommended"],
    env:{
        node:true,//启用node中的全局变量
        browser:true,//启用浏览器中全局变量
    },
    parserOptions:{
        ecmaVersion:6, //es6
        sourceType:"module",//es module
    },
    rules:{
        "no-var":2//不能使用var定义变量
    }
}

 由于eslint插件会对dist下的文件进行检测,我们可以优化一下,在根目录创建.eslintignore文件并写入dist文件:

dist

处理js文件:Babel

主要用于将ES6语法编写的代码,转换为向后兼容的js语法,以便在当前和旧版本的浏览器中运行

配置文件:

babel.config.* : 新建文件在根目录,

babel.config.js,

babel.config.json,

.babelrc.json,

package.json中babel:不需要创建文件,在原有文件基础上写,babel会自动查找和读取他们,所以以上配置文件只需要存在一个即可。

我们以babel.config.js为例(以下均为实例),在根目录创建babel.config.js文件:

添加预设:

简单理解就是一组babel插件,扩展babel功能

@babel/preset-env : 一个智能预设,允许您使用最新的js,

@babel/preset-react : 一个用来编译react jsx语法的预设,

@babel/preset-typescript : 一个用来编译typescript语法的预设

module.exports={

        //预设:

        presets:[ ]

}

接下来看看webpack中的使用:

babel是使用babel-loader,需要下载loader:

npm install -D babel-loader @babel/core @babel/preset-env

配置webpack.config.js:

{
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }

 在根目录下创建babel.config.js文件并配置babel.config.js:

module.exports = {
    //智能预设:能够编译Es6语法
    presets:['@babel/preset-env']
}

处理html文件:

下载插件:

npm install --save-dev html-webpack-plugin

删除public下index.html的main.js的引用,手动引入不需要了,通过插件自动引入,在wepack.config.js中引入并使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack</title>
</head>
<body>
    <div class="box"></div>
    //手动引入不需要了,通过插件自动引入
    <!-- <script src="../dist//main.js"></script> -->
</body>
</html>
//插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "src")
        }),
        new HtmlWebpackPlugin()
    ],

此时打包后的dist中已经存在index.html文件,但是内容已经和之前的不同了,可以通过配置插件的template属性来制定模版:

new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template:path.resolve(__dirname,"public/index.html")
        }

之后只需关注dist下的index.html即可


开发服务器&自动化

不用每次手动打包,编辑保存后可自动打包输出

npm install --save-dev webpack-dev-server 

 在webpack.config.js配置:

//插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "public/index.html")
        })
    ],
    devServer: {
        host:"localhost", //启动服务器域名
        port: 3000, //启动服务端口号
        open:true //是否自动打开浏览器
    },
    //模式
    mode: "development"

此时,我们只需要启动命令:npx webpack serve 即可

注意:开发服务器是不会输出资源的,是在内存中编译打包的,所以即使清空了dist也不影响开发服务器的编译。现在也可以将clean功能注释,开发服务器下也不会有输出。


生产模式

因为要部署上线,在这个模式下,主要对代码进行优化,让其运行性能更好。

主要两个角度:代码运行性能以及打包速度。

所以分别准备两个配置文件:

根目录创建config文件夹,将webpack.config.js转存如config文件夹下,并改名为webpack.dev.js,复制一份,并改名为webpack.prod.js

3f60104eea4048ffa848f339aafa8529.png

 首先对开发环境的文件配置做一下处理:

修改path路径:为undefined

output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: undefined,//开发环境没有输出
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
    },

修改plugins的绝对路径

new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        })

此时我们通过命令启动开发服务器:

npx webpack serve --config ./config/webpack.dev.js

下面看生产配置:

前面的绝对路径也要重新配置,和开发环境的一样

//输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容
        //原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },

修改mode为'production',且生产模式不需要devServer

//插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        })
    ],
    //模式
    mode: "production"

现在输入执行:

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

即可看到生产环境打包的dist文件 

由于指令过长,我们在package.json中配置指令:

"scripts": {
    "start": "npm run dev",
    "dev": "webpack serve --config ./config/webpack.dev.js",
    "build": "webpack --config ./config/webpack.prod.js"
  },

现在即可按照指令运行和打包 


处理css,提取css成单独文件:

css文件被打包到js文件中,当js加载时会创建一个style样式标签来生成样式,但对于网站来说会出现闪屏现象,用户体验不好(f12改变网络速度为3g可以查看),最好是单独的css文件,通过link标签加载性能才好。

下载插件:

npm install --save-dev mini-css-extract-plugin

引入插件并使用,需要配置plugins和rules,这里要注意,把之前所有的style-loader替换为MiniCssExtractPlugin.loader 即可

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容
        //原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },
    //加载器
    module: {
        rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    MiniCssExtractPlugin.loader,  //提取css为单独文件
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    MiniCssExtractPlugin.loader,//提取css为单独文件
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 提取css为单独文件
                    MiniCssExtractPlugin.loader,
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 提取css为单独文件
                    MiniCssExtractPlugin.loader,
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },
            {
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        }),
        new MiniCssExtractPlugin()
    ],
    //模式
    mode: "production"
}

执行 npm run build 即可发现dist下出现了main.css文件


css兼容性处理,postcss-loader,下载插件:

npm install --save-dev postcss-loader postcss postcss-preset-env

配置postcss-loader必须在css-loader后面,在其他样式loader的前面:

rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    MiniCssExtractPlugin.loader,  //提取css为单独文件
                    "css-loader",  //将css资源编译为common.js的模块到js中
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    'postcss-preset-env'
                                ],
                            },
                        },
                    },
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    MiniCssExtractPlugin.loader,//提取css为单独文件
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    'postcss-preset-env'
                                ],
                            },
                        },
                    },
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 提取css为单独文件
                    MiniCssExtractPlugin.loader,
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    'postcss-preset-env'
                                ],
                            },
                        },
                    },
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 提取css为单独文件
                    MiniCssExtractPlugin.loader,
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: {
                                plugins: [
                                    'postcss-preset-env'
                                ],
                            },
                        },
                    },
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },
            {
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }
        ]

在package.json中配置browserslist:

"last 2 version" :指浏览器最近的两个版本

"> 1%" :覆盖百分之99的浏览器

"not dead" :不要已死浏览器

"browserslist":[
    "last 2 version",
    "> 1%",
    "not dead"
  ]

下面对loader进行一下优化,将公共的部分写成一个函数:

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },
    //加载器
    module: {
        rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: getStyleLoader()
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: getStyleLoader("less-loader")
            },
            {
                test: /\.s[ac]ss$/i,
                use:getStyleLoader("sass-loader")
            },
            {
                test: /\.styl$/,
                use:getStyleLoader("stylus-loader")
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },
            {
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        }),
        new MiniCssExtractPlugin({
            filename: "static/css/main.css"
        })
    ],
    //模式
    mode: "production"
}

css压缩,下载插件:

npm install css-minimizer-webpack-plugin --save-dev

引入,并配置:

const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

...
//插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        }),
        new MiniCssExtractPlugin({
            filename: "static/css/main.css"
        }),
        new cssMinimizerWebpackPlugin()
    ],

注:html和js压缩在生产环境已经开启了,不需要额外配置

简单的基础配置就是这些了:

webpack.dev.js:

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: undefined,//开发环境无输出
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
    },
    //加载器
    module: {
        rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: [
                    "style-loader",  //将js中css通过style标签添加到html文件中生效
                    "css-loader"   //将css资源编译为common.js的模块到js中
                ],//use的执行顺序是从后到前
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                    'style-loader',
                    'css-loader',
                    'less-loader', //将less编译成css
                ],
            },
            {
                test: /\.s[ac]ss$/i,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'sass-loader',
                ],
            },
            {
                test: /\.styl$/,
                use: [
                    // 将 JS 字符串生成为 style 节点
                    'style-loader',
                    // 将 CSS 转化成 CommonJS 模块
                    'css-loader',
                    // 将 Sass 编译成 CSS
                    'stylus-loader',
                ], // 将 Stylus 文件编译为 CSS
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },
            {
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }

        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        })
    ],
    devServer: {
        host:"localhost", //启动服务器域名
        port: 3000, //启动服务端口号
        open:true //是否自动打开浏览器
    },
    //模式
    mode: "development"
}

webpack.prod.js 

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },
    //加载器
    module: {
        rules: [
            {
                test: /\.css$/i, //只会检测css文件
                use: getStyleLoader()
            },
            {
                test: /\.less$/,//只检测less文件
                //loader:只能使用一个loader
                use: getStyleLoader("less-loader")
            },
            {
                test: /\.s[ac]ss$/i,
                use:getStyleLoader("sass-loader")
            },
            {
                test: /\.styl$/,
                use:getStyleLoader("stylus-loader")
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                type: 'asset',
                parser: {
                    dataUrlCondition: {
                        //小于10kb转为base64,url的字符串形式
                        //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                        maxSize: 10 * 1024 // 10kb
                    }
                },
                generator: {
                    //输出图片名称
                    //hash值取前10位
                    filename: 'static/images/[hash:10][ext][query]'
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|avi)$/,
                type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                generator: {
                    //输出名称
                    //hash值取前10位
                    filename: 'static/media/[hash:10][ext][query]'
                }
            },
            {
                test: /\.js$/,
                exclude: /(node_modules)/,//排除node_modules不处理
                loader: 'babel-loader',
                //预设我们可以写在babel.config.js
                // options: {
                //     presets: ['@babel/preset-env']
                // }
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src")
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        }),
        new MiniCssExtractPlugin({
            filename: "static/css/main.css"
        }),
        new cssMinimizerWebpackPlugin()
    ],
    //模式
    mode: "production"
}

接下来高级配置

其实就是对webpack进行优化,从以下几个方面来优化:

1.提升开发体验

2.提升打包构建速度

3.减少代码体积

4.优化代码运行性能


1.提升开发体验

sourceMap:(源代码映射)是一个用来生成源代码和构建后代码意义映射的方案。

它会生成一个.map文件,里面包含源代码和构建后代码每一行和每一列的映射关系,当构建后的代码出错了,会通过.map文件从构建后代码的出错位置,找到对应的源代码出错位置,从而让浏览器提示源代码报错的位置,帮助我们更快找到错误根源。

在devtool中可查看相关配置,Devtool | webpack 中文文档

开发模式:cheap-module-source-map

优点:打包编译速度快,只包含行映射

缺点:没有列映射

配置webpack-dev.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: undefined,//开发环境无输出
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
    },
    ......
    //模式
    mode: "development",
    devtool:"cheap-module-source-map"
}

生产模式:source-map

优点:包含行列映射

缺点:打包编译速度很慢

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },
    ......
    //模式
    mode: "production",
    devtool:"source-map"
}

2.提升打包构建速度

① HotModuleReplacement,仅用于开发环境

开发时,如果修改了一个模块,webpack会默认将所有模块重新打包编译,所以我们需要只打包编译修改的模块那么会大大降低打包速度。

HotModuleReplacement(HMR/热模块更新替换):在程序运行中,模块进行增删改查,无需重新加载整个页面。

我们现在基于webpack5,所以在devServer可以直接配置:

webpack.dev.js

devServer: {
        host:"localhost", //启动服务器域名
        port: 3000, //启动服务端口号
        open:true, //是否自动打开浏览器
        hot:true //开启热模块更新
    },

样式改变,style-loader其实内部已经处理了样式的热模块更新,所以开启了hot,就可以执行热模块更新的功能,但是js文件不可以,如果需要配置的话如下,两个参数,一个是对应的模块路径,另一个是模块改变时的回调:

main.js

import "./tes.styl"
import count from './js/count'

count()
//每个要实现热模块更新的模块都要一一配置
if(module.hot){
    module.hot.accept("./js/count.js",(count)=>{
        console.log(count);
    })
}

每个要实现热模块更新的模块都要一一配置,如此说来就变得异常麻烦,但是在实际开发中,会使用其他loader来解决,比如vue-loader,react-hot-loader。

注:如果是webpack4那么热模块需要以插件的形式配置在plugins。

const webpack = require('webpack');
 
  module.exports = {

    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ],
  };


 ② Oneof

在webpack各个loader进行编译的时候,会依次匹配每一个loader,即使匹配到了对应的loader还是会继续走下去,非常影响效率,所以我们的目的就是配到 合适的loader以后,就不继续往下执行,用于开发和生产环境。

webpack.prod.js

rules: [
            {
                oneOf: [
                    {
                        test: /\.css$/i, //只会检测css文件
                        use: getStyleLoader()
                    },
                    {
                        test: /\.less$/,//只检测less文件
                        //loader:只能使用一个loader
                        use: getStyleLoader("less-loader")
                    },
                    {
                        test: /\.s[ac]ss$/i,
                        use: getStyleLoader("sass-loader")
                    },
                    {
                        test: /\.styl$/,
                        use: getStyleLoader("stylus-loader")
                    },
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                //小于10kb转为base64,url的字符串形式
                                //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                                maxSize: 10 * 1024 // 10kb
                            }
                        },
                        generator: {
                            //输出图片名称
                            //hash值取前10位
                            filename: 'static/images/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.(ttf|woff2?|mp3|avi)$/,
                        type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                        generator: {
                            //输出名称
                            //hash值取前10位
                            filename: 'static/media/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.js$/,
                        exclude: /(node_modules)/,//排除node_modules不处理
                        loader: 'babel-loader',
                        //预设我们可以写在babel.config.js
                        // options: {
                        //     presets: ['@babel/preset-env']
                        // }
                    }
                ]
            }
        ]

webpack.dev.js

rules: [
            {
                oneOf: [
                    {
                        test: /\.css$/i, //只会检测css文件
                        use: [
                            "style-loader",  //将js中css通过style标签添加到html文件中生效
                            "css-loader"   //将css资源编译为common.js的模块到js中
                        ],//use的执行顺序是从后到前
                    },
                    {
                        test: /\.less$/,//只检测less文件
                        //loader:只能使用一个loader
                        use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                            'style-loader',
                            'css-loader',
                            'less-loader', //将less编译成css
                        ],
                    },
                    {
                        test: /\.s[ac]ss$/i,
                        use: [
                            // 将 JS 字符串生成为 style 节点
                            'style-loader',
                            // 将 CSS 转化成 CommonJS 模块
                            'css-loader',
                            // 将 Sass 编译成 CSS
                            'sass-loader',
                        ],
                    },
                    {
                        test: /\.styl$/,
                        use: [
                            // 将 JS 字符串生成为 style 节点
                            'style-loader',
                            // 将 CSS 转化成 CommonJS 模块
                            'css-loader',
                            // 将 Sass 编译成 CSS
                            'stylus-loader',
                        ], // 将 Stylus 文件编译为 CSS
                    },
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                //小于10kb转为base64,url的字符串形式
                                //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                                maxSize: 10 * 1024 // 10kb
                            }
                        },
                        generator: {
                            //输出图片名称
                            //hash值取前10位
                            filename: 'static/images/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.(ttf|woff2?|mp3|avi)$/,
                        type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                        generator: {
                            //输出名称
                            //hash值取前10位
                            filename: 'static/media/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.js$/,
                        exclude: /(node_modules)/,//排除node_modules不处理
                        loader: 'babel-loader',
                        //预设我们可以写在babel.config.js
                        // options: {
                        //     presets: ['@babel/preset-env']
                        // }
                    }
                ]
            }
        ]

③ include / exclude 

开发时第三方的插件库都在node_modules,这些文件是不需要编译可以直接使用的,所以在对js进行处理时,要排除node_modules下的文件,开发和生产都需要做这个配置。

两个文件的babel-loader,eslint插件都做如下处理,但是要注意,exclude和include只能使用一个,否则会报错

{
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include:path.resolve(__dirname,"../src"),//只处理src下的文件
                        loader: 'babel-loader',
                        //预设我们可以写在babel.config.js
                        // options: {
                        //     presets: ['@babel/preset-env']
                        // }
                    }
new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude:"node_modules" //默认就是排除node_modules,不写也可以
        }),

④ cache缓存

每次打包js都要进过eslint和babel编译,速度较慢,我们可以缓存之前的eslint检查和babel编译结果,这样第二次打包就会更快。开发环境和生产环境都要配置。

{
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include:path.resolve(__dirname,"../src"),//只处理src下的文件
                        loader: 'babel-loader',
                        //预设我们可以写在babel.config.js
                        options: {
                            //presets: ['@babel/preset-env']
                            cacheDirectory:true,//开启babel缓存
                            cacheCompression:false//关闭缓存文件压缩
                        }
                    }
new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude:"node_modules", //默认就是排除node_modules,不写也可以
            cache:true,
            cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache')
        }),

注意:eslint-webpack-plugin插件是有对文件进行缓存的,所以eslint的缓存可以不写

fe581ae9e0ab4fe1ab0219deb6488c7d.png


 ⑤ Thread 多线程压缩js文件

当项目越来越大时,打包速度越来越慢,我们想要提升打包速度,其实还是要提升js的打包速度,对于js文件处理,我们主要使用,eslint,babel,terser,所以我们要提升运行速度,关于terser,在生产环境下webapck会自动激活这个插件并且压缩js代码,我们没有配置过,所以我们看不见。

我们可以开启多进程同时处理js文件,这样就比单进程打包快多了,但是要注意,请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右的消耗。

我们启动进程数就是cpu的核数,生产和开发模式均可配置,下载插件:

npm install --save-dev thread-loader

获取cpu核数,并且配置thread-loader,terser 以及 eslint

 webpack.dev.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const os = require('os')

const threads = os.cpus().length
module.exports = {
    ...
    //加载器
    module: {
        rules: [
                    ...

                    {
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include:path.resolve(__dirname,"../src"),
                        use: [
                            {
                                loader: "thread-loader",//开启多进程
                                options: {
                                    works: threads//进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                //预设我们可以写在babel.config.js
                                options: {
                                    //presets: ['@babel/preset-env']
                                    cacheDirectory: true,//开启babel缓存
                                    cacheCompression: false//关闭缓存文件压缩
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude:"node_modules", //默认就是排除node_modules,不写也可以
            cache:true,
         cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache'),
            threads,//开启多进程和数量
        }),
        ...
    ],
}

webpack.prod.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const os = require('os')

const threads = os.cpus().length
...
module.exports = {
    ...
    //加载器
    module: {
        rules: [
            {
                oneOf: [
                    ...
                    {
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include: path.resolve(__dirname, "../src"),//只处理src下的文件
                        use: [
                            {
                                loader: "thread-loader",//开启多进程
                                options: {
                                    works: threads//进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                //预设我们可以写在babel.config.js
                                options: {
                                    //presets: ['@babel/preset-env']
                                    cacheDirectory: true,//开启babel缓存
                                    cacheCompression: false//关闭缓存文件压缩
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", //默认就是排除node_modules,不写也可以
            cache: true,
            cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintCache'),
            threads,//开启多进程和数量
        }),
        ...
        // new cssMinimizerWebpackPlugin(),
        // new TerserWebpackPlugin({
        //     parallel:threads
        // })
    ],
    optimization: {
        minimizer: [
            //压缩的操作,
            new cssMinimizerWebpackPlugin(),//压缩css
            new TerserWebpackPlugin({ //压缩js
                parallel: threads
            })
        ]
    },
    //模式
    mode: "production",
    devtool: "source-map"
}

在生产环境中,我们修改了压缩插件的位置,统一放在了optimization下的minimizer中,这样统一配置管理,效果和在plugins是一样的。打包时时间会变长是正常现象,等项目越来越大,超过某个临界值,就能有显著的效果


3.减少代码体积

① tree-shaking 

开发时,我们定义了一些工具函数库,或者引用了第三方的组件库,如果没有特殊处理,打包时会引入整个库,但是我们实际上只用了一小部分,这样把整个库打包进来体积就太大了。

tree-shaking 通常用于移除js中没有使用的代码,依赖ES6 module(即用import引入的写法),webpack已经默认开启了这个功能,我们无需配置。

② Babel

Babel为每个编译的文件都插入了辅助代码,使代码体积过大。

Babel对一些公共方法使用了非常小的辅助代码,比如_extend。默认情况下会添加到每个需要它的文件中,我们可以将这些辅助代码作为一个独立模块,来避免重复引入

@babel/plugin-transform-runtime:禁用了babel自动对每个文件的runtime注入,而是引入@babel/plugin-transform-runtime,并使所有的辅助代码都从这里引用

适用于开发和生产模式

下载插件:

npm install -D @babel/plugin-transform-runtime

配置一下:

{
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include: path.resolve(__dirname, "../src"),//只处理src下的文件
                        use: [
                            {
                                loader: "thread-loader",//开启多进程
                                options: {
                                    works: threads//进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                //预设我们可以写在babel.config.js
                                options: {
                                    //presets: ['@babel/preset-env']
                                    cacheDirectory: true,//开启babel缓存
                                    cacheCompression: false,//关闭缓存文件压缩
                                    plugins: ['@babel/plugin-transform-runtime']//减少代码体积
                                }
                            }
                        ]
                    }
npm install image-minimizer-webpack-plugin --save-dev

所以当文件体积越大将越明显


③ Image-Minimizer

开发项目中如果引入了很多的图片,图片的体积会很大,将来请求速度会比较慢,我们可以对图片进行压缩,减少图片体积。

注:如果项目中图片都是在线链接,那就不用配置,只有本地的图片才需要压缩

下载插件image-minimizer-webpack-plugin:

npm install image-minimizer-webpack-plugin imagemin --save-dev

压缩方式:

无损压缩:

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev

有损压缩:

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev

我们这里使用无损压缩

在下载的过程中会比较慢,可以多尝试几次,只配置生产环境:

...
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
...
//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    ...
    optimization: {
        minimizer: [
            //压缩的操作,
            new cssMinimizerWebpackPlugin(),//压缩css
            new TerserWebpackPlugin({ //压缩js
                parallel: threads
            }),
            new ImageMinimizerPlugin({
                minimizer: {
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
                        plugins: [
                            ['gifsicle', { interlaced: true }],
                            ['jpegtran', { progressive: true }],
                            ['gifsicle', { interlaced: true }],
                            ['svgo', {
                                plugins: [
                                    'preset-default',
                                    'prefixIds', {
                                        name: "sortAttrs",
                                        params: {
                                            xmlnsOrder: "alphabeical"
                                        }
                                    }
                                ]
                            }],
                        ]
                    }
                }
            })
        ]
    },
    //模式
    mode: "production",
    devtool: "source-map"
}

然后进行打包比较一下图片的大小 

到现在为止两个文件的配置如下:

webpack.prod.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const os = require('os')

const threads = os.cpus().length
//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        clean: true,
    },
    //加载器
    module: {
        rules: [
            {
                oneOf: [
                    {
                        test: /\.css$/i, //只会检测css文件
                        use: getStyleLoader()
                    },
                    {
                        test: /\.less$/,//只检测less文件
                        //loader:只能使用一个loader
                        use: getStyleLoader("less-loader")
                    },
                    {
                        test: /\.s[ac]ss$/i,
                        use: getStyleLoader("sass-loader")
                    },
                    {
                        test: /\.styl$/,
                        use: getStyleLoader("stylus-loader")
                    },
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                //小于10kb转为base64,url的字符串形式
                                //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                                maxSize: 10 * 1024 // 10kb
                            }
                        },
                        generator: {
                            //输出图片名称
                            //hash值取前10位
                            filename: 'static/images/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.(ttf|woff2?|mp3|avi)$/,
                        type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                        generator: {
                            //输出名称
                            //hash值取前10位
                            filename: 'static/media/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include: path.resolve(__dirname, "../src"),//只处理src下的文件
                        use: [
                            {
                                loader: "thread-loader",//开启多进程
                                options: {
                                    works: threads//进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                //预设我们可以写在babel.config.js
                                options: {
                                    //presets: ['@babel/preset-env']
                                    cacheDirectory: true,//开启babel缓存
                                    cacheCompression: false,//关闭缓存文件压缩
                                    plugins: ['@babel/plugin-transform-runtime']//减少代码体积
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", //默认就是排除node_modules,不写也可以
            cache: true,
            cacheLocation: path.resolve(__dirname, '../node_modules/.cache/eslintCache'),
            threads,//开启多进程和数量
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        }),
        new MiniCssExtractPlugin({
            filename: "static/css/main.css"
        }),
        // new cssMinimizerWebpackPlugin(),
        // new TerserWebpackPlugin({
        //     parallel:threads
        // })
    ],
    optimization: {
        minimizer: [
            //压缩的操作,
            new cssMinimizerWebpackPlugin(),//压缩css
            new TerserWebpackPlugin({ //压缩js
                parallel: threads
            }),
            new ImageMinimizerPlugin({
                minimizer: {
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
                        plugins: [
                            ['gifsicle', { interlaced: true }],
                            ['jpegtran', { progressive: true }],
                            ['gifsicle', { interlaced: true }],
                            ['svgo', {
                                plugins: [
                                    'preset-default',
                                    'prefixIds', {
                                        name: "sortAttrs",
                                        params: {
                                            xmlnsOrder: "alphabeical"
                                        }
                                    }
                                ]
                            }],
                        ]
                    }
                }
            })
        ]
    },
    //模式
    mode: "production",
    devtool: "source-map"
}

webpack.dev.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const os = require('os')

const threads = os.cpus().length
module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: undefined,//开发环境无输出
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
    },
    //加载器
    module: {
        rules: [
            {
                oneOf: [
                    {
                        test: /\.css$/i, //只会检测css文件
                        use: [
                            "style-loader",  //将js中css通过style标签添加到html文件中生效
                            "css-loader"   //将css资源编译为common.js的模块到js中
                        ],//use的执行顺序是从后到前
                    },
                    {
                        test: /\.less$/,//只检测less文件
                        //loader:只能使用一个loader
                        use: [ //use可以使用多个loader,执行顺序也是从后往前执行
                            'style-loader',
                            'css-loader',
                            'less-loader', //将less编译成css
                        ],
                    },
                    {
                        test: /\.s[ac]ss$/i,
                        use: [
                            // 将 JS 字符串生成为 style 节点
                            'style-loader',
                            // 将 CSS 转化成 CommonJS 模块
                            'css-loader',
                            // 将 Sass 编译成 CSS
                            'sass-loader',
                        ],
                    },
                    {
                        test: /\.styl$/,
                        use: [
                            // 将 JS 字符串生成为 style 节点
                            'style-loader',
                            // 将 CSS 转化成 CommonJS 模块
                            'css-loader',
                            // 将 Sass 编译成 CSS
                            'stylus-loader',
                        ], // 将 Stylus 文件编译为 CSS
                    },
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                //小于10kb转为base64,url的字符串形式
                                //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                                maxSize: 10 * 1024 // 10kb
                            }
                        },
                        generator: {
                            //输出图片名称
                            //hash值取前10位
                            filename: 'static/images/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.(ttf|woff2?|mp3|avi)$/,
                        type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                        generator: {
                            //输出名称
                            //hash值取前10位
                            filename: 'static/media/[hash:10][ext][query]'
                        }
                    },
                    {
                        test: /\.js$/,
                        //exclude: /node_modules/,//排除node_modules不处理
                        include:path.resolve(__dirname,"../src"),
                        use: [
                            {
                                loader: "thread-loader",//开启多进程
                                options: {
                                    works: threads//进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                //预设我们可以写在babel.config.js
                                options: {
                                    //presets: ['@babel/preset-env']
                                    cacheDirectory: true,//开启babel缓存
                                    cacheCompression: false,//关闭缓存文件压缩
                                    plugins: ['@babel/plugin-transform-runtime']//减少代码体积
                                }
                            }
                        ]
                    }
                ]
            }
        ]
    },
    //插件
    plugins: [
        new ESLintPlugin({
            //检测哪些文件
            context: path.resolve(__dirname, "../src"),
            exclude:"node_modules", //默认就是排除node_modules,不写也可以
            cache:true,
            cacheLocation:path.resolve(__dirname,'../node_modules/.cache/eslintCache'),
            threads,//开启多进程和数量
        }),
        new HtmlWebpackPlugin({
            //模版:以public/index.html的文件创建的的新html文件
            //特点:结构和原来一致,自动引入打包输出的资源
            template: path.resolve(__dirname, "../public/index.html")
        })
    ],
    devServer: {
        host: "localhost", //启动服务器域名
        port: 3000, //启动服务端口号
        open: true, //是否自动打开浏览器
        hot: true //开启热模块更新
    },
    //模式
    mode: "development",
    devtool: "cheap-module-source-map"
}

4.优化代码运行性能

① code split

打包代码时会将所有的js文件打包到一个文件夹中,体积太大了。如果我们只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载,所以我们需要将打包生成的文件进行代码分割,生成多个js文件,这样加载的资源就越少,速度越快。

代码分割主要做了两件事:

分割文件:将打包生成的文件进行分割,生成多个js文件。

按需加载:需要哪个文件就加载那个文件。

代码分割实现有不同的方式,生产和开发均可配置,这里我们单独新项目配置,只写webpack.config.js一个文件

多入口:

下载依赖并配置webpack.config.js

 npm i webpack webpack-cli html-webpack-plugin -D
const path =require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
    entry:{
        //有多个入口文件,多入口
        app:"./src/app.js",
        main:"./src/main.js",
    },
    output:{
        path:path.resolve(__dirname,'dist'),
        filename:"[name].js" //webpack命名方式,[name]以文件名自己命名
    },
    plugins:[
        new HtmlWebpackPlugin({
            template : path.resolve(__dirname,'public/index.html')
        })
    ],
    mode:"production"
}

使用npx webpack查看打包后结果

81deac36835d49409efddba01218c8d8.png


在多文件入口时,如果两个文件同时引入了同一个方法,那么会打包两次,影响代码体积大小,应该是打包成单独一个文件,建立math.js文件,抛出方法,app.js和main.js分别调用,然后复用,配置splitChunks:

const path =require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
    entry:{
        //有多个入口文件,多入口
        app:"./src/app.js",
        main:"./src/main.js",
    },
    output:{
        path:path.resolve(__dirname,'dist'),
        filename:"[name].js" //webpack命名方式,[name]以文件名自己命名
    },
    plugins:[
        new HtmlWebpackPlugin({
            template : path.resolve(__dirname,'public/index.html')
        })
    ],
    mode:"production",
    optimization: {
        //代码分割配置
        splitChunks: {
          chunks: 'all',//对所有模块进行分割
          //以下都是默认值
          //minSize: 20000, //分割代码最小的大小为20kb
          //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,
          //  },
          //},
        },
      },
}

以上就是默认配置,在实际开发中就已经够用了,但是我们自己的项目中需要修改一下配置,因为我们的文件最小达不到20kb,所以开发过程中要按照实际情况来配置:

optimization: {
        //代码分割配置
        splitChunks: {
            chunks: 'all',//对所有模块进行分割
            //以下都是默认值
            //minSize: 20000, //分割代码最小的大小为20kb
            //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, //这里minChunks权重更大
               priority: -20,
               reuseExistingChunk: true,
             },
            },
        },
    },

再次打包npx  webpack,效果如下:

c445a1392a274c9a9ccf9fdb8a46345f.png

math.js生成了单独的模块,并且可以被引入复用


按需加载,动态导入

新建文件,count.js,main.js引入,我们观察资源加载情况:

//count.js
export default function count(x,y){
    return x-y
}


//main.js
import { sum } from './math'
import count from "./count"

console.log('hello main');
sum(1, 2, 3, 4)
document.getElementById("btn").onclick = function () {
    console.log(count(2, 1))
}




//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>hello Webpack</h1>
    <button id="btn">计算</button>
</body>
</html>

npx webpack 打包以后,在浏览器运行dist下的index.html

84bbefd77da04cc0896921e8874655f9.png

打包后的count.js为293.js 

028d9fdf43c3472a934a1c323c8183ff.png

一上来三个文件都已经加载了,并且点击按钮,文件不再加载,但我们需要在点击按钮时,再加载对应的资源,修改写法:

//main.js
import { sum } from './math'
// import count from "./count"

console.log('hello main');
sum(1, 2, 3, 4)
document.getElementById("btn").onclick = function () {
    
    //import 动态导入会将动态导入的文件代码分割(拆成单独模块),在需要时自动加载
    import("./count").then(res=>{
        console.log(res)
    })
}

重新打包:npx webpack ,页面加载后再次点击按钮,293.js加载出来了。

4debf0e3e7a9464faefe2e075a3419c5.png

 没有动态导入时,第一种情况没有出现是因为在main.js中引入了,都已经一起打包进了main.js,所以没有显示加载293.js文件,这就是按需加载


下面说说单入口应用,大多数时候都是我们可能是单页面应用,只有一个入口文件,那么我们需要这样配置:

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
    entry: {
        //单入口
        main: "./src/main.js",
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: "[name].js" //webpack命名方式,[name]以文件名自己命名
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'public/index.html')
        })
    ],
    mode: "production",
    optimization: {
        //代码分割配置
        splitChunks: {
            chunks: 'all',//对所有模块进行分割
            //以下都是默认值
            //minSize: 20000, //分割代码最小的大小为20kb
            //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的重写配置。splitChunks代码分割,仅能将node_modules提取成单独的一个chunk,但我们没有用到,所以这里打包后也没有体现,未来我们用上了以后,就会有单独打包的一个文件了。其次就是我们有些动态导入的语法,一定会被单独打包成一个文件的。

现在我们回到之前的项目

配置webpack.prod.js

optimization: {
        minimizer: [
            //压缩的操作,
            new cssMinimizerWebpackPlugin(),//压缩css
            new TerserWebpackPlugin({ //压缩js
                parallel: threads
            }),
            new ImageMinimizerPlugin({
                minimizer: {
                    implementation: ImageMinimizerPlugin.imageminGenerate,
                    options: {
                        plugins: [
                            ['gifsicle', { interlaced: true }],
                            ['jpegtran', { progressive: true }],
                            ['gifsicle', { interlaced: true }],
                            ['svgo', {
                                plugins: [
                                    'preset-default',
                                    'prefixIds', {
                                        name: "sortAttrs",
                                        params: {
                                            xmlnsOrder: "alphabeical"
                                        }
                                    }
                                ]
                            }],
                        ]
                    }
                }
            })
        ],
        //代码分割配置
        splitChunks: {
            chunks: 'all',//对所有模块进行分割,其余使用默认值
        },

同样我们也可以进行动态导入,形式如上述一样,可以自行尝试。

main.js

import "./tes.styl"
// import count from './js/count'

document.getElementById('btn').onclick = function(){
    //eslint不能识别动态导入需要,需要额外追加配置
    import("./js/count").then(res=>{
        res.default()
    })
}
if(module.hot){
    module.hot.accept("./js/count.js",(count)=>{
        console.log(count);
    })
}

.eslintrc.js,配置一下 plugins,解决动态导入的问题。

module.exports = {
    //继承eslint规则
    extends:["eslint:recommended"],
    env:{
        node:true,//启用node中的全局变量
        browser:true,//启用浏览器中全局变量
    },
    parserOptions:{
        ecmaVersion:6, //es6
        sourceType:"module",//es module
    },
    rules:{
        "no-var":2//不能使用var定义变量
    },
    plugins:['import'] //解决动态导入语法报错的问题
}

但是在编译过程中,依旧报错:

59e775a11b014c5080a508bd569827e7.png

那么下载 eslint-plugin-import 插件:

 npm i eslint-plugin-import -D

 但是编译还是报错:

e927e085c7664a7197540d5613439f47.png

 解饿下来解决这个问题,我们可以使用babel-eslint,注意:babel-eslint现在是@babel/eslint-parser,下载插件:

npm i @babel/eslint-parser -D 

再次配置 .eslintrc.js:

module.exports = {
    //继承eslint规则
    extends: ["eslint:recommended"],
    env: {
        node: true,//启用node中的全局变量
        browser: true,//启用浏览器中全局变量
        es6: true,
    },
    parser: "@babel/eslint-parser",
    parserOptions: {
        ecmaVersion: 6, //es6
        sourceType: "module",//es module
        "allowImportExportEverywhere": true
    },
    rules: {
        "no-var": 2,//不能使用var定义变量
    },
    plugins: ['import'] //解决动态导入语法报错的为
}

这样编译就不会报错了,在浏览器可以尝试点击按钮查看效果。


codeSplit命名

main.js动态导入的语法:

import "./tes.styl"
// import count from './js/count'

document.getElementById('btn').onclick = function(){
    //eslint不能识别动态导入需要,需要额外追加配置
    ///*webpackChunkName:"count"*/ webpack魔法名
    import(/*webpackChunkName:"count"*/"./js/count").then(res=>{
        res.default()
    })
}
if(module.hot){
    module.hot.accept("./js/count.js",(count)=>{
        console.log(count);
    })
}

除此之外还要配置一下webpack.prod.js

output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/main.js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        chunkFilename:"static/js/[name].js",//给打包输出的其他文件命名
        clean: true,
    },

这样就可以打包文件成自己的名字了。


统一命名配置webpack.prod.js和webpack.dev.js:

filename入口文件名修改,如果是多入口也适用,使用文件名。

chunkFilename添加一个后缀chunk,可以辨别chunk文件和主文件。所有额外的文件,都有chunk的后缀。

assetModuleFilename图片,字体等通过type:assets处理资源命名方式,与此同时,两个loader的generator可以注释不写了,写一个即可。

output: {
        ...
        filename: "static/js/[name].js",//是指入口文件打包输出的文件名,
        chunkFilename:"static/js/[name].chunk.js",//给打包输出的其他文件命名
        //图片,字体等通过type:assets处理资源命名方式
        assetModuleFilename:"static/media/[hash:10][ext][query]",
        ...
    },
{
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                //小于10kb转为base64,url的字符串形式
                                //优点:减少请求数量 缺点:体积会大一点,10kb以下无太大差别
                                maxSize: 10 * 1024 // 10kb
                            }
                        },
                        // generator: {
                        //     //输出图片名称
                        //     //hash值取前10位
                        //     filename: 'static/images/[hash:10][ext][query]'
                        // }
                    },
                    {
                        test: /\.(ttf|woff2?|mp3|avi)$/,
                        type: 'asset/source', //asset会转为base64这里不需要转,加上resource,只对文件原封不动的输出
                        // generator: {
                        //     //输出名称
                        //     //hash值取前10位
                        //     filename: 'static/media/[hash:10][ext][query]'
                        // }
                    },

 css单独生成文件也要修改输出名称,有可能会出现多入口打包,会生成多个css文件;同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename,这个只用配置生产环境,开发环境没有用到MiniCssExtractPlugin

plugins: [
        ...
        new MiniCssExtractPlugin({
            filename: "static/css/[name].css",// 有可能会出现多入口打包,会生成多个css文件
            chunkFilename:"static/css/[name].chunk.css"//同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename
        }),
    ],

Preload/Prefetch(兼容性不是那么好,个人不太推荐使用,可以去caniuse查询下兼容情况)

我们前面已经做了代码分割,同时通过静态导入按需加载,也就是懒加载,但是加载速度还不够好,比如,如果用户点击按钮才加载这个资源,如果资源体积很大,那么用户会有明显的卡顿效果,我们想在浏览器空闲时间,加载后续要使用的资源,Preload或Prefetch技术。

Preload:告诉浏览器立即加载资源。

Prefetch:告诉浏览器在空闲时才开始加载资源。

都只会加载资源,并不执行,都有缓存。

区别:Preload加载优先级高,Prefetch加载优先级低。Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。当前页面优先级高使用Preload加载,下一个页面需要使用的资源用Prefech加载。

Preload兼容情况:

c0c3be0de98447699d15367ed5277d54.png

覆盖率在95.81,还可以,有的版本需要开启特定的特性,但是ie完全不兼容,对于很多公司不能接受的

 Prefetch兼容情况:

774caf31408840be99a6d37c6ef9a7f5.png

只有78.76% ,兼容ie11,但是safari要开启浏览器特性等。所以Preload还是可以用用,prefetch兼容性太差了。

下载插件并配置:

npm install --save-dev @vue/preload-webpack-plugin

webpack.prod.js

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")

    plugins: [
        ...
        new PreloadWebpackPlugin({
            rel:"preload", //js采用preload方式下载
            as:"script" //作为script的优先级去做
        })
        // new cssMinimizerWebpackPlugin(),
        // new TerserWebpackPlugin({
        //     parallel:threads
        // })
    ],

重新打包npm run build ,查看dist下index.html文件,已经生成了preload的标签:

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>webpack</title>
    <script defer="defer" src="static/js/main.js"></script>
    <link href="static/js/count.chunk.js" rel="preload" as="script">
    <link href="static/css/main.css" rel="stylesheet">
</head>

<body>
    <div class="box"></div>
    <div class="box2"></div><button id="btn">计算</button>
</body>

</html>

如果是Prefetch:

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")

    plugins: [
        ...
        new PreloadWebpackPlugin({
            rel:"prefetch", //js采用prefetch方式下载
        })
        // new cssMinimizerWebpackPlugin(),
        // new TerserWebpackPlugin({
        //     parallel:threads
        // })
    ],

下面我们给各个输出文件的名称都加上contenthash值:

webpack.prod.js

const path = require("path")
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const cssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin")
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin")
const os = require('os')

const threads = os.cpus().length
//用来获取样式的loader函数
function getStyleLoader(pre) {
    return [
        MiniCssExtractPlugin.loader,  //提取css为单独文件
        "css-loader",  //将css资源编译为common.js的模块到js中
        {
            loader: 'postcss-loader',
            options: {
                postcssOptions: {
                    plugins: [
                        'postcss-preset-env'
                    ],
                },
            },
        },
        pre
    ].filter(Boolean)//use的执行顺序是从后到前
}

module.exports = {
    //入口
    entry: "./src/main.js",//取相对路径
    //输出
    output: {
        //__dirname nodejs的变量,代表当前文件夹的目录
        path: path.resolve(__dirname, "../dist"),//所有文件输出的路径,取绝对路径
        filename: "static/js/[name].[contenthash:10].js",//是指入口文件打包输出的文件名,
        //自动清空上次打包的内容,原理:在打包前,将path整个目录清空,在进行打包
        chunkFilename:"static/js/[name].chunk.[contenthash:10].js",//给打包输出的其他文件命名
        assetModuleFilename:"static/media/[contenthash:10][ext][query]",//图片,字体等通过type:assets处理资源命名方式
        clean: true,
    },
    //加载器
    ...
    //插件
    plugins: [
        ...
        new MiniCssExtractPlugin({
            filename: "static/css/[name].[contenthash:10].css",//有可能会出现多入口打包,会生成多个css文件
            chunkFilename:"static/css/[name].chunk.[contenthash:10].css"//同时如果动态导入的js文件里也有css文件也会生成chunk,所以也要chunkFilename
        }),
        ...
    ],
    ...
    //模式
    mode: "production",
    devtool: "source-map",
    
}

打包后我们会发现,每个文件都有hash值

8071f5c5caf84778ba820c6305c4b014.png但是我们修改了count.js文件,再次打包,main.js的hash值也发生了变化

60077c59650249fe9e9b6b4411ab01c9.png 因为main.js引入了count,count发生变化,main.js也发生了变化,很浪费资源,非常得不偿失,所以我们需要将main文件中保存的的math的hash值单独放在一个文件runtime运行时文件,也就是说main引入了文件count,count具体的文件地址在runtime文件中去找,所以,将来count发生变化,只有count和runtime会发生变化,main是不会发生变化的。

optimization: {
        ...
        runtimeChunk:{
            name:(entrypoint)=>`runtime~${entrypoint.name}.js`
        }
    },

生成这个文件后,打包即使其他文件发生变化,main的hash值也不会再变化,让体验更好。


Core-js

过去我们使用babel对js进行了兼容性处理,使用@babel/preset-env智能预设来处理兼容性问题,能将es6的语法编译转换,比如箭头函数,拓展运算符等,但是如果是async函数,promise对象,数组的一些方法(es6以后推出的),它没办法处理。

所以此时js代码任然存在加绒性问题,一旦遇到低版本浏览器就会直接报错,所以我们要将js兼容问题彻底解决。

core-js是专门用来做es6以及以上api的polyfill,也就是补丁,就是使用社区上提供的一些代码,让我们在不兼容某些新特性的浏览器上,使用该特性。

下载包:

npm i core-js

直接在main.js中引入模块就可以实现目的,但是还是按需加载比较好:

设置babel.config.js的智能预设,这个可以帮助我们按需加载,会自动分析是否存在es6+的语法,如果存在就会自动引入对应的包。

module.exports = {
    //智能预设:能够编译Es6语法
    presets:[
        ['@babel/preset-env',{
            useBuiltIns:'usage', //按需加载自动引入
            corejs:3 //corejs版本
        }]
    ]
}

 重新打包后会生成node_modules的包,因为引入了node_modules的模块。


PWA

开发app一旦断网就没有办法访问了,希望给项目提供离线体验,PWA(progressive web application-pwa)是一种提供类似于native app体验的web app技术,其最重要的是,离线时可以继续运行功能。

内部通过service workers实现的。我们使用workBox插件:

npm install workbox-webpack-plugin --save-dev

配置webpack.prod.js

const WorkboxPlugin = require('workbox-webpack-plugin');


...
plugins: [
        ...
        new WorkboxPlugin.GenerateSW({
            // 这些选项帮助快速启用 ServiceWorkers
            // 不允许遗留任何“旧的” ServiceWorkers
            clientsClaim: true,
            skipWaiting: true,
          }),
        // new cssMinimizerWebpackPlugin(),
        // new TerserWebpackPlugin({
        //     parallel:threads
        // })
    ],
...

注册生成Service Worker,配置main.js

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js').then(registration => {
      console.log('SW registered: ', registration);
    }).catch(registrationError => {
      console.log('SW registration failed: ', registrationError);
    });
  });
}

打包后生成service-worker.js文件,但我们运行打包后的index.html却注册失败:

6cda67c9c1444f18b8a057b58ad0f822.png

 

0ee74e9c66f745af9132b9693641c70f.png

 是因为寻找service-worker.js的路径不对,在根目录下是不存在的,我们打包在dist下的,那么我们安装一个部署静态资源服务器的库,serve

npm i serve -g

输入指令:serve dist,打开对应要部署的目录,会启动一个开发服务器,来部署我们dist下的所有资源,此时我们访问即可,然后访问service.js就不用在/dist下,直接再部署的项目的根目录下可以访问了。

eb22089c44cc4c8794cf83a891810fa2.png

7ebf19053ca34515834ff4876a69d292.png

 将网络断开:

26d6e6c0908e448f97d6ac030cfa27a3.png

页面依旧存在,在application可以查看service-workers以及cache storage缓存的内容 。目前兼容性很差,用的比较少。

webpack配置项目的步骤就是这些了。

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于使用Webpack搭建Vue2项目,你可以按照以下步骤进行操作: 1. 确保你已经安装了Node.js和npm。你可以在终端中运行以下命令来验证它们是否已安装: ``` node -v npm -v ``` 如果没有安装,可以前往 Node.js 官方网站(https://nodejs.org/)下载并安装最新的稳定版本。 2. 创建一个新目录,作为你的项目根目录,并进入该目录。 3. 初始化一个新的npm项目,运行以下命令并按照提示进行操作: ``` npm init ``` 4. 在项目根目录下安装所需的依赖,包括Vue、Vue Loader、Vue Router等。运行以下命令: ``` npm install vue@2 vue-loader@15 vue-router@3 webpack webpack-cli --save-dev ``` 5. 在项目根目录下创建一个`webpack.config.js`文件,并配置Webpack的基本设置。可以参考以下示例: ```javascript const path = require('path'); module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', }, module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, }, ], }, resolve: { alias: { vue$: 'vue/dist/vue.esm.js', }, extensions: ['*', '.js', '.vue', '.json'], }, }; ``` 6. 在项目根目录下创建一个`src`目录,并在其中创建一个`main.js`文件作为Vue应用的入口文件。 7. 在`src`目录下创建一个`components`目录,并在其中创建你的Vue组件。 8. 创建一个简单的Vue组件,并在`main.js`中引入该组件: ```javascript import Vue from 'vue'; import App from './components/App.vue'; new Vue({ render: (h) => h(App), }).$mount('#app'); ``` 9. 创建一个HTML文件作为你的应用的模板,例如`index.html`: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vue App</title> </head> <body> <div id="app"></div> <script src="dist/bundle.js"></script> </body> </html> ``` 10. 在`package.json`文件中添加以下脚本命令: ```json "scripts": { "build": "webpack" } ``` 这将允许你在终端中运行`npm run build`命令来构建你的Vue项目。 11. 运行以下命令来构建和打包你的Vue项目: ``` npm run build ``` 这将生成一个`dist`目录,并将打包后的Vue应用文件输出到其中。 12. 打开你的HTML文件,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Goat恶霸詹姆斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值