Webpack5不完全指南-基础篇

2,884 阅读10分钟

金石有声,不拷不鸣

引言

在webpack之类的构建工具诞生之前,前端项目构建一直处于"高耕火种"时代,项目代码压缩混淆、 js/css等依赖的合并都需要手动操作。webpack的出现解决了前端项目构建的很多痛点,可以说是前端工程化中必学必会的一环。

什么是Webpack?

Webpack 是前端项目的构建工具,它可以解析项目的依赖(js/css/图片资源等),对它们进行打包处理。

Webpack主要功能:

  • 代码转译
  • 模块合并
  • 混淆压缩
  • 代码分割
  • 代码刷新
  • 自动刷新
  • 自动部署

webpack环境搭建

webpack基于Node环境,Node请自行下载

Webpack安装

全局安装: npm install webpack webpack-cli -g

局部安装(推荐): npm install webpack webpack-cli -D

搭建Demo

  • 新建basic文件夹并用Vscode打开
  • 局部安装 webpack webpack-cli
    • npm install webpack webpack-cli -D

Webpack-cli

Webpack-cli之前是再webpack包中的,webpack 4.0以后作为单独的模块进行管理。

执行Webpack

Webpack 通过指令 webpack 来对项目进行打包,但是这个指令只在全局安装的Webpack有用。npm5.2 提供了 npx 指令,npx 可以在项目中直接执行模块的指令。

终端执行npx webpack

image.png

webpack执行了,但是依赖和配置有问题,打包的入口、出口文件等都需要配置。

新建默认入口文件

webpack默认入口文件为: 根目录/src/index.js

  • 新建 根目录/src/index.js

    // /src/index.js
    const init = () => {
      console.log("webpack真好玩")
    }
    init()
    
  • 执行 npx webpack,打包成功生成 /dist/main.js

    image.png

应用场景

在真实开发中会涉及多个模块依赖的场景

  • 新建 src/a.js

    // moduleA
    console.log("我是模块A");
    module.exports = {
      name: "moduleA"
    }
    
  • src/index.js 导入 a.js 并重新构建

    const a = require("./a")
    console.log(a.name);
    const init = () => {
      console.log("webpack真好玩")
    }
    init()
    
  • 安装vscode插件 - run code ,并执行 main.js

    image.png

在浏览器端执行

前面的 requiremodule.exports属于 CommonJS模块化规范 ,浏览器默认不支持该语法。但是代码经过webpack打包后,会自动转换模块化规范。

  • 新建 index.html (src/index.html),引入 src/index.js

    <!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>
    </head>
    <body>
      <!-- 使用CommonJS模块化规范 -->
      <script src="./index.js"></script>
    </body>
    </html>
    

    image.png

  • 引入 dist/main.js

    <body>
      <!-- 使用CommonJS模块化规范 -->
      <!-- <script src="./src/index.js"></script> -->
      <!-- 引入webpack打包后的js -->
      <script src="./dist/main.js"></script>
    </body>
    

    image.png

Webpack配置

webpack四大核心概念:

  • 入口(entry):程序入口的js
  • 出口(output):打包之后存放的位置
  • loader:对项目依赖、文件进行转换处理
  • plugin:可以处理loader做不了的工作

Webpack配置文件

Webpack配置文件名默认为 webpack.config.js,配置文件必须遵守 CommmonJS 模块化规范。

const path = require("path")
// webpack配置文件必须遵循CommonJS规范
module.exports = {
  // 项目打包入口文件
  entry: "./src/index.js",
  // 项目出口配置
  output: {
    // path.resolve("path")/path.join(_dirname,"path"):将相对路径解析为绝对路径
    path: path.resolve("./dist/"),
    // 打包后的文件名
    filename: 'bundle.js'
  },
  // 打包模式(默认:production):production || development 
  mode: "development"
}

webpack指令参数

  • --config 文件名

    使用自定义配置文件执行打包指令:npx webpack --config webpack.custom.config.js

    可以根据开发 / 上线 不同环境使用不同的配置文件

  • 新增script指令

    {
      "scripts": {
         // 终端输入 npm run build,使用 webpack.custom.config.js进行打包
        "build": "npx webpack --config webpack.custom.config.js"
      }
    }
    

    image.png

