Webpack 是当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还能够将按需加载的模块进行代码分隔,等到实际须要的时候再异步加载。webpack如此强大,其内部机制究竟是怎样的,今天咱们来一探究竟。前端
新建一个目录,执行npm init -y 初始化一个package.json文件node
新建src下index.js,做为入口文件,export.js做为依赖的模块webpack
新建webpack.config.js,这里就按照webpack4的基础配置写一下git
新建bundle.js做为入口进行webpack构建 建立lib下webpack.jsgithub
bundle.js:web
//读取到webpackd 的配置文件
const options = require("./webpack.config.js")
const Webpack = require("./lib/webpack.js")
new Webpack(options).run()
复制代码
webpack.js 这里写了一个Webpack类,bundle.js实例化这个类,将参数(引入的基础配置的内容)传进来并执行run方法npm
module.exports = class Webpack {
constructor(options){
console.log(options)
}
run(){
console.log("hello")
}
}
复制代码
此时执行node bundle.js : json
我这里写的index.js是这样:数组
import { sayHi } from './export.js'
sayHi("hfj")
console.log("hello webpack index")
复制代码
此时须要借助node的核心模块-- filesystem来读到内容浏览器
const conts = fs.readFileSync(entryFile,'utf-8')
复制代码
接下来借助@babel/parser(记得本身安装和引入。。)把内容抽象成语法树
const ast = parser.parse(conts, {
sourceType: "module"
});
复制代码
打印下ast.program.body
const dependencies = {}
traverse(ast,{
ImportDeclaration({node}){
const newPath = "./" + path.join(
path.dirname(entryFile),
node.source.value
)
dependencies[node.source.value] = newPath
console.log(dependencies)
}
})
复制代码
注意下,这里作了一个处理,将依赖模块以对象的形式放到了dependencies里。
接下来,经过@babel/core将ast处理成内容
const {code} = transformFromAst(ast,null,{
presets: ["@babel/preset-env"]
})
console.log(code)
复制代码
打印的code以下:
以上是对入口文件进行的编译,接下来还要看入口模块的依赖模块有哪些,分别按照刚才的方法进行内容分析,最后作一个汇总(根据dependence来判断的是否还有依赖模块):
run(){
const info = this.analysis(this.entry)
this.modulesArr.push(info)
for(let i=0;i<this.modulesArr.length;i++) {
const item = this.modulesArr[i]
const { dependencies } = item;
if(dependencies) {
for(let j in dependencies){
this.modulesArr.push(this.analysis(dependencies[j]))
}
}
}
// console.log(this.modules)
//数组结构转换
const obj = {}
this.modulesArr.forEach((item) => {
obj[item.entryFile] = {
dependencies:item.dependencies,
code:item.code
}
})
}
复制代码
最终是将modulesArr转化成了对象,方便后续处理
此处划重点! webpack最终是将模块化的js打包成一个chunk,打包后的js到dist(webpack基础配置里设置的output)
首先用了fs.writeFileSync将生成的bundle写入filePath(filePath以前有在this.output保存) 接下来就是处理obj(上一步骤中的汇总后的模块)了:
核心是用eval()来执行obj中的code,同时须要处理requrie和export require和export经过形参传入,以后执行本身写的reqire和export require处理了模块路径,exports是一个对象,看源码--
const bundle = `(function(graph){
function require(moduleId){
function localRequire(relativePath){
return require(graph[moduleId].dependencies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[moduleId].code)
return exports;
}
require('${this.entry}')
})(${newCode})`
复制代码
执行node bundle.js,在dist文件夹下输出以下js:
(function(graph){
function require(moduleId){
function localRequire(relativePath){
return require(graph[moduleId].dependencies[relativePath])
}
var exports = {};
(function(require,exports,code){
eval(code)
})(localRequire,exports,graph[moduleId].code)
return exports;
}
require('./src/index.js')
})({"./src/index.js":{"dependencies":{"./export.js":"./src\\export.js"},"code":"\"use strict\";\n\nvar _export = require(\"./export.js\");\n\n(0, _export.sayHi)(\"hfj\");\nconsole.log(\"hello webpack index\");"},"./src\\export.js":{"dependencies":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.sayHi = void 0;\n\nvar sayHi = function sayHi(params) {\n console.log(\"hello ~\" + params);\n};\n\nexports.sayHi = sayHi;"}})
复制代码
在浏览器执行:输出:
完成~
webpack经过入口文件逐层遍历到模块依赖,进行代码分析、代码转换,最终生成可在浏览器运行的打包后的代码
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个 bundle。
以上实现了一个简易版本的webpack
下一篇:webpack原理解析(二)loader和plugin机制
若有兴趣,欢迎进一步交流探讨~
附github地址:github.com/manli-tongh…
参考:Webpack官网