简单实现一个bundler

首先创建好文件
简单实现一个bundler_第1张图片
./src/word.js

export const word = "hello";

./src/message.js

import { word } from "./word.js";

const message = "say " + word;

export default message;

./src/index.js

import message from "./message.js";

console.log(message);

首先需要读取文件内容,在bundler.js中:

const fs = require("fs");
const parser = require("@babel/parser");

//模块分析
const moduleAnalyser = (filename) => {
    // 读取文件内容
    const content = fs.readFileSync(filename,"utf-8");
    // 并将其转换为AST抽象语法树
    //安装@babel/parser
    const ast = parser.parse(content,{
        sourceType:"module"
    })
    //打印ast
    console.log(ast);
   // console.log(ast.program.body);

}

打印显示:
简单实现一个bundler_第2张图片
在打印出它进程中的body内容

console.log(ast.program.body);

显示:
简单实现一个bundler_第3张图片
type为‘ImportDeclaration‘表示为引入模块,所以我们需要提取出该Node中中的value值,将相对路径变成绝对路径,在这里我们使用babel/traverse工具遍历抽象语法树:

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const path = require("path");

//模块分析
const moduleAnalyser = (filename) => {
    // 读取文件内容
    const content = fs.readFileSync(filename, "utf-8");
    // 并将其转换为AST抽象语法树
    //安装@babel/parser
    const ast = parser.parse(content, {
        sourceType: "module"
    })
    //遍历抽象语法树
    // console.log(ast);
    // console.log(ast.program.body);

    //创建一个对象,收集模块名字
    const dependencies = {};

    //利用@babel/traverse工具遍历ast
    traverse(ast, {
        ImportDeclaration({ node }) {
            //可找出node.source.value为相对路径
            //获取绝对路径
            const dirname = path.dirname(filename);
            //注意直接使用path.join() 会变成'src\\message‘,所以我用的是path.posix.join()
            const newFile = path.posix.join(dirname, node.source.value)
            //key为相对路径,value为绝对路径
            dependencies[node.source.value] = newFile;
        }
    })
    //打印出收集的模块
    console.log(dependencies);

}
moduleAnalyser("./src/index.js");

收集到的模块打印出来:

{ './message': 'src/message' }

利用@babel/core将语法树转换为代码,并将内容返回

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");
    // 并将其转换为AST抽象语法树
    //安装@babel/parser
    const ast = parser.parse(content, {
        sourceType: "module"
    })
    //遍历抽象语法树
    // console.log(ast);
    // console.log(ast.program.body);

    //创建一个对象,收集模块名字
    const dependencies = {};

    //利用@babel/traverse工具遍历ast
    traverse(ast, {
        ImportDeclaration({ node }) {
            //可找出node.source.value为相对路径
            //获取绝对路径
            const dirname = path.dirname(filename);
            //注意直接使用path.join() 会变成'src\\message‘,所以我用的是path.posix.join()
            const newFile = path.posix.join(dirname, node.source.value)
            //key为相对路径,value为绝对路径
            dependencies[node.source.value] = newFile;
        }
    });
    //利用@babel/core将语法树转换为代码
    const { code } = babel.transformFromAst(ast, null, {
        presets: ["@babel/preset-env"]
    });
    return {
        filename, //入口文件
        dependencies,//收集模块名字
        code//转换好的代码
    }

}
moduleAnalyser("./src/index.js");

我们需要利用一个依赖图谱存放所有的模块信息

//依赖图谱存放所有的模块信息
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;
        //如果存在依赖的模块,并对其进行分析,存放到graphArray中
        //依赖的模块仍有自己依赖的模块并继续深入分析
        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
        }
    });
    console.log(graph);
}

makeDependenciesGraph("./src/index.js");

打印出来:
简单实现一个bundler_第4张图片
做好这些之后我们就需要让代码成可以在浏览器中执行


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");
    // 并将其转换为AST抽象语法树
    //安装@babel/parser
    const ast = parser.parse(content, {
        sourceType: "module"
    })
    //遍历抽象语法树
    // console.log(ast);
    // console.log(ast.program.body);

    //创建一个对象,收集模块名字
    const dependencies = {};

    //利用@babel/traverse工具遍历ast
    traverse(ast, {
        ImportDeclaration({ node }) {
            //可找出node.source.value为相对路径
            //获取绝对路径
            const dirname = path.dirname(filename);
            //注意直接使用path.join() 会变成'src\\message‘,所以我用的是path.posix.join()
            const newFile = path.posix.join(dirname, node.source.value)
            //key为相对路径,value为绝对路径
            dependencies[node.source.value] = newFile;
        }
    });
    //利用@babel/core将语法树转换为代码
    const { code } = babel.transformFromAst(ast, null, {
        presets: ["@babel/preset-env"]
    });
    return {
        filename, //入口文件
        dependencies,//收集模块名字
        code//转换好的代码
    }

}
//依赖图谱存放所有的模块信息
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;
        //如果存在依赖的模块,并对其进行分析,存放到graphArray中
        //依赖的模块仍有自己依赖的模块并继续深入分析
        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 generateCode = (module) => {
    //将其转换成JSON字符串
    const graph = JSON.stringify(makeDependenciesGraph(module));
    return `
    (function (graph) {
        function require(module) {
            function localRequire(relativePath) {
                return require(graph[module].dependencies[relativePath]);
            }
            var exports = {};
            (function (require,exports,code) {
                eval(code)
            })(localRequire,exports,graph[module].code)
            return exports;
        }
        require('${ module}')
    })(${ graph})
    `
}


console.log(generateCode("./src/index.js"))

注意:

  • 利用闭包为了防止全局污染
  • localRequire()函数是为了让代码中的相对路径转换为绝对路径
  • 代码中有变量exports,刚开始需要创建成一个空对象

运行的结果显示为:
简单实现一个bundler_第5张图片
将其放在浏览器中执行:
简单实现一个bundler_第6张图片

你可能感兴趣的:(webpack)