webpack

294 阅读8分钟

一、什么是webpack

image.png

WebPack是一个现代JS应用程序的静态模块打包器(module bundler)

模块(模块化开发,可以提高开发效率,避免重复造轮子)

打包(将各个模块,按照一定的规则组装起来)

官网:webpack.js.org/ 中文网 webpack.docschina.org/

特点:功能强大(打包,构建,发布web服务),学习成本高

什么是前端工程化

前端工程化是依据业务特点,将前端开发的规范、流程、技术、工具、经验等形成规范并建立成一种标准的体系

构建工具

构建工具的主要功能就是实现自动化处理,例如对代码进行检查、预编译、合并、压缩;生成雪碧图、sourceMap、版本管理;运行单元测试、监控等,当然有的工具还提供模块化、组件化的开发流程功能。

网上各类的构建工具非常多,有家喻户晓的 Webpack、Gulp、 Grunt,也有各大公司团队开源的构建工具,这里通过 Github 的 Star 数量来简单的对比下各个工具的流行度

image.png

二、安装webpack

1.初始化包描述文件

csharp
复制代码
npm init

2.全局安装webpack

css
复制代码
npm i webpack webpack-cli -g

3.将webpack添加到package.json的依赖中

css
复制代码
npm i webpack webpack-cli -D

4.运行命令

复制代码
npx webpack   或者 webpack

1).开发环境

默认会生成mian.js的文件

bash
复制代码
webpack ./src/index.js -o ./build  --mode=development

2).生产环境

bash
复制代码
webpack ./src/index.js -o ./build  --mode=production

npx会把当前项目下node_modules的bin当前运行环境

三、五大核心概念

  1. entry(入口)

指示 Webpack 从哪个文件开始打包

  1. output(输出)

指示 Webpack 打包完的文件输出到哪里去,如何命名等

  1. loader(加载器)

webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析

  1. plugins(插件)

扩展 Webpack 的功能

  1. mode(模式)

主要由两种模式:

  • 开发模式:development
  • 生产模式:production

1).配置文件

js
复制代码
// Node.js的核心模块,专门用来处理文件路径
const path = require("path");

module.exports = {
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "dist"),
    // filename: 输出文件名
    filename: "main.js",
  },
  // 加载器
  module: {
    rules: [],
  },
  // 插件
  plugins: [],
  // 模式
  mode: "development", // 开发模式
};

2).运行命令

js
复制代码
npx webpack

四、loader

Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源

我们找 Loader 都应该去官方文档中找到对应的 Loader,然后使用

官方文档找不到的话,可以从社区 Github 中搜索查询

Webpack 官方 Loader 文档

css 资源

1.下载包

js
复制代码
npm i css-loader style-loader -D

注意:需要下载两个 loader

  1. 功能介绍
  • css-loader:负责将 Css 文件编译成 Webpack 能识别的模块
  • style-loader:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
    此时样式就会以 Style 标签的形式在页面上生效
  1. 配置
js
复制代码
const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
    ],
  },
  plugins: [],
  mode: "development",
};
  1. 添加 Css 资源
  • src/css/index.css
css
复制代码
.box1 {
    width: 100px;
    height: 100px;
    background-color: pink;
}
  • src/main.js
js
复制代码
import count from "./js/count";
import sum from "./js/sum";
// 引入 Css 资源,Webpack才会对其打包
import "./css/index.css";

console.log(count(2, 1));
console.log(sum(1, 2, 3, 4));
  • public/index.html
html
复制代码
<!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>webpack5</title>
  </head>
  <body>
    <h1>Hello Webpack5</h1>
    <!-- 准备一个使用样式的 DOM 容器 -->
    <div class="box1"></div>
    <!-- 引入打包后的js文件,才能看到效果 -->
    <script src="../dist/main.js"></script>
  </body>
</html>

Less 资源

1.下载包

