webpack5-入门(二)

96 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

1.6 loader

1.6.1 什么是loader

  • webpack 默认支持处理 JS 与 JSON 文件,其他类型都处理不了。
  • Loader 就是将 Webpack 不认识的内容转化为认识的内容

1.6.2 loader-CSS

  • css-loader
    • 安装:npm i css-loader -D
    • 文档:https://github.com/webpack-contrib/css-loader
  • style-loader
    • 安装:npm i style-loader -D
    • 文档:https://github.com/webpack-contrib/style-loader
module: {
  rules: [
    //...
    {
      test: /.css$/, //匹配所有的 css 文件
      use: ["style-loader", "css-loader"],
    },
  ],
},

⚠️ Loader 的执行顺序是固定从后往前,即按 css-loader --> style-loader 的顺序执行 

  • less-loader``sass-loader
    • npm install less less-loader --save-dev
    • npm install sass-loader sass --save-dev
module: {
  rules: [
    // ...
    {
      test: /.(css|sass)$/,
      use: ["style-loader", "css-loader", "sass-loader"],
    },
  ],
},

1.6.3 抽离、压缩CSS

抽离CSS

  • 安装插件: npm i mini-css-extract-plugin -D(webpack5)
  • 修改 webpack.config.js配置
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
 	// ...
  plugins: [
    // ...
    new MiniCssExtractPlugin()
  ],
  module: {
    rules: [
      // ...
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};
  • 配置输出的css路径和文件名
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: "styles/[contenthash].css",
    }),
  ],

压缩CSS

  • 安装插件: npm i css-minimizer-webpack-plugin -D
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  mode: "production", // 模式

  // ...
  optimization: {
    minimizer: [new CssMinimizerWebpackPlugin()], // 压缩CSS
  },
};

1.6.4 CSS加载images

tag {
  background-image: url('../asset/logo.png')
}

1.6.5 加载fonts字体

module: {
  rules: [
    // ...
    {
      test: /.(woff|woff2|eot|ttf|otf)$/,
      type: "asset/resoure",
    },
  ],
},
@font-face {
  font-family: 'iconfont';
  src: url('../asset/iconfont.ttf') format('truetype');
}

.icon {
  font-family: 'iconfont';
  font-size: 30px;
}

1.6.6 加载其他数据

CSV``TSV``XML

  • 安装loader : npm i csv-loader xml-loader -D
module: {
  rules: [
    // ...
    {
      test: /.(csv|tsv)/,
      use: "csv-loader",
    },
    {
      test: /.xml$/,
      use: "xml-loader",
    },
  ],
},
import Data from './asset/data.xml'
import Notes from './asset/data.csv'

console.log(Data)
console.log(Notes)

1.6.7 parser

npm i toml yaml json5 -D

const toml = require("toml");
const yaml = require("yaml");
const json5 = Frequire("json5");
//... 
module: {
  rules: [
    // ...
    {
      test: /.toml$/,
      type: "json",
      parser: {
        parse: toml.parse,
      },
    },
    {
      test: /.yaml$/,
      type: "json",
      parser: {
        parse: yaml.parse,
      },
    },
    {
      test: /.json5$/,
      type: "json",
      parser: {
        parse: json5.parse,
      },
    }
  ],
},
import toml from './asset/data.toml'
import yaml from './asset/data.yaml'
import json5 from './asset/data.json5'

console.log(toml)
console.log(yaml)
console.log(json5)

1.7 babel-loader

1.7.1 为什么要babel-loader

ES6--->ES5

1.7.2 使用 babel-loader

  • 安装 npm i babel-loader @babel/core @babel/preset-env -D
    • babel-loader webpack里应用babel解析ES6的桥梁
    • @babel/core babel 的核心代码
    • @babel/preset-env babel预设,babel插件的集合

配置

rules: [
  // ...
  {
    test: /.js$/,
    exclude: "/node_modules/", //排除
    use: {
      loader: "babel-loader",
      options: {
        presets: ["@babel/preset-env"],
      },
    },
  },
],
  • 再打包,js代码就转换成了ES5

1.7.3 regeneratorRuntime

1.8

1.8.1 代码分离

  • 入口起点:使用entry配置手动的分离代码
  • 防止重复:使用Entry dependencies或者SplitChunksplugin去重和代码分离
  • 动态导入:通过模块的内联函数调用来分离代码

1.8.2 入口起点

