webpack机制

算法琉璃客
• 阅读 2088

简介

以下仅为个人粗略总结和代码,看不懂的稍加理解,本文主要用做个人记录。

先大致总结一下

  • 1.从哪里开始:webpack根据入口模块开始。
  • 2.如何进行:递归读取每个文件,会形成一个依赖列表,依赖列表的,依赖列表是一个以文件相对路径为key,文件内容为value的对象。
  • 3.如何处理:对于每个文件会通过AST解析语法树,返回源码。
  • 4.loader在哪:loader是何时何地进行处理?它是在读取文件的时候开始起作用,通过正则匹配文件是否需要处理。然后读取配置文件的loader配置,然后通过递归的方式处理文件。
  • 5. Plugins呢:plugins是在合适的时机开始工作,那么这个合适时机如何控制呢,是通过tapable事件流机制,实现发布订阅模式。
  • 6.最后:最后返回的是一个匿名自执行函数,定义了一个webpack__require方法,解析传入的依赖列表,递归执行。

然后看下代码

核心代码如下:

//入口文件webpack.js
#! /usr/bin/env node

//第一步:找到当前执行命令的路径,拿到webpack.config.js
let path = require("path")
let config = require(path.resolve('webpack.config.js'))
console.log(path.resolve(),'resolve--------------->')
let Compiler = require('../lib/Compiler')
let compiler = new Compiler(config)

//标识运行编译
compiler.run()
//Compiler.js
const fs = require('fs')
const path = require('path')
const babylon = require('babylon')
const travere = require('@babel/traverse').default
const t = require('@babel/types')
const generator = require('@babel/generator').default
const ejs = require('ejs')
const {SyncHook} = require('tapable')
//babylon把源码转为AST
//@babel/traverse
//@babel/generator
//@babel/types

class Compiler {
  constructor(config) {
    this.config = config //保存入口文件路径
    this.entryID = '' //主模块入口路径
    this.modules = {} //存放模块依赖关系
    this.entry = config.entry //入口路径
    this.root = process.cwd() //当前工作目录
    this.hooks = {
      entryOption: new SyncHook(),
      compile: new SyncHook(),
      afterCompile: new SyncHook(),
      afterPlugins: new SyncHook(),
      run: new SyncHook(),
      emit: new SyncHook(),
      done: new SyncHook(),
    }
    //如果传递了plugins参数
    let plugins = this.config.plugins
    if(Array.isArray(plugins)) {
      plugins.forEach(plugin => {
        plugin.apply(this)
      })
    }
  }
  
  run() {
    //执行,并创建模块的依赖关系
    this.buildModule(path.resolve(this.root, this.entry), true)
    //发射一个文件,就是打包后的文件
    this.emitFile()
  }

  //构建模块
  buildModule(modulePath, isEntry) {
    //首先读取入口文件
    let source = this.getSource(modulePath)
    //模块的ID = this.root - modulePath
    let moduleName = './' + path.relative(this.root, modulePath)
    if(isEntry) {
      this.entryID = moduleName //保存入口名字
    }
    //解析,需要把source源码进行改造,返回一个依赖列表
    let {sourceCde, dependencies } = this.parse(source, path.dirname(moduleName))
    this.modules[moduleName] = sourceCde

    dependencies.forEach(dep => { //附模块的递归加载
      this.buildModule(path.join(this.root, dep), false)
    })
  }

  //解析源码, AST解析语法树
  parse(source, parentPath) {
    // console.log(source, parentPath)
    let ast = babylon(source)
    let dependencies = [] //依赖数组
    travere(ast, {
      CallExpression() {
        let node = p.node
        if(node.callee.name === 'require') {
          node.callee.name = '_webpack_require_'
          let moduleName = node.arguments[0].value //这里就是引用模块的名字
          moduleName = moduleName + (path.extname(moduleName) ? '' : '.js')
          moduleName = './' + path.join(parentPath, moduleName) //'src/a.js'
          dependencies.push(moduleName)
          node.arguments = [t.stringLiteral(moduleName)]
        }
      }
    })
    let sourceCode = generator(ast).code
    return {sourceCode, dependencies}
  }

  //公用读文件的方法
  getSource(modulePath) {
    let content = fs.readFileSync(modulePath, 'utf8')
    let rules = this.config.module.rules //拿到规则
    //拿到每个规则来处理
    for(let i=0; i<rules.length; i++) {
      let rule = rules[i]
      let { test, use } = rule
      let len = use.length - 1
      if(test.test(modulePath)) { //这个模块需要通过loader转换
        function normalLoader() {
          let loader = require(use[len]) //获取对应loader函数
          content = loader(content)
          //递归调用loader
          if(len >= 0) {
            normalLoader()
          }
        }
        normalLoader()
      }
    }
    
    return content
  }