css
复制代码
npm i less-loader -D    //建议是less-loader@6
  1. 功能介绍
  • less-loader:负责将 Less 文件编译成 Css 文件
  1. 配置
js
复制代码
const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
    ],
  },
  plugins: [],
  mode: "development",
};

4.添加Less资源

  • src/less/index.less
css
复制代码
.box2 {
  width: 100px;
  height: 100px;
  background-color: deeppink;
}
  • src/main.js
javascript
复制代码
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/index.css";
import "./less/index.less";

console.log(count(2, 1));
console.log(sum(1, 2, 3, 4));
  • public/index.html
xml
复制代码
<!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>webpack5</title>
  </head>
  <body>
    <h1>Hello Webpack5</h1>
    <div class="box1"></div>
    <div class="box2"></div>
    <script src="../dist/main.js"></script>
  </body>
</html>

Sass 和 Scss 资源

1.下载包

css
复制代码
npm i sass-loader sass -D

注意:需要下载两个

2.功能介绍

  • sass-loader:负责将 Sass 文件编译成 css 文件
  • sass:sass-loader 依赖 sass 进行编译

3.配置

javascript
复制代码
const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
  plugins: [],
  mode: "development",
};

4.添加 Sass 资源

  • src/sass/index.sass
arduino
复制代码
/* 可以省略大括号和分号 */
.box3
  width: 100px
  height: 100px
  background-color: hotpink
  • src/sass/index.scss
css
复制代码
.box4 {
  width: 100px;
  height: 100px;
  background-color: lightpink;
}
  • src/main.js
javascript
复制代码
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";

console.log(count(2, 1));
console.log(sum(1, 2, 3, 4));
  • public/index.html
xml
复制代码
<!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>webpack5</title>
  </head>
  <body>
    <h1>Hello Webpack5</h1>
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>
    <div class="box4"></div>
    <script src="../dist/main.js"></script>
  </body>
</html>

图片资源

过去在 Webpack4 时,我们处理图片资源通过 file-loaderurl-loader 进行处理

现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源

javascript
复制代码
const path = require("path");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  module: {
    rules: [
      {
        // 用来匹配 .css 结尾的文件
        test: /.css$/,
        // use 数组里面 Loader 执行顺序是从右到左
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /.styl$/,
        use: ["style-loader", "css-loader", "stylus-loader"],
      },
      {
        test: /.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
          }
        }
        generator: {
          // 将图片文件输出到 static/imgs 目录中
          // 将图片文件命名 [hash:8][ext][query]
          // [hash:8]: hash值取8位
          // [ext]: 使用之前的文件扩展名
          // [query]: 添加之前的query参数
         filename: "static/imgs/[hash:8][ext][query]",
      },
    ],
  },
  plugins: [],
  mode: "development",
};

使用图片资源

css
复制代码
.box2 {
  width: 100px;
  height: 100px;
  background-image: url("../images/1.jpeg");
  background-size: cover;
}

自动清空上次打包资源

lua
复制代码
output: {
    path: path.resolve(__dirname, "dist"),
    filename: "static/js/main.js",
    clean: true, // 自动将上次打包目录资源清空
},

处理字体图标资源

  1. 打开阿里巴巴矢量图标库
  2. 选择想要的图标添加到购物车,统一下载到本地
  • src/main.js
arduino
复制代码
import "./css/iconfont.css";
  • public/index.html
css
复制代码
<i class="iconfont icon-arrow-down"></i>
    <i class="iconfont icon-ashbin"></i>
    <i class="iconfont icon-browse"></i>
  • 配置
bash
复制代码
      {
        test: /.(ttf|woff2?)$/,
        type: "asset/resource",
        generator: {
          filename: "static/media/[hash:8][ext][query]",
        },
      },

type: "asset/resource"和type: "asset"的区别:

  1. type: "asset/resource" 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理
  2. type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式

视频音频也可以使用以上配置,只需要修改 test: /.(ttf|woff2?|map4|map3|avi)$/

TypeScript

1.安装模块

