本文将以 Webpack 5为例,通过简单实现,整体介绍一下Wwebpack 构建过程与实现细节。
1. 项目结构
simple-webpack
├── dist
├── src
│ ├── index.js
│ ├── module1.js
│ └── module2.js
├── loaders
│ ├── babelLoader.js
│ └── exampleLoader.js
├── plugins
│ └── examplePlugin.js
├── webpack.config.js
└── index.js
2. 设置项目和安装依赖
在项目根目录下运行以下命令初始化项目并安装依赖:
npm init -y
npm install @babel/core @babel/preset-env @babel/traverse babylon
3. 实现核心模块
3.1. 创建createAsset函数
在 index.js 文件中实现 createAsset 函数,用于读取和解析模块,并根据 loader 进行处理:
const fs = require('fs');
const path = require('path');
const babylon = require('babylon');
const traverse = require('@babel/traverse').default;
let ID = 0;
function applyLoaders(source, filename, loaders) {
for (let i = loaders.length - 1; i >= 0; i--) {
const loader = loaders[i];
if (loader.test.test(filename)) {
const loaderFn = require(loader.use);
source = loaderFn(source);
}
}
return source;
}
function createAsset(filename, loaders) {
let content = fs.readFileSync(filename, 'utf-8');
content = applyLoaders(content, filename, loaders);
const ast = babylon.parse(content, { sourceType: 'module' });
const dependencies = [];
traverse(ast, {
ImportDeclaration: ({ node }) => {
dependencies.push(node.source.value);
},
});
const id = ID++;
return {
id,
filename,
dependencies,
code: content,
};
}
module.exports = { createAsset };
3.2. 创建createGraph函数
在 index.js 文件中实现 createGraph 函数,用于构建依赖图:
const { createAsset } = require('./index');
const path = require('path');
function createGraph(entry, loaders) {
const mainAsset = createAsset(entry, loaders);
const queue = [mainAsset];
for (const asset of queue) {
const dirname = path.dirname(asset.filename);
asset.mapping = {};
asset.dependencies.forEach((relativePath) => {
const absolutePath = path.join(dirname, relativePath);
const child = createAsset(absolutePath, loaders);
asset.mapping[relativePath] = child.id;
queue.push(child);
});
}
return queue;
}
module.exports = { createGraph };
3.3. 创建bundle函数
在index.js文件中实现bundle函数,用于生成打包文件:
const { createGraph } = require('./index');
function bundle(graph) {
let modules = '';
graph.forEach((mod) => {
modules += `${mod.id}: [
function (require, module, exports) { ${mod.code} },
${JSON.stringify(mod.mapping)},
],`;
});
return `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports: {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;
}
module.exports = { bundle };
4. 编写配置文件
在webpack.config.js中定义基本的配置:
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
module: {
rules: [
{
test: /.js$/,
use: './loaders/babelLoader.js'
},
{
test: /.js$/,
use: './loaders/exampleLoader.js'
}
]
},
plugins: [
new (require('./plugins/examplePlugin.js'))()
]
};
5. 实现Loader和Plugin
5.1. Babel Loader
在loaders/babelLoader.js中实现Babel Loader:
const babel = require('@babel/core');
module.exports = function (source) {
const result = babel.transform(source, {
presets: ['@babel/preset-env']
});
return result.code;
};
5.2. Example Loader
在 loaders/exampleLoader.js中实现一个简单的 Loader:
module.exports = function (source) {
// Example loader logic
return source.replace(/console.log(/g, 'console.warn(');
};
5.3. Plugin
在 plugins/examplePlugin.js 中实现一个简单的 Plugin:
class ExamplePlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('ExamplePlugin', (compilation, callback) => {
console.log('ExamplePlugin is working!');
callback();
});
}
}
module.exports = ExamplePlugin;
6. 编写启动脚本
在 index.js 文件中整合所有模块,执行打包过程:
const fs = require('fs');
const path = require('path');
const { createGraph, bundle } = require('./index');
const config = require('./webpack.config');
function run() {
const graph = createGraph(config.entry, config.module.rules);
const result = bundle(graph);
if (!fs.existsSync(config.output.path)) {
fs.mkdirSync(config.output.path);
}
fs.writeFileSync(path.join(config.output.path, config.output.filename), result);
console.log('Bundle created successfully!');
}
run();
7. 示例源码
在 src 目录中编写示例 JavaScript 文件:
7.1. src/index.js
import module1 from './module1.js';
import module2 from './module2.js';
console.warn('This is the entry file.');
module1();
module2();
7.2. src/module1.js
export default function module1() {
console.warn('This is module 1.');
}
7.3. src/module2.js
export default function module2() {
console.warn('This is module 2.');
}
8. 运行示例
在项目根目录下运行以下命令打包项目:
node index.js
运行成功后,dist 目录下会生成 bundle.js 文件,其中包含打包后的代码。
9. 补充资料
Webpack 官方文档:webpack
Webpack 核心概念:Concepts | webpack
Webpack 相关库:Awesome webpack | webpack
官方 loader:Loaders | webpack
编写 loader 指南:Writing a Loader | webpack
编写 plugin 指南:Writing a Plugin | webpack
loader 执行原理: https://github.com/webpack/webpack/blob/40479852054817bb765631217c27739968768a0/lib/NormalModule.js#L903
官方 plugin:Plugins | webpack
插件机制实现:https://github.com/webpack/tapable
钩子列表: https://github.com/webpack/webpack/blob/40479852054817bb765631217c27739968768a0/lib/Compiler.js#L140
插件模式补充:Plugin Patterns | webpack
暂无评论内容