html插件

webpack每次编译打包不会将index.html打包进来,通过html插件可以打包html文件同时自动引入其他css/js等依赖

html插件作用
1、打包后自动复制一份目标html文件
2、html自动引入打包后的js(注意:不要手动引入js!!!)

  • 安装:npm i html-webpack-plugin -D

  • 在webpack配置文件中配置插件

    // webpack.config.js
    const HtmlPlugin = require("html-webpack-plugin")
    module.exports = {
      mode: "production",
      // 配置插件字段
      plugins: [
        // 新建一个HtmlPlugin实例
        new HtmlPlugin(
          {
            // 打包输出文件名
            filename: "index.html",
            // 被复制的目标html
            template: "./src/index.html"
          }
        )
      ]
    }
    
  • 修改index.html

    <body>
    <!-- 使用CommonJS模块化规范 -->
    <!-- <script src="./src/index.js"></script> -->
    <!--新增节点-->
      <ul>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
      </ul>
      <!--使用html-webpack-plugin无需引入js,它会自动帮你引入-->
      <!--<script src="./src/index.js"></script>-->
    </body>
    
  • 执行指令 npx webpack进行打包

    image.png

自动编译工具

每次修改代码后,都要执行npm run build重新编译,我们可以借助插件或工具让webpack进行自动编译

  • watch mode
  • webpack-dev-server(最推荐)
  • webpack-dev-middleware

watch mode

webpack 开启watch模式,会监视项目文件的变化,当发现有修改的代码时会自动编译打包,输出文件。

方式一:webpack-cli打包指令增加--wtach参数

1、package.json 新增 watch 脚本

"scripts": {
  "build": "npx webpack --config webpack.custom.config.js",
  "watch": "npx webpack --config webpack.config.js --watch"
}

2、终端执行 npm run watch,开始监听项目文件

方式二:webpack配置文件开启watch模式

// webpack.config.js
module.exports = {
  // 打包模式(默认:production):production || development 
  mode: "production",
  // 开启watch模式
  watch: true
}

webpack-dev-server

这个npm包可以在本地开启服务,在内存中生成打包的bundle.js.打包效率高在修改代码后重新构建并刷新页面。注意:使用webpack-dev-server 必须先安装webpack

  • 1、安装 webpack-dev-servernpm i webpack-dev-server -D
  • 2、 webpack-dev-server 指令:npx webpack-dev-server --port 3001
    • --port:指定服务端口号
    • --hot:开启热更新
    • --open:是否自动打开浏览器访问页面
  • 3、配置 serve 脚本 执行脚本
  "scripts": {
    "serve": "npx webpack-dev-server --port 3001 --open",
  }

image.png

当然也可以通过webpack配置文件的 devServer 字段开启 webpack-dev-server 服务

  • 1、 配置 devServer 字段
 // webpack.config.js
 module.exports = {
  // 开启watch模式
  // watch: false,
  // 配置 webpack-dev-server
  devServer: {
    // 编译完成是否自动打开页面
    open: true,
    // 服务端口号
    port: 3000,
    // 是否启用压缩
    compress: true,
    // 是否启用热更新
    hot: true,
    // 服务的基准路径
    contentBase: "./src"
  }
}
  • 2、配置 dev 脚本 执行脚本
 "scripts": {
    "build": "npx webpack --config webpack.custom.config.js",
    "watch": "npx webpack --config webpack.config.js --watch",
    "serve": "npx webpack-dev-server --port 3001 --open",
    "dev": "npx webpack serve"
  }

webpack-dev-middleware

