Node.js 与 webpack(四)

上一篇:Node.js与webpack(三)-CSDN博客

webpack原理 yu 优化

本章节主要介绍 Webpack 高级配置。

所谓高级配置其实就是进行 Webpack 优化,让我们代码在编译/运行时性能更好~

我们会从以下角度来进行优化:

  1. 提升开发体验
  2. 提升打包构建速度
  3. 减少代码体积
  4. 优化代码运行性能

待优化配置文件webpack.prod.js

javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinizerPlugin = require('css-minimizer-webpack-plugin');


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    entry: './src/main.js',
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        filename: "static/js/main.js",
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            // 处理css资源
            {
                // 正则匹配一个 以 .css结尾的文件
                test: /\.css$/,
                // loader在数组中的执行顺序是从右到左
                // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                // css-loader将css资源编译成commonJS的模板到js中
                // use: ['style-loader', 'css-loader'],
                // use: [MiniCssExtractPlugin.loader, 
                //     {
                //         loader: "postcss-loader",
                //         options: {
                //           postcssOptions: {
                //             plugins: [
                //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                //             ],
                //           },
                //         },
                //       },
                //     'css-loader'],
                use: getStyleLoaders(),
            },
            // 处理less资源
            {
                test: /\.less$/,
                // use: ['style-loader', "css-loader", "less-loader"],
                // use: [MiniCssExtractPlugin.loader, "css-loader", 
                // {
                //     loader: "postcss-loader",
                //     options: {
                //       postcssOptions: {
                //         plugins: [
                //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                //         ],
                //       },
                //     },
                //   },
                // "less-loader"],
                use: getStyleLoaders("less-loader"),
            },
            // 处理sass 和 scss资源
            {
                test: /\.s[ac]ss$/,
                // use: ["style-loader", "css-loader", "sass-loader"],
                // use: [MiniCssExtractPlugin.loader, "css-loader", 
                // {
                //     loader: "postcss-loader",
                //     options: {
                //       postcssOptions: {
                //         plugins: [
                //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                //         ],
                //       },
                //     },
                //   },
                // "sass-loader"],
                use: getStyleLoaders("sass-loader"),
            },
            // 处理stylus资源
            {
                test: /\.styl$/,
                // use: ["style-loader", "css-loader", "stylus-loader"],
                // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                // {
                //    loader: "postcss-loader",
                //    options: {
                //      postcssOptions: {
                //        plugins: [
                //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                //        ],
                //      },
                //    },
                //  },
                // 'stylus-loader'],
                use: getStyleLoaders("stylus-loader"),
            },
            // 处理媒体图片资源
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/,
                // asset提供dataUrl来处理小图片转base64编码
                type: "asset",
                parser: {
                    // 小于 10k的图片,进行转字符串处理
                    // 体积略大一点,换来的是减少一次资源请求
                    dataUrlCondition: {
                        maxSize: 10 * 1024, 
                    },
                    // 修改图片资源输出路径, dist下的
                    // hash: 8,命名只取生成的hash值的前8位
                    // ext: 后缀名
                    // query: 请求头查询字符串(可选)
                    generator: {
                        filename: 'static/img/[hash: 8][ext][query]',
                    },
                }
            },
            // 处理字体图标文件,以及音视频文件
            {
                test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                // 修改媒体资源输出路径,dist下的
                generator: {
                    filename: 'static/media/[hash: 8][ext][query]',
                },
            },
            // 处理JS文件,JS解析器:Babel
            {
                test: /\.js$/,
                exclude: /node_modules/, // 排除第三方js文件
                loader: 'babel-loader',
            },
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/main.css",
        }),
        // CSS压缩
        new CssMinizerPlugin(),
    ],
    // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
    mode: "production", // 选择开发模式
    //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
    devtool: "source-map", // 生产模式专用,行列都映射
}

1.提升开发体验

SourceMap增强调试(开发和生产模式)

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。

当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

问题引入

所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。

一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。

