【新手向】如何写一个超超超简单的 webpack?
栏目: JavaScript · 发布时间: 6年前
内容简介: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
的输出了呢
三. 结语
这篇文章确确实实很简单了,希望看到这里的朋友们没有觉得是在浪费时间。第一次写博客肯定有不足的地方,希望大家多多担待。有问题的话可以在评论区提问哦。文中有不正确的地方也请大家指出来,共同进步。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python算法教程
[挪威] Magnus Lie Hetland 赫特兰 / 凌杰、陆禹淳、顾俊 / 人民邮电出版社 / 2016-1-1 / 69.00元
本书用Python语言来讲解算法的分析和设计。本书主要关注经典的算法,但同时会为读者理解基本算法问题和解决问题打下很好的基础。全书共11章。分别介绍了树、图、计数问题、归纳递归、遍历、分解合并、贪心算法、复杂依赖、Dijkstra算法、匹配切割问题以及困难问题及其稀释等内容。本书在每一章结束的时候均有练习题和参考资料,这为读者的自我检查以及进一步学习提供了较多的便利。在全书的最后,给出了练习题的提......一起来看看 《Python算法教程》 这本书的介绍吧!