webpack-dev-middleware 使用node的中间件,它可以作为一个容器将webpack打包后的资源提供给服务器。webpack-dev-server 也是通过这个实现的

  • 安装 expresswebpack-dev-middleware
    npm i express webpack-dev-middleware -D

  • 根目录新建 server.js

    // 新建服务 并运行  
    // 导入express框架
    const express = require("express")
    // 导入webpack
    const webpack = require("webpack")
    // 导入中间件
    const webpackDevMiddleware = require("webpack-dev-middleware")
    // 导入webpack配置对象
    const config = require("./webpack.config.js")
    
    
    // 新建服务
    const app = express()
    const compiler = webpack(config)
    
    // app注册中间件
    app.use(webpackDevMiddleware(compiler, { publicPath: "/" }))
    
    // 监听服务
    app.listen(3001, function () {
      console.log("3000端口服务运行");
    })
    
  • 新增脚本 server, 并执行

     "scripts": {
      "server": "node server.js"
    }
    

处理css文件

webpack默认无法处理css文件,我们需要借助相关loader进行处理

image.png image.png

  • 安装 css-loading npm i css-loader style-loader -D

  • 配置文件中配置loader(webpack.config.js)

      // 配置各种loader
    module: {
      rules: [
        //  配置用来解析css相关loader 
        {
          // loader匹配到的文件
          test: /\.css$/i,
          // webpack调用loader的顺序是从右到左
          use: ['style-loader', 'css-loader']
        }
      ]
    },
    
  • 新建 src/css/index.css

    li {
     line-height: 100px;
     background-color: red;
    }
    
  • 入口文件导入css
    html引入的资源文件 webpack不会打包,我们需要将css文件引入到 src/index/js

    // src/index.js
    // 在webpack的入口文件中引入css
    import style from './css/index.css'
    
  • 执行 npm run serve

    image.png

处理less/scss文件

css预处理器的文件 webpack也可以打包处理,这里以 less 为例

  • 安装 loader
    npm i less-loader -D
  • 新建 src/less/index.less
    ul {
      li:nth-child(1) {
        background-color: pink;
        color: green;
      }
    }
    
  • 在入口文件中导入less
    // 在webpack的入口文件中引入less
    import './less/index.less'
    
  • 配置less-loader (webpack.config.js)
      // 配置各种loader
    module: {
      rules: [
        {
          test: /\.css$/i,
          // webpack调用loader的顺序是从右到左
          use: ['style-loader', 'css-loader']
        },
        //  配置用来解析.less相关loader 
        {
          test: /\.less$/i,
          // webpack调用loader的顺序是从右到左
          use: ['style-loader', 'css-loader', 'less-loader']
        }
      ]
    }
    
  • 执行 npm run serve ,查看效果

处理图片字体文件

图片和字体文件也需要loader来处理

webpack4

file-loader:处理图片等文件
url-loader:处理图片等文件,可将图片转为base64格式
注意:url-loader 是对 file-loader 的包装,使用 url-loader 必须安装 file-loader

  • 安装
    npm i fild-loader url-loader -D
  • 配置相关loader (webpack.config.js)
    module: {
      rules: [
      //  配置用来解析图片等文件相关loader 
        {
          test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i,
          // 图片大小大于16940自动转为base64格式
          use: 'url-loader?limit=16940'
          // 使用file-loader
          // use: 'file-loader'
        }]
    }
    

webpack5

webpack5 通过内置的 Asset Modules 引入任何其他类型的文件
1、asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
2、asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
3、asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
4、asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

  • 配置文件(webpack.config.js)
    module: {
      rules: [
        //  配置用来解析图片等文件相关loader 
        {
          test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i,
          type: "asset",
          //解析
          parser: {
            //转base64的条件
            dataUrlCondition: {
              // 大于100kb转为base64格式
              maxSize: 100, // 25kb
            }
          },
          generator: {
            //输出路径
            // name:使用原文件名  
            // hash:文件名增加hash字符
            // ext:使用原文件拓展名
            filename: 'img/[name].[hash:6][ext]',
            //打包后对资源的引入,文件命名已经有/img了
            publicPath: './'
          }
        }
      ]
    },
    

配置babel

webpack可以通过babel打包解析高级语法,将它们转换成可兼容绝大多数浏览器的代码

  • 安装
    npm install babel-loader babel-core babel-preset-env
  • 配置 bable-loader (webpack.custom.config.js)
    // 配置各种loader
    module: {
      rules: [
        // 解析所有.js文件
        {
          test: /\.js$/,
          // 排除node_modules等文件
          exclude: /(node_modules|bower_components)/,
          use: {
            // 使用 babel-loader
            loader: 'babel-loader',
            options: {
              // 使用 babel的预设
              presets: ['@babel/preset-env']
            }
          }
        }
      ]
    }
    