所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。
开发时,我们的在开发模式下,编译输出的效果是这样的(开发模式一般不输出,所以可以在浏览器中查看)


一旦发生了报错


这里的报错提示有可能不太正确

一旦项目代码多了起来,这里的报错提示等于没有

官方文档教程

Devtool | webpack 中文文档 (docschina.org)

乱七八糟有很多,不过老师说一般开发用这两种

  • cheap-module-source-map(开发模式)

cheap(便宜): 只映射行,不管列(缺点)

打包构建速度快一些(优点)

javascript 复制代码
module.exports = {
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • source-map(生产模式)

行列都映射(优点)

打包构建速度更慢(缺点)

javascript 复制代码
module.exports = {
  // 其他省略
  mode: "production",
  devtool: "source-map",
};
使用

正常打包


2.提升打包构建速度

HMR热模块(开发模式)

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

问题引入

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。(几分钟,甚至几小时)

所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

javascript 复制代码
module.exports = {
  // 其他省略
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
  },
};

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行。

  • 入口js代码

npm start

  • 修改一下设置了热更新的count.js

OneOf(开发和生产模式)
问题引入

假如资源目录中有一个.css文件,他要先找到【css-loader】,如果第一个loader不是【css-loader】,那么他会一直向下找,即使找到后,并不会立即处理css文件,而是接着匹配下面所有的loader为止。即使第一个就是要找的【css-loader】,他也会把下面所有loader匹配一遍。每个文件都要将所有loader匹配一遍才会处理文件,太慢。

我们要优化的效果是:匹配成功就不找了,直接处理文件。
将下列所有loader剪切到{ oneOf: [] }中

javascript 复制代码
{
    oneOf: [


           ]
}
javascript 复制代码
{
                    // 每个文件匹配loader成功则立即处理,不再匹配其他loader
                    oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        use: ['style-loader', 'css-loader'],
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        use: ['style-loader', "css-loader", "less-loader"],
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        use: ["style-loader", "css-loader", "sass-loader"],
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        use: ["style-loader", "css-loader", "stylus-loader"],
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        exclude: /node_modules/, // 排除第三方js文件
                        loader: 'babel-loader',
                    },
                ]

Include/exclude(开发和生产模式)
问题引入

之前学过这个配置,之前用的是忽略(exclude),现在加一个Include(只包含)
开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

比如我这个配置里的babel 和 eslint是用来处理js文件的

javascript 复制代码
include
包含,只处理 xxx 文件

exclude
排除,除了 xxx 文件以外其他文件都处理
Cache缓存(开发和生产模式)
问题引入

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

构建时,处理样式图片等都比较快,就是js最慢
优化效果:只对于修改状态的js代码进行Eslint检查和Babel编译,对于没修改的使用之前缓存的的检查结果和编译结果


Thread多进程打包(开发和生产模式)
问题引入

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。

我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

而对 js 文件处理主要就是 eslint 、babel、Terser(webpack自带,生产模式自动压缩) 三个工具,所以我们要提升它们的运行速度。

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

安装依赖
javascript 复制代码
npm i thread-loader -D
生产模式

给eslint babel terser等处理js资源的插件或loader配置多进程
生产模式webpack 会自动启动这个插件,我们只是要设置一些配置,所以要重写

  • webpack.prod.js
javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    entry: './src/main.js',
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        filename: "static/js/main.js",
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/main.css",
        }),
    ],
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],
      },
    // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
    mode: "production", // 选择开发模式
    //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
    devtool: "source-map", // 生产模式专用,行列都映射
}
开发模式(不需要压缩)
  • webpack.dev.js
javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');


// node.js核心模块,直接使用
const os = require('os');
// 检查服务器cpu核数
const threads = os.cpus().length;

