webpack从零开始(四)

148 阅读6分钟

打包环境的区分

打包工具的配置在生产环境中,不同于开发模式,所以我们需要对配置文件进行区分。

既然要开始区分,我们就单独将分开的配置文件单独命名放在一个文件夹中,方便管理

image.png

webpack.prod.js(到本章为止,最新的配置)

生产环境

const Version = new Date().getTime();
// nodejs中的包,用来解析当前路径
const path = require('path')
// 引入动态插入JS
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入动态插入CSS
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 压缩Css代码
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 合并同类配置
function getStyleLoader(exLoader) {
    return [
       // 将Css提取成单独文件,插入html的link
       MiniCssExtractPlugin.loader,
       "css-loader",
       {
           loader: "postcss-loader",
           options: {
             postcssOptions: {
               plugins: [
                 "postcss-preset-env", // 能解决大多数样式兼容性问题
               ],
             },
           },
       },
       exLoader
    ].filter(Boolean)
}
// commonJS语法
module.exports = {
    // 环境模式:开发模式development,生产模式production
    mode: 'production',
    // 入口
    // 拼接要打包的文件路径,参数1起名约定俗成,参数二定位文件夹位置, 参数三打包的入口
    // __dirname,是一个成员,用来动态获取当前文件模块所属的绝对路径
    // __filename,可以动态获取当前文件夹的绝对路径(包含文件名)
    entry: path.join(__dirname, '../src', 'index.js'),
    // 输出
    // 入口文件打包输出的文件名
    output: {
        path: path.join(__dirname, '../dist'),
        filename: 'static/js/bundle'+Version+'.js',
        // 原理:在打包前,将path路径下的文件清空
        clean: true
    },
    // 插件
    plugins: [
        new HtmlWebpackPlugin({
            // 同entry注释
            template: path.join(__dirname, '../src', 'index.html'),
            // 打包后的文件名
            filename: 'bundle.html'
        }),
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.join(__dirname, "../src"),
        }),
        new MiniCssExtractPlugin({
            filename: 'static/css/bundle.css'
        }),
        // css压缩
        new CssMinimizerPlugin(),
    ],
    // 模块
    module: {
        rules: [
            { 
                test: /\.css$/i, 
                use: getStyleLoader()
            },
            // 配置样式文件
            {
                // 正则匹配文件
                test: /\.s[ac]ss$/i,
                use: getStyleLoader('sass-loader') // 将sass/scss编译成css
            },
            {
                test: /\.less$/i,
                use: getStyleLoader('less-loader') // 将less编译成css
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/i,
                // 设置资源类型用于匹配模块。它防止了 defaultRules 和它们的默认导入行为发生。
                type: 'asset',
                // 解析选项对象,匹配条件
                parser: {
                  dataUrlCondition: {
                    // 小于10kb的图片转base64
                    maxSize: 10 * 1024 // 10kb
                  }
                },
                generator: {
                    // 定义图片资源输出路径
                    // hash生成文件唯一名,10代表取名字前十位。ext是文件扩展名。query代表图片?携带的参数。
                    filename: "static/images/[hash:10][ext][query]"
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|mp4|avi|rmvb)$/i,
                // 这样可以避免文件转化为base64的格式
                type: 'asset/resource',
                generator: {
                    // 定义字体图标资源输出路径
                    filename: "static/asset/[hash:10][ext][query]"
                }
            },
            {
                test: /\.js$/,
                exclude: /node_modules/, // 排除node_modules中的js文件(这些不处理)
                loader: "babel-loader",
                options: {
                    // @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
                    // @babel/preset-react:一个用来编译 React jsx 语法的预设
                    // @babel/preset-typescript:一个用来编译 TypeScript 语法的预设
                    presets: ["@babel/preset-env"],
                }
            },
        ]
    },
    devServer: {
        static: [
            {
              directory: path.join(__dirname, '../dist'),
            }
          ],
        // 是否自动打开浏览器
        // open: true,
        // 启动服务器域名
        // host: 'localhost',
        // 指定运行的端口
        // vue项目默认运行在8080
        port: 3000,
        // 存放静态资源的文件夹
        // static: path.join(__dirname, 'dist')
    }
}

webpack.dev.js(到本章为止,最新的配置)

开发环境中,无需输出打包文件