使用bebal处理generator

js一些最新语法或者处于草案阶段的语法,babel需要借助相应的插件进行处理。

// index.js
......
const zs = new Person({ name: "张三" })
console.log("zs", zs.name);
const init = () => {
  console.log("webpack真好玩123fsd")
}
init()

// 使用generator
function* fn() {
 yield 1
 yield 2
 return 3
}
let newFn = fn()
console.log("next1:", newFn.next())
console.log("next2:", newFn.next())

打包后会报错

image.png

  • 安装 plugin-transform-runtime
    npm i @babel/plugin-transform-runtime -D
    // @babel/runtime 是 @babel/plugin-transform-runtime的核心依赖
    npm install --save @babel/runtime  
    
  • 配合打包配置 babel-loader (webpack.custom.config.js)
     module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env'],
              // 注册babel插件
              plugins: ["@babel/plugin-transform-runtime"]
            }
          }
        }
      ]
    },
    
  • npm run build 执行 webpack.custom.config.js

babel配置文件

babel作为一个工具,可能会有大量需要配置的地方,如果在webpack配置babel可能会造成webpack配置文件体量过于庞大、可读性差等问题。这时候可以通过babel的独立配置文件解决这个问题

.babelrc.json

在项目根目录新建 .babelrc.json ,将 babel-loader 的 options 字段移植到 .babelrc.json 中.