// webpack默认支持的是CommonJS语法
module.exports = {
    // publicPath : '/static/',
    // 入口,相对路径,从项目根目录为起点, 这里还必须加 ./ 
    entry: './src/main.js',
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        path: undefined, // 开发模式不输出
        // 出口文件名,dist下的
        filename: "static/js/main.js",
        // 打包时自动清空上次打包资源
        // clean: true, // 开发模式没有输出,不清空
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                // 每个文件匹配loader成功则立即处理,不再匹配其他loader
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        use: ['style-loader', 'css-loader'],
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        use: ['style-loader', "css-loader", "less-loader"],
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        use: ["style-loader", "css-loader", "sass-loader"],
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        use: ["style-loader", "css-loader", "stylus-loader"],
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'),// 只处理../src下的js文件
                        use: [
                                {
                                    loader: 'thread-loader',// 开启多进程
                                    options: {
                                        works: threads, // 设置多进程数量
                                    }
                                },
                                {
                                    loader: 'babel-loader',
                                    options: {
                                    cacheDirectory: true, // 开启babel缓存
                                    cacheCompression: false, // 关闭缓存文件的压缩
                                    }
                                }
                            ]
                        },
                    ]
                }
            ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略node_modules下的js文件
            cache: true, // 开启缓存
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/eslintcache"
            ),
            threads, // 开启多进程和设置进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
    ],
    
    // 开发服务器配置, webpack官方提供的小型服务器
    devServer: {
        host: 'localhost', // 启动服务器域名
        port: "3030", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
        // historyApiFallback: true
        // historyApiFallback:{
        //     index:'/dist/static/index.html'
        //   },
    },
    // 模式
    mode: "development", // 选择开发模式
    //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
    devtool: "cheap-module-source-map", // 开发模式专用,只映射行,不管列
}

3.减少代码体积

Tree Shaking(默认)

开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。

如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。

这样将整个库都打包进来,体积就太大了。
Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。

注意:它依赖 ES Module
Webpack 已经默认开启了这个功能,无需其他配置。


Babel(开发和生产模式)

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

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以将这些辅助代码作为一个独立模块,来避免重复引入。避免重复定义,定义一次,只要引用就好
@babel/plugin-transform-runtime : 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

复制代码
npm i @babel/plugin-transform-runtime -D
Image Minimizer(开发和生产模式)(安装依赖失败)

开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。

我们可以对图片进行压缩,减少图片体积。

注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。(老师说在线图片无法进行压缩)

安装依赖(Github,要加速喔)
复制代码
npm i image-minimizer-webpack-plugin imagemin -D

关于无损压缩与有损压缩: 有损压缩 与 无损压缩-CSDN博客

  • 无损压缩(我这里使用这个)

    npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

  • 有损压缩

    npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D

安装依赖失败,就不压缩了,等以后选择其他的方法压缩图片

4.优化代码运行性能

Code Split(生产模式,开发也可以但是我不用)
多入口(代码分割)

代码分割(Code Split)主要做了两件事:

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
  2. 按需加载:需要哪个文件就加载哪个文件。
    打包代码时会将所有js 文件(入口文件)打包到一个js文件(出口文件)中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

文件目录(之前的main.js删掉)
javascript 复制代码
├── public
├── src
|   ├── app.js
|   └── count.js
├── package.json
└── webpack.config.js
javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    // entry: './src/main.js', // 单入口用字符串
    entry: {
        // 有多个入口文件,多入口
        app: "./src/js/app.js",
        main: "./src/js/count.js",
    },
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        // filename: "static/js/main.js", // 单入口

        filename: "static/js/[name].js", // webpack命名方式,[name]以入口文件名命名入口文件,使得名字相同
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/main.css",
        }),
    ],
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],
      },
    // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
    mode: "production", // 选择开发模式
    //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
    devtool: "source-map", // 生产模式专用,行列都映射
}

提取重复代码

如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。

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

重复引用的js代码写到一个文件中,复用

  • webpack.prod.js
javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    // entry: './src/main.js', // 单入口用字符串
    entry: {
        // 有多个入口文件,多入口
        app: "./src/js/app.js",
        main: "./src/js/main.js",
    },
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        // filename: "static/js/main.js", // 单入口

        filename: "static/js/[name].js", // webpack命名方式,[name]以入口文件名命名入口文件,使得名字相同
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/main.css",
        }),
    ],
    // 压缩和优化都在这里做
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],


        // 代码分割配置
        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,
              },
            },
          },
        },
        // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
        mode: "production", // 选择开发模式
        //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
        devtool: "source-map", // 生产模式专用,行列都映射
}
  • math.js
javascript 复制代码
export const sum = (...args) => {
    return args.reduce((p, c) => p + c, 0);
  };
  
  • app.js
javascript 复制代码
import { sum } from "./math";

console.log("hello app");
console.log(sum(1, 2, 3, 4, 5));
  • main.js
javascript 复制代码
import { sum } from "./math";

console.log("hello main");
console.log(sum(1, 2, 3, 4, 5));
  • 打包
javascript 复制代码
npm run build

按需加载,动态导入
问题引入

默认情况下,用户进入网站,所有的资源都会同时加载,如果用户只是想点个按钮,还要等其他资源加载好,会发生阻塞

我们想做到按需加载,点击按钮就加载按钮的资源,不需要的资源可以不加载,提高性能

使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)

  • app.js
javascript 复制代码
console.log("hello app");
  • main.js
javascript 复制代码
// import  { sum } from "./math.js";

console.log("hello main");

document.getElementById("btn").onclick = function () {
  // 动态导入 --> 实现按需加载
  // 即使只被引用了一次,也会代码分割
  import("./math.js").then((res) => {
    console.log("模块加载成功", res.sum(1, 2, 3));
  })
  .catch((err) => {
    console.log("模块加载失败", err);
  })
};
  • math.js
javascript 复制代码
export const sum = (...args) => {
    return args.reduce((p, c) => p + c, 0);
  };
  
  • 页面加个按钮
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    
</head>
<body>
    <span>哈哈哈</span>
    <button id="btn">计算</button>
</body>
</html>
  • 打包,动态导入的代码没有直接加载

只是加载了两个hello

  • 点击按钮才加载

报错

这个错误通常是由于ESLint配置不正确或不支持ES6模块的语法导致的。(我也不懂,明明一直用起来都支持ES module和CommonJS的,一到按需加载就报错)

后来得知:eslint不能识别动态导入需要,需要额外追加配置

解决办法

参考文章:ESLint配置中报错:Parsing error: 'import' and 'export' may only appear at the top leve-代码-码姐姐 (dude6.com)

  • 安装依赖
javascript 复制代码
npm i @babel/eslint-parser -D
  • .eslintrc.js

还有一个办法是这样的,我也没用过


单入口

开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。

javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    entry: './src/main.js', // 单入口用字符串
    // entry: {
    //     // 有多个入口文件,多入口
    //     app: "./src/js/app.js",
    //     main: "./src/js/main.js",
    // },
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        // filename: "static/js/main.js", // 单入口

        filename: "static/js/[name].js", // webpack命名方式,[name]以入口文件名命名入口文件,使得名字相同
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            generator: {
                                filename: 'static/img/[hash: 8][ext][query]',
                            },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        generator: {
                            filename: 'static/media/[hash: 8][ext][query]',
                        },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/main.css",
        }),
    ],
    // 压缩和优化都在这里做
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],


        // 代码分割配置
        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,
            //   },
            // },
          },
        },
        // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
        mode: "production", // 选择开发模式
        //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
        devtool: "source-map", // 生产模式专用,行列都映射
}
动态导入分割模块自定义命名(魔法命名)
问题引入

默认的命名莫名其妙,我们希望自定义命名

  • 要分割的部分自定义命名为math
  • webpack.prod.js

这里的[name]引用的是自定义的魔法命名,后缀多加的.chunk,也可以不加,主要是用来区分


统一命名配置

推荐使用这种命名规则([name]),单入口和多入口随意切换,还能自定义文件名

目前涉及到的命名有:

  • 图片、字体等资源
  • 样式资源也可能会多入口
