Bootstrap

webpack基础配置

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具,在许多项目中都有应用,但是往往平台搭建以后很少去修改配置,熟悉基础配置可以更快的修复配置问题。

一、默认入口和默认出口

默认目录结构


webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js // 可以先不配置,最后一步再加
|- /dist
  |- index.html
|- /src
  |- index.js

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>起步</title>
  </head>
  <body>
  // 默认出口就是dist/main.js
  // 这里是直接指定,包括使用插件默认也是引入dist/main.js
   <script src="main.js"></script>
  </body>
</html>

index.js

// 引入的包可以被识别的打包
import _ from "lodash";

function component() {
  const element = document.createElement("div");

  // lodash 现在使用 import 引入
  element.innerHTML = _.join(["Hello", "webpack"], " ");

  return element;
}

document.body.appendChild(component());

检查npm

// 检查npm registry
npm config get registry
// 改成淘宝源的新地址,下载包更快,不容易出错
npm config set registry https://registry.npmmirror.com
// npm 开发依赖;只会在开发环境需要,不会打包到dist中
// 也就是package.json中的devDependencies
// --save-dev 简写 -D
npm i package --save-dev // 示意,不用安装
// npm 生产依赖;会打包到dist中,影响包的大小和打包速度
// 也就是package.json中的dependencies
// --save 简写 -S
npm i package --save // 示意,不用安装

安装包

 npm init -y // y表示yes,跳过一步
 npm i webpack webpack-cli -D
 npm i lodash -S
  • 运行npx webpack 打包;
  • vscode安装插件Live Server,右键index.html启动Open with Live Server
  • 可以看到已经启动了页面
  • 增加webpack.config.js
  • path.resolve相关用法可以查看另一篇 path.resolve相关用法
  • 在package.json中script对象中加入一行 “build”: “webpack”(注意最后一行没有,号)
  • npm run build进行打包;发现打包后正常访问;
  • 之前没有webpack.config.js,也可以访问,也就是说这里的配置是webpack的默认打包配置
// webpack.config.js
const path = require("path");

module.exports = {
  entry: "./src/index.js", // 入口
  output: { // 出口 // 输出文件
    filename: "main.js",
    // 注意这个dist是相对于文件夹根目录,也就是创建一个dist目录并且把输出文件放在里面
    path: path.resolve(__dirname, "dist"),
  },
};

二、资源配置

  • src下增加style.css, 引入index.js中,给dom添加上类名
// style.css
.red {
    color: red;
    font-size: 20px;
}

// index.js
import _ from "lodash";
import "./style.css";

function component() {
  const element = document.createElement("div");

  // lodash 现在使用 import 引入
  element.innerHTML = _.join(["Hello", "webpack"], " ");
  element.classList.add("red");

  return element;
}

document.body.appendChild(component());

  • npm i style-loader css-loader -D 安装loader
  • webpack.config.js配置加上处理style的loader
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
      	// 正则,以.css文件结尾,$表示结尾,\表示转义
        test: /\.css$/,
        // 从右边的loader开始加载处理,返回结果给左边的继续处理
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};

图片资源

// .style.css
.red {
    color: red;
    font-size: 20px;
    height: 300px;
    background: url('./logo.png'); // 增加图片的使用
}
// webpack.config.js中module.rules添加一条
{
  test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
  type: 'asset/resource', // 这个type应该是webpack自带的插件进行处理
},

其他资源就不一一列举了,要么有默认处理插件,要么需要安装对应的插件再配置rules;比如csv表格、json文件、字体文件、ts文件

三、输出文件

3.1 多文件入口

增加/print.js,修改index.html、index.js、webpack.config.js

// src/print.js
export default function print(msg = "Hello World") {
  console.log(msg);
}
// index.html
<!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8" />
     <title>起步</title>
     // index.html就在dist目录下,dist目录新增了两个出口文件print.js、index.js
     <script src="./print.js"></script>
   </head>
   <body>
    <script src="./index.js"></script>
   </body>
 </html>
