webpack打包原理了解

本文详细解析webpack打包过程,从配置文件创建到脚本执行,深入探讨如何分析依赖、递归处理模块,并最终生成可由浏览器解析的打包文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先搭建webpack环境配置:
1.在package.json文件中创建
在这里插入图片描述
可以直接黑窗中使用testWebpack执行bin目录下的这个脚本
注: 需要在运行的脚本中声明node环境执行 #!/usr/bin/env node

//执行的脚本文件
/**
  1.获取打包的配置内容
  2.使用自己写的testWebpack分析依赖来打包
*/

  //src下创建js脚本 news.js index.js
  //news.js  导出一个对象属性
  module.exports = {
    name:'张三'
}

  //index.js  使用commentjs规范 导入news.js文件
  let news = require('./news');
  console.log(news.name);

  //testWeb.pack.js  模拟webpack中的entry 入口
  module.exports =  {
    entry:'./src/index.js',
  }
  

package.json执行的脚本
1.首先获取打包的配置文件
2.创建lib下compiler.js来执行打包,分析打包入口entry依赖

 //bin目录testWebpack.js
  //bin/testWebpack.js
  #!/usr/bin/env node
  //导入node path模块处理文件路径
const path = require('path');
// 读取需要打包的配置文件    获取配置文件的绝对路径
let webpack = require(path.resolve("testWeb.pack.js"))
//配置文件内容 模拟webpack中的entry
console.log(webpack)
// 通过面向对象的方式来进行项目推进
let compiler = require("../lib/compiler.js")
//实例化compiler执行打包依赖分析
new compiler(webpack).start()

lib下compiler.js。
webpack打包文件是通过{"./src/news.js“:webpack_require()}来执行
1.读取entry入口的文件,获取该入口文件的内容 使用node的fs模块来获取脚本内容fs.readFileSync(path, ‘utf-8’);
2.获取的入口文件包含require 浏览器无法解析需要将require替换成__webpack_require__
(此时需要使用到3个babel全家桶替换获取到的内容,1.使用@babel/parser将es6代码转换成ast抽象语法树。 2.使用@babel/traverse替换抽象语法树符合条件的内容。3.使用@babel/generator将对象语法树转换成源码)

//lib/compiler.js
const path = require('path');
const fs = require('fs');

// 将es6代码转换成ast抽象语法树
const paser = require('@babel/parser')

// 将ast抽象语法树替换内容    es6模块导出的需要点一下default
const traverse = require('@babel/traverse').default;

// 将ast转换成源码使用generator转换
const generator = require('@babel/generator').default;



class Compiler {
    constructor(config) {
        this.config = config;;
        this.entry = config.entry;
        //  获取执行指令的目录
        this.root = process.cwd();
    }

    getSource(path) {
        //读取当前路径下的文件 获取文件的代码
        return fs.readFileSync(path, 'utf-8');
    }

    depAnalyse(modlePath) {
        let result = this.getSource(modlePath)
        //    将代码转换成抽象语法树  对象
        let ast = paser.parse(result)
        // console.log(ast);
        // 使用traverse模块将ast抽象语法树找到的内容进行替换  修改ast
        traverse(ast, {
            CallExpression(p) {
                console.log(p.node.callee.name)
                if (p.node.callee.name == 'require') {
                    // 将代码中requre替换成__webpack_require__
                    p.node.callee.name = '__webpack_require__';

                    // 将./news.js 替换成./src/news.js
                    let value = p.node.arguments[0].value
                    value = "./" + (path.join("src", value));
                    //    多做一个处理将所有\\改成/
                    value.replace(/\\+/g, "/")
                    p.node.arguments[0].value = value
                    console.log(p.node.arguments[0].value)
                }
            }
        })
        // 获取替换后的代码
        let sourceCode = generator(ast).code;
        console.log(sourceCode)
    }

    start() {
        //开始打包了
        // 依赖分析
        this.depAnalyse(path.resolve(this.root, this.entry))
    }
}


module.exports = Compiler

此时已经分析完单个文件的依赖了,但一个入口文件可能会有多个依赖,需要对这些依赖递归获取内容处理,创建一个内部数组存储所有的文件依赖,遍历该数组将依赖一层一层递归下去修改依赖替换相应的代码

depAnalyse(modlePath) {
        let result = this.getSource(modlePath)
        //    将代码转换成抽象语法树  对象
        let ast = paser.parse(result)
        // console.log(ast);
        //存储所有的依赖
        let depArr =[]
        // 使用traverse模块将ast抽象语法树找到的内容进行替换  修改ast
        traverse(ast, {
            CallExpression(p) {
                console.log(p.node.callee.name)
                if (p.node.callee.name == 'require') {
                    // 将代码中requre替换成__webpack_require__
                    p.node.callee.name = '__webpack_require__';

                    // 将./news.js 替换成./src/news.js
                    let value = p.node.arguments[0].value
                    value = "./" + (path.join("src", value));
                    //    多做一个处理将所有\\改成/
                    value.replace(/\\+/g, "/")
                    p.node.arguments[0].value = value
                    console.log(p.node.arguments[0].value)
                    depArr.push(p.node.arguments[0].value)
                }
            }
        })
        // 获取替换后的代码
        let sourceCode = generator(ast).code;
        console.log(sourceCode)
        //遍历所有的依赖文件 递归处理所有依赖
        // 遍历该数组  将依赖一层一层递归获取内容替换相应代码
        depArr.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))
    }