const Version = new Date().getTime();
// nodejs中的包,用来解析当前路径
const path = require('path')
// 引入动态插入JS
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入动态插入CSS
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 合并同类配置
function getStyleLoader(exLoader) {
    return [
       // 将Css提取成单独文件,插入html的link
       MiniCssExtractPlugin.loader,
       "css-loader",
       {
           loader: "postcss-loader",
           options: {
             postcssOptions: {
               plugins: [
                 "postcss-preset-env", // 能解决大多数样式兼容性问题
               ],
             },
           },
       },
       exLoader
    ].filter(Boolean)
}
// commonJS语法
module.exports = {
    // 环境模式:开发模式development,生产模式production
    mode: 'development',
    // 入口
    // 拼接要打包的文件路径,参数1起名约定俗成,参数二定位文件夹位置, 参数三打包的入口
    // __dirname,是一个成员,用来动态获取当前文件模块所属的绝对路径
    // __filename,可以动态获取当前文件夹的绝对路径(包含文件名)
    entry: path.join(__dirname, '../src', 'index.js'),
    // 输出
    // 入口文件打包输出的文件名
    // 开发模式不用输出
    output: {
        path: undefined,
        filename: 'static/js/bundle'+Version+'.js',
    },
    // 插件
    plugins: [
        new HtmlWebpackPlugin({
            // 同entry注释
            template: path.join(__dirname, '../src', 'index.html'),
            // 打包后的文件名
            filename: 'bundle.html'
        }),
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.join(__dirname, "../src"),
        }),
        new MiniCssExtractPlugin(),
    ],
    // 模块
    module: {
        rules: [
            { 
                test: /\.css$/i, 
                use: getStyleLoader()
            },
            // 配置样式文件
            {
                // 正则匹配文件
                test: /\.s[ac]ss$/i,
                use: getStyleLoader('sass-loader') // 将sass/scss编译成css
            },
            {
                test: /\.less$/i,
                use: getStyleLoader('less-loader') // 将less编译成css
            },
            {
                test: /\.(png|jpe?g|gif|webp|svg)$/i,
                // 设置资源类型用于匹配模块。它防止了 defaultRules 和它们的默认导入行为发生。
                type: 'asset',
                // 解析选项对象,匹配条件
                parser: {
                  dataUrlCondition: {
                    // 小于10kb的图片转base64
                    maxSize: 10 * 1024 // 10kb
                  }
                },
                generator: {
                    // 定义图片资源输出路径
                    // hash生成文件唯一名,10代表取名字前十位。ext是文件扩展名。query代表图片?携带的参数。
                    filename: "static/images/[hash:10][ext][query]"
                }
            },
            {
                test: /\.(ttf|woff2?|mp3|mp4|avi|rmvb)$/i,
                // 这样可以避免文件转化为base64的格式
                type: 'asset/resource',
                generator: {
                    // 定义字体图标资源输出路径
                    filename: "static/asset/[hash:10][ext][query]"
                }
            },
            {
                test: /\.js$/,
                exclude: /node_modules/, // 排除node_modules中的js文件(这些不处理)
                loader: "babel-loader",
                options: {
                    // @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
                    // @babel/preset-react:一个用来编译 React jsx 语法的预设
                    // @babel/preset-typescript:一个用来编译 TypeScript 语法的预设
                    presets: ["@babel/preset-env"],
                }
            },
        ]
    },
    devServer: {
        static: [
            {
              directory: path.join(__dirname, '../dist'),
            }
          ],
        // 是否自动打开浏览器
        // open: true,
        // 启动服务器域名
        // host: 'localhost',
        // 指定运行的端口
        // vue项目默认运行在8080
        port: 3000,
        // 存放静态资源的文件夹
        // static: path.join(__dirname, 'dist')
    }
}

优化CSS加载速度(基于webpack5新特性)

如果Css文件是引入JS中被打包,这样在页面被加载时,执行顺序为html-->js-->css,用户看到的就会是,先是无样式的文本信息,然后会突然变换文本样式,弹出各种图片媒体信息。这样的闪屏,非常影响体验。

解决思路为:将Css单独提取为文件,通过html中的link标签加载

由此我们引入插件mini-css-extract-plugin

image.png

引入插件

// 引入动态插入CSS
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

加载插件

plugins: [
    new MiniCssExtractPlugin(),
]

使用插件(将所有style-loader替换)

image.png

CSS兼容处理

前文中提到了JS的浏览器兼容问题,CSS同样也是需要解决的。

我们所需要的依赖有:

postcss-loader(用来处理css的load)

postcss(postcss-loader依赖于它)

postcss-preset-env(兼容,智能预设)

要将配置项插入css-loader之后

{
   loader: "postcss-loader",
   options: {
      postcssOptions: {
         plugins: [
             "postcss-preset-env", // 能解决大多数样式兼容性问题
             ],
         },
     },
},

我们需要在 package.json 文件中添加 browserslist 来控制样式的兼容性做到什么程度。

// 兼容IE8以上
"browserslist": ["ie >= 8"]

想要知道更多的 browserslist 配置,查看browserslist 文档

合并同类项

在loader中已经有太多的重复配置项了,为方便统一管理,使用方法集中配置

// 合并同类配置
function getStyleLoader(exLoader) {
    return [
       // 将Css提取成单独文件,插入html的link
       MiniCssExtractPlugin.loader,
       "css-loader",
       {
           loader: "postcss-loader",
           options: {
             postcssOptions: {
               plugins: [
                 "postcss-preset-env", // 能解决大多数样式兼容性问题
               ],
             },
           },
       },
       exLoader
    ]
}

image.png

CSS压缩优化处理

通过格式化等方式减少Css文件的体积,达到压缩的目的

引入webpack5新插件css-minimizer-webpack-plugin 较前一代optimize-css-assets-webpack-plugin,在 source maps 和 assets 中使用查询字符串会更加准确,支持缓存和并发模式下运行。

// 压缩Css代码
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
plugins: [
    // css压缩
    new CssMinimizerPlugin(),
]