复制代码
npm install ts-loader typescript -D

2.生成tsconfig.json

csharp
复制代码
tsc --init

3.配置规则

bash
复制代码
rules: [
      {
        test: /.ts$/,
        loader: "ts-loader",
      },
    ],

alias别名设置

java
复制代码
const path = require('path');
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, "src"),
      '_c': path.resolve(__dirname, "src/components"),
    },
    // 配置省略文件路径的后缀名
    extensions: [".ts", ".js",'.cjs','.json'],
    // 告诉webpack 解析模块失去找哪个个目录
    modules: [path.resolve(__dirname, '../../node_modules'), 'node_modules'], // 迅速找到node_modules所在
  }
};

tree shaking

生产环境下默认有tree shaking

Tree Shaking 指的就是当我引入一个模块的时候,我不引入这个模块的所有代码,我只引入我需要的代码,这就需要借助 webpack 里面自带的 Tree Shaking 这个功能来帮我们实现。

官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination)

在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 Tree-Shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。

在production 模式下不用在webpack.config.js中配置

yaml
复制代码
optimization: {
	usedExports: true
}

Eslint

使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查,完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理

1.配置文件

配置文件由很多种写法:

  • .eslintrc.*:新建文件,位于项目根目录
    • .eslintrc
    • .eslintrc.js
    • .eslintrc.json
    • 区别在于配置格式不一样
  • package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

2.具体配置

我们以 .eslintrc.js 配置文件为例:

ruby
复制代码
module.exports = {
  // 解析选项
  parserOptions: {},
  // 具体检查规则
  rules: {},
  // 继承其他规则
  extends: [],
  // ...
  // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};
  1. parserOptions 解析选项
arduino
复制代码
parserOptions: {
  ecmaVersion: 6, // ES 语法版本
  sourceType: "module", // ES 模块化
  ecmaFeatures: { // ES 其他特性
    jsx: true // 如果是 React 项目,就需要开启 jsx 语法
  }
}
  1. rules 具体规则
  • "off" 或 0 - 关闭规则
  • "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
perl
复制代码
rules: {
  semi: "error", // 禁止使用分号
  'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告
  'default-case': [
    'warn', // 要求 switch 语句中有 default 分支,否则警告
    { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了
  ],
  eqeqeq: [
    'warn', // 强制使用 === 和 !==,否则警告
    'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告
  ],
}

更多规则详见:规则文档

  1. extends 继承

开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。

现有以下较为有名的规则:

java
复制代码
// 例如在React项目中,我们可以这样写配置
module.exports = {
  extends: ["react-app"],
  rules: {
    // 我们的规则会覆盖掉react-app的规则
    // 所以想要修改规则直接改就是了
    eqeqeq: ["warn", "smart"],
  },
};

3. 在 Webpack 中使用

  1. 下载包
css
复制代码
npm i eslint-webpack-plugin eslint -D
  1. 定义 Eslint 配置文件
  • .eslintrc.js
java
复制代码
module.exports = {
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  env: {
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  parserOptions: {
    ecmaVersion: 6,
    sourceType: "module",
  },
  rules: {
    "no-var": 2, // 不能使用 var 定义变量
  },
};
  1. 修改 js 文件代码
  • main.js
ini
复制代码
var result1 = count(2, 1);
console.log(result1);
var result2 = sum(1, 2, 3, 4);
console.log(result2);

4.配置

  • webpack.config.js
ini
复制代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
module.exports = {

  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
  ],
  mode: "development",
};
  • . eslintignore
bash
复制代码
# 忽略dist目录下所有文件
dist

Babel

JavaScript 编译器。

主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

1.配置文件

配置文件由很多种写法:

  • babel.config.*:新建文件,位于项目根目录
    babel.config.js
    babel.config.json
  • .babelrc.*:新建文件,位于项目根目录
    .babelrc
    .babelrc.js
    .babelrc.json
  • package.json 中 babel:不需要创建文件,在原有文件基础上写
    Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

2.具体配置

我们以 babel.config.js 配置文件为例:

ini
复制代码
module.exports = {
  // 预设
  presets: [],
};
  1. presets 预设

简单理解:就是一组 Babel 插件, 扩展 Babel 功能

  • @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
  • @babel/preset-react:一个用来编译 React jsx 语法的预设
  • @babel/preset-typescript:一个用来编译 TypeScript 语法的预设

3. 在 Webpack 中使用

  1. 下载包
bash
复制代码
npm i babel-loader @babel/core @babel/preset-env -D
  1. 定义 Babel 配置文件
  • babel.config.js
ini
复制代码
module.exports = {
  presets: ["@babel/preset-env"],
};
  1. 配置
  • webpack.config.js
javascript
复制代码
module.exports={
  module:{
    rules:[
        {
        test: /.js$/,
        exclude: /node_modules/, // 排除node_modules代码不编译
        loader: "babel-loader",
      },
    ]
  }
}

Html 资源

1.下载包

css
复制代码
npm i html-webpack-plugin -D

2.配置

  • webpack.config.js
javascript
复制代码
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true, // 自动将上次打包目录资源清空
  },
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ],
  mode: "development",
};