Preload / Prefetch(生产模式)

webpack配置preload和prefetch预加载技术_webpack 预加载-CSDN博客

我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。

但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 PreloadPrefetch 技术。

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

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

它们共同点:

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

它们区别:

  • Preload加载优先级高,Prefetch加载优先级低。
  • Preload只能加载当前页面需要使用的资源,加载下一个页面时,上一个页面的资源会清空
  • Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源,不会清空有缓存。

总结:

  • 当前页面优先级高的资源用 Preload 加载。
  • 下一个页面需要使用的资源用 Prefetch 加载。

它们的问题:兼容性较差。

安装依赖
javascript 复制代码
npm i @vue/preload-webpack-plugin -D
javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");

// 预加载
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    entry: './src/main.js', // 单入口用字符串
    // entry: {
    //     // 有多个入口文件,多入口
    //     app: "./src/js/app.js",
    //     main: "./src/js/main.js",
    // },
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        // filename: "static/js/main.js", // 单入口

        filename: "static/js/[name].js", // webpack命名方式,[name]以入口文件名命名入口文件,使得名字相同
        chunkFilename: "static/js/[name].chunk.js", // 这里name引用的是自定义的魔法命名
        assetModuleFilename: "static/media/[name].[hash: 8][ext]", // 图片、字体等资源命名方式(注意用hash)
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            // generator: {
                            //     filename: 'static/img/[hash: 8][ext][query]',
                            // },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        // generator: {
                        //     filename: 'static/media/[hash: 8][ext][query]',
                        // },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/[name].css",
            chunkFilename: "static/css/[name].chunk.css",
        }),
        // 空闲时加载资源
        new PreloadWebpackPlugin({
            rel: "preload", // preload兼容性更好
            as: "script",
            // rel: 'prefetch' // prefetch兼容性更差
        }),
    ],
    // 压缩和优化都在这里做
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],


        // 代码分割配置
        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,
            //   },
            // },
          },
        },
        // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
        mode: "production", // 选择开发模式
        //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
        devtool: "source-map", // 生产模式专用,行列都映射
}

Network Cache(生产模式)

将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。

但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。

所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。
它们都会生成一个唯一的 hash 值。

  • fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

  • chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

  • contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。

runtime文件
  • 问题:

当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)。

但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效。明明我们只修改 math.js, 为什么 main.js 也会变身变化呢?

  • 原因:

    • 更新前:math.xxx.js, main.js 引用的 math.xxx.js
    • 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名发生了变化,间接导致 main.js 也发生了变化
  • 解决:

将 hash 值单独保管在一个 runtime 文件中。

我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。

main.js引入了math.js,当修改math.js的内容时,math.js的hash值文件名会改变,但同样也带着main.js改变,因为引入了

javascript 复制代码
// node.js核心模块path,专门处理路径
const path = require('path');
// node.js核心模块os
const os = require('os');
// 获取服务器的cpu核数
const threads = os.cpus().length;

// 处理HTML文件
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Eslint插件
const ESLintPlugin = require('eslint-webpack-plugin');

// 处理CSS从默认style标签 到 link标签
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

// CSS压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

// js压缩
const TerserPlugin = require("terser-webpack-plugin");

// 预加载
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");


// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    return [
      MiniCssExtractPlugin.loader,
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: {
            plugins: [
              "postcss-preset-env", // 能解决大多数样式兼容性问题
            ],
          },
        },
      },
      preProcessor,
    ].filter(Boolean);
  };