// index.js
import _ from "lodash";
import "./style.css";
import print from "./print";
function component() {
  const element = document.createElement("div");

  // lodash 现在使用 import 引入
  element.innerHTML = _.join(["Hello", "webpack"], " ");
  element.classList.add("red");

  const btn = document.createElement("button");
  btn.innerHTML = "click me";
  btn.onclick = print;
  element.appendChild(btn);

  return element;
}

document.body.appendChild(component());
// webpack.config.js
const path = require("path");

module.exports = {
  // 配置多个入口,那么就会产生多个出口文件; 多个入口文件都会在index.html中单独引入
  entry: {
    index: "./src/index.js",
    print: "./src/print.js",
  },
  output: {
    filename: "[name].js", // name表示动态文件名,entry中指定的key
    path: path.resolve(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
        type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
      },
    ],
  },
};

重新打包,可以正常访问和点击按钮

3.2 HtmlWebpackPlugin插件

  • 帮助自动生成index.html文件(前面是手动引入出口文件),安装HtmlWebpackPlugin插件
  • npm i html-webpack-plugin -D
  • 修改webpack.config.js
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: { 
    index: "./src/index.js",
    print: "./src/print.js", // 注意这个顺序,index.html中的引入顺序也是如此
  },
  output: {
  	// 增加contenthash值,是一个随机字符串
    filename: "[name].[contenthash].js",
    path: path.resolve(__dirname, "dist"),
    // 可能改造过配置文件,导致dist目录有之前构建的很多不需要文件,构建前清理一下dist目录
    clean: true, 
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
        type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack Test",
    }),
  ],
};

四、环境

4.1 环境变量

  • 修改package.json;webpack.config.js;print.js
  • 通过命令行传入参数;通过mode选择打包模式;通过process.env.NODE_ENV获取当前打包环境
  • webpack.config.js中module.exports改造成匿名函数,以便获取参数
  • 增加source-map源代码映射
// package.json 
// 这一条改成这个 增加打包的参数;在env对象里增加goal: local, production: true
// --progress 是增加进步条;--color增加颜色;打包时间长的时候出现; 
"build": "webpack --env goal=local --env production --progress --color"

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 改造成匿名函数,这样可以获取参数
module.exports = (env, argv) => {
  // env中增加goal和production属性; env是argv中的一个属性
  console.log("env, argv", env, argv);
  return {
  	// mode是指定模式,可以通过env参数来决定是什么模式
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      index: "./src/index.js",
      print: "./src/print.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
    },
    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
    ],
  };
};
// print.js
export default function print(msg = "Hello World") {
  // 注意在一般文件中访问process或者process.env会报错,node只暴漏了process.env.NODE_ENV变量出来
  // process.env.NODE_ENV就是webpack.config.js中的mode属性
  console.log("process", process.env.NODE_ENV);
}

4.2 热更新

  • 之前手动build打包,还要在页面上刷新一下才能看到最新内容
  • 现在安装插件,npm i webpack-dev-server -D
// package.json scripts增加
// webpack-dev-server --open 和 webpack serve --open 都是可以的;出现错误可以都试一下;
"start-dev": "webpack-dev-server --open --env production=false",
"start": "webpack serve --open --env production=false",

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = (env, argv) => {
  console.log("env, argv", env, argv);
  return {
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      print: "./src/print.js",
      index: "./src/index.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
      publicPath: "/", // 解决静态资源加载路径问题
    },

    optimization: {
      // runtimeChunk是模块关系文件,single表示这里打包成一个runtime名称的文件
      runtimeChunk: "single", 
    },

    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
    ],

    // 开发模式下开启热更新
    devServer: {
      static: "./dist", // 插件寻找启动文件
      hot: true, // 开启热更新
      open: true, // 自动打开浏览器
      port: 3000, // 端口号
    },

    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
  };
};

五、代码分离

5.1 公共模块

  • index.js\print.js中都引入了lodash
  • 添加splitChunks让lodash单独打包成一个文件