3.修改 index.html

去掉引入的 js 文件,因为 HtmlWebpackPlugin 会自动引入

devServer

1).安装

css
复制代码
npm i webpack-dev-server -D

2).配置

arduino
复制代码
 // 开发服务器
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器,
    compress:true, //开启gzip 
    hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了
  },

3).启动

json
复制代码
"serve": "webpack-dev-server"

vue-loader配置

1).安装

json
复制代码
"vue": "^2.7.14",
"vue-loader": "^15.7.2",
"vue-template-compiler": "^2.6.10"

2).配置 rules

bash
复制代码
    {
        test: /.vue$/,
        use: ["vue-loader"],
      },
      {
        test: /.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    }

3).配置plugins

ini
复制代码
const VueLoaderPlugin = require('vue-loader/lib/plugin')

plugins:[
     new VueLoaderPlugin()
]
javascript
复制代码
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount("#app");

package.json

bash
复制代码
{
  "name": "mywepack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "serve": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "css-loader": "^6.7.3",
    "css-minimizer-webpack-plugin": "^4.2.2",
    "less-loader": "^6.2.0",
    "node-sass": "^8.0.0",
    "sass-loader": "^13.2.0",
    "style-loader": "^3.3.1",
    "terser-webpack-plugin": "^5.3.6",
    "vue": "^2.7.14",
    "vue-style-loader": "^4.1.3"
  },
  "devDependencies": {
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.11.1",
    "vue-loader": "^15.7.2",
    "vue-template-compiler": "^2.6.10"
  }
}

webpack.config.js

js
复制代码
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
// cpu核数
const os=require("os");
// 线程
const threads = os.cpus().length;

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "/src/main.js"),
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "build.js",
    clean: true,
  },
  module: {
    rules: [
    //   {
    //     test: /.css$/i,
    //     use: ["style-loader", "css-loader"],
    //   },
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /.s(a|c)ss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },

      {
        test: /.(jpg|jpeg|png|gif|webp)$/,
        type: "asset",
        parser: {
          dataUrlCondition: {
            maxSize: 30 * 1024, // 小于10kb的图片会被base64处理
          },
        },
        generator: {
          filename: "./images/[hash:8][ext][query]",
        },
      },
      {
        test: /.vue$/,
        use: ["vue-loader"],
      },
      {
        test: /.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      }
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public/index.html"),
      filename: "index.html",
      inject: "body",
    }),
    new VueLoaderPlugin(),
  ],
  optimization: {
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
        parallel: threads, // 开启多进程
      }),
    ]
},
  // 开发服务器
  devServer: {
    compress:false,
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
  },
};

作者:暖阳_xm
链接:juejin.cn/post/729832…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。