wo-pkg

```bash npm install -D webpack webpack-cli npx webpack ```

Usage no npm install needed!

<script type="module">
  import woPkg from 'https://cdn.skypack.dev/wo-pkg';
</script>

README

webpack 学习

Install

npm install -D webpack webpack-cli
npx webpack

核心概念

Entry

打包入口

// 单入口:字符串
entry: "./src/index.js",

// 多入口:对象
entry: {
    app: "./src/app.js",
    search: "./src/search.js",
},

webpack.config.js

module.exports = {
  // 单入口:字符串
  entry: "./src/index.js",
};

module.exports = {
  // 多入口:对象
  entry: {
    app: "./src/app.js",
    search: "./src/search.js",
  },
};

Output

打包出口

// 单入口对应的出口
output: {
  filename: "bundle.js",
  path: path.resolve(__dirname, "dist/"),
},

// 多入口对应的出口
output: {
  filename: "[name].js",
  path: path.resolve(__dirname, "dist/"),
},

webpack.config.js

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  // 单入口对应的出口
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist/"),
  },
};

module.exports = {
  entry: "./src/index.js",
  // 多入口对应的出口
  // [name] 是个文件名占位符,对应 output 对象的 key
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist/"),
  },
};

Loader

webpack 原生支持 js 和 json 两种文件类型的打包,其他类型文件需要通过 Loader 来支持。

本身是一个函数。以 raw-loader 为例:

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.txt$/i,
        use: "raw-loader",
      },
    ],
  },
};

file.js

import txt from "./file.txt";

Plugins

用户 bundle 文件的优化,资源管理和环境变量注入。

作用于整个构建过程。以 HtmlWebpackPlugin 为例:

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

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist/"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/template.html",
    }),
  ],
};

Mode

构建环境。production(默认值), development, none。

webpack.config.js

module.exports = {
  mode: "development",
};

转译 ES6+ 和 JSX

ES6+

将 ES6+ 代码转译成 ES5 代码,需要使用 babel-loader

npm install -D babel-loader @babel/core @babel/preset-env webpack
npm install --save-dev @babel/plugin-proposal-class-properties

webpack.config.js

module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"],
          plugins: ["@babel/plugin-proposal-class-properties"],
        },
      },
    },
  ];
}

或者是 webpack.config.js + babel.config.json

//  webpack.config.js
module: {
  rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: "babel-loader",
    },
  ];
}
// babel.config.json
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

React

为了能正确处理 React 代码,需要用到 @babel/preset-react

// babel.config.json
{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}
// index.js
import React from "react";
import ReactDOM from "react-dom";

const App = () => {
  return <div>Hello World</div>;
};

ReactDOM.render(<App />, document.body);

CSS & Sass

CSS

在 webpack 中解析 CSS,至少需要两个 loader: css-loader, style-loader

css-loader 将 css 文件转成 commonjs 对象,style-loader 通过 <style> 标签,将样式插入到 <head> 中去

npm install --save-dev css-loader
npm install --save-dev style-loader
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
// file.js
import css from "file.css";

Sass

为了支持使用 Sass,需要安装 sass-loader.

npm install sass-loader sass webpack --save-dev
module.exports = {
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
};

加载图片和字体文件

使用 file-loader

加载图片

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: "file-loader",
          },
        ],
      },
    ],
  },
};
// 使用方式一:通过 url() 引入图片
h1 {
  background-image: url(./images/webpack-site-logo.svg);
  background-repeat: no-repeat;
  background-size: contain;
  font-size: 0;
  height: 40px;
}
// 使用方式二:作为 img src 资源引入
import img from "./file.png";

const App = () => {
  return <img src={logo} />;
};

加载字体文件

module.exports = {
  module: {
    rules: [
      {
        test: /\.(woff2?|eot|ttf|otf)$/i,
        use: [
          {
            loader: "file-loader",
          },
        ],
      },
    ],
  },
};
@font-face {
  font-family: "FiraCode";
  src: url("./fonts/FiraCode-Regular.woff2") format("woff2");
}

body {
  font-family: FiraCode, "Courier New", Courier, monospace;
}

还可以使用 url-loader,它是 file-loader 的封装,不过支持较小资源直接 base64 嵌入

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: "url-loader",
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  },
};

文件监听

两种方式:

  1. webpack 命令行参数指定:webpack --watch
  2. 在配置文件中通过设置 watch: true 实现

