一直以来,对webpack总有种说不清道不明的感觉,一来对于我这种前端小白来说webpack用的频率太低,二来即便用到也是各种插件loader啥的满天飞,配置文件里面一把梭,从来没有关注过webpack本身的“pack”这个功能。这回有空看了这个库,算是对webpack有了一定的感性认知。故此写下一点笔记。

我们为什么需要webpack?

随着前端越来越复杂,前端代码需要像后端一样拥有模块化开发。但是浏览器本身(早年间)并不支持模块化,但对于开发者而言模块化开发是有诸多好处的。webpack在此看见了“商机”,与其等代理浏览器支持模块化,不如发明一种转换工具。让开发者开发的时候使用模块化,等到要上生产环境时,把所有代码打包到一起供浏览器使用。
那么我们如何将模块化的项目打包在一个文件里面呢?
写过后端代码肯定知道,纵使代码之间依赖错综复杂,但一定有一个入口文件,这个入口文件便是这个项目启动的关键。因此同理也适用于前端模块化项目。
当我们找到入口文件后,通过一些方法可以将所有依赖的文件全部遍历到。这样,我们就收集了项目中的所有用到的模块。
最后,将所有的模块打包到一起,就构成了最终的打包的代码。

总共分两步:
step1:从入口文件出发,收集沿途的模块信息以及依赖。
step2:将上一步收集的模块依赖信息打包到一起。

step1:从入口文件出发,收集沿途的模块信息以及依赖。

  1. let ID = 0;
  2. function createAsset(filename) {
  3. // 将文件内容以字符串形式保存到content中
  4. const content = fs.readFileSync(filename, "utf-8");
  5. // 将文件内容解析成AST树
  6. const ast = babylon.parse(content, {
  7. sourceType: "module"
  8. });
  9. const dependencies = [];
  10. // 收集文件AST树中的imoprt requre这种引入语句。并将其内容作为依赖存入dependencies数组中
  11. traverse(ast, {
  12. ImportDeclaration: ({ node }) => {
  13. dependencies.push(node.source.value);
  14. }
  15. });
  16. const id = ID++;
  17. // 将content的文件内容转换成浏览器可执行的语法。
  18. const { code } = transformFromAst(ast, null, {
  19. presets: ["env"]
  20. });
  21. return {
  22. id,
  23. filename,
  24. dependencies,
  25. code
  26. };
  27. }
  1. function createGraph(entry) {
  2. // 作为入口
  3. const mainAsset = createAsset(entry);
  4. // 这个队列存放所有模块文件代码以及模块对应的依赖。
  5. const queue = [mainAsset];
  6. for (const asset of queue) {
  7. asset.mapping = {}; // 该模块asset的依赖{path: id}这种形式
  8. const dirname = path.dirname(asset.filename);
  9. asset.dependencies.forEach(relativePath => {
  10. const absolutePath = path.join(dirname, relativePath);
  11. const child = createAsset(absolutePath);
  12. asset.mapping[relativePath] = child.id;
  13. queue.push(child);
  14. });
  15. }
  16. return queue;
  17. }

createGraph函数的作用从入口文件开始收集沿途的模块信息以及依赖。

step2:将上一步收集的模块依赖信息打包到一起。

  1. function bundle(graph) {
  2. let modules = "";
  3. graph.forEach(mod => {
  4. modules += `${mod.id}: [
  5. function (require, module, exports) {
  6. ${mod.code}
  7. },
  8. ${JSON.stringify(mod.mapping)},
  9. ],`;
  10. });
  11. const result = `
  12. (function(modules) {
  13. function require(id) {
  14. const [fn, mapping] = modules[id];
  15. function localRequire(name) {
  16. return require(mapping[name]);
  17. }
  18. const module = { exports : {} };
  19. fn(localRequire, module, module.exports);
  20. return module.exports;
  21. }
  22. require(0);
  23. })({${modules}})
  24. `;
  25. return result;
  26. }
  27. const graph = createGraph("./entry.js");
  28. const result = bundle(graph);
  29. // 写入到我们的dist目录下
  30. fs.mkdirSync("./dist");
  31. fs.writeFileSync("./dist/bundle.js", result);

这一步,不算很好理解。
大概原理就是:bundle函数参数graph,拥有所有模块的代码。这一步的目的就是重写require方法以及export对象,require的代码内容应该在graph里面取得。最后将这些代码通过字符串拼接的方式拼接在一起,生成一个文件,写入到文件中就是打包后的文件了。

  1. (function(modules) {
  2. function require(id) {
  3. const [fn, mapping] = modules[id];
  4. function localRequire(name) {
  5. return require(mapping[name]);
  6. }
  7. const module = { exports: {} };
  8. fn(localRequire, module, module.exports);
  9. return module.exports;
  10. }
  11. require(0);
  12. })({
  13. 0: [
  14. function(require, module, exports) {
  15. "use strict";
  16. var _message = require("./message.js");
  17. var _message2 = _interopRequireDefault(_message);
  18. function _interopRequireDefault(obj) {
  19. return obj && obj.__esModule ? obj : { default: obj };
  20. }
  21. console.log(_message2.default);
  22. },
  23. { "./message.js": 1 }
  24. ],
  25. 1: [
  26. function(require, module, exports) {
  27. "use strict";
  28. Object.defineProperty(exports, "__esModule", {
  29. value: true
  30. });
  31. var _name = require("./name.js");
  32. exports.default = "hello " + _name.name + "!";
  33. },
  34. { "./name.js": 2 }
  35. ],
  36. 2: [
  37. function(require, module, exports) {
  38. "use strict";
  39. Object.defineProperty(exports, "__esModule", {
  40. value: true
  41. });
  42. var name = (exports.name = "world");
  43. },
  44. {}
  45. ]
  46. });