webpack 打包 bundle 原理

webpack 打包 bundle 原理分析

const compier = webpack(options)
compier.run()

通过 webpack 生命周期概率,可以知道打包大致过程

  • 读取到配置的入口

    • 入口模板的路径
    • 模块分析
      • 模块依赖(依赖的路径)
        • 可以用递归的方式处理依赖模块
      • 内容处理(处理后的内容代码)
      • 依赖图谱对象
    • webpackBootstrap 入口函数
  • 读取到配置的出口

    • 生产文件
      • 文件存放的位置

根据以上的分析,编写一个自定义 webpack 打包文件

编写自定义 webpack 打包项目

新增 webpack.config.js, 跟 webpack 默认配置一样

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, './dist'), // 输出的文件目录,必须是绝对路径
    filename: 'main.js' // 输出的文件名称
  },
  mode: 'development'
}

在此之前,先安装 @babel 库

npm i -D @babel/parser @babel/traverse @babel/core @babel/preset-env

@babel/parser 来支持解析内容转 ast 树方便解析
@babel/traverse 来可以过滤 ast
@babel/core@babel/preset-envast 转为 js
Ast 抽象语法书:以 node 节点为单位的 tree 树结构

新建 /lib/webpack.js, 打包的核心文件

const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const {transformFromAst} = require('@babel/core')

module.exports = class webpack {
  constructor(options) {
    // console.log(options)
    const {entry, output} = options
    this.entry = entry
    this.output = output
    this.modules = []
  }

  run() {
    const info = this.parse(this.entry)

    // 输出依赖图谱
    this.modules.push(info)
    for (let i = 0; i < this.modules.length; i++) {
      const item = this.modules[i]
      const {yilai} = item

      for (let j in yilai) {
        this.modules.push(this.parse(yilai[j]))
      }
    }

    // 数据结构转换 arr->obj
    const obj = {}
    this.modules.forEach(item => {
      obj[item.entryFile] = item
    })

    this.file(obj)
  }

  parse(entryFile) {
    // 读取入口文件
    const content = fs.readFileSync(entryFile, 'utf-8')

    // 解析内容为 ast 树
    const ast = parser.parse(content, {sourceType: 'module'})

    // 过滤 ast 树
    const dirname = path.dirname(entryFile)

    //分析依赖 以及依赖路径
    // 内容处理
    const yilai = {}
    traverse(ast, {
      ImportDeclaration({node}) {
        const newPathName = './' + path.join(dirname, node.source.value)
        yilai[node.source.value] = newPathName
      }
    })

    // ast 转 js
    const {code} = transformFromAst(ast, null, {
      presets: ['@babel/preset-env']
    })

    return {
      entryFile,
      yilai,
      code
    }
  }

  file(code) {
    // 生产文件,放入到 dist 的目录
    const filePath = path.join(this.output.path, this.output.filename)

    // 生产 bundle 文件内容
    const newCode = JSON.stringify(code)

    const bundle = `(function (modules) {
      function require(module) {
        function pathRequire(relaPath) {
          // relaPath: ./a.js -> ./src/a.js
          return require(modules[module].yilai[relaPath])
        }
        const exports = {};
        (function (require,exports, code) {
          eval(code)
        })(pathRequire,exports, modules[module].code)
        
        return exports
      }

      require('${this.entry}')
    })(${newCode})`

    // webpackBootstrap
    fs.writeFileSync(filePath, bundle, 'utf-8')
  }
}

以下测试代码,新建 src/b.js

export const strb = 'b.js'

新建 src/a.js

import {strb} from "./b.js";
export const stra = 'a.js' + strb

新建入口文件 src/index.js

import {stra} from './a.js'
console.log(stra + 'index.js')

最后新建打包文件 bundle.js

const webapck = require('./lib/webpack')
const options = require('./webpack.config')

new webapck(options).run();

执行 node bundle.js, 即可生产 ./dist/main.js

(function (modules) {
      function require(module) {
        function pathRequire(relaPath) {
          // relaPath: ./a.js -> ./src/a.js
          return require(modules[module].yilai[relaPath])
        }
        const exports = {};
        (function (require,exports, code) {
          eval(code)
        })(pathRequire,exports, modules[module].code)
        
        return exports
      }

      require('./src/index.js')
    })({"./src/index.js":{"entryFile":"./src/index.js","yilai":{"./a.js":"./src\\a.js"},"code":"\"use strict\";\n\nvar _a = require(\"./a.js\");\n\nconsole.log(_a.stra + 'index.js');"},"./src\\a.js":{"entryFile":"./src\\a.js","yilai":{"./b.js":"./src\\b.js"},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports.stra = void 0;\n\nvar _b = require(\"./b.js\");\n\nvar stra = 'a.js' + _b.strb;\nexports.stra = stra;"},"./src\\b.js":{"entryFile":"./src\\b.js","yilai":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n  value: true\n});\nexports.strb = void 0;\nvar strb = 'b.js';\nexports.strb = strb;"}})

运行以上代码结果

a.jsb.jsindex.js

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注