webpack-bundler-demo 发表于 2019-01-03 | 分类于 Demo | Webpack 简单实现123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147/** * 读取内容 * * yarn add babylon babel-core babel-traverse babel-preset-env * 首先我们传入一个文件路径参数,然后通过 fs 将文件中的内容读取出来 * 接下来我们通过 babylon 解析代码获取 AST,目的是为了分析代码中是否还引入了别的文件 * 通过 dependencies 来存储文件中的依赖,然后再将 AST 转换为 ES5 代码 * 最后函数返回了一个对象,对象中包含了当前文件路径、当前文件依赖和当前文件转换后的代码 */function readCode(filePath) { // 读取文件内容(以字符串的形式) const content = fs.readFileSync(filePath, 'utf-8') // 生成 AST(转换字符串为 AST 抽象语法树) const ast = babylon.parse(content, { sourceType: 'module' }) // 寻找当前文件的依赖关系 const dependencies = [] // 遍历抽象语法树 traverse(ast, { // 每当遍历到 import 语法的时候 ImportDeclaration: ({ node }) => { // 依赖文件的相对路径 // 把依赖的模块加入到数组中 dependencies.push(node.source.value) } }) // 转换为浏览器可运行的代码 // 通过 AST 将代码转为 ES5 const { code } = transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return { filePath, dependencies, code }} /** * 提取依赖关系 * * 调用 readCode 函数,传入入口文件 * 分析入口文件的依赖 * 识别 JS 和 CSS 文件 * 首先我们读取入口文件,然后创建一个数组,该数组的目的是存储代码中涉及到的所有文件 * 接下来我们遍历这个数组,一开始这个数组中只有入口文件,在遍历的过程中,如果入口文件有依赖其他的文件,那么就会被 push 到这个数组中 * 在遍历的过程中,我们先获得该文件对应的目录,然后遍历当前文件的依赖关系 * 在遍历当前文件依赖关系的过程中,首先生成依赖文件的绝对路径,然后判断当前文件是 CSS 文件还是 JS 文件 * 如果是 CSS 文件的话,我们就不能用 Babel 去编译了,只需要读取 CSS 文件中的代码,然后创建一个 style 标签,将代码插入进标签并且放入 head 中即可 * 如果是 JS 文件的话,我们还需要分析 JS 文件是否还有别的依赖关系 * 最后将读取文件后的对象 push 进数组中 */function getDependencies(entry) { // 读取入口文件 // 从入口开始,分析所有依赖项,形成依赖图,采用深度优先遍历 const entryObject = readCode(entry) // 定义一个保存依赖项的数组 const dependencies = [entryObject] // 遍历所有文件依赖关系 for (const asset of dependencies) { // 获得文件目录 const dirname = path.dirname(asset.filePath) // 遍历当前文件依赖关系 asset.dependencies.forEach(relativePath => { // 获得绝对路径 const absolutePath = path.join(dirname, relativePath) // CSS 文件逻辑就是将代码插入到 `style` 标签中 if (/\.css$/.test(absolutePath)) { const content = fs.readFileSync(absolutePath, 'utf-8') const code = ` const style = document.createElement('style') style.innerText = ${JSON.stringify(content).replace(/\\r\\n/g, '')} document.head.appendChild(style) ` dependencies.push({ filePath: absolutePath, relativePath, dependencies: [], code }) } else { // JS 代码需要继续查找是否有依赖关系 const child = readCode(absolutePath) // 给子依赖项赋值 child.relativePath = relativePath // 将子依赖也加入队列中,循环处理 dependencies.push(child) } }) } return dependencies} /** * 实现打包的功能,生成浏览器可执行文件 * *(Babel 将我们 ES6 的模块化代码转换为了 CommonJS,浏览器不支持,所以自己实现 CommonJS 相关的代码) * 首先遍历所有依赖文件,构建出一个函数参数对象 * 对象的属性就是当前文件的相对路径,属性值是一个函数,函数体是当前文件下的代码,函数接受三个参数 module、exports、require * module 参数对应 CommonJS 中的 module * exports 参数对应 CommonJS 中的 module.export * require 参数对应我们自己创建的 require 函数 * 接下来就是构造一个使用参数的函数了,函数做的事情很简单,就是内部创建一个 require 函数,然后调用 require(entry),也就是 require('./entry.js'),这样就会从函数参数中找到 ./entry.js 对应的函数并执行,最后将导出的内容通过 module.export 的方式让外部获取到 * 最后再将打包出来的内容写入到单独的文件中 */ function bundle(dependencies, entry) { let modules = '' // 构建函数参数,生成的结构为 // { './entry.js': function(module, exports, require) { 代码 } } dependencies.forEach(dep => { const filePath = dep.relativePath || entry modules += `'${filePath}': ( function (module, exports, require) { ${dep.code} } ),` }) // 构建 require 函数,目的是为了获取模块暴露出来的内容 // module, exports, requir 不能直接在浏览器中使用,这里模拟了模块加载、执行、导出操作 const result = ` (function(modules) { // 创建一个 require 函数: 它接受一个 模块ID 并在我们之前构建的模块对象查找它 function require(id) { const module = { exports : {} } // modules[id](module, module.exports, require) return module.exports } // 执行入口文件 require('${entry}') })({${modules}}) ` // 当生成的内容写入到文件中 fs.writeFileSync('./bundle.js', result)} 坚持原创技术分享,您的支持将鼓励我继续创作! 赏 微信打赏 支付宝打赏