// webpack默认支持的是CommonJS语法
module.exports = {
    // 入口,相对路径
    // entry: './src/main.js', // 单入口用字符串
    entry: {
        // 有多个入口文件,多入口
        app: "./src/js/app.js",
        main: "./src/js/main.js",
    },
    // 输出
    output: {
        // __dirname获取当前项目根目录的绝对路径
        // dist 为输出目录,自动创建
        path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
        // 出口文件名,dist下的
        // filename: "static/js/main.js", // 单入口

        filename: "static/js/[name].[contenthash:8].js", // webpack命名方式,[name]以入口文件名命名入口文件,使得名字相同
        chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 这里name引用的是自定义的魔法命名
        assetModuleFilename: "static/media/[name].[hash: 8][ext]", // 图片、字体等资源命名方式(注意用hash)
        // 打包时自动清空上次打包资源
        clean: true,
    },
    // loader加载器
    module: {
        // 写正则作为匹配规则
        rules: [
            {
                oneOf: [
                    // 处理css资源
                    {
                        // 正则匹配一个 以 .css结尾的文件
                        test: /\.css$/,
                        // loader在数组中的执行顺序是从右到左
                        // style-loader将js中的css通过创建style标签的形式添加到html文件中生效
                        // css-loader将css资源编译成commonJS的模板到js中
                        // use: ['style-loader', 'css-loader'],
                        // use: [MiniCssExtractPlugin.loader, 
                        //     {
                        //         loader: "postcss-loader",
                        //         options: {
                        //           postcssOptions: {
                        //             plugins: [
                        //               "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //             ],
                        //           },
                        //         },
                        //       },
                        //     'css-loader'],
                        use: getStyleLoaders(),
                    },
                    // 处理less资源
                    {
                        test: /\.less$/,
                        // use: ['style-loader', "css-loader", "less-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "less-loader"],
                        use: getStyleLoaders("less-loader"),
                    },
                    // 处理sass 和 scss资源
                    {
                        test: /\.s[ac]ss$/,
                        // use: ["style-loader", "css-loader", "sass-loader"],
                        // use: [MiniCssExtractPlugin.loader, "css-loader", 
                        // {
                        //     loader: "postcss-loader",
                        //     options: {
                        //       postcssOptions: {
                        //         plugins: [
                        //           "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //         ],
                        //       },
                        //     },
                        //   },
                        // "sass-loader"],
                        use: getStyleLoaders("sass-loader"),
                    },
                    // 处理stylus资源
                    {
                        test: /\.styl$/,
                        // use: ["style-loader", "css-loader", "stylus-loader"],
                        // use: [MiniCssExtractPlugin.loader, 'css-loader', 
                        // {
                        //    loader: "postcss-loader",
                        //    options: {
                        //      postcssOptions: {
                        //        plugins: [
                        //          "postcss-preset-env", // 能解决大多数样式兼容性问题
                        //        ],
                        //      },
                        //    },
                        //  },
                        // 'stylus-loader'],
                        use: getStyleLoaders("stylus-loader"),
                    },
                    // 处理媒体图片资源
                    {
                        test: /\.(png|jpe?g|gif|webp|svg)$/,
                        // asset提供dataUrl来处理小图片转base64编码
                        type: "asset",
                        parser: {
                            // 小于 10k的图片,进行转字符串处理
                            // 体积略大一点,换来的是减少一次资源请求
                            dataUrlCondition: {
                                maxSize: 10 * 1024, 
                            },
                            // 修改图片资源输出路径, dist下的
                            // hash: 8,命名只取生成的hash值的前8位
                            // ext: 后缀名
                            // query: 请求头查询字符串(可选)
                            // generator: {
                            //     filename: 'static/img/[hash: 8][ext][query]',
                            // },
                        }
                    },
                    // 处理字体图标文件,以及音视频文件
                    {
                        test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
                        type: "asset/resource",// 为什么是这个类型我也不知道,官网上写的
                        // 修改媒体资源输出路径,dist下的
                        // generator: {
                        //     filename: 'static/media/[hash: 8][ext][query]',
                        // },
                    },
                    // 处理JS文件,JS解析器:Babel
                    {
                        test: /\.js$/,
                        // exclude: /node_modules/, // 排除第三方js文件
                        include: path.resolve(__dirname, '../src'), // 只处理 ../src下的js文件
                        use: [
                            {
                                loader: 'thread-loader',// 开启多进程
                                options: {
                                    works: threads, // 设置多进程数量
                                }
                            },
                            {
                                loader: 'babel-loader',
                                options: {
                                    cacheDirectory: true, // 开启babel编译缓存
                                    cacheCompression: false, // 缓存文件不要压缩
                                    plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                                }
                            }
                        ]
                    },
                ]
            }
        ]
    },
    // 插件
    plugins: [
        // plugin的配置
        new ESLintPlugin({
            // context指定需要检查的文件根目录,一般只检查src下的文件
            context: path.resolve(__dirname, "../src"),
            exclude: "node_modules", // 忽略    node_modules下的js文件
            cache: true, // 开启缓存
            // 缓存目录
            cacheLocation: path.resolve(
                __dirname,
                "../node_modules/.cache/.eslintcache"
            ),
            threads, // 开启多进程和设置多进程数量
        }),
        new HtmlWebpackPlugin({
            // 以public/index.html为模板创建文件
            // 新html文件,内容结构与源文件一致,自动引入打包生成的js等资源
            template: path.resolve(__dirname, '../public/index.html'),
        }),
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "static/css/[name].[contenthash:8].css",
            chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
        }),
        // 空闲时加载资源
        new PreloadWebpackPlugin({
            rel: "preload", // preload兼容性更好
            as: "script",
            // rel: 'prefetch' // prefetch兼容性更差
        }),
    ],
    // 压缩和优化都在这里做
    optimization: {
        minimize: true,// 开启最小化压缩
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          new CssMinimizerPlugin(),
          // js压缩,当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程和设置进程数量
          })
        ],


        // 代码分割配置
        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,
              },
            },
          },
          // 提取runtime文件
            runtimeChunk: {
            name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
        },
        },
        // 开发服务器配置, webpack官方提供的小型服务器
    // devServer: {
    //     host: 'localhost', // 启动服务器域名
    //     port: "3030", // 启动服务器端口号
    //     open: true, // 是否自动打开浏览器
    // },
    // 模式
        mode: "production", // 选择开发模式
        //webpack官方提供:映射源代码与打包输出代码,增强调式,生成.map文件
        devtool: "source-map", // 生产模式专用,行列都映射
}
Core-js(生产模式)
问题引入

