Webpack实现

356 阅读3分钟

本文将实现一个基础版本的webpack,既可以对依赖JS文件进行打包的基础版本。

本文需要安装的包:

image.png

文件目录:

image.png

文件内容:

  • 1,根目录下index.js既入口JS文件:

    // .index.js:入口JS文件
    import { add, mul } from './math/index.js'
    console.log('1+1:', add(1, 1));
    console.log('2*2:', mul(2, 2));
    
  • 2,根目录下的math文件夹中所有文件被入口文件直接或者间接引用:

    //  ./math/index.js
    import add from './tools/add.js'
    import mul from './tools/mul.js'
    
    console.log('欢迎使用 math 工具方法!');
    export { add, mul }
    
    //  ./math/tools/add.js
    export default function add(...args) {
        return args.reduce((p, c) => p + c)
    }
    
    //  ./math/tools/mul.js
    export default function add(...args) {
        return args.reduce((p, c) => p * c)
    }
    
  • 3,根目录下的dist文件夹既打包后的bundle文件输出位置

webpack实现:

  • 4,根目录下的webpack.js既我们实现的webpack(代码全注释,按照注释阅读)

    const fs = require('fs')
    const { dirname, join } = require('path')
    // 主要使用parser.parse 将ES6代码转成抽象语法树
    const parser = require('@babel/parser')
    // 使用traverse遍历ES6代码的抽象语法树,找到当前JS文件所有依赖文件路径
    const traverse = require('@babel/traverse').default
    // 使用transformFromAstSync将ES5的抽象语法树转成ES5代码
    const { transformFromAstSync } = require('@babel/core')
    
    function getModule(path) {
        // 1,根据文件路径读取对应文件内容
        const content = fs.readFileSync(path, 'utf-8')
        // 2,将文件内容ES6转换成抽象语法树(AST)
        const ast = parser.parse(content, { sourceType: 'module' })
        // 3,在抽象语法树中找到当前文件所有依赖的文件路径保存
        const dependencies = []
        traverse(ast, { ImportDeclaration: ({ node }) => { dependencies.push(node.source.value) } })
        // 4,将当前文件的语法树转成ES5代码,并添加函数体包裹,模拟CJS中的require行为
        const code = `function (require,module,exports){${transformFromAstSync(ast, null, { presets: ['@babel/preset-env'] }).code}}`
        // 5,返回当前文件的模块信息:文件路径,所有依赖的文件路径,函数体包裹的ES6转ES5后的代码
        return { path, dependencies, code }
    }
    
    function getGraph(module, path) {
        // 1, 首先创建当前模块的路径与代码映射关系:
        // `'./index.js'        : some code ~ ,`
        const initialMappingCodeToPath = `'${path}':${module.code},`
        // 2,  递归处理当前模块的依赖,创建所有模块的路径与代码映射关系,最终合并在一起(字符串),像下面这样
        // `
        //     './index.js'        : some string_code ~ ,
        //     './math/index.js'    : some string_code ~ ,
        //     './tools/add.js'     : some string_code ~ ,
        //     './tools/mul.js'     : some string_code ~ ,
        // `
        return module.dependencies.reduce(
            (mappingCodeToPath, depPath) => {
                // 2.1,根据当前模块的路径获取依赖模块的绝对路径
                const depAbspath = join(dirname(module.path), depPath)
                // 2.2,获取依赖模块的路径与代码映射关系
                const depMappingCodeToPath = getGraph(getModule(depAbspath), depPath)
                // 2.3,合并所有模块的路径与代码映射关系,生成依赖关系图
                return mappingCodeToPath + depMappingCodeToPath
            },
            // 2.4,reduce方法初始模块的路径与代码映射关系
            initialMappingCodeToPath
        )
    }
    
    function getBundle(entryPath) {
        // 1.1,首先创建入口文件模块信息:入口文件路径  入口文件所有依赖文件路径    入口文件ES6转ES5代码
        const entryModule = getModule(entryPath)
        // 1.2,之后从入口文件开始递归创建所有模块的依赖关系图(字符串),每个模块依赖关系既模块路径与模块内部代码之间的映射关系:
        // `
        //     {
        //         './index.js'         : some string_code ~ ,
        //         './math/index.js'    : some string_code ~ ,
        //         './tools/add.js'     : some string_code ~ ,
        //         './tools/mul.js'     : some string_code ~ 
        //     }
        // `
        const graph = `{${getGraph(entryModule, entryPath)}}`
        // 1.3,返回一个立即执行函数字符串,既打包后的代码块:
        // 立即执行函数参数既模块依赖关系图,且立即执行函数内部实现了类CJS的require,即可根据模块路径找到对应模块代码执行
        // 当立即执行函数运行,我们会从入口文件路径开始require,执行入口文件内部代码,遇到require依赖模块,因为注入了实现的类CJS的require
        // 所以会继续执行当前依赖模块代码,最终所有模块代码执行将完毕
        return `(function(modules){
            function require(path){
                const module= {exports:{}}
                modules[path](require,module,module.exports)
                return module.exports
            }
           require('${entryPath}')
        })(${graph})`
    
    }
    // 1,开始打包
    // 传入入口文件路径,开始打包
    const bundle = getBundle('./index.js')
    
    // 4,按照webpack,最后会将打包后的代码注入到dist文件夹下面的bundle.js中既完成打包
    !fs.existsSync("./dist") && fs.mkdirSync("./dist"); // 若没有dist文件夹创建dist文件夹
    fs.writeFileSync("./dist/bundle.js", bundle);       // 将打包后内容写进dist文件夹下面的bundle.js中
    
    

打包后的JS代码:

(function(modules){
        function require(path){
            const module= {exports:{}}
            modules[path](require,module,module.exports)
            return module.exports
        }
       require('./index.js')
    })({'./index.js':function (require,module,exports){"use strict";

var _index = require("./math/index.js");

console.log('1+1:', (0, _index.add)(1, 1));
console.log('2*2:', (0, _index.mul)(2, 2));},'./math/index.js':function (require,module,exports){"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
Object.defineProperty(exports, "add", {
  enumerable: true,
  get: function get() {
    return _add["default"];
  }
});
Object.defineProperty(exports, "mul", {
  enumerable: true,
  get: function get() {
    return _mul["default"];
  }
});

var _add = _interopRequireDefault(require("./tools/add.js"));

var _mul = _interopRequireDefault(require("./tools/mul.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

console.log('欢迎使用 math 工具方法!');},'./tools/add.js':function (require,module,exports){"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = add;

function add() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  return args.reduce(function (p, c) {
    return p + c;
  });
}},'./tools/mul.js':function (require,module,exports){"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = add;

function add() {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  return args.reduce(function (p, c) {
    return p * c;
  });
}},})