今日,学了个手撸webpack。

    1. const path = require("path");
    2. const fs = require("fs");
    3. const parser = require("@babel/parser");
    4. const { traverse, transformFromAst } = require("@babel/core");
    5. module.exports = class Webpack {
    6. constructor(options) {
    7. this.entry = options.entry;
    8. this.output = options.output;
    9. }
    10. run() {
    11. // 构建chunks
    12. const modulesSet = [this.parse(this.entry)]; // 存放模块代码信息的集合
    13. const existing = new Set([this.entry]); // 给chunk去重
    14. for (let i = 0; i < modulesSet.length; i++) {
    15. const { denpendencies } = modulesSet[i];
    16. for (const [, absPath] of Object.entries(denpendencies)) {
    17. if (!existing.has(absPath)) {
    18. existing.add(absPath);
    19. modulesSet.push(this.parse(absPath));
    20. }
    21. }
    22. }
    23. const modules = {};
    24. modulesSet.forEach((module) => {
    25. modules[module.path] = module;
    26. });
    27. this.file(modules);
    28. }
    29. parse(entryFile) {
    30. const sourceCode = fs.readFileSync(entryFile, "utf8");
    31. // 1 获取语法树
    32. const ast = parser.parse(sourceCode, {
    33. sourceType: "module",
    34. });
    35. // 2 查找依赖
    36. const denpendencies = {};
    37. traverse(ast, {
    38. ImportDeclaration({ node }) {
    39. const { value } = node.source; // 相对路径
    40. denpendencies[value] = path.join(path.dirname(entryFile), value); // 绝对路径
    41. },
    42. });
    43. // 3 转换代码
    44. const { code } = transformFromAst(ast, sourceCode, {
    45. presets: ["@babel/preset-env"],
    46. });
    47. return {
    48. path: entryFile,
    49. code,
    50. denpendencies,
    51. };
    52. }
    53. file(modules) {
    54. // 1 构建bundle
    55. modules = JSON.stringify(modules, null, 2);
    56. const bundle = `(function(modules) { // 启动函数
    57. function require(module) {
    58. function newRequire(relativePath) {
    59. return require(modules[module].denpendencies[relativePath]);
    60. }
    61. var exports = {};
    62. (function(require, exports, code) {
    63. eval(code);
    64. })(newRequire, exports, modules[module].code);
    65. return exports;
    66. }
    67. require("${this.entry}");
    68. })( // chunks
    69. ${modules});`;
    70. // 2 写到磁盘
    71. const filePath = path.join(this.output.path, this.output.filename);
    72. fs.writeFileSync(filePath, bundle, "utf-8");
    73. }
    74. };