// .babelrc.json  
{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

// webpack.custom.config.js  
      {
        test: /\.js$/,
        // 排除node_modules等文件
        exclude: /(node_modules|bower_components)/,
        use: {
          // 使用 babel-loader
          loader: 'babel-loader',
          // 移植到 .babelrc.json中
          // options: {
          //   // 使用 babel的预设
          //   presets: ['@babel/preset-env'],
          //   plugins: ["@babel/plugin-transform-runtime"]
          // }
        }
      }

使用bebal处理实例的原型方法

某些实例的原型方法babel不会进行转换成低级代码,可能会出现兼容性问题。例如数组实例的map、includes等方法。

image.png

@babel/polyfill

他是 @babel/plugin-transform-runtime的补丁,只需要安装一下在使用实例原型方法的位置导入进行即可

  • 安装
    npm i @babel/polyfill -D
  • 导入
    // src/index.js
    // 导入polyfill
    import '@babel/polyfill'
    const arr = [1, 2, 3]
    console.log("arr是否包含 '1'", arr.includes(1));
    console.log(arr.map(e => e + 2));
    ``
    

sourceMap

Sourcemap 本质上是一个信息文件,里面储存着代码转换前后的对应位置信息。它记录了转换压缩后的代码所对应的转换前的源代码位置。webpack会对源码进行压缩、编译、高版本代码到低版本代码的转换,会导致处理后代码阅读性很差,无法追踪bug错误,Sourcemap就是解决这一问题的利器。Sourcemap可以在生产环境使用但是极不推荐!!会造成源码泄露的风险

devtool

devtool是webpack配置项,用来控制使用sourceMap的方式
[inline-|hiddeb-|eval-][nosources-][cheap-[module-]]source-map

source-map

devtool:source-map 是最基本的用法
特点

  • 外部单独一个sourcemap文件

  • 可以提供错误代码准确位置,源代码的错误位置 示例

  • 手写一个bug

    const a = require("./a")
    import './css/index.css'
    import './less/index.less'
    import img from './img/avatar.jpg'
    console.log(a.name);
    
    class Person {
      constructor(opt) {
        this.name = opt.name
      }
    }
    // 手动声明一个错误(在console.log()后加上())
    const zs = new Person({ name: "张三" }); console.log("zs", zs.name)();
    
  • 配置 webpack.config.js

    const path = require("path")
    const HtmlPlugin = require("html-webpack-plugin")
    module.exports = {
      entry: "./src/index.js",
      output: {
        path: path.resolve(__dirname, "./dist"),
        filename: 'bundle.js'
      },
      mode: "production",
      plugins: [
        new HtmlPlugin(
          {filename: "index.html",template: "./src/index.html"}
        )
      ],
      module: {
        rules: [
          { test: /\.css$/i,use: ['style-loader', 'css-loader']},
          { test: /\.less$/i, use: ['style-loader', 'css-loader', 'less-loader']},
          {
            test: /\.jpg|png|gif|bmp|ttf|eot|svg|woff|wpff2$/i,
            type: "asset",
            parser: {
              dataUrlCondition: {
                maxSize: 100, // 25kb
              }
            },
            generator: {
              filename: 'img/[name].[hash:6][ext]',
              publicPath: './'
            }
          },
          {
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {loader: 'babel-loader' }
          }
        ]
      },
      // 开启source-map模式 
      devtool: "source-map"
    }
    
  • 执行 npx webpack 构建项目,dist目录下生成.map文件

    image.png image.png

    image.png

inline-source-map

特点

  • 内联sourcemap,构建速度快。只生成一个sourcemap

  • 可以提供错误代码准确位置,源代码的错误位置 示例

  • 修改 webpack.config.js,将 devtool 改为 "inline-source-map"

    devtool: "inline-source-map"
    
  • 删除dist目录,执行 npx webpack 重新构建项目,没有 .map 文件所有sourcemap数据会以base64的形式内嵌到 bundle.js

    image.png

hidden-source-map

特点

  • 外部sourcemap文件
  • 可以提示错误代码错误原因,没有错误位置
  • 不能追踪源代码错误位置,只能提示构建后错误代码位置

eval-source-map

特点

  • 内联sourcemap,每一个文件都有对应的source-map 都在eval
  • 可以提供错误代码信息,源代码的错误位置

nosources-source-map

特点

  • 外部单独一个sourcemap文件
  • 可以提供错误代码信息,没有任何源代码的信息

cheap-source-map

特点

  • 外部单独一个sourcemap文件
  • 可以提供错误代码信息和源代码错误位置(只精确到行)

cheap-module-source-map

特点

  • 外部单独一个sourcemap文件
  • 可以提供错误代码信息和源代码错误位置

开发环境:

开发环境 要求速度快、调试友好 速度
eval > inline > cheap > ...
eval-cheap-source-map>eval-source-map
调试
source-map
cheap-module-source-map
cheap-source-map
建议
eval-source-map(MVVM框架一般用这个) / eval-cheap-module-source-map

生产环境

生产环境要考虑代码是否要隐藏、调试友好,不能使用内联,内联会让代码体积变大
nosources-source-map
hidden-source-map
建议
source-map / cheap-module-source-map

clean-webpack-plugin

webpack每次构建生成新的dist目录,默认不会清空dist下的文件,这对导致有些缓存文件一直存在,clean-webpack-plugin会在构建项目前清空dist

  • 安装
    npm i clean-webpack-plugin -D
  • 配置plugin
    // webpack.custom.config.js  
    const CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin
    module.exports = {
        plugins: [
         new HtmlPlugin(
          {
            filename: "index.html",
            template: "./src/index.html"
          }
        ),
        // new插件实例
        new CleanWebpackPlugin()
       ],
    }
    

copy-webpack-plugin

webpack默认打包src下的所有资源,目录外的资源可以通过 copy-webpack-plugin 打包进来

image.png

  • 安装
    npm i copy-webpack-plugin -D
  • 配置plugin
    // webpack.custom.config.js  
    {
      plugins:[
        new CopyWebpackPlugin({
        patterns: [{
          // 将项目的assets目录复制一份到dist/assets中
          from: path.join(__dirname, "assets"),
          to: 'assets'
        }]
      })
      ]
    }
    

BannerPlugin

这是webpack内部的一个插件,用来为打包js添加注释、版本等信息

  • webpack.custom.config.js
    // 导入webpack
    const Webpack = require("webpack")
    module.exports = {
      plugins: [
         new Webpack.BannerPlugin("这是一段版权信息")
      ]
    }