一、 webapck的本质
- 本质上就是打包工具,不是框架也不是库
- 实质上是一个nodejs运行程序
- webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
- 项目地址
文章图片
- 初始化项目
yarn init -y
- 安装webpack和webpack-cli
yarn add webpack webpack-cli -D
- 编写webpack.config.js
const path = require('path')const RunPlugins = require('./plugins/run-plugins') const DonePlugins = require('./plugins/done-plugins')module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './dist'), filename: 'main.js' }, module:{ rules:[ { test:/\.js/, use:[ path.resolve(__dirname,'loaders','loader1.js'), path.resolve(__dirname,'loaders','loader2.js') ] } ] }, plugins:[ new RunPlugins(), new DonePlugins() ] }
- nodejs入口
app.js
const webpack = require('./webpack') const config = require('./webpack.config')const compiler = webpack(config)compiler.run()
- webpack.js(自己定义的)
const Compiler = require('./compiler')function webpack(config){const shellOptions = process.argv.slice(2).reduce((config,arg)=>{ let [key,value] = arg.split('=') config[key.slice(2)] = value return config },{}) const finallOptions ={...config,...shellOptions} const compiler = new Compiler(finallOptions)// 创建完comppiler后挂载插件finallOptions.plugins.forEach(plugin => { plugin.apply(compiler) }); return compiler } module.exports = webpack
- compiler.js
编译的核心 webpack的功能实现
const fs = require('fs') const path = require('path') const options = require('./webpack.config') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const { transformFromAst } = require('@babel/core') const { SyncHook } = require('tapable') const Parser = { getAst: path => { // 读取入口文件 const content = fs.readFileSync(path, 'utf-8') // 将文件内容转为AST抽象语法树 return parser.parse(content, { sourceType: 'module' }) }, getDependecies: (ast, filename) => { const dependecies = {} // 遍历所有的 import 模块,存入dependecies traverse(ast, { // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句) ImportDeclaration({ node }) { const dirname = path.dirname(filename) // 保存依赖模块路径,之后生成依赖关系图需要用到 const filepath = './' + path.join(dirname, node.source.value) dependecies[node.source.value] = filepath } }) return dependecies }, getCode: ast => { // AST转换为code const { code } = transformFromAst(ast, null, { presets: ['@babel/preset-env'] }) return code } }class Compiler { constructor(options) { this.options = options // webpack 配置 const { entry, output } = options // 入口 this.entry = entry // 出口 this.output = output // 模块 this.modules = [] this.hooks = { run: new SyncHook(), done: new SyncHook(), emit: new SyncHook() } } // 构建启动 run() { // 解析入口文件 this.hooks.run.call()this.build(this.entry)this.hooks.done.call()// 生成依赖关系图 const dependencyGraph = this.modules.reduce( (graph, item) => ({ ...graph, // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容 [item.filename]: { dependecies: item.dependecies, code: item.code } }), {} ) this.generate(dependencyGraph) } build(filename) {const { getAst, getDependecies, getCode } = Parser const ast = getAst(filename) const dependecies = getDependecies(ast, filename) let code = getCode(ast)// 加载loader let loaders = []const rules = this.options.module.rulesfor (let i = 0; i < rules.length; i++) { if (rules[i].test.test(filename)) { loaders = [...loaders, ...rules[i].use] } } for (let i = loaders.length - 1; i >= 0; i--) { code = require(loaders[i])(code) } const obj = { // 文件路径,可以作为每个模块的唯一标识符 filename, // 依赖对象,保存着依赖模块路径 dependecies, // 文件内容 code } this.modules.push(obj) for (let key in dependecies) { if (dependecies[key]) { this.build(dependecies[key]) } } } // 重写 require函数 (浏览器不能识别commonjs语法),输出bundle generate(code) { // 输出文件路径 const filePath = path.join(this.output.path, this.output.filename) const bundle = `(function(graph){ function require(module){ function localRequire(relativePath){ return require(graph[module].dependecies[relativePath]) } var exports = {}; (function(require,exports,code){ eval(code) })(localRequire,exports,graph[module].code); return exports; } require('${this.entry}') })(${JSON.stringify(code)})`// 把文件内容写入到文件系统 fs.writeFileSync(filePath, bundle, 'utf-8') } }module.exports = Compiler
- loader的本质 具有单一职责的转换器。
- 参数为转换过的js代码,然后在这个函数里面做处理,然后返回更改后的代码。
- 后续会更新一篇手写loader的文章
function loader(content, map, meta){ // content:资源文件的内容。对于起始loader,只有这一个参数 // map:前面loader生成的source map,可以传递给后方loader共享 // meta:其他需要传给后方loader共享的信息,可自定义 console.log('loader1 执行了') return source }
```
四、plugin
- 插件的本质
【js插件|webpack的构建原理小结】plugin的本质是一个类,类里面有一个apply方法,apply的参数为compiler,在其上面会有一些hooks,不同的hooks对应不同的tapable实例。
class DonePlugin{
apply(compiler){
compiler.hooks.done.tap('DonePlugin',()=>{
console.log('done-plugin钩子触发了')
})
}
}module.exports = DonePlugin
推荐阅读
- 手写webpack核心原理,再也不怕面试官问我webpack原理
- webpack|webpack5原理
- nuxtjs|闲云旅游项目开发-(第一篇(使用Element-ui实现主页轮播图))
- 商城项目学习|牛客网的项目梳理
- javascript|Set与Map的区别
- 小程序|(已更新)超火的微信养宠小程序源码+十二生肖素材
- 工作第一年|快速上手微信小程序(纯原生)基于微信开发者工具+云开发
- es6|ES学习之路(六)-Set&Map数据结构
- 逆向进阶,利用 AST 技术还原 JavaScript 混淆代码