webpack底层原理 实现一个打包工具 bundler源码编写

类似于webpack打包工具的底层原理
步骤:1.拿到入口文件的代码并读出来转化为js对象(抽象语法术parser)
2.拿到所有模块的依赖 ‘./message.js’,放进数组中 引入第三方模块和babel相关
3.对代码进行转换使浏览器能够执行从es6/es module语法转化
目的:入口文件的完整分析
node bundler.js //输出文件内容

	实现
			创建函数
			传入入口文件
			引入fs模块
			使用fs.readFileSync方法读取文件,格式为utf-8
			安装babel/parser模块
			使用parser分析出抽象语法术
			安装traverse模块
			使用traverse进行模块分析获取内容 获取依赖路径
			存储相对路径和绝对路径至对象中
			安装babel/core模块进行代码转换,使代码能够在浏览器上运行
			对所有模块进行分析
			makeDependenciesGraph
			获取模块的路径对象和浏览器运行的代码entryModule
			放入数组中graphArray = [ entryModule]
			循环graphArray .length
			获取当前对象
			获取对象当前的路径对象
			如果路径存在
			遍历路径对象
			将绝对路径传递给dependencies方法并添加进graphArray 数组 可以继续进行遍历 获取依赖当前模块的模块
			创建graph对象
			将graphArray数组遍历
			将对象中的绝对路径 路径对象和代码作为键值存储在graph对象中
			返回graph对象
			generateCode
			将makeDependenciesGraph函数传入的对象转化为字符串graph = JSON.stringify(makeDependenciesGraph(entry))
			输出代码 构造export 和 require依赖
			写一个大的闭包避免污染环境${graph} graph为依赖图谱
			创建require函数传入'${entry}' 拿到入口
			拿到依赖图谱执行对应的代码
			再一个闭包拿到 入口对应的对象.code对应的代码eval(code) ,拿到其中的代码又调用了require,但拿到的是相对路径,无法找到相应的对象
			
			
			module对应的是./message.js
			
			对相对路径做出转换,执行require函数时,变成执行localRequire函数获取relativePath中的值./src/message.js!
			exports创建对象并返回

.src
    index.js
        import message from './message.js';
        console.log(message)

    message.js
        import word from './word.js'
        const message = `say ${word}`;
        export default message;

    word.js
        const word = 'hello';
        export word;

bundler.js
    cnpm install @babel/traverse --save //帮助遍历找出对应项,分析模块
    cnpm install @babel/parser -D
    cnpm install @babel/core -save
    cnpm install @babel/preset-env -save
    const fs = require('fs');
    const parser = require('@babel/parser');
    const traverse = require('@babel/traverse').default;
    const path = require('path');
    const babel = require('@babel/core');

    const moduleAnalyser = (filename) => {
        const content = fs.readFileSync(filename, 'utf-8');
        const ast = parser.parse(content, {
            sourceType: 'module'
        });
        const dependencies = { };

        traverse(ast, {
            ImportDeclaration({ node }) {
                const dirname = path.dirname(filename);
                const newFile = './' + path.join(dirname, node.source.value);       //组成绝对路径
                dependencies[node.source.value] = newFile;      //相对路径为key,绝对路径为value对俩者进行保存
            }
        });
        const { code } = babel.transformFromAst(ast, null, {
            presets: ["@babel/preset-env"]
        })
        return {
            filename,
            dependencies,
            code 
        }
    }


    https://www.babeljs.cn/docs/babel-parser ->网址
 
    //parser.parse  方法分析出抽象语法术来解析出当前的代码 
    //sourceType    es6 module方式编写原代码 做代码分析的时候使用sourceType
    //content       代码的内容传递
    //moduleAnalyser    帮助分析模块
    //:fs               模块是文件操作的封装,它提供了文件的读取,写入,更名,删除,遍历目录,链接POSIX文件系统操
    //ast.program.body  program对应的内容 节点显示代码的声明和结构
    //require('@babel/traverse').default  它默认导出的内容是esmodule导出,如果想用export default导出的内容,必须加一个default这样的属性
    //ast   抽象语法术内容
    //ImportDeclaration     节点
    //node      内容里面的子内容
    //dependencies      数组,只要碰到这种依赖就放到里面去
    //抽象语法术将js代码转化为js对象
    //transformFromAst  抽象语法术转化为对象,这个对象会有code这样的字段,code就是可以在浏览器运行的代码

Dependencies Graph依赖图谱
目标:对工程里所有模块进行分析
难点:使用队列的方式实现递归的效果

const makeDependenciesGraph = (entry) => {  //入口文件
    const entryModule = moduleAnalyser(entry)
    const graphArray = [ entryModule ];
    for (let i = 0; i < graphArray.length; i++) {
        const item = graphArray[i];
        const { dependencies } = item;
        if(dependencies) {
            for(let j in dependencies)//使用对象遍历的语法{
                graphArray.push(moduleAnalyser(dependencies[j]))
            }
        }
    }
    const  graph = {};
    graphArray.forEach(item => {
        graph[item.filename] = {
            dependencies: item.dependencies,
            code: item.code
        }
    })

    return graph
}

 
//const { dependencies } = item;    拿到对象文件进一步分析,拿到dependencies就可以对其中的依赖模块进行分析

对代码进行转换使浏览器能够执行
闭包的方式
构造require函数,export对象

const generateCode = (entry) => {
    const graph = JSON.stringify(makeDependenciesGraph(entry));
    return `                                                                            //返回的是一段字符串,代码
        (function(graph) {                                                              //代码放在大的闭包中执行避免污染环境
            function require(module) {                                                 
                function localRequire(relativePath) {                                   //将相对路径进行转化
                    return require(graph[module].dependencies[relativePath])            //relativePath === ./message.js  在graph对象中dependencies中寻找键对应的值//返回真实的路径
                }                                                                       //再调用外部的require,递归着执行下面的函数


                var exports = {};                                                       //
                (function(require, exports, code) {                                     //每一个模块的代码放在一个闭包中执行,避免污染全局
                    eval(code)                                                          //eval(code) 的时候require方法又会重新被调用,但传递的是相对路径,然后去graph[module]中寻找./message中的相应对象,但graph中的路径为./src/message.js所以找不到对应的模块,所以需要对相对路径做出转换
                                                                                        //所以构造localRequire方法  function(require)   localRequire eval(code) 的时候执行的函数为localRequire而不是require
                })(localRequire, exports, graph[module].code)                           //graph[module].code 对应模块中的代码
                return exports;                                                         //export就是一个空对象,代码执行的时候接受一些内容并导出
            };
            require('${entry}')                                                         //调用require方法,拼接字符串所以需要添加引号
        })(${graph});                                                                   
    `;
}

const code = generateCode('./src/index.js')
console.log(code)
//JSON.stringify    将对象转化为字符串
//relativePath   ./message.js

最后把console出来的code信息在chrome浏览器中运行

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值