webpack-bundler-demo-2 发表于 2019-01-03 | 分类于 Demo | 参考链接 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123const fs = require("fs");const path = require("path");const babylon = require("@babel/parser");const traverse = require("@babel/traverse").default;const babel = require("@babel/core");let ID = 0;//读取文件信息,并获得当前js文件的依赖关系function createAsset(filename) { //获取文件,返回值是字符串 const content = fs.readFileSync(filename, "utf-8"); //讲字符串为ast(抽象语法树, 这个是编译原理的知识,说得简单一点就是,可以把js文件里的代码抽象成一个对象,代码的信息会存在对象中) //babylon 这个工具是是负责解析字符串并生产ast。 const ast = babylon.parse(content, { sourceType: "module" }); //用来存储 文件所依赖的模块,简单来说就是,当前js文件 import 了哪些文件,都会保存在这个数组里 const dependencies = []; //遍历当前ast(抽象语法树) traverse(ast, { //找到有 import语法 的对应节点 ImportDeclaration: ({ node }) => { //把当前依赖的模块加入到数组中,其实这存的是字符串, //例如 如果当前js文件 有一句 import message from './message.js', //'./message.js' === node.source.value dependencies.push(node.source.value); } }); //模块的id 从0开始, 相当一个js文件 可以看成一个模块 const id = ID++; //这边主要把ES6 的代码转成 ES5 const { code } = babel.transformFromAstSync(ast, null, { presets: ["@babel/preset-env"] }); return { id, filename, dependencies, code };}//从入口开始分析所有依赖项,形成依赖图,采用广度遍历function createGraph(entry) { const mainAsset = createAsset(entry); //既然要广度遍历肯定要有一个队列,第一个元素肯定是 从 "./example/entry.js" 返回的信息 const queue = [mainAsset]; for (const asset of queue) { const dirname = path.dirname(asset.filename); //新增一个属性来保存子依赖项的数据 //保存类似 这样的数据结构 ---> {"./message.js" : 1} asset.mapping = {}; asset.dependencies.forEach(relativePath => { const absolutePath = path.join(dirname, relativePath); //获得子依赖(子模块)的依赖项、代码、模块id,文件名 const child = createAsset(absolutePath); //给子依赖项赋值, asset.mapping[relativePath] = child.id; //将子依赖也加入队列中,广度遍历 queue.push(child); }); } return queue;}//根据生成的依赖关系图,生成对应环境能执行的代码,目前是生产浏览器可以执行的function bundle(graph) { let modules = ""; //循环依赖关系,并把每个模块中的代码存在function作用域里 graph.forEach(mod => { modules += `${mod.id}:[ function (require, module, exports){ ${mod.code} }, ${JSON.stringify(mod.mapping)}, ],`; }); //require, module, exports 是 cjs的标准不能再浏览器中直接使用,所以这里模拟cjs模块加载,执行,导出操作。 const result = ` (function(modules){ //创建require函数, 它接受一个模块ID(这个模块id是数字0,1,2) ,它会在我们上面定义 modules 中找到对应是模块. function require(id){ const [fn, mapping] = modules[id]; function localRequire(relativePath){ //根据模块的路径在mapping中找到对应的模块id return require(mapping[relativePath]); } const module = {exports:{}}; //执行每个模块的代码。 fn(localRequire,module,module.exports); return module.exports; } //执行入口文件, require(0); })({${modules}}) `; return result;}const graph = createGraph("./example/entry.js");const ret = bundle(graph);// 打包生成文件fs.writeFileSync("./bundle.js", ret); 坚持原创技术分享,您的支持将鼓励我继续创作! 赏 微信打赏 支付宝打赏