【新手向】如何写一个超超超简单的 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
的输出了呢
三. 结语
这篇文章确确实实很简单了,希望看到这里的朋友们没有觉得是在浪费时间。第一次写博客肯定有不足的地方,希望大家多多担待。有问题的话可以在评论区提问哦。文中有不正确的地方也请大家指出来,共同进步。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
如何构建敏捷项目管理团队
丽萨·阿金斯 / 徐蓓蓓、白云峰、刘江华 / 电子工业出版社 / 2012-6 / 49.00元
《敏捷项目管理系列丛书•PMI-ACPSM考试指定教材•如何构建敏捷项目管理团队:ScrumMaster、敏捷教练与项目经理的实用指南》结合作者的亲身经历告诉读者如何建立一个高性能的敏捷项目管理团队,以及最终成为一名优秀的敏捷教练。作者将敏捷教练定义为导师、协助者、老师、问题解决者、冲突领航员、协作指挥者,正是这种不同角色之间的细微区别才使敏捷教练的工作富有深度。《敏捷项目管理系列丛书•PMI-A......一起来看看 《如何构建敏捷项目管理团队》 这本书的介绍吧!