webpack配置从入门到放弃(3)

146 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

缓存

webpack打包后生成 dist 目录,然后启动服务器就可以访问这个目录里的资源了。浏览器会自动缓存 dist 目录的资源,当我们修改代码后,重新去获取资源,如果有 100 个资源,修改了 1 个,就没有必要去重新获取所有资源,所以通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。

webpack.config.js

output: {
    path: path.join(__dirname, "./dist"), 
    //contenthash:每个文件都会根据内容生成hash,所以各个文件之间肯定不一样
    filename: "[name].[contenthash].bundle.js",
    clean: true, 
    assetModuleFilename: "images/[name]-[contenthash][ext]",
},

文件保存后,再重新打包时,如果文件内容有修改,hash就肯定不一样,如果没修改,hash就还是之前那个。

像一些 nod_modules 的第三方库,不用频繁被修改,就可以提取出来长期缓存,减少服务器请求资源。

webpack.config.js

runtimeChunk: "single", //值 "single" 会创建一个在所有生成 chunk 之间共享的运行时文件
moduleIds: "deterministic", //文件代码修改才会重新打包
splitChunks: {
    //缓存组
    cacheGroups: {
        vendors: {
            test: /[\/]node_modules[\/]/,
            name: "vendors",
            chunks: "all",
        }
    },
},

执行 npm run build 看看效果吧

生产环境

development(开发环境) :强大的 source map 和一个有着 live reloading(实时重新加载) 或 hot module replacement(热模块替换) 能力的 localhost server。

production(生产环境) 压缩 bundle、更轻量的 source map、资源优化等,通过这些优化方式改善加载时间。

通常建议为每个环境编写彼此独立的 webpack 配置

  1. 安装 webpack-merge,终端执行 npm i webpack-merge,创建三个配置文件 webpack.common.jswebpack.dev.jswebpack.pros.js

  2. 在 webpack.common.js 文件中

    const path = require("path");
    //引入html打包插件
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    //引入独立css文件的插件
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    //压缩css
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
    ​
    const toml = require('toml');
    const yaml = require('yamljs');
    const json5 = require('json5');
    ​
    module.exports = {
        //代码分离方式一
        // entry: {
        //   index: {
        //     import: "./src/index.js",
        //     dependOn: "shared",
        //   },
        //   welcome: {
        //     import: "./src/welcome.js",
        //     dependOn: "shared",
        //   },
        //   shared: ["jquery", "./src/CalcArea.js"],
        // },
        entry: {
          index: "./src/index.js",
          welcome: "./src/welcome.js",
        },
        output: {
          path: path.join(__dirname, "./dist"), //出口路径必须是绝对路径
          // name是入口文件的key
          // hash随机生成的字符串
          filename: "[name].[contenthash].bundle.js",
          clean: true, // 每次打包都会删除上次打包的文件
          //配置图片文件的打包路径和文件名
          assetModuleFilename: "images/[name]-[contenthash][ext]",
        },
        // watch: true, //监听代码修改,保存后自动重新打包
        //创建本地服务器
        devServer: {
          static: {
            directory: path.join(__dirname, "./dist"),
          },
          //使用gzip压缩
          compress: true,
          port: 8080,
          // open配置服务器打开时,自动打开的页面
          // open: true,
          // host: "0.0.0.0", //默认值
          hot: true, //热更新 默认开启
          // liveReload: false, //热加载 默认开启
          proxy: {
            "/abc": {
              target: "http://localhost:3000",
              pathRewrite: { "/abc": "" },
            },
          },
        },
        module: {
          rules: [
            //编译css文件
            {
              test: /.css$/,
              use: [MiniCssExtractPlugin.loader, "css-loader"],
            },
            {
              test: /.txt$/,
              use: ["raw-loader"],
            },
            {
              test: /.(png|jpg|jpeg|gif|svg|webp)$/,
              type: "asset",
              parser: {
                dataUrlCondition: {
                  maxSize: 10 * 1024,
                },
              },
              // 比assetModuleFilename优先高,就近原则
              // generator: {
              //   filename: "imgs/[name][ext]",
              // },
            },
            {
              test: /.(woff|woff2|eot|ttf|otf)$/i,
              type: "asset/resource",
              generator: {
                filename: "fonts/[name][ext]",
              },
            },
            {
              test: /.(csv|tsv)$/i,
              use: ["csv-loader"],
            },
            {
              test: /.xml$/i,
              use: ["xml-loader"],
            },
            {
              test: /.toml$/i,
              type: "json",
              parser: {
                parse: toml.parse,
              },
            },
            {
              test: /.(yaml|yml)$/i,
              type: "json",
              parser: {
                parse: yaml.parse,
              },
            },
            {
              test: /.json5$/i,
              type: "json",
              parser: {
                parse: json5.parse,
              },
            },
          ],
        },
        resolve: {
          alias: {
            "@": path.resolve(__dirname, './src')
          },
          extensions: ['.js', '.json'],
        },
        optimization: {
          usedExports: true,
          // runtimeChunk: "single", //值 "single" 会创建一个在所有生成 chunk 之间共享的运行时文件
          minimizer: [
            // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`)
            `...`,
            new CssMinimizerPlugin(),
          ],
          //第二种代码分离
          // splitChunks: {
          //   chunks: "all",
          //   minSize: 10, //设置能够打包的最小体积,默认是20000Bytes
          //   minChunks: 2, //打包的模块的最小引用次数,默认是1
          //   name: "vendors" //指定output中打包文件的name
          // },
    ​
          moduleIds: "deterministic", //文件代码修改才会重新打包
          splitChunks: {
            //缓存组
            cacheGroups: {
              vendors: {
                test: /[\/]node_modules[\/]/,
                name: "vendors",
                chunks: "all",
              },
              // commons: {
              //   test: /src/,
              //   name: 'commons',
              //   chunks: 'all',
              //   minSize: 10,
              // },
            },
          },
        },
        plugins: [
          //创建一个实例,打包html文件
          new HtmlWebpackPlugin({
            //以路径中的文件为模板
            template: "./src/index.html",
            filename: "index.html",
            inject: "body",
          }),
          new MiniCssExtractPlugin({
            filename: "index.css",
          }),
        ],
    ​
        //CDN方式代码分离
        // externalsType: "script",
        // externals: {
        //   jquery: ["https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js", "$"],
        // },
      };
    
  3. 在 webpack.dev.js 文件中

    const { merge } = require('webpack-merge');
    const common = require('./webpack.common.js');
    const path = require("path");
    ​
    module.exports = merge(common, {
      mode: 'development',
      //源码映射
      devtool: "inline-source-map",
      //创建本地服务器
      devServer: {
        static: {
          directory: path.join(__dirname, "./dist"),
        },
        //使用gzip压缩
        compress: true,
        port: 8080,
        // open配置服务器打开时,自动打开的页面
        // open: true,
        // host: "0.0.0.0", //默认值
        // hot: true, //热更新 默认开启
        liveReload: true, //热加载 默认开启
        proxy: {
          "/abc": {
            target: "http://localhost:3000",
            pathRewrite: { "/abc": "" },
          },
        },
      },
      module: {
        rules:[
          //使用babel将ES6+代码转换成ES5的代码
          {
            test: /.m?js$/,
            exclude: /node_modules/,
            use: {
              loader: "babel-loader",
            },
          },
        ]
      }
    });
    
  4. 在 webpack.prod.js 文件中

    const { merge } = require('webpack-merge');
     const common = require('./webpack.common.js');module.exports = merge(common, {
       mode: 'production',
     });
    
  5. 在 package.json 文件中

    "serve": "webpack serve --config ./webpack.prod.js",
    "start": "webpack --config ./webpack.prod.js",
    "build": "webpack --config ./webpack.dev.js",
    

