跳至主要內容

内置方法

星星大约 5 分钟

内置方法

require.context

  • 用法

    const context = require.context(
      directory: string, // 必填,一个目录路径,用于创建上下文环境
      includeSubdirs?: boolean = true, // 可选,是否包含子目录,默认值为 true
      filter?: RegExp = /^\.\/.*$/, // 可选,过滤结果的正则表达式,默认值为 /^\.\/.*$/ 表示所有文件
      mode?: string = 'sync', // 可选, 加载模式,可选值为 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once', 默认值为 'sync'
    )
    
  • 返回值 context 是一个函数,同时也是一个对象,它有三个属性,分别是 resolve, keys, id 。

    • context.keys 是一个函数,返回匹配到的所有模块路径字符串组成的数组,如 ['./a.js', './b.js'] ,将返回数组的任一元素传回给 context() 则可以得到这个文件的 ES Module ,访问这个 ES Module 的 default 就可以访问模块的默认导出。其他命名导出也按对应方法访问。
    • context.resolve 也是一个函数,返回解析后得到的模块 id 。传入 context.keys() 返回的某个文件的 key ,可以得到这个文件相对于项目启动目录的一个相对路径。
    • context.id 是上下文模块的模块 id 。这可能对 module.hot.accept 有用。
    • 导出
      const models = context.keys().map(v => {
          return context(v).default
      })
      
  • 使用ts报错 Property 'context' does not exist on type 'NodeRequire'

    • 原因: ts 找不到 webpack 中一些方法的类型
    • 解决:
      1. npm i @types/webpack-env -D 安装 webpack 类型
      2. tsconfig.json 中添加 types
      {
          "compilerOptions": {
              "types": ["webpack-env"]
          }
      }
      

webpack 打包过程

  • (1)搭建结构,根据 mode 读取配置参数

    • Webpack 本质上是一个函数,它接受一个配置信息作为参数,执行后返回一个 compiler 对象,调用 compiler 对象中的 run 方法就会启动编译。
    • run 方法接受一个回调,可以用来查看编译过程中的错误信息或编译信息。
  • (2)用配置参数对象初始化 Compiler 对象

    • Compiler 它就是整个打包过程的大管家,它里面放着各种你可能需要的编译信息和生命周期 Hook,而且是单例模式。
  • (3)挂载配置文件中的插件

    • Webpack Plugin 其实就是一个普通的函数,在该函数中需要我们定制一个 apply 方法。当 Webpack 内部进行插件挂载时会执行 apply 函数。我们可以在 apply 方法中订阅各种生命周期钩子,当到达对应的时间点时就会执行。
  • (4)执行 Compiler 对象的 run 方法开始执行编译

    • 在正式开始编译前,我们需要先调用 Compiler 中的 run 钩子,表示开始启动编译了;在编译结束后,需要调用 done 钩子,表示编译完成。
    • 编译这个阶段需要单独解耦出来,通过 Compilation 来完成
    class Compiler {
        //省略其他
        run(callback) {
            //省略
        }
    
        compile(callback) {
            //虽然webpack只有一个Compiler,但是每次编译都会产出一个新的Compilation,
            //这里主要是为了考虑到watch模式,它会在启动时先编译一次,然后监听文件变化,如果发生变化会重新开始编译
            //每次编译都会产出一个新的Compilation,代表每次的编译结果
            let compilation = new Compilation(this.options)
            compilation.build(callback) //执行compilation的build方法进行编译,编译成功之后执行回调
        }
    }
    class Compilation {
        constructor(webpackOptions) {
            this.options = webpackOptions
            this.modules = [] //本次编译所有生成出来的模块
            this.chunks = [] //本次编译产出的所有代码块,入口模块和依赖的模块打包在一起为代码块
            this.assets = {} //本次编译产出的资源文件
            this.fileDependencies = [] //本次打包涉及到的文件,这里主要是为了实现watch模式下监听文件的变化,文件发生变化后会重新编译
        }
    
        build(callback) {
            //这里开始做编译工作,编译成功执行callback
            callback()
        }
    }
    
  • (5)根据配置文件中的 entry 配置项找到所有的入口

    • 入口的配置方式 有多种,可以配置成字符串,也可以配置成一个对象,这一步骤就是为了统一配置信息的格式,然后找出所有的入口(考虑多入口打包的场景)
    let entry = {}
    if (typeof this.options.entry === 'string') {
        entry.main = this.options.entry //如果是单入口,将entry:"xx"变成{main:"xx"},这里需要做兼容
    } else {
        entry = this.options.entry
    }
    
  • (6)从入口文件出发,调用配置的 loader 规则,对各模块进行编译

    • Loader 本质上就是一个函数,接收资源文件或者上一个 Loader 产生的结果作为入参,最终输出转换后的结果。
    • 获取入口文件的绝对路径,考虑到操作系统的兼容性问题,需要将路径的 \ 都替换成 /
  • (7)找出此模块所依赖的模块,再对依赖模块进行编译

  • (8)等所有模块都编译完成后,根据模块之间的依赖关系,组装代码块 chunk

    • 一般来说,每个入口文件会对应一个代码块 chunk,每个代码块 chunk 里面会放着本入口模块和它依赖的模块
  • (9)把各个代码块 chunk 转换成一个一个文件加入到输出列表

  • (10)确定好输出内容之后,根据配置的输出路径和文件名,将文件内容写入到文件系统

Loader

  • Loader 本质上是导出为函数的 JavaScript 模块。它接收资源文件或者上一个 Loader 产生的结果作为入参,也可以用多个 Loader 函数组成 loader chain(链),最终输出转换后的结果。
  • loader chain(链)可以保证每个 Loader 的职责单一。同时,也方便后期 Loader 的组合和扩展。

Plugin

  • plugin 系统 本质上就是一种事件流的机制,到了固定的时间节点就广播特定的事件,用户可以在事件内执行特定的逻辑,类似于生命周期:
    • 在打包前需要校验用户传过来的参数,判断格式是否符合要求
    • 在打包过程中,需要知道哪些模块可以忽略编译,直接引用 cdn 链接
    • 在编译完成后,需要将输出的内容插入到 html 文件中
    • 在输出到硬盘前,需要先清空 dist 文件夹
上次编辑于:
贡献者: wanghongjie