玩转 webpack 学习笔记
ES module:
import * as largeNumber from 'large-number';
// ...
largeNumber.add('999', '1');
CJS:
const largeNumbers = require('large-number');
// ...
largeNumber.add('999', '1');
AMD:
require(['large-number'], function (large-number) {
// ...
largeNumber.add('999', '1');
});
抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
在线:demo: https://esprima.org/demo/parse.html
__webpack_require
用来加载模块,返回 module.exports
WEBPACK_REQUIRE_METHOD(0)
启动程序可以将 ES6 语法转换成 ES5 的语法,生成的 JS 文件可以在浏览器中运行
可以分析模块之间的依赖关系
新建 mini-webpack 文件夹,执行下面命令,初始化项目
npm init -y
npm i babylon babel-core babel-traverse
这里需要安装下面这个插件,不安装到时会报错
npm i babel-preset-env
里面模仿 webpack 的配置
const path = require('path');
module.exports = {
// 入口
entry: path.join(__dirname, './src/index.js'),
// 输出文件
output: {
path: path.join(__dirname, './dist'),
filename: 'kaimo.js'
}
}
新建 src,里面添加 index.js
文件,里面依赖 common 文件夹里的 kaimo666.js
里的方法
index.js
文件
import { hello } from './common/kaimo666.js';
document.write(hello('kaimo666'));
kaimo666.js
文件
export function hello(name) {
return `hello ${name}`;
}
结构如下:
新建 lib 文件夹,首先添加 index.js 文件,到时执行 node ./lib/index.js
就可以进行编译打包了。
// 编译模块
const Compiler = require('./compiler.js');
// 获取配置
const options = require('../minipack.config.js');
// 实例化 compiler
new Compiler(options).run();
然后实现 compiler.js
功能里面需要结束 config 的配置,以及 run 去执行。
const { getAst, getDependencis, transform } = require("./parser.js");
const path = require('path');
const fs = require('fs');
module.exports = class Compiler {
constructor(options) {
const { entry, output } = options;
this.entry = entry;
this.output = output;
this.modules = [];
}
run() {
// 从入口文件开始构建
const entryModule = this.buildModule(this.entry, true);
this.modules.push(entryModule);
// 遍历模块依赖进行构建
this.modules.map(_module => {
_module.dependencies.map(dependency => {
this.modules.push(this.buildModule(dependency));
})
})
// 构建完成输出文件
this.emitFiles();
}
/**
* 构建模块:用于获取文件的路径,ast,相关依赖
* @param filename 文件路径
* @param isEntry 是否是入口文件
* */
buildModule(filename, isEntry) {
let ast;
if(isEntry) {
ast = getAst(filename);
} else {
// 获取文件的绝对路径:process.cwd()是指当前node命令执行时所在的文件夹目录
let absolutePath = path.join(process.cwd(), './src', filename);
ast = getAst(absolutePath);
}
return {
filename,
dependencies: getDependencis(ast),
transformCode: transform(ast)
}
}
// 输出文件
emitFiles() {
// 输出的文件路径
const outputPath = path.join(this.output.path, this.output.filename);
// 组装依赖的 modules
let modules = '';
this.modules.map(_module => {
modules += `'${_module.filename}': function (require, module, exports) { ${_module.transformCode} },`
})
// 组装生成的代码 bundle
const bundle = `
(function(modules){
function require(fileName) {
const fn = modules[fileName];
const module = { exports: {} };
fn(require, module, module.exports);
return module.exports;
}
require('${this.entry}');
})({${modules}})
`;
console.log("emitFiles--->", outputPath, bundle)
// recursive: true 参数,不管创建的目录是否存在
fs.mkdir(this.output.path, { recursive: true }, function(err) {
if (err) throw err;
console.log("目录创建成功");
// 使用 fs.writeFileSync 将数据同步写入文件
fs.writeFileSync(outputPath, bundle, 'utf-8');
console.log("打包完毕");
});
}
}
最后实现 parser 里的相关方法
const fs = require('fs');
const babylon = require('babylon');
const { default: traverse } = require('babel-traverse');
const { transformFromAst } = require('babel-core');
module.exports = {
// 获取文件的 ast
getAst: path => {
// 同步读取文件
console.log("getAst----path>", path)
const content = fs.readFileSync(path, 'utf-8');
console.log("getAst---->", content)
// 分析AST,从中得到 import 的模块信息(路径)
return babylon.parse(content, {
sourceType: 'module'
})
},
// 获取文件的依赖
getDependencis: ast => {
const dependencies = [];
traverse(ast, {
// ImportDeclaration 方法:当遍历到 import 时的一个回调
ImportDeclaration: ({ node }) => {
// 将依赖 push 到 dependencies 中
dependencies.push(node.source.value);
}
});
return dependencies;
},
transform: ast => {
// es6 转化为 es5
const { code } = transformFromAst(ast, null, {
presets: ['env']
});
return code;
}
}
结构如下:
在 package.json 里添加下面脚本
"build": "node ./lib/index.js"
然后我们执行
npm run build
打包完成之后我们可以看到多了一个 dist 的文件夹,里面有打包好的 kaimo.js
文件
我们在 dist 文件夹下面添加 index.html
文件,添加下面代码
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="./kaimo.js">script>
body>
html>
浏览器访问 index.html
文件,效果如下
我们改动一下 src 下 index.js
的代码
import { hello } from './common/kaimo666.js';
document.write(hello('凯小默 kaimo777'));
然后打包,成功之后刷新页面,我们可以看到效果也变了。