过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决
core-js 是专门用来做 ES6 以及以上 API 的 polyfill

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

  • 此时 Eslint 会对 Promise 报错
javascript 复制代码
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";

const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);
// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
  console.log("hello promise");
});
安装依赖
javascript 复制代码
npm i @babel/eslint-parser -D
javascript 复制代码
npm i core-js
三种引入方法
  • 手动全部引入
  • 手动按需引入
  • 自动按需引入(推荐)
  • babel.config.js
javascript 复制代码
module.exports = {
  // 智能预设:能够编译ES6语法
  presets: [
    [
      "@babel/preset-env",
      // 按需加载core-js的polyfill
      { useBuiltIns: "usage", corejs: { version: "3", proposals: true } },
    ],
  ],
};

PWA(生产模式)
问题引入

开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。

我们希望给项目提供离线体验。
渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。

其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。

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

安装依赖
javascript 复制代码
npm i workbox-webpack-plugin -D
  • js文件中插入代码
javascript 复制代码
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);
      });
  });
}
BUG

此时如果直接通过 VSCode 访问打包后页面,在浏览器控制台会发现 SW registration failed

因为我们打开的访问路径是:http://127.0.0.1:5500/dist/index.html。此时页面会去请求 service-worker.js 文件,请求路径是:http://127.0.0.1:5500/service-worker.js,这样找不到会 404。

实际 service-worker.js 文件路径是:http://127.0.0.1:5500/dist/service-worker.js

安装依赖
javascript 复制代码
npm i serve -g

serve 也是用来启动开发服务器来部署代码查看效果的。

运行指令
javascript 复制代码
serve dist

此时通过 serve 启动的服务器我们 service-worker 就能注册成功了。

总结

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

  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。

本节完

下一节:

相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment11 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax