# 迷你版webpack的实现
# webpack的模块机制
//bundle.js文件
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// __webpack_require来加载模
function __webpack_require__(moduleId) {
// Check if module is in cache
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./index.js");
})
({
"./index.js":
(function (module, exports) {
eval("console.log('test world')\n\n//# sourceURL=webpack:///./index.js?");
})
});
分析webpack构建完成后生成的bundle文件,可以了解到,打包出来的是一个立即执行函数IIFE,其中modules 是一个数组,每一项是一个模块初始化函数,通过__webpack_require来加载模块并返回module.export,并 通过__webpack_require__(0)启动。
通过打包构建后的bundle文件分析,并结合之前webpack源码的了解,我们可以知道,webpack核心就是将ES6语法转换 为浏览器可识别的ES5语法,并通过分析入口文件的文件依赖和模块之间的依赖关系,最后根据依赖树来构建bundle文件。
所以,我们需要实现的功能有:
- 将ES6语法转换为ES5语法
通过babylon生成AST
通过babel-core将AST重新生成源码 - 分析模块之间的依赖关系
通过babel-traverse的ImportDeclaration方法获取依赖属性 - 生成可在浏览器中运行的JS文件
# 迷你版webpack实现
# 模拟webpack配置文件,新建easypack文件设置入口出口配置项。
//easypack.js
const path = require('path')
module.exports = {
entry: path.join(__dirname, './src/index.js'), // 入口文件src/index.js
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist') //bundle文件导出到dist文件夹下
}
}
# 新建index.js,参考webpack作为入口文件,接受easypack.js配置并执行compiler.run项目构建。
// lib/index.js
const Compiler = require('./Compiler')
const options = require('../easypack.config')
new Compiler(options).run()
则Compiler.js需要有run入口,构建模块方法和文件输出方法。
module.exports = class Compiler {
//初始化参数,接受easypack配置
constructor(options){
const { entry, output } = options
this.entry = entry
this.output = output
}
// 提供run,入口方法
run() {
}
// 模块构建
buildModules(){
}
//文件输出:easypack提供的output输出路径
emitFiles(){
}
}
# 新建好Compiler.js入口文件后,需要Parser.js,实现将ES6+转换为ES5和分析依赖功能。
- 将ES6+转换为AST树
const babylon = require('babylon')
module.exports ={
getAST: path => {
// 提供path路径,通过fs读取内容
const source = fs.readFileSync(path,'utf-8')
// 通过babylon,将source转换为ast
return babylon.parse(source,{
sourceType: 'module'
})
},
}
- 依赖分析
const traverse = require('babel-traverse').default
module.exports = {
getDepeendencies:(ast) => {
const dependencies = []
traverse(ast, {
// 分析import语句,获取依赖内容
ImportDeclaration:({node}) => {
dependencies.push(node.source.value)
}
})
return dependencies
},
}
- 将AST转换为ES5源码
const { transformFromAst } = require('babel-core')
module.exports = {
transform: (ast) => {
const { code } = transformFromAst(ast, null, {
presets: ['env']
})
return code
}
}
最后,完整的Parser.js源码:
const fs = require('fs')
const babylon = require('babylon')
const traverse = require('babel-traverse').default
const { transformFromAst } = require('babel-core')
module.exports = {
getAST: path => {
// 提供path路径,通过fs读取内容
const source = fs.readFileSync(path,'utf-8')
// 通过babylon,将source转换为ast
return babylon.parse(source,{
sourceType: 'module'
})
},
getDepeendencies:(ast) => {
const dependencies = []
traverse(ast, {
// 分析import语句,获取依赖内容
ImportDeclaration:({node}) => {
dependencies.push(node.source.value)
}
})
return dependencies
},
// 将ast转换为源码
transform: (ast) => {
const { code } = transformFromAst(ast, null, {
presets: ['env']
})
return code
}
}
# 完善Compiler.js
- 模块构建并遍历每个模块的依赖,然后获取所有模块的依赖
const {getAST, getDepeendencies, transform } = require('./Parser')
module.exports = class Compiler {
run(){
const entryModule = this.buildModules(this.entry, true)
this.modules.push(entryModule)
//遍历获取所有依赖
this.modules.map((module)=>{
module.dependencies.map((dependency)=>{
this.modules.push(this.buildModules(dependency))
})
})
}
// 模块构建
buildModules(filename,isEntry){
let ast
if(isEntry){
ast = getAST(filename)
} else {
//由于不是入口文件,则返回的为相对路径,转换为绝对路径
const absolutePath = path.join(process.cwd(),'./src',filename)
ast = getAST(absolutePath)
}
return {
filename,
dependencies: getDepeendencies(ast),
source: transform(ast)
}
}
}
- 参考bundle.js的输出结构,输出bundle文件,自定义require方法实现模块化方法。
module.exports = class Compiler {
//文件输出:easypack提供的output输出路径
emitFiles(){
const outputPath = path.join(this.output.path,this.output.filename)
let modules = ''
this.modules.map((module)=>{
modules += `'${module.filename}': function (require, module, exports){${module.source}},`
})
const bundle = `(function(modules){
function require(filename){
var fn = modules[filename];
var module = { exports: {} };
fn(require, module, module.exports);
return module.exports
}
require('${this.entry}')
})({${modules}})`
//console.log(bundle)
fs.writeFileSync(outputPath, bundle, 'utf-8')
}
}
完整的Compiler.js源码:
const path = require('path')
const fs = require('fs')
const {getAST, getDepeendencies, transform } = require('./Parser')
module.exports = class Compiler {
//初始化参数
constructor(options){
const { entry, output } = options
this.entry = entry
this.output = output
// 最终构建的模块依赖列表
this.modules = []
}
// 提供run,入口方法
run(){
const entryModule = this.buildModules(this.entry, true)
//console.log(entryModule)
this.modules.push(entryModule)
//遍历获取所有依赖
this.modules.map((module)=>{
module.dependencies.map((dependency)=>{
this.modules.push(this.buildModules(dependency))
})
})
//console.log(this.modules)
this.emitFiles()
}
// 模块构建
buildModules(filename,isEntry){
let ast
if(isEntry){
ast = getAST(filename)
} else {
//由于不是入口文件,则返回的为相对路径,转换为绝对路径
const absolutePath = path.join(process.cwd(),'./src',filename)
ast = getAST(absolutePath)
}
return {
filename,
dependencies: getDepeendencies(ast),
source: transform(ast)
}
}
//文件输出:easypack提供的output输出路径
emitFiles(){
const outputPath = path.join(this.output.path,this.output.filename)
let modules = ''
this.modules.map((module)=>{
modules += `'${module.filename}': function (require, module, exports){${module.source}},`
})
const bundle = `(function(modules){
function require(filename){
var fn = modules[filename];
var module = { exports: {} };
fn(require, module, module.exports);
return module.exports
}
require('${this.entry}')
})({${modules}})`
//console.log(bundle)
fs.writeFileSync(outputPath, bundle, 'utf-8')
}
}
完整代码可查看:https://github.com/PCAaron/blogCode/tree/master/webpack/easypack