【新手向】如何写一个超超超简单的 webpack?
栏目: JavaScript · 发布时间: 5年前
内容简介:webpack 的定义和重要程度在此我不再赘述,直接进入正题,一个最最简单的 webpack 是如何实现的?我们从 webpack 的打包过程来进行分析首先创建两个项目,一个叫每个文件对应内容如下:
webpack 的定义和重要程度在此我不再赘述,直接进入正题,一个最最简单的 webpack 是如何实现的?我们从 webpack 的打包过程来进行分析
一. 准备活动
首先创建两个项目,一个叫 webpack-project
,另一个叫 mn-webpack
。前者用于观察打包结果,后者用于简单实现 webpack。
项目建立好之后,首先进入到 webpack-project
中,新建一个 src
文件夹和 webpack.config.js
文件, src
下具有 index.js
和 a.js
两个文件以及一个 base
文件夹, base
文件夹下有 b.js
一个文件。结构如下:
|--src |--index.js |--a.js |--base |--b.js |--webpack.config.js 复制代码
每个文件对应内容如下:
// b.js module.exports = 'b' // a.js const b = require('./base/b.js') module.exports = 'a' + b // index.js const str = require('./a.js') console.log(str) 复制代码
// webpack.config.js const path = require('path') module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, mode: 'development' } 复制代码
到此,准备活动就结束了
二. webpack 编写
设想一下,当我们在 webpack-project
完成上述配置并执行 npx webpack
之后会发生什么?答案是:会在目录下生成一个 dist
目录,其中包含打包结果 bundle.js
,运行 bundle.js
会在控制台中打印出 ab
字符串。如果你已经知道这其中的机制,那么恭喜你,这篇文章对你来说已经没什么用处了,再往下看也是浪费时间而已。如果你还不知道的话,接下来可要注意咯~
执行 npx wepack
,结果和上述一样。打开 bundle.js
,删掉一部分代码,剩下最关键的,在控制台中执行 bundle.js
,同样可以输出 ab
。删后的 bundle.js
如下所示:
(function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } return __webpack_require__(__webpack_require__.s = "./src/index.js"); }) ({ "./src/a.js": (function(module, exports, __webpack_require__) { eval("const b = __webpack_require__(/*! ./base/b.js */ \"./src/base/b.js\")\r\nmodule.exports = 'a' + b\n\n//# sourceURL=webpack:///./src/a.js?"); }), "./src/base/b.js": (function(module, exports) { eval("module.exports = 'b'\n\n//# sourceURL=webpack:///./src/base/b.js?"); }), "./src/index.js": (function(module, exports, __webpack_require__) { eval("const str = __webpack_require__(/*! ./a.js */ \"./src/a.js\")\r\nconsole.log(str)\n\n//# sourceURL=webpack:///./src/index.js?"); }) }); 复制代码
可以看到,我们整个项目中用到的文件地址和内容在 bundle.js
中都体现了出来。如何拿到这些文件呢?待会儿会讲。
切换至 mn-webpack
文件夹,在其下建立一个 bin
文件夹和 lib
文件夹, bin
文件夹下有 mn-webpack.js
文件, lib
文件夹下有 Compiler.js
和 main.ejs
文件。结构如下:
|--bin |--mn-webpack.js |--lib |--Compiler.js |--main.ejs 复制代码
mn-webpack.js
接着介绍一下每个文件的作用, mn-webpack.js
为整个项目的入口文件,我们需要在 package.json
文件中增加一个 bin
选项对该文件进行配置,如下所示:
"bin": { "mn-pack": "./bin/mn-pack.js" } 复制代码
bin
用于放置用户的自定义命令。再在控制台中运行 npm link
,可将命令链接到全局中。这一步是为了可以在 webpack-project
中使用 mn-webpack
。使用方式也很简单,首先在 webpack-project
控制台下运行 npm link mn-webpack
建立连接,然后直接运行 npx mn-webpack
即可。对于该命令不太了解的朋友可以查看这篇文章。
mn-webpack.js
文件内容如下:
const path = require('path') const Compiler = require('../lib/Compiler.js') // 拿到配置文件 const config = require(path.resolve('webpack.config.js')) // Compiler 为一个类,实例化后执行 run 方法开始编译 const compiler = new Compiler(config) compiler.run() 复制代码
main.ejs
该文件为一个模板文件,我们将之前修改过的 bundle.js
内容复制进来。对其中的依赖部分进行修改:
(function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { if(installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); module.l = true; return module.exports; } return __webpack_require__(__webpack_require__.s = "<%-entryId%>"); }) ({ <%for(let key in modules){%> "<%-key%>": (function(module, exports, __webpack_require__) { eval(`<%-modules[key]%>`); }), <%}%> }); 复制代码
entryId
和 modules
通过其他文件进行传入,下面会介绍到。
Compiler.js
该文件最开始内容很简单,导出一个 Compiler 类:
class Compiler { constructor (config) { this.config = config this.entry = config.entry this.modules = {} this.root = process.cwd() } run () { // 从 entry 开始分析依赖 this.buildModule() // 发射输出文件 this.emitFile() } buildModule () {} emitFile () {} } 复制代码
buildModule
方法需要接收 modulePath
作为一个参数,拿到对应的文件后对其中的依赖进行解析(这里的依赖就是我们 require
进来的东西)。这个步骤需要用到 babylon
生成 AST树
,然后利用 @babel/traverse
遍历 AST树
并修改成我们想要的样子,最终使用 @babel/generator
生成代码返回。 AST树
的信息可以在 这里
进行查看
const types = require('@babel/types') const babylon = require('babylon') const traverse = require('@babel/traverse').default const generator = require('@babel/generator').default parse (source, parentPath) { const ast = babylon.parse(source) const dependencies = [] traverse(ast, { CallExpression(p) { const node = p.node if (node.callee.name === 'require') { // 将 require 改为 webpack 自定义的 __webpack_require__ node.callee.name = '__webpack_require__' let moduleName = node.arguments[0].value moduleName = moduleName + (path.extname(moduleName) ? '' : '.js') moduleName = ('./' + path.join(parentPath, moduleName)).replace(/\\/g, '/') dependencies.push(moduleName) node.arguments = [types.stringLiteral(moduleName)] } } }) const sourceCode = generator(ast).code return { sourceCode, dependencies } } getSource(modulePath) { return fs.readFileSync(modulePath, 'utf8') } buildModule (modulePath, isEntry) { // 首先拿到 modulePath 里的文件内容 const source = this.getSource(modulePath) // 拿到 moduleName,为相对路径 const moduleName = ('./' + path.relative(this.root, modulePath)).replace(/\\/g, '/') // 拿到父目录 const parentDir = path.dirname(moduleName) if(isEntry) { this.entryId = moduleName } // dependicies 表示当前文件依赖了哪些文件,如果是用 require 进行了调用则表示依赖 const { sourceCode, dependencies } = this.parse(source, parentDir) this.modules[moduleName] = sourceCode dependencies.forEach(dep => { // 遍历附属模块,递归加载 this.buildModule(path.join(this.root, dep), false) }) } run () { this.buildModule(path.resolve(this.root, this.entry), true) this.emitFile() } 复制代码
emitFile
方法的功能是生成一个输出文件。
const ejs = require('ejs') emitFile () { // 用数据渲染模板 // 输出路径 const main = path.join(this.config.output.path, this.config.output.filename) const templateStr = this.getSource(path.join(__dirname, 'main.ejs')) const code = ejs.render(templateStr, {entryId: this.entryId, modules: this.modules}) this.assets = {} // 资源中 路径对应的代码 this.assets[main] = code fs.writeFileSync(main, this.assets[main]) } 复制代码
到这里基本就结束啦!!
见证奇迹的时刻
切换到 webpack-project
文件夹下,执行 npx mn-pack
命令,等待一会儿,你会看见文件夹下多了一个 dist
目录,里面的 bundle.js
就是我们打包出来的文件啦~执行一下,是不是看见 ab
的输出了呢
三. 结语
这篇文章确确实实很简单了,希望看到这里的朋友们没有觉得是在浪费时间。第一次写博客肯定有不足的地方,希望大家多多担待。有问题的话可以在评论区提问哦。文中有不正确的地方也请大家指出来,共同进步。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。