webpack机制

栏目: JavaScript · 发布时间: 5年前

内容简介:以下仅为个人粗略总结和代码,看不懂的稍加理解,本文主要用做个人记录。核心代码如下:

简介

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

先大致总结一下

  • 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

以上所述就是小编给大家介绍的《webpack机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Redis开发与运维

Redis开发与运维

付磊、张益军 / 机械工业出版社 / 2017-3-1 / 89.00

本书全面讲解Redis基本功能及其应用,并结合线上开发与运维监控中的实际使用案例,深入分析并总结了实际开发运维中遇到的“陷阱”,以及背后的原因, 包含大规模集群开发与管理的场景、应用案例与开发技巧,为高效开发运维提供了大量实际经验和建议。本书不要求读者有任何Redis使用经验,对入门与进阶DevOps的开发者提供有价值的帮助。主要内容包括:Redis的安装配置、API、各种高效功能、客户端、持久化......一起来看看 《Redis开发与运维》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具