module.exports = {
  mode: "development", // 模式
  entry: {
    index: "./src/index.js",
    other: "./src/other.js",
  }, // 打包入口地址
  output: {
    filename: "[name].bundle.js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
  },
  //...
};
  • 将 entry改为一个对象,output 的filename改为动态
  • 这种方式,会将entry中的js文件引入的其他公共文件重复打包

1.8.3 防止重复

Entry dependencies

module.exports = {
  mode: "development", // 模式
  entry: {
    index: {
      import: "./src/index.js",
      dependOn: "shared",
    },
    other: {
      import: "./src/other.js",
      dependOn: "shared",
    },
    shared: "lodash",
  },
  output: {
    filename: "[name].bundle.js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
  },
  //...
};
  • lodash库抽离出来

SplitChunksplugin

module.exports = {
  mode: "development", // 模式
  entry: {
    index: "./src/index.js",
    other: "./src/other.js",
  }, // 打包入口地址
  output: {
    filename: "[name].bundle.js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
  },
  //...
  optimization: {
  	//...
    splitChunks: {
      chunks: "all",
    },
  },
};

1.8.4 动态导入

function getComponent() {
  return import("lodash").then(({ default: _ }) => {
    const element = document.createElement("div");
    element.innerHTML = _.join(["1", "2", "3"], "-");
    return element;
  });
}

getComponent().then((element) => {
  document.body.appendChild(element);
});
module.exports = {
  mode: "development", // 模式
  entry: {
    index: "./src/index.js",
    other: "./src/other.js",
  }, // 打包入口地址
  output: {
    filename: "[name].bundle.js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
  },
  //...
  optimization: {
  	//...
    splitChunks: {
      chunks: "all",
    },
  },
};

1.8.5 懒加载

按需加载

const button = document.createElement("button");
button.textContent = "add";
button.addEventListener("click", () => {
  import(/* webpackChunkName: 'math' */ "./math.js").then(({ add }) => {
    console.log(add(1, 2));
  });
});
document.body.appendChild(button);
  • 只有点击按钮的时候才会加载math.js的资源
  • /* webpackChunkName: 'math' */ 自定义打包文件的名字

1.8.6 预加载

const button = document.createElement("button");
button.textContent = "add";
button.addEventListener("click", () => {
  import(/* webpackChunkName: 'math', webpackPrefetch: true */ "./math.js").then(({ add }) => {
    console.log(add(1, 2));
  });
});
document.body.appendChild(button);
  • webpackPrefetch:在其他资源加载完成后,网络空闲时再加载
  • webpackPreload: 类似webpackPrefetch

preload和prefetch在用法上相差不大,效果上的差别如下(引自官方文档):

preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。

1.9 缓存

1.9.1 输出文件的文件名

  output: {
    filename: "[name].[contenthash].js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
    assetModuleFilename: "images/[contenthash][ext]", // 配置名字和扩展名
  },
  • 发布新版本时,如果文件名没有改,浏览器会认为文件没有改动,继续从缓存里读取资源。
  • [name].[contenthash]生成动态文件名