// print.js
import _ from "lodash";
export default function print(msg = "Hello World") {
  console.log("process", process.env.NODE_ENV, 1111);
  console.log(_.join([1, 2, 3]));
}

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = (env, argv) => {
  console.log("env, argv", env, argv);
  return {
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      print: "./src/print.js",
      index: "./src/index.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
      publicPath: "/", // 解决静态资源加载路径问题
    },

    optimization: {
      runtimeChunk: "single",
      // all 表示公共的依赖模块提取到各自的chunk中;
      // 比如两个文件里引入了lodash,那么lodash会被打包lodash的chunk中
      splitChunks: {
        chunks: "all",
      },
    },

    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
    ],

    // 开发模式下开启热更新
    devServer: {
      static: "./dist", // 插件寻找启动文件
      hot: true, // 开启热更新
      open: true, // 自动打开浏览器
      port: 3000, // 端口号
    },

    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
  };
};

5.2 懒加载

  • index.js改成动态引入print.js
  • import导入时加上/* webpackChunkName: “print” /,可以让这个文件单独打包,不会加载,等需要加载的时候才会请求资源;注意/ 之间的空格
index.js
import _ from "lodash";
import "./style.css";
// import print from "./print"; // 注释掉,使用动态导入 否则也不会单独打包

function component() {
  const element = document.createElement("div");

  // lodash 现在使用 import 引入
  element.innerHTML = _.join(["Hello", "webpack"], " ");
  element.classList.add("red");

  const btn = document.createElement("button");
  btn.innerHTML = "click me";
  btn.onclick = (e) =>
  // 
    import(/* webpackChunkName: "print" */ "./print").then((module) =>
      module.default()
    );
  element.appendChild(btn);

  return element;
}

document.body.appendChild(component());

// webpack.config.js
// 用5.1配置就可以了

5.3 预获取/预加载模块

预获取,空闲时进行;加载将来需要的资源
import(/* webpackPrefetch: true */ './path/to/LoginModal.js');
预加载,父 chunk 加载时以并行方式开始加载;加载当前路由下可能需要的资源
import(/* webpackPreload: true */ './path/to/LoginModal.js');

六、缓存

  • contenthash是文件名唯一标识,这样文件更新了,客户端就能进行资源重新请求和更新
  • 公共依赖包不会经常更新,所以对应的打包文件也不需要更新,把这些不需要经常更新的包进行缓存
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = (env, argv) => {
  console.log("env, argv", env, argv);
  return {
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      index: "./src/index.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
      publicPath: "/", // 解决静态资源加载路径问题
    },

    optimization: {
      // moduleIds 模块标识符,使模块更容易被缓存和重复使用;
      // 项目中新增和删除文件,导致解析顺序发生变化,会导致vendor包变化; 创建唯一模块标识符
      moduleIds: "deterministic",
      runtimeChunk: "single",
      // all 表示公共的依赖模块提取到各自的chunk中;
      // 比如两个文件里引入了lodash,那么lodash会被打包lodash的chunk中
      splitChunks: {
        chunks: "all",
        cacheGroups: {
          vendor: {
            // node_modules下的包都打包到vendors这个chunk中
            test: /[\\/]node_modules[\\/]/,
            name: "vendors",
            chunks: "all",
          },
        },
      },
    },

    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
    ],

    // 开发模式下开启热更新
    devServer: {
      static: "./dist", // 插件寻找启动文件
      hot: true, // 开启热更新
      open: true, // 自动打开浏览器
      port: 3000, // 端口号
    },

    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
  };
};