所有依赖获取并修改内容后创建一个modules对象,存储所有源码 将所有依赖文件处理成{’./src/index.js’:’./src/index.js源码’}以此类推

  let result = this.getSource(modlePath)
        //    将代码转换成抽象语法树  对象
        let ast = paser.parse(result)
        // console.log(ast);
        // 用来存储所有获得的文件路径
        let depArr = []
        // 使用traverse模块将ast抽象语法树找到的内容进行替换  修改ast  CallExpression()钩子
        traverse(ast, {
            CallExpression(p) {
                console.log(p.node.callee.name)
                if (p.node.callee.name == 'require') {
                    // 将代码中requre替换成__webpack_require__
                    p.node.callee.name = '__webpack_require__';

                    // 将./news.js 替换成./src/news.js
                    let value = p.node.arguments[0].value
                    value = "./" + (path.join("src", value));
                    //    多做一个处理将所有\\改成/
                    value.replace(/\\+/g, "/")
                    p.node.arguments[0].value = value
                    // 每找到一个require调用,修改路径后将其存入依赖数组中 
                    depArr.push(p.node.arguments[0].value)
                }
            }
        })
        // 获取替换后的代码 将ast语法树转换成源码
        let sourceCode = generator(ast).code;

        // 获取相对路径  
        let newModulesPath = path.relative(this.root, modlePath)
        // 将获取到到路径手动拼接修改得到一个新的相对路径
        newModulesPath = "./"+newModulesPath.replace(/\\+/g, "/");
        //将所有模块存入modules中  {'./src/inde.js':'xxx代码'}
        this.modules[newModulesPath] = sourceCode;
        console.log(this.modules)

        // 遍历该数组  将依赖一层一层递归获取内容替换相应代码
        depArr.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))

此时打印的this.modules内容如下
在这里插入图片描述
使用模版拼接代码然后使用node fs模块读写到打包配置文件output出口目录下

//ejs文件
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};

// The require function
function __webpack_require__(moduleId) {

// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};

// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// Flag the module as loaded
module.l = true;

// Return the exports of the module
return module.exports;
}


// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "<%-entry%>");
})
({
<% for (let k in modules) { %>

"<%-k%>": (function (module, exports, __webpack_require__) {
eval(`<%-modules[k]%>`);
}),
<% } %>
});

// 将文件输出
    outputFile() {
        // 读取写好的模版字符串
        let template = this.getSource(path.resolve(__dirname, "../template/output.ejs"));

        //读取模版
        let result = ejs.render(template, {
            entry: this.entry,
            modules: this.modules
        })

        console.log(result)
        //   拼接打包配置文件的路径获取输出文件的路径
        let outputFilePath = path.join(this.config.output.path, this.config.output.filename);
        //使用fs模块写出内容到配置输出目录下
        fs.writeFileSync(outputFilePath, result)
    }

    start() {
        //开始打包了
        // 依赖分析
        this.depAnalyse(path.resolve(this.root, this.entry))

        // 依赖分析完后 将文件输出
        this.outputFile()

    }

打印的模版内容如下
在这里插入图片描述
此时已经完成基本的打包步骤了,浏览器正常解析运行
在这里插入图片描述
总结:ps纯属练习不好莫怪

/**
 * webpack打包原理     将所有依赖文件处理成{'./src/index.js':'./src/index.js源码'}
 * 1.创建配置文件
 * 2.创建执行脚本 package.json中配置 bin/**.js文件
 * 3.运行脚本中获取配置文件配置
 * 4.创建打包分析依赖脚本Compiler  1.获取配置文件的入口路径 2.使用node中fs模块读取该配置文件的内容,因webpack无法解析require 需要将内容的require修改成__webpack_require__字段,将路径修改成./src/index.js 使用babel全家桶 @babel/parser将获取到的入口文件内容转换成ast语法树  使用@babel/traverse修改ast语法树匹配的内容,使用@babel/generator将ast语法树转换成源码
 * 5.一个入口文件可能会有多个依赖,需要对这些依赖递归获取内容处理。创建一个内部数组存储所有的文件依赖,遍历该数组将依赖一层一层递归下去修改依赖替换相应的代码
 * 6.创建modules对象 存储所有源码 将所有依赖文件处理成{'./src/index.js':'./src/index.js源码'}以此类推 
 * 7.将打包js使用模版生成字符串 写出到打包配置中到出口也就是dist/bundels.js中
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值