  //发射文件
  emitFile() {
    //用数据 渲染我们的
    //拿到输出到哪个目录下
    let main = path.join(this.config.output.path, this.config.output.filename)
    //模板路径
    let templateStr = this.getSource(path.join(__dirname, 'main.ejs'))
    let code = ejs.render(templateStr, {entryId: this.entryID, modules: this.modules})
    this.assets = {
      //资源中路径对应的代码
     }
    this.assets[main] = code
    fs.writeFileSync(main, this.assets[main])
  }
}

module.exports = Compiler
点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
vue 中安装使用sass 报错遇到的问题整理
不出错的情况下,正常安装:1.安装包:npminstallnodesasssavedevnpminstallsassloadersavedev(sassloader依赖于nodesass)2.在build文件夹下的webpack.base.conf.js的rules里面添加配置
晴空闲云 晴空闲云
3年前
webpack配置typescript详解
随着现在typescript使用越来越多,作为打包工具界的webpack怎么编译typescript呢?下面我把自己的实践记录一下,成功编译了typescript文件,并且引入typescript模块后,也可以成功编译。我们从新建webpack项目开始,在此之前先贴一下环境,经常环境不同会造成不同的状况,这边先贴上的环境:$nodevv14.15.4$
皮卡皮卡皮 皮卡皮卡皮
4年前
webpack 基本配置
概念本质上,webpack是一个现代JavaScript应用程序的静态模块打包器(modulebundler)。当webpack处理应用程序时,它会递归地构建一个依赖关系图(dependencygraph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。安装确保安装了nodejs项目文件环境cd
Jacquelyn38 Jacquelyn38
4年前
React.js中JSX的原理与关键实现
在开始开发之前,我们需要创建一个空项目文件夹。安装1.初始化npm init y2.安装webpack相关依赖npm install webpack webpackcli D3.安装babelloader相关依赖npm install babelloader @babel/core @babel/presetenv D4.
Jacquelyn38 Jacquelyn38
4年前
Webpack学习整理集锦【从最基础的demo入手,自己实现一个脚手架 】
前言本质上,webpack是一个现代JavaScript应用程序的静态模块打包器(modulebundler)。当webpack处理应用程序时,它会递归地构建一个依赖关系图(dependencygraph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。开源网址https://github.com/maomi
Stella981 Stella981
3年前
JS 中的require 和 import 区别
在研究react和webpack的时候,经常看到在js文件中出现require,还有import,这两个都是为了JS模块化编程使用。CSS的是@import1.ES6模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。Require是CommonJS的语法,CommonJS的模块是对象,输入时
Stella981 Stella981
3年前
Redis 列表(List)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)一个列表最多可以包含2321个元素(4294967295,每个列表超过40亿个元素)。实例redis127.0.0.1:6379LPUSHw3ckeyredis(integer)1
Wesley13 Wesley13
3年前
KO
KOKO是一个基于Webpack开发的快速开始Web开发的脚手架工具,具有以下特性:可以当做一个Webpack配置种子来使用,无需二次配置、开箱即用自动支持多页应用(可选)Vue单文件组件的开发方式资源分块加载,内联和异步加载方式管理,低成本实现首屏优化支
Stella981 Stella981
3年前
Harmony OS 开发避坑指南——源码下载和编译
本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个目标平台的二进制文件。坑点总结:1.下载源码基本上没有太多坑,可以很顺利的进行2.编译源码主要的一个大坑是,默认版本的scons依赖python3.7,鸿蒙基础编译代码依赖p
Stella981 Stella981
3年前
Redis压缩列表
此篇文章是主要介绍Redis在数据存储方面的其中一种方式,压缩列表。本文会介绍1.压缩列表(ziplist)的使用场景2.如何达到节约内存的效果?3.压缩列表的存储格式4.连锁更新的问题 5.conf文件配置。在实践上的操作主要是对conf配置文件进行配置,具体上没有确切的一个值,更多是经验值。也有的项目会直接使用原本的默认值。此篇对于更好地理解
算法琉璃客
算法琉璃客
Lv1
思欲委符节,引竿自刺船。
文章
3
粉丝
0
获赞
0