webpack是模块化打包工具,可以加载多种资源的模块,我们这里只实现加载js模块的功能(主要其他的我不会)。
实现commonjs模块加载
先看下流程图,这是我们要实现的功能,把main.js模块依赖的子模块打包到bundle.js ,最后执行bundle文件就能访问到各个模块依赖的资源。

首先先定义一下我们的配置文件
const path = require('path')
module.exports = {
entry:"./main.js",
output: {
path:path.resolve(__dirname,"dist"),
filename: "bundle.js"
}
}
接着实现构建的逻辑
收集modules依赖
首先我们需要通过入口文件分析依赖的其他模块,然后有子模块继续进行分析,并定义一个modules收集filename和module的关系,方便我们在执行代码的时候require(filename)能找到对应的module。
但分析依赖的过程需要我们借助babel的 transformFileSync可以把js代码转成AST树,然后通过traverse遍历ast树找到require依赖的子模块。
getAstAndCode(path) {
let { code, ast } = transformFileSync(path)
return {
ast,
code
}
},
getDependencies(ast) {
let dependencies = []
traverse(ast, {
CallExpression: ({ node: { callee, arguments } }) => {
if (callee.name === "require") {
dependencies.push(arguments[0].value)
}
}
})
return dependencies
}有了dependencies后,我们就可以遍历生成modules(filename到modue的映射)
buildModule(dependencies) {
let modules = {}
while (dependencies.length) {
let filename = dependencies.pop()
let absolutePath = path.resolve(__dirname, '../src', filename)
const {ast,code} = getAstAndCode(absolutePath)
dependencies.push(...getDependencies(ast))
modules[filename] = code
}
return modules
}
生成bundle
有了modules我们就可以去生成bundle了,bundle.js是我们最终要执行的文件,怎样把模块都通过正确的方式调用呢?
首先我们思考一个问题。在main.js中我们require去依赖其他的模块然后拿到一个对象,这很像我们的函数,只要我们传进去一个路径就返回我们想要的结果,但是根据commonjs 模块的规范,想要的结果不是通过return返回的而是在module.export上。所以我们可以对函数进行包装,在子模块加载的时候,传进去一个参数当做module.export,在子模块中通过module.export收集要返回的内容,最后在包装函数中把收集好的module.export返回。
emitFile(modules) {
const code = `(function(modules){
function require(filename){
const code = modules[filename]
let module = {};
(function(module){
eval(code)
})(module)
return module.exports
}
require('${this.entry}')
})(${ JSON.stringify(modules)})`
let output = path.resolve(this.output.path, this.output.filename)
fs.exists(this.output.path, (exists) => {
if (!exists) {
fs.mkdirSync(this.output.path)
}
fs.writeFileSync(output, code, 'utf-8')
});
}
到这一个简单实现commonjs模块打包已经完成了。当然我不能容忍不能加载我们熟悉的ejs模块
实现ejs模块加载
首先得配置一下我们.babellr使用babel-presets-env翻译es6语法。
{
"presets":["env"]
}我们在看一下es6的模块翻译成es5是个啥样,babel转换地址:www.babeljs.cn/repl

我们发现import转成了require我们就不需要特别处理bundle的方法了,只需要再支持exports方法记录返回结果就可以了
emitFile(modules) {
const code = `(function(modules){
function require(filename) {
const code = modules[filename]
let module = {};
(function (module,exports) { //增加exports收集结果
eval(code)
})(module,module)
return module.exports||module
}
require('${this.entry}')
})(${ JSON.stringify(modules)})`
...
}简单的处理,支持了es6模块的加载
完整的代码 github:https://github.com/13866368297/webpack.git
到这webpack模块加载的功能就实现了,还是那句话,主要让大家了解下webpack加载模块的大致过程,具体的实现细节就不扣了。
看到这的小伙伴谢谢大家的支持!!!