七、Tree Shaking

  • optimization.usedExports = true;移除未使用的代码
  • 如果引入了资源(函数funTest),但是觉得可以不用打包他,他不会对结果有影响,可以设置标识;/#PURE/ funTest();
  • sideEffects表示该文件引入,但是未使用,需要确定这个文件有没有必要引入;在package.json中增加 “sideEffects”: [“./index.js”],表示如果你在index.js中引入了另外一个文件,但是该文件内容没有使用,可以放心移除这个文件不必打包进index.js,index.js被标记为无副作用
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = (env, argv) => {
  console.log("env, argv", env, argv);
  return {
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      index: "./src/index.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
      publicPath: "/", // 解决静态资源加载路径问题
    },

    optimization: {
      // moduleIds 模块标识符,使模块更容易被缓存和重复使用;
      // 项目中新增和删除文件,导致解析顺序发生变化,会导致vendor包变化; 创建唯一模块标识符
      moduleIds: "deterministic",
      runtimeChunk: "single",
      // all 表示公共的依赖模块提取到各自的chunk中;
      // 比如两个文件里引入了lodash,那么lodash会被打包lodash的chunk中
      splitChunks: {
        chunks: "all",
        cacheGroups: {
          vendor: {
            // node_modules下的包都打包到vendors这个chunk中
            test: /[\\/]node_modules[\\/]/,
            name: "vendors",
            chunks: "all",
          },
        },
      },
      // tree shaking
      // // 标记未使用的导出(代码级别),production模式下不会被打包进输出文件(代码体积会减少)
      usedExports: true, 
    },

    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
    ],

    // 开发模式下开启热更新
    devServer: {
      static: "./dist", // 插件寻找启动文件
      hot: true, // 开启热更新
      open: true, // 自动打开浏览器
      port: 3000, // 端口号
    },

    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
  };
};

八、公共路径

  • 默认公共路径是从output.publicPath去引用
  • 可以通过webpack.DefinePlugin自己定义环境变量,在页面可以使用;没有定义就使用否则会报错(process not defined)
  • webpack_public_path = window.test_public_path,这一行单独放在一个文件里,在入口文件最上面引入,可以在运行时规定公共路径;注意在服务器上挂载环境变量配置,给变量赋值window.test_public_path
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

// 尝试使用环境变量,否则使用根路径; process.env.ASSET_PATH 默认值是undefined
const ASSET_PATH = process.env.ASSET_PATH || "/"; // 
console.log("ASSET_PATH", process.env.ASSET_PATH, ASSET_PATH);

module.exports = (env, argv) => {
  console.log("env, argv", env, argv);
  return {
    mode: "development", // 开发模式 development // 默认是 production 生产模式
    // devtool 打包后的文件是压缩的不是源代码,报错了找不到错误位置,需要源代码映射
    devtool: "inline-source-map", // 开发模式是 inline-source-map // 生产模式是 source-map
    entry: {
      index: "./src/index.js",
    },
    output: {
      filename: "[name].[contenthash].js",
      path: path.resolve(__dirname, "dist"),
      clean: true,
      publicPath: "/", // 解决静态资源加载路径问题
    },

    optimization: {
      // moduleIds 模块标识符,使模块更容易被缓存和重复使用;
      // 项目中新增和删除文件,导致解析顺序发生变化,会导致vendor包变化; 创建唯一模块标识符
      moduleIds: "deterministic",
      runtimeChunk: "single",
      // all 表示公共的依赖模块提取到各自的chunk中;
      // 比如两个文件里引入了lodash,那么lodash会被打包lodash的chunk中
      splitChunks: {
        chunks: "all",
        cacheGroups: {
          vendor: {
            // node_modules下的包都打包到vendors这个chunk中
            test: /[\\/]node_modules[\\/]/,
            name: "vendors",
            chunks: "all",
          },
        },
      },
      // tree shaking
      // 标记未使用的导出(代码级别),production模式下不会被打包进输出文件(代码体积会减少)
      usedExports: true, 
    },

    plugins: [
      new HtmlWebpackPlugin({
        title: "Webpack Test",
      }),
      // 这可以帮助我们在代码中安全地使用环境变量
      // 让你可以自己定义一些变量,在代码中使用
      new webpack.DefinePlugin({
        "process.env.ASSET_PATH": JSON.stringify("ASSET_PATH"),
      }),
    ],

    // 开发模式下开启热更新
    devServer: {
      static: "./dist", // 插件寻找启动文件
      hot: true, // 开启热更新
      open: true, // 自动打开浏览器
      port: 3000, // 端口号
    },

    module: {
      rules: [
        {
          test: /\.css$/,
          use: ["style-loader", "css-loader"],
        },
        {
          test: /\.(png|svg|jpg|jpeg|gif)$/i, // i表示不区分大小写
          type: "asset/resource", // 这个type应该是webpack自带的插件进行处理
        },
      ],
    },
  };
};

;