准备工作
新建一个目录,执行npm init -y 初始化一个package.json文件
新建src下index.js,作为入口文件,export.js作为依赖的模块
新建webpack.config.js,这里就按照webpack4的基础配置写一下
新建bundle.js作为入口进行webpack构建 创建lib下webpack.js
根据配置读取入口文件
bundle.js:
//读取到webpackd 的配置文件 const options = require("./webpack.config.js") const Webpack = require("./lib/webpack.js") new Webpack(options).run()
webpack.js 这里写了一个Webpack类,bundle.js实例化这个类,将参数(引入的基础配置的内容)传进来并执行run方法
module.exports = class Webpack { constructor(options){ console.log(options) } run(){ console.log("hello") } }
此时执行node bundle.js :
已经可以根据基础配置读取到入口模块了
分析出依赖模块
我这里写的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
看type,哪里是依赖模块,哪里是表达式,已经很明显了,接下来通过@babel/traverse来提取依赖模块:
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转化成了对象,方便后续处理
输出浏览器可执行的js代码
此处划重点! 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官网