webpack原理的简易实现

webpack打包的过程 : 从入口文件开始分析模块依赖,然后深度遍历生成依赖图谱,再然后根据依赖图谱生成抽象语法树AST,最后分析AST拼接js代码输出,这个时候输出的代码就可以在浏览器直接执行了。

在上面文件目录中,index.js引入了message.js ,在message.js中引入了word.js ,我们把index.js作为入口文件打包这三个文件

新建src同级目录文件bundler.js,

首先要分析index.js的页面结构,返回文件名,依赖,可执行代码

const moduleAnalyser = (filename) => {
    //利用node fs模块读取文件内容
    const content = fs.readFileSync(filename, 'utf-8');
    //使用@babel/parser 分析文件内容生成AST
    const ast = parser.parse(content, {
        sourceType: 'module'
    });
    // console.log(ast);
    //使用 @babel/traverse 分析AST
    const dependencies = {};
    traverse(ast, {
        ImportDeclaration({ node }) {
            //利用 node path 拼接出依赖的完整路径
            const dirname = path.dirname(filename)
            const newFile = path.join(dirname, node.source.value)
            dependencies[node.source.value] = newFile
        }
    })

    // console.log(dependencies);
    //利用@babel/core 解析import语句 ,并且 @babel/preset-env把es6语法转为浏览器可执行的es5
    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;
        if (dependencies) {
            for (let j in dependencies) {
                graphArray.push(moduleAnalyser(dependencies[j]))
            }
        }
    }

    const graph = {};
    graphArray.forEach((item) => {
        graph[item.filename] = {
            dependencies: item.dependencies,//{ './word.js': 'src\\word.js' }
            code: item.code
        }
    })
     console.log(graphArray,'graph');
    return graph;
}

生成浏览器可执行代码

 const generateCode = (entry) => {
    const graph = JSON.stringify(makeDependenciesGraph(entry));
    //要先把对象转换为字符串,不然在下面的模板字符串中会默认调取对象的toString方法,参数变成[Object object]
    console.log(graph);
    //定义require exports 以便浏览器执行
    return `
    (function(graph){
            //第一层的require
            function require(module){
                function localRequire(relativePath){
                    console.log(relativePath,module)
                    //这里执行第一层的require方法
                    return require(graph[module].dependencies[relativePath])
                }
                var exports={};
                (function (require,exports,code) {
                    eval(code);
                    //执行eval(code)时,code里面还有依赖就继续执行require,
                    //不过这个时候需要把相对路径转成绝对路径,就执行作为require参数名传进来的localRequire,完成路径转换后继续执行第一层的require
                })(localRequire,exports,graph[module].code);
                return exports;
            }
            require('${entry}')
        })(${graph});
    `;

}

}

最后执行 generateCode(‘./src/index.js’); 就可以拿到最后打包的代码了

git https://github.com/dz333333/easy_webpack.git

参考 : https://juejin.im/post/5d00fbbb5188255e780b64b1#heading-3

发表评论

您的电子邮箱地址不会被公开。