上面的配置几乎包含了全部基础配置

开发环境执行 npm run build

生产环境执行 npm run serve

Tree Shaking

在打包时有些没有使用到的模块也会被打包进dist目录,造成了资源的浪费,所以需要移除 JavaScript 上下文中的未引用代码(dead-code)。

配置也非常简单,在webpack.common.js中

optimization:{
  usedExports: true,
}

sideEffects

"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。也就是尽管导出并在另一个文件导入了这个模块,如果没有使用,还是会被删除。

在 package.json 文件中

"sideEffects": false

注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:

"sideEffects": ["*.css",……你不想被删除的模块]

Shimming 预置依赖

shimming预置全局变量

在webpack.common.js文件中

const webpack = require("webpack");
​
//在plugins数组中
new webpack.ProvidePlugin({
    $: 'jquery',
    join: ["lodash","join"]
}),

现在可以不用导入jQuery模块,在任何地方使用 $ 来使用jQuery了,还可以直接将模块的方法变成全局方法,在任何地方都可以直接使用lodash的join方法了

细粒度 Shimming

假设index.js中有如下代码:

// 假设我们处于 `window` 上下文
this.alert('hello world')

此时用服务器打开,hello world并没有弹出,是因为此时的this是指向 module.exports 这个对象,并没有指向window。

在webpack.common.js文件中

module: {
    rules: [
      {
        test: require.resolve('./src/index.js'),
        use: 'imports-loader?wrapper=window',
      },
    ],
  },

通过使用 imports-loader 覆盖 this 指向,此时在指向命令打开服务器就可以看到弹出 hello world 了。

小结

webpack完,官网webpack