边学边想webpack5源码一起读(1)

740 阅读5分钟

引言

本文章基于webpack5源码进行解读,编写本文仅为方便作者学习以及记忆,如果有什么说的不对或者瞎猜的不对的地方,请大家指出,请勿乱喷,大家都是自己人感谢大家~

开始

思路是这样的,在学习之前,本身webpack的最基础的功能就是提供构建,所以我们创建一个最基础的文件结构方便我们做一个基础的构建。

第一步

首先创建一个文件夹并运行(文件夹的名称自己开心就行)

// 初始化
npm run init
// 填写的信息
// 下载webpack相关依赖
npm install webpack webpack-cli --save-dev
// 随便装个依赖后面要用
npm install lodash --save-dev

然后创建两个文件分别是index.jswebpack.config.js

// index.js
import _ from 'lodash'

export default {
  cloneDeepWithLog () {
    console.log('Deep Clone Obj:', ...arguments)
    return _.cloneDeep(...arguments)
  }
}
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './index.js',
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist'),
  },
};

最后简单在package.json配置相关命令"build": "webpack"并运行npm run build,就可以在./dist/main.js看到已经构建好的内容。那么我们已经有了一个最基础的工程,接着我们就要来看在我们运行命令的过程中到底发生了什么事情

第二步

首先我们要对过程进行分析,那么我们肯定是要理解这个过程中先后顺序,以及相互调用的方式方法。那么至少我们要有一个最大的框架然后我们再根据这个大的框架开始持续深挖其中的内容,基于刚刚的过程其实我们目前所知道的内容就是如图(小弟画图水平拙略请勿嘲笑)

流程图

那我们第一步就先看看npm run build这个命令执行后发生了什么,那么实际上npm run ***会来到package.json寻找我们对应的指令,比如我们这个对应的就是"build": "webpack",接着我们拿到webpack这个以后他会类似调用npx webpack一般去到./node_modules/.bin/*下去寻找同名的命令,如果大家跟着我一起安装了对应依赖相信也可以看到webpack的文件

./bin文件夹截图 然后我们通过我们IDE打开可以看到,里面相关的代码大概如下(我大致的简略了一下)

#!/usr/bin/env node

/**
 * @param {string} packageName name of the package
 * @returns {boolean} is the package installed?
 */
const isInstalled = packageName => {
	try {
		require.resolve(packageName);

		return true;
	} catch (err) {
		return false;
	}
};

/**
 * @param {CliOption} cli options
 * @returns {void}
 */
const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`);
	// eslint-disable-next-line node/no-missing-require
	const pkg = require(pkgPath);
	// eslint-disable-next-line node/no-missing-require
	require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

/**
 * @typedef {Object} CliOption
 * @property {string} name display name
 * @property {string} package npm package name
 * @property {string} binName name of the executable file
 * @property {boolean} installed currently installed?
 * @property {string} url homepage
 */

/** @type {CliOption} */
const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};

if (!cli.installed) {
    // 省略了一堆代码,主要核心就是如果你没安装webpack-cli那就安装了在执行runCLi 
} else {
	runCli(cli);
}

这一堆代码的核心在于runCli这个函数上,函数本身只负责了一件事情,那就是require("webpack-cli的package.json里bin.webpack-cli的路径")那这个路径就等同于我们当前项目项目的node_modules/webpack-cli/bin/cli.js大家可以尝试的自己找一下。ok来到这个文件本身

// node_modules/webpack-cli/bin/cli.js
#!/usr/bin/env node

'use strict';

const Module = require('module');

const originalModuleCompile = Module.prototype._compile;

require('v8-compile-cache');

const runCLI = require('../lib/bootstrap');
const utils = require('../lib/utils');

if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) {
    // 暂时忽略
}

process.title = 'webpack';

if (utils.packageExists('webpack')) {
    runCLI(process.argv, originalModuleCompile);
} else {
    // 省略 如果没有安装webpack就先安装,再运行runCLI
}

这里会有两个陌生的外部朋友Module,v8-compile-cache我们一一看看是干啥用的~

  • Module 先来看看最熟悉的陌生人,我们在编写node.js的模块的时候就经常有类似的语句比如module.exports = ***就跟他有关,因为node的CommonJs就与他有关,以下图来自于官方

Module 然后我们并没有找到关于prototype._compile的介绍,经过我的一番搜索在阮一峰大大的博客中找到一些内容(感谢大大!),大致说起就是,给我们的JS module中的内容提供执行环境并进行编译执行(如果理解有误,请讨论)。

  • v8-compile-cache 根据github上的说明,是当前库为require提供了一个hook(应该是引入了就默认挂载),然后利用V8对这些代码进行缓存以及缩短实例化时间~,看起来像是一个优化操作(随意猜想)

回归到执行,这里的关键路径函数依旧是runCLI(...),我们先关注到他的两个参数,一个就是我们刚刚已经了解过的陌生朋友,另一个是则是我们命令行的参数列表process.argv

process.argv

那么接着让我们来看看runCli(...)里面发生了什么吧~

// node_modules/webpack-cli/lib/bootstrap.js
const WebpackCLI = require('./webpack-cli');
const utils = require('./utils');

const runCLI = async (args, originalModuleCompile) => {
    try {
        // Create a new instance of the CLI object
        const cli = new WebpackCLI();

        cli._originalModuleCompile = originalModuleCompile;

        await cli.run(args);
    } catch (error) {
        utils.logger.error(error);
        process.exit(2);
    }
};

module.exports = runCLI;

我们发现runCli(...)实际上祝就是实例化我们webpack-cli,并赋予了它技术的编译执行模块参数,最后执行了里面的run方法~,这一篇先到这里,下一篇很快就会来我们会在一开头的地方把我们目前的整体所看到的内容集成在我们之前的图里~ 感谢大家观看我们下篇继续看,到底发生了什么情况。

下一篇的跳转地址: 点击这里