1.9.2 第三方库

  • 第三方库一般不会更新,尽量使用浏览器缓存的资源。
  optimization: {
  	//...
    splitChunks: {
      // chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
  • 第三方库都打包进vendors.*******.js文件里

1.9.3 将js文件放到一个文件夹中

  • js文件统一放到script文件夹中
  output: {
    filename: "script/[name].[contenthash].js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
    assetModuleFilename: "images/[contenthash][ext]", // 配置名字和扩展名
  },

1.10 环境拆分

1.10.1 公共路径

publicPath

  output: {
    filename: "script/[name].[contenthash].js", // 输出文件名
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
    assetModuleFilename: "images/[contenthash][ext]", // 配置名字和扩展名
    publicPath: 'http://localhost:8080'
  },
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  <link href="http://localhost:8080/styles/4ab8b30553ab33e8fb28.css" rel="stylesheet"></head>
  <body><script defer src="http://localhost:8080/script/vendors.d53cf84e4b1532ebba33.js"></script><script defer src="http://localhost:8080/script/index.71743e14d7f58f5748dc.js"></script><script defer src="http://localhost:8080/script/other.53cde6b793d0fb0883d0.js"></script></body>
</html>

1.10.2 环境变量

module.exports = (env) => {
  return {
    // console.log(env.goal)
    mode: env.production ? 'production' : 'development', // 模式
  	// ...
  };
};
// 生产环境
npx webpack --env production

// 开发环境
npx webpack --env development

// 携带参数
npx webpack --env production --env goal=local

js代码压缩terser-webpack-plugin

  • 生产环境压缩,开发环境不压缩
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

module.exports = (env) => {
  return {
    mode: env.production ? 'production' : 'development', // 模式Ï
    //...
    
    optimization: {
      minimizer: [
        new CssMinimizerWebpackPlugin(), // 压缩CSS
        new TerserPlugin(),
      ],
    },
  };
};

1.10.3 拆分配置文件

拆分配置文件,一个做生产环境配置,一个做开发环境配置

根目录新建两个js 文件

  • webpack.config.dev.js
    • npx webpack -c webpack.config.dev.js
    • npx webpack serve -c webpack.config.dev.js
  • webpack.config.prod.js
    • npx webpack -c webpack.config.prod.js
    • npx webpack serve -c webpack.config.prod.js

1.10.4 npm脚本

  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "b": "npx webpack",
    "bp": "npx webpack --env production",
    "bd": "npx webpack --env development",
    "bdev": "npx webpack -c webpack.config.dev.js",
    "bprod": "npx webpack -c webpack.config.prod.js",
    "s": "npx webpack-dev-server --open"
  },

1.10.5 提取公共配置

根目录新建webpack.config.common.js

  • 里面写生产环境和开发环境通用的配置代码
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const toml = require("toml");
const yaml = require("yaml");
const json5 = require("json5");

module.exports = {
  entry: {
    index: "./src/index.js",
    other: "./src/other.js",
  }, // 打包入口地址
  output: {
    path: path.join(__dirname, "dist"), // 输出文件目录
    clean: true,
    assetModuleFilename: "images/[contenthash][ext]", // 配置名字和扩展名
  },

  performance: {
    hints: "warning",
    //入口起点的最大体积
    maxEntrypointSize: 50000000,
    //生成文件的最大体积
    maxAssetSize: 30000000,
    //只给出 js 文件的性能提示
    assetFilter: function (assetFilename) {
      return assetFilename.endsWith(".js");
    },
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "管理输出", // 输出html的title, 指定template时不生效
      template: "./index.html", // 基于哪个文件生成html
      filename: "app.html", // 生成的html文件名
      inject: "body", // script标签生成到哪里
    }),

    new MiniCssExtractPlugin({
      filename: "styles/[contenthash].css", // 配置输出的css路径和文件名
    }),
  ],
  module: {
    rules: [
      {
        test: /.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/inline",
        parser: {
          dataUrlCondition: {
            maxSize: 1024 * 10 * 10,
          },
        },
      },
      {
        test: /.(css|sass)$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
      {
        test: /.(woff|woff2|eot|ttf|otf)$/,
        type: "asset/resoure",
      },
      {
        test: /.(csv|tsv)/,
        use: "csv-loader",
      },
      {
        test: /.xml$/,
        use: "xml-loader",
      },

      {
        test: /.toml$/,
        type: "json",
        parser: {
          parse: toml.parse,
        },
      },
      {
        test: /.yaml$/,
        type: "json",
        parser: {
          parse: yaml.parse,
        },
      },
      {
        test: /.json5$/,
        type: "json",
        parser: {
          parse: json5.parse,
        },
      },
      {
        test: /.js$/,
        exclude: "/node_modules/", //排除
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
          },
        },
      },
    ],
  },

  optimization: {
    splitChunks: {
      // chunks: "all",
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
};
module.exports = {
  mode: "development", // 模式
  output: {
    filename: "script/[name].[contenthash].js", // 输出文件名
  },
  devtool: "inline-source-map",
  devServer: {
    static: "./dist",
  },
};
const CssMinimizerWebpackPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  mode: "production", // 模式

  output: {
    filename: "script/[name].[contenthash].js", // 输出文件名
    publicPath: "http://localhost:8080",
  },

  optimization: {
    minimizer: [
      new CssMinimizerWebpackPlugin(), // 压缩CSS
      new TerserPlugin(),
    ],
  },

  performance: { 
    hints: false, //不显示警告
  },
};

1.10.6 合并配置文件

webpack-merge:npm i webpack-merge -D

const { merge } = require("webpack-merge");

const commonConfig = require("./webpack.config.common");
const productionConfig = require("./webpack.config.prod");
const developmentConfig = require("./webpack.config.dev");

module.exports = (env) => {
  switch (true) {
    case env.development:
      return merge(commonConfig, developmentConfig);

    case env.production:
      return merge(commonConfig, productionConfig);

    default:
      return new Error("No matching configuration was found");
  }
};

运行

  • npx webpack -c webpack.config.js --env development
  • npx webpack serve -c webpack.config.js --env development
  • npx webpack -c webpack.config.js --env production
  • npx webpack serve -c webpack.config.js --env production