使用 webpack-dev-server

webpack-dev-server

webpack-dev-server: 实现热更新:不刷新浏览器

npm install --save-dev webpack-dev-server
devServer: {
  contentBase: './dist',
  // 启用热更新能力
  // See: https://webpack.js.org/guides/hot-module-replacement/
  hot: true,
},
"scripts": {
  "dev": "webpack serve --open"
},

入口文件需要加点代码,支持热更新,否则是全更新的

// ...

if (module.hot) {
  module.hot.accept();
}

热更新原理

hot-module-replacement 为 webpack 打包出来的 bundle 文件中注入 HMR runtime。一旦文件有修改,HMR server 将修改的 js module 信息发送给(通过 websocket) HMR runtime, 由 HMR runtime 负责布局页面的更新操作。

hot-module-replacement 包给 webpack-dev-server 提供了热更新能力。

整个热更新分成两个过程:

  • 启动阶段:将源代码编译成 bundle 文件 - 通过 bundle serve 提供 bundle 文件
  • 更新阶段:将被修改的 js module 编译,通过 HMR server 将修改通知 HMR runtime,由后者来负责页面局部刷新

文件指纹

打包文件的后缀名。好处:新文件立即生效;旧文件使用缓存,依旧可用。

根据作用范围,文件指纹还分:

  • Hash: 项目级别指纹 - 只要项目中有文件修改,此值就发生改变
  • ChunkHash: entry 级别的指纹 - 不同的 entry 会生成不同的值
  • ContentHash: 文件级别的指纹 - 文件内容不变,此值就不发生改变

outout 使用 [chunkhash]

 output: {
    filename: "index_bundle.[chunkhash:6].js",
    path: path.resolve(__dirname, "dist/"),
  },

单独 css 文件使用 [contenthash],需要安装 mini-css-extract-plugin

npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  output: {
    // See: https://stackoverflow.com/questions/64294706/webpack5-automatic-publicpath-is-not-supported-in-this-browser
    publicPath: "",
    // ...
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: `[name].[contenthash:6].css`,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};

图片字体文件 [hash]

  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "images/[name].[hash:6].[ext]",
            },
          },
        ],
      },
      {
        test: /\.(woff2?|eot|ttf|otf)$/i,
        use: [
          {
            loader: "file-loader",
            options: {
              // 包含存储目录
              name: "fonts/[name].[hash:6].[ext]",
            },
          },
        ],
      },
    ],
  },

代码压缩

压缩 CSS

https://github.com/NMFR/optimize-css-assets-webpack-plugin

npm install --save-dev optimize-css-assets-webpack-plugin
npm i -D cssnano
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");

  plugins: [
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require("cssnano"),
    }),
  ],

清理构建产物

https://github.com/johnagan/clean-webpack-plugin

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

const webpackConfig = {
  plugins: [
    /**
     * All files inside webpack's output.path directory will be removed once, but the
     * directory itself will not be. If using webpack 4+'s default configuration,
     * everything under <PROJECT_DIR>/dist/ will be removed.
     * Use cleanOnceBeforeBuildPatterns to override this behavior.
     *
     * During rebuilds, all webpack assets that are not used anymore
     * will be removed automatically.
     *
     * See `Options and Defaults` for information
     */
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: [
        "**/*",
        "!static-files*",
        "!directoryToExclude/**",
      ],
    }),
  ],
};

module.exports = webpackConfig;

CSS3 自动补充前缀

要用到 postcss-loader

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

postcss-preset-env 中包含 autoprefixer

{
  test: /\.s[ac]ss/i,
  use: [
    MiniCssExtractPlugin.loader,
    "css-loader",
    "sass-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-preset-env",
              {
                browsers: 'last 2 versions'
              },
            ],
          ],
        },
      },
    },
  ],
},

静态资源引入

inde.html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <%= require('raw-loader!./meta.html').default %>
    <title>demo page</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

提取公共资源

使用 HtmlWebpackExternalsPlugin

new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: "react",
          entry: "https://unpkg.com/react@17/umd/react.development.js",
          global: "React",
        },
        {
          module: "react-dom",
          entry: "https://unpkg.com/react-dom@17/umd/react-dom.development.js",
          global: "ReactDOM",
        },
      ],
    }),

使用

// See: https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /(react|react-dom)/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },