1. 首页

webpack简易打包原理

关于最近学习webapck打包原理的一些新的分享 

我们都知道webpack是静态模块打包器,他能根据我们的项目入口,找到模块与模块之间的依赖,帮我们构建出我们最终想要的代码, 并顺利的在浏览器运行,但是平时工作中可能我们只知道这些东西

 1.webpack需要配置很多loader,插件… 

 2.webpack源码复杂,生命周期特别多… 

 3.想深入了解无处下手

 那现在就来看看webpack是如何进行打包,以及依赖查找的 

 模块化打包 

最终打包结果得到两样东西

1.一个与模块依赖的对象参数

2.自执行函数

(function (modules) {
  function exec(id) {
    let [fn, mapping] = modules[id];
    console.log(fn, mapping)
    let module = { exports: {} };
    fn && fn(require, module.exports, module);
    function require(path) {
      //根据模块路径,返回模块执行的结果
      return exec(mapping[path]);
    }
    return module.exports;
  }
  exec(0)
})(
  {
    0: [
      function (require, exports, module) {
        let action = require('./action.js').action;
        let name = require('./name.js').name;

        let message = `${name} is ${action}`;
        console.log(message);
      },
      { "./action.js": 1, "./name.js": 2 }
    ], 1: [
      function (require, exports, module) {
        let action = '我是action模块';

        exports.action = action;
      },
      {}
    ], 2: [
      function (require, exports, module) {
        exports.name = '我是name模块';

      },
      {}
    ],
  }
)


各个模块具体内容

index.js


let action = require('./action.js').action; let name = require('./name.js').name; let message = `${name} and ${action}`; console.log(message);

action.js


let action = '我是action模块';exports.action = action;

name.js


exports.name = '我是name模块';

需要构建的数据结构

我们发现一个模块的定义的数据结构是这样的

  • 模块自增id
  • 被一个函数包裹的模块内容,为了在浏览器能运行,重写了require方法,requier作为一个模块的参数传递
  • 与模块相关的依赖模块

Javascript中文网是以前端进阶资源教程分享为主的专业网站

实现这个数据结构

模块定义

  • 唯一id

  • 文件路径

  • 包含依赖文件的数组

  • 模块内容

  • id与模块间的引用关系

我们先来尝试处理前面三个


const fs = require('fs'); const path = require('path'); let ID = 0; /** * 递归获取模块依赖 * @param {*} str 模块内容 * @return 模块依赖的数组集合 */ function getDependencies(str) { let reg = /require\(['"](.+?)['"]\)/g;//正则匹配ruquire后面的路径 let result = null; let dependencies = []; while (result = reg.exec(str)) { dependencies.push(result[1]); } return dependencies; } /** * 获取模块对象 * @param {*} filename 文件路径 * @return 模块对象 */ function createAsset(filename) { let fileContent = fs.readFileSync(filename, 'utf-8'); const id = ID++; return { id: id, filename: filename, dependencies: getDependencies(fileContent), //当前模块所依赖模块 code: `function(require, exports, module) { ${fileContent} }` } }

 createAsset返回值,但是这个返回值无法帮我们拿到模块之间的引用,也就是这个对象{‘./action.js’: 1},如果我知道了这个对象引用的id为1,那么我们就能拿到完整的模块集合了


{ id: 0, filename: './src/index.js', dependencies: [ './action.js', './name.js' ], code: 'function(require, exports, module) { \n' + " let action = require('./action.js').action;\n" + "let name = require('./name.js').name;\n" + '\n' + 'let message = `${name} is ${action}`;\n' + 'console.log(message);\n' + ' }' }

通过id获取模块引用

  • id与模块间的引用关系

使用let of 进行遍历,let of会继续遍历新添加的元素


/**通过id获取模块引用 * @param {*} filename 文件路径 * @return 模块对象 */ function createGraph(filename) { let asset = createAsset(filename); //模块 let queue = [asset]; // 把模块放入数组,遍历,push,遍历... for (let asset of queue) { const dirname = path.dirname(asset.filename); asset.mapping = {}; asset.dependencies.forEach(relativePath => { const absolutePath = path.join(dirname, relativePath); const child = createAsset(absolutePath); asset.mapping[relativePath] = child.id; queue.push(child); }); } return queue; }

最终可以拿到这样的数据


[{ id: 0, filename: './src/index.js', dependencies: ['./action.js', './name'], mapping: { './action.js': 1, './name': 2 }, // 模块与id引用关系 code: 'function(require, exports, module) { let action = require(\'./action.js\').action; let name = require(\'./name\').name; let message = `${name} is ${action}`; console.log(message); }' }, { 1, // 模块id为1 xxx.js //模块路径 }]

最后我们再通过循环构建出一个开始想要的一个模块集合,加一个处理引用的递归函数,再拼成一个完成的代码,走你!


function createBundle(graph) { let modules = ''; graph.forEach(mod => { modules += `${mod.id}: [ ${mod.code}, ${JSON.stringify(mod.mapping)} ],`; }); const result = `(function(modules){ function exec(id) { let [fn, mapping] = modules[id]; console.log(fn, mapping) let module = { exports: {} }; fn && fn(require, module.exports, module); function require(path) { //根据模块路径,返回模块执行的结果 return exec(mapping[path]); } return module.exports; } exec(0) })( {${modules}} )` fs.writeFileSync('./dist/bundle.js', result); }createBundle(createGraph('./src/index.js'))

最后打包生成的文件


(function (modules) { function exec(id) { let [fn, mapping] = modules[id]; console.log(fn, mapping) let module = { exports: {} }; fn && fn(require, module.exports, module); function require(path) { //根据模块路径,返回模块执行的结果 return exec(mapping[path]); } return module.exports; } exec(0) })( { 0: [ function (require, exports, module) { let action = require('./action.js').action; let name = require('./name.js').name; let message = `${name} is ${action}`; console.log(message); }, { "./action.js": 1, "./name.js": 2 } ], 1: [ function (require, exports, module) { let action = '我是action模块'; exports.action = action; }, {} ], 2: [ function (require, exports, module) { exports.name = '我是name模块'; }, {} ], } )

理一下执行顺序

1.exec(0)会拿到模块的入口文件,也就是id为0的这项,并执行fn()

2.exec(mapping[path]),  mapping是结构的第二个模块的引用对象


{ "./action.js": 1, "./name.js": 2 }

path能拿到key,mapping[path]得到模块对应的id,继续递归执行下一个模块

最后在浏览器跑一下,完美

Javascript中文网是以前端进阶资源教程分享为主的专业网站

最后想说,其实总的代码是挺简单的,稍微花点时间,我觉得每个人都看得懂,但是这么一点代码能带来非常多的收获,我觉得是很值得一看,结束!

作者:诺克斯
链接:https://juejin.im/post/6893868628220280846

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:webpack简易打包原理

链接:https://www.javascriptc.com/4631.html

« 事件循环:微任务和宏任务
webpack在vue-cli3中详细配置指南»
Flutter 中文教程资源

相关推荐

QR code