Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
Webpack的配置文件的作用是激活Webpack的加载项和插件,通过定义和配置这些加载项和插件,我们可以更好地处理前端资源,优化构建过程,提高构建效率和应用程序的性能。
webpack --config myconfig.js
// 在这个例子中,Webpack将使用myconfig.js作为配置文件,而不是默认的webpack.config.js。
当Webpack启动时,它会读取webpack.config.js文件,并将配置项拷贝到options对象中,并加载用户配置的插件,并将它们添加到构建过程中。通过这些配置项和插件,我们可以定制Webpack的行为,以满足项目的特定需求,更好地处理前端资源、优化构建过程和提高构建效率。
shell 脚本参数一般通过 shell 直接输入或者通过 npm script 输入。
如上图所示,
①通过 shell 执行 npm run start 后,会自动执行 webpack --config webpack.config.js ,
②通过 shell 的 config 参数将配置文件webpack.config.js传入 webpack 中。
每次在命令行中输入webpack后,操作系统会查找(当前目录下)./node_modules/.bin/webpack并执行该文件作为shell脚本,以便启动Webpack构建过程。在脚本中,它可能使用了一些命令行参数来进一步定制构建过程。
这个脚本通常会包含以下逻辑:
①调用 ./node_modules/webpack/bin/webpack.js :这是Webpack核心的入口点,它实际上执行了构建过程。
②追加任何附加的命令行参数:例如,-p和-w是两个常见的命令行参数。
这样做是为了提供一种灵活的方式来启动Webpack,并允许用户通过命令行参数来定制构建过程。
(下图中, webpack.js 是 webpack 的启动文件,而 $@ 是后缀参数)
在命令行中运行Webpack时,可以通过--参数 来传递一些指令给Webpack。这些指令会被解析为一个对象,并与Webpack的配置对象合并。合并后的配置对象会被传递给Webpack的插件和加载器,以便它们根据这些配置来执行相应的任务。
在 webpack.js 这个文件中 webpack 通过 optimist 将用户配置的 webpack.config.js 和 shell 脚本传过来的参数整合成 options 对象传到了下一个流程的控制对象中。这个 options 对象包含了所有用于控制 Webpack 构建过程的配置和参数。
为了整合 webpack.config.js 中的配置和命令行参数,Webpack 使用了一个叫做 optimist 的库。optimist 是一个用于解析命令行参数的库,可以将命令行参数解析为 JavaScript 对象,这样它们就可以与 webpack.config.js 中的配置进行合并。它可以帮助 Webpack 将用户通过命令行传递的参数与配置文件中的选项整合在一起,形成一个完整的 options 对象。这个 options 对象会被传递给后续的插件或加载器,以便它们可以根据这些配置来执行相应的任务。
ifBooleanArg("hot", function() {
ensureArray(options, "plugins");
var HotModuleReplacementPlugin = require("../lib/HotModuleReplacementPlugin");
options.plugins.push(new HotModuleReplacementPlugin());
});
...
return options;
//这段代码的目的是检查是否存在一个名为"hot"的参数,
//如果存在并且为真,那么它会确保options对象有一个插件数组,并向该数组中添加一个热模块替换插件的实例。
//最后,返回修改后的options对象。
{
entry: {},//入口配置
output: {}, //输出配置
plugins: [], //插件集合(配置文件 + shell指令)
module: { loaders: [ [Object] ] }, //模块配置
context: //工程路径
...
}
var webpack = require("../lib/webpack.js");
var compiler = webpack(options);
//options是一个对象,包含了Webpack的配置信息,这些配置可以来自文件、环境变量、命令行参数等。
在Webpack中直接获取Shell脚本参数并不直接可能,因为Webpack和Shell脚本在运行时是相互独立的。Webpack主要处理JavaScript和相关资源文件的打包,而Shell脚本则用于执行操作系统命令和操作。只有使用CLI进行构建才能自动获取到Shell里输入的参数;如果是不依赖CLI,手动构建的,那么就需要自己写个脚本去读取和处理Shell 的参数。
如果想要在Webpack中获取Shell脚本参数,可以使用Node.js的命令行参数解析库,例如yargs或commander。这些库可以帮助你解析命令行参数,并在Webpack的配置文件中使用这些参数。
yargs 是一个非常有用的 Node.js 库,用于解析命令行参数,可以轻松地定义和解析命令行选项、参数和子命令,并将它们转换为 JavaScript 对象,这个对象可以作为参数传递给其他函数或流程,使得命令行参数的处理更加灵活和方便。
假设你有一个 webpack.config.js 文件,其中定义了一些 webpack 配置选项,还有一些 shell 脚本参数,你想将它们整合到一个 options 对象中,然后将这个对象传递给下一个流程。下面是一个简单的示例,展示了如何使用 yargs 来实现这个目标:
npm install yargs
// 引入 yargs 模块,它是命令行参数解析库
const yargs = require('yargs/yargs');
// 引入 yargs 的 helpers,它提供了一些辅助函数,如 hideBin,用于隐藏 yargs 的内部命令
const { hideBin } = require('yargs/helpers');
// 引入 path 模块,用于处理文件和目录的路径
const { join } = require('path');
// 引入 webpack.config.js,假设它包含了一些 webpack 的配置选项
const webpackConfig = require('./webpack.config.js');
// 使用 yargs 创建一个命令行参数解析器,并隐藏 yargs 的内部命令
const argv = yargs(hideBin(process.argv))
// 定义一个命令行参数 'port',它的别名是 'p',类型是 'number',描述是 '指定服务器监听的端口'
.option('port', {
alias: 'p',
type: 'number',
description: '指定服务器监听的端口',
})
// 定义一个命令行参数 'env',它的别名是 'e',类型是 'string',描述是 '指定环境变量'
.option('env', {
alias: 'e',
type: 'string',
description: '指定环境变量',
})
// 添加一个帮助选项,当用户在命令行中输入 --help 时,会显示帮助信息
.help()
// 解析命令行参数,将解析结果存储在 argv 对象中
.argv;
// 将 webpack.config.js 中的配置和命令行参数合并到一个 options 对象中
const options = { ...webpackConfig, port: argv.port, env: argv.env };
// 打印 options 对象,以供调试或输出给下一个流程使用
console.log(options);
在这个示例中,我们定义了两个命令行参数:port 和 env。然后,我们将 webpack.config.js 中的配置和这两个命令行参数合并到一个 options 对象中。最后,我们打印出这个 options 对象。
node cli.js --port 3000 --env production
以下是另外一个使用yargs库的示例(必须传要求的参数):
npm install yargs
// 导入yargs库
const yargs = require('yargs/yargs');
// 导入hideBin帮助函数,它用于隐藏命令行中的二进制部分
const { hideBin } = require('yargs/helpers');
// 使用yargs创建一个命令行参数对象
const argv = yargs(hideBin(process.argv))
// 定义一个名为'input'的命令行参数,用户可以通过'-i'或'--input'来指定
.option('input', {
alias: 'i',
type: 'string', // 参数类型为字符串
description: 'Input file path', // 描述该参数的作用,即输入文件路径
})
// 定义一个名为'output'的命令行参数
.option('output', {
alias: 'o',
type: 'string', // 参数类型为字符串
description: 'Output file path', // 描述该参数的作用,即输出文件路径
})
// 如果用户没有提供'input'和'output'这两个参数,则显示错误信息
.demandOption(['input', 'output'], 'You must provide both input and output file paths')
// 显示帮助信息,当用户在命令行中输入'-h'或'--help'时显示
.help()
// 获取解析后的命令行参数对象
.argv;
module.exports = {
// ...其他配置项...
// 入口文件路径,从命令行参数中获取
entry: argv.input,
// 输出配置
output: {
// 输出文件的路径,相对于当前目录,使用命令行参数指定
path: path.resolve(__dirname, argv.output),
// 输出文件的名称
filename: 'bundle.js',
},
};
npx webpack --input=src/index.js --output=dist/bundle.js
在这个例子中,--input和--output参数分别指定了输入和输出文件路径,你可以在Webpack配置中使用argv.input和argv.output访问这些参数,根据传递的参数来指定打包的入口和输出文件的位置。
执行之后,Shell命令行中传入的参数值将被合并到配置对象中,并覆盖默认值或先前指定的值。
下面是大佬的代码,码住:
// 定义一个异步函数,接受一个可选参数platform,默认为空字符串
async function runWebpackBuild(platform = '' ) {
// 返回一个新的Promise对象
return new Promise(async (resolve, reject) => {
try {
// 根据传入的platform参数,构建webpack构建脚本的路径
const builderPath = path.resolve(__dirname, `./webpack.${platform}.build.js`)
// 构建并执行Node.js命令,用于运行webpack构建脚本
const nodeCmd = `node ${builderPath} --lang=${argvs.lang || 'my-en'} --secondLang=${argvs.secondLang || ''} --env=${argvs.env} --staticSource=${argvs.staticSource}`}
// 这行代码被注释掉了,如果取消注释,它将打印出要执行的Node.js命令
// consola.info(`>>>> 执行命令: ${nodeCmd}` )
// 执行Node.js命令,等待命令执行完成
await execCmd( nodeCmd )
// 如果命令执行成功,则返回一个解析的Promise,表示构建成功
return resolve(true)
} catch (error) {
// 如果命令执行出错,则返回一个拒绝的Promise,表示构建失败
return reject(error)
}
})
}
在加载配置文件和shell后缀参数申明的插件,并传入构建信息options对象后,开始整个webpack打包最漫长的一步。而这个时候,真正的webpack对象才刚被初始化,具体的初始化逻辑在 ./node_modules/webpack/lib/webpack.js中,如下所示:
//Compiler 类继承了 Tapable。Compiler对象定义在./node_modules/webpack/lib/Compiler.js
//Tapable 是一个提供钩子(hooks)功能的基类,
//这些钩子允许我们在特定的事件点上执行自定义逻辑。
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
//生命周期钩子
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
make: new AsyncParallelHook(["compilation"]),
entryOption: new SyncBailHook(["context", "entry"])
// 定义了很多不同类型的钩子
};
// ...
}
}
//这是一个Webpack的外部API函数,它接受一个配置对象options作为参数。
//在函数内部创建了一个新的Compiler实例。这意味着使用Webpack时,实际上是在使用这个Compiler类来处理和打包资源。
function webpack(options) {
var compiler = new Compiler();
...// 检查options,若watch字段为true,则开启watch线程
return compiler;
}
...
在 lib/webpack.js 文件中,Compiler 对象的初始化逻辑主要包括以下几个步骤:
① 创建 Compiler 实例:通过调用 new Compiler() 创建一个新的 Compiler 实例。
② 配置 Compiler:根据传入的配置对象(如 webpack.config.js 中定义的配置),对 Compiler 进行相应的配置,例如设置输出目录、加载器等。
③ 注册插件: 通过调用compiler.hooks上的方法,注册各种插件。这些插件可以在构建过程中执行各种自定义操作,如资源优化、代码分割等。
④ 初始化其他组件:根据配置 初始化其他与构建相关的组件,如Compilation、ResolverFactory等。
⑤ 返回 Compiler 实例:将初始化完成的 Compiler 实例返回,以便在构建过程中使用。
当 Webpack 初始化完成后,Compiler 对象会存储所有的配置和组件,但实际的构建过程还没有开始,要开始构建过程,需要调用 Compiler 对象的 run 方法。调用 Compiler 对象的 run 方法真正启动webpack构建流程,它是启动构建过程的最后一步,也是实际生成打包文件的开始,在 run 方法中,Webpack 会执行一系列的步骤来处理资源、打包代码、执行插件等,这些步骤会根据配置和插件的逻辑来执行,最终生成打包后的文件。
webpack 的实际入口是 Compiler 中的 run 方法,run 一旦执行后,就开始了编译和构建流程 ,其中有几个比较关键的 webpack 事件节点。
在 Webpack 的构建过程中,当调用compiler.run方法后,会触发一系列的编译阶段。其中,compile阶段是这些阶段中的第一个。
compiler.run 后首先会触发 compile ,这一步会构建出 Compilation 对象。
这个Compilation对象是编译阶段的主要执行者,有两个作用,
Compilation对象是Webpack内部工作机制的重要部分,它具有大量的钩子(hooks),这些钩子可以被插件用来插入自定义的逻辑。当检测到一个文件变化时,Webpack会创建一个新的Compilation对象,这样它就可以重新编译并加载进内存。在编译阶段,模块会被加载、封存、优化、分块、哈希和重新创建。这些过程都与Compilation对象紧密相关。
在创建module之前,Compiler会触发make,并调用Compilation.addEntry方法,通过options对象的entry字段找到我们的入口js文件。
之后,在addEntry中调用私有方法_addModuleChain,
这个_addModuleChain方法主要做了两件事情。
一是根据模块的类型获取对应的模块工厂并创建模块,
二是构建模块。
_addModuleChain 是 Webpack 的内部方法,用于处理模块链,能根据给定的依赖项创建新的模块实例。这个方法主要的工作是根据给定的 module 和 context 对象,以及一些其他参数,来创建一个新的模块。具体来说,这个方法会根据模块的类型(例如,JavaScript、CSS、图片等)来获取对应的模块工厂并创建模块。
_addModuleChain(context, dependency, onModule, callback) {
...
// 根据依赖查找对应的工厂函数
const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);
// 调用工厂函数moduleFactory的create来生成一个空的NormalModule对象,NormalModul是Webpack中表示一个模块的类。
moduleFactory.create({
dependencies: [dependency]
...
}, (err, module) => {
...
//定义一个afterBuild函数,这个函数在模块编译完成后会被调用。
const afterBuild = () => {
this.processModuleDependencies(module, err => {
if (err) return callback(err);//处理过程中发生错误,则通过回调函数返回错误信息
callback(null, module); //处理成功,则通过回调函数返回新创建的模块
});
};
//开始模块编译。这里会调用buildModule方法来进行实际的编译操作。
this.buildModule(module, false, null, null, err => {
...
afterBuild();//当模块编译完成后,调用之前定义的 afterBuild函数。
})
})
}// _addModuleChain 方法结束
_addModuleChain 是 Webpack 的内部方法,用于处理模块链。以下是该方法的主要过程:
1. 接收参数:该方法接收四个参数,包括上下文对象 context、依赖项 dependency、回调函数 onModule 和回调函数 callback。
2. 获取模块工厂:根据依赖项的构造类型,从 dependencyFactories 集合中获取对应的模块工厂。
3. 创建新模块:使用获取到的模块工厂,通过 create 方法创建一个新的模块实例。
4. 添加模块到编译队列:将新创建的模块加入到编译对象的 modules 数组中,以便在后续的打包过程中使用该模块。
5. 处理模块依赖:开始处理新创建模块的依赖项,递归地将它们也加入到编译队列中。
6. 编译模块:当所有依赖项都处理完毕后,开始编译当前模块。Webpack 会调用 buildModule 方法来执行实际的编译操作,包括解析模块代码、生成依赖图等。
7. 处理编译结果:当模块编译完成后,Webpack 会进行一些后续处理,例如将模块添加到编译对象的 modules 数组中,以便在后续的打包过程中使用该模块。
8. 回调函数:当整个过程完成后,通过回调函数返回编译结果或错误信息。
而构建模块作为最耗时的一步,又可细化为三步:
1、调用各 loader 处理模块之间的依赖
webpack提供的一个很大的便利就是能将所有资源都整合成模块,不仅仅是 js文件。因此,需要一些loader ,比如url-loader、jsx-loader、css-loader等,来让我们可以直接在源文件中引用各类资源。
对每一个require()用对应的loader进行加工:webpack调用doBuild(),对每一个require()用对应的loader进行加工,最后生成一个js module。当webpack遇到require()或import语句时,它会使用对应的加载器(loader)处理这些依赖项。加载器可以将源文件转换成Webpack能够理解和处理的模块。然后,Webpack会调用acorn来解析这些经过加载器处理的源文件,生成抽象语法树(AST)。
// 定义在Compilation对象上的_addModuleChain方法,该方法用于处理模块链。
// 接收四个参数:context(上下文)、dependency(依赖项)、onModule(处理模块的回调函数)和callback(回调函数)。
Compilation.prototype._addModuleChain = function process(context, dependency, onModule, callback) {
// 如果Compilation对象启用了性能分析,则记录当前时间,用于后续计算该方法的执行时间。
var start = this.profile && +new Date();
...
// 根据模块的类型获取对应的模块工厂并创建模块
// 根据传入的依赖项的构造函数,从dependencyFactories集合中获取对应的模块工厂。
// 这个集合中存储了各种类型的模块工厂,用于创建不同类型的模块。
var moduleFactory = this.dependencyFactories.get(dependency.constructor);
...
moduleFactory.create(context, dependency, function(err, module) {
var result = this.addModule(module); // 将新创建的模块添加到Compilation对象的modules数组中。
...
this.buildModule(module, function(err) { // 调用buildModule方法来构建模块。
...
// 构建模块,添加依赖模块
}.bind(this)); // 由于使用了bind(this)来绑定回调函数的执行上下文为Compilation对象,所以后续操作可以在Compilation对象的上下文中进行。
}.bind(this));
};
2、调用 acorn 解析经 loader 处理后的源文件,生成抽象语法树 AST
Acorn是一个轻量级、流式的JavaScript解析器,可以生成抽象语法树(AST)。通过Acorn解析源文件后,可以获得源文件的语法结构,以便进行进一步的分析、转换或打包。虽然加载器在处理源文件时起到重要作用,但AST的生成是由Acorn解析器完成的。
// 定义Parser对象的parse方法,该方法用于解析源代码并返回抽象语法树(AST)。
// 它接收两个参数:source(待解析的源代码)和initialState(初始状态)。
Parser.prototype.parse = function parse(source, initialState) {
var ast;
if (!ast) {
// acorn以es6的语法进行解析
ast = acorn.parse(source, {
ranges: true, // 解析时保留每个语法元素的原始位置范围信息。
locations: true, // 解析时保留每个语法元素的原始位置信息。
ecmaVersion: 6, // 使用ECMAScript 6(即ES6)的语法版本进行解析。
sourceType: "module" // 将源代码视为模块,解析为模块的抽象语法树(AST)。
});
}
...
};
3、遍历AST,构建该模块所依赖的模块。
对于当前模块,或许存在着多个依赖模块。当前模块会开辟一个依赖模块的数组,在遍历AST时,将require()中的模块通过addDependency()添加到数组中。当前模块构建完成后,webpack调用processModuleDependencies开始递归处理依赖的module,接着就会重复之前的构建步骤。
// 定义Compilation对象的addModuleDependencies方法,用于向给定的模块添加依赖。
// 它接收五个参数:module(要添加依赖的模块)、dependencies(依赖数组)、bail(是否立即停止编译)、cacheGroup(缓存组)和recursive(是否递归添加依赖)。
// 最后一个参数callback是回调函数,用于在添加依赖完成时进行回调。
Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {
// 根据依赖数组(dependencies)创建依赖模块对象
var factories = []; // 用于存储依赖模块的工厂对象
for (var i = 0; i < dependencies.length; i++) { // 为每个依赖创建一个工厂对象并存储到factories数组中。
var factory = _this.dependencyFactories.get(dependencies[i][0].constructor); // 获取对应的模块工厂
factories[i] = [factory, dependencies[i]]; // 将工厂对象和依赖项一起存储到factories数组中。
}
...
// 与当前模块构建步骤相同
}
module是webpack构建的核心实体,也是所有module的父类,它有几种不同子类:NormalModule,MultiModule,ContextModule,DelegatedModule 等。但这些核心实体都是在构建中都会去调用对应方法,也就是模块构建函数build()。
它主要完成了以下几件事情:
① 初始化模块信息:设置模块的构建时间戳,并标记模块为已构建。
② 检查是否需要跳过解析:如果提供了module.noParse选项,并且是字符串或正则表达式数组,代码会检查当前模块的请求是否匹配其中的某个模式。如果匹配,则直接返回回调,不进行后续解析操作。
③ 解析源代码:使用acorn库解析模块的源代码,生成抽象语法树(AST)。
④ 错误处理:如果在解析过程中发生错误,代码会捕获异常,获取源代码内容,清空源代码引用,并抛出一个包含错误信息和源代码内容的ModuleParseError异常。
⑤ 返回回调:如果一切正常,或者在跳过解析的情况下,函数会返回回调。
对于每一个module,它都会有这样一个构建方法build()。当然,它还包括了从构建到输出的一系列的有关module生命周期的函数,我们通过module父类类图其子类类图(这里以NormalModule为例)来观察其真实形态:
可以看到,无论是构建流程,处理依赖流程,还是后面的封装流程都是与module密切相关的。
在所有模块及其依赖模块编译(build)完成后,webpack会触发一个名为seal的生命周期事件,这个事件标志着构建过程的结束,并且webpack将开始对构建后的结果进行封装。在seal事件触发后,webpack会逐个对每个module和chunk进行整理,这个阶段的目标是生成编译后的源码,进行合并、拆分以及生成hash。
当seal事件被触发时,Webpack会执行以下操作:
1. 调用插件的seal方法:Webpack会通知所有已注册的插件,构建过程已经完成,并可以开始执行自定义的seal方法。这允许插件对构建后的结果进行自定义处理或添加额外的元数据。
2. 整理模块和chunks:Webpack会对所有模块和chunks进行整理,确保它们按照正确的顺序排列。这包括根据模块的依赖关系进行排序,以及合并和拆分代码块。
3. 生成最终的assets:在整理完成后,Webpack会生成最终的assets,包括JavaScript、CSS、图片等文件。这些assets是构建过程的输出结果,可以直接部署到生产环境中使用。
4. 触发其他插件事件:在seal事件被触发后,Webpack可能还会触发其他插件事件,例如optimize-tree等。这些事件允许插件执行进一步优化或其他自定义操作。
简而言之,seal事件是Webpack构建过程中的一个重要环节,它标志着构建过程的结束,并允许插件对构建后的结果进行封装和自定义处理。
同时这是我们在开发时进行代码优化和功能添加的关键环节。例如,可以利用seal事件对代码进行压缩、混淆或优化,以减少文件大小和提高性能。还可以使用插件来添加元数据、生成文档或执行其他与构建结果相关的任务。可以触发其他插件事件,如optimize-tree,以进一步优化模块和代码树的结构。这些优化可以提高应用程序的性能、减少加载时间并改善用户体验。
// 定义Compilation对象的seal方法,该方法接受一个回调函数作为参数
Compilation.prototype.seal = function seal(callback) {
this.applyPlugins("seal");//调用并触发所有已注册的插件的seal事件。这允许插件执行一些自定义逻辑或操作
this.preparedChunks.sort(function(a, b) {//对已准备的chunks进行排序。排序的依据是chunk的名称。
if (a.name < b.name) {
return -1;//如果名称a小于名称b,则返回-1;
}
if (a.name > b.name) {
return 1;//如果名称a大于名称b,则返回1;
}
return 0;//如果两者相等,则返回0
});
// 遍历已排序的chunks,并对每个chunk执行以下操作:
this.preparedChunks.forEach(function(preparedChunk) {
var module = preparedChunk.module;//获取与当前chunk关联的模块
var chunk = this.addChunk(preparedChunk.name, module);//向当前的chunks集合中添加一个新的chunk,chunk的名称为preparedChunk.name,并且与module关联
chunk.initial = chunk.entry = true;//设置新添加的chunk的一些属性:它是一个初始chunk,也是一个入口chunk
// 整理每个Module和chunk,每个chunk对应一个输出文件。将module添加到chunk中,并把chunk添加到module中。这表示module和chunk之间存在依赖关系
chunk.addModule(module);
module.addChunk(chunk);
}, this);//使用bind确保回调函数中的this指向Compilation对象实例
//异步调用并触发所有已注册的插件的"optimize-tree"事件。这个事件允许插件对chunks和modules进行优化。优化完成后,会执行回调函数。
this.applyPluginsAsync("optimize-tree", this.chunks, this.modules, function(err) {
if (err) {
return callback(err);//如果回调函数中发生错误,则将错误传递给外部的callback函数
}
... // 触发插件的事件
this.createChunkAssets(); //生成最终assets,可能包括JavaScript、CSS、图片等。生成的assets会被存储在compiler的assets属性中。
... // 触发插件的事件
}.bind(this));// 使用bind确保回调函数中的this指向Compilation对象实例
};// seal方法结束
在封装过程中,webpack会调用Compilation中的createChunkAssets方法进行打包后代码的生成。createChunkAssets函数通过收集和封装代码片段,将它们转化为可执行的模块,并将这些资源文件存储在compiler.assets对象中,以便后续的输出和引用。createChunkAssets流程如下:
createChunkAssets的流程主要包括以下步骤:
① 判断是否是入口chunk:createChunkAssets函数首先判断当前处理的chunk是否是入口chunk。如果是入口chunk,则使用MainTemplate进行封装,否则使用ChunkTemplate进行封装。
② 处理依赖关系:在语法树解析阶段,createChunkAssets函数会收集文件的依赖关系,包括需要插入或替换的部分。
③ 生成代码片段:在generate阶段,createChunkAssets函数会生成各种代码片段,包括需要打包输出的资源。
④ 封装代码:createChunkAssets函数会对生成的代码片段进行收集和封装,将它们封装成可执行的模块。一旦代码片段被封装完毕,它们就会被存储在compiler.assets对象中。
在Webpack的打包过程中,createChunkAssets方法执行完毕后,会调用Compiler中的emitAssets()方法。所以最后一步是,webpack调用Compiler中的emitAssets(),将生成的代码输入到output的指定位置,完成最终文件的输出,从而webpack整个打包过程结束。
output对象在Webpack配置中定义了输出目录、文件名等选项,emitAssets()方法会将生成的代码按照output配置的规则输出到相应的目录中。输出的文件可以是JavaScript、CSS、图片等资源文件,以及其他依赖图和其他元数据。
值得注意的是,若想对结果进行处理,则需要在emitAssets()触发后对自定义插件进行扩展,可以通过编写自定义插件来实现。例如,可以编写一个自定义插件,并在emitAssets()触发后对生成的代码进行进一步处理或修改。在插件的apply方法中,可以访问compiler对象并监听emit事件。当emit事件触发时,可以执行自己的逻辑来处理生成的代码或资源。
具体来说,可以在插件中实现以下步骤:
①访问compiler对象并监听emit事件。
②在emit事件触发时,获取生成的代码或资源。
③对获取的代码或资源进行进一步处理或修改。
④如果你想将修改后的结果输出到文件系统,可以使用compiler.outputFileSystem来写入文件。
⑤完成处理后,你可以选择将修改后的结果重新赋值给compiler.assets对象,以便Webpack将其输出到指定的目录中。
使用webpack 可以有两种方式:
①使用 webpack-cli 构建;
②使用 webpack 核心对象构建;
1.安装依赖:在项目根目录下,打开终端并运行以下命令来安装webpack和webpack-cli
npm install --save-dev webpack webpack-cli
// --save-dev 参数的作用是将安装的包作为开发依赖添加到项目的package.json文件中。
// 这意味着这些包仅在开发过程中需要,而不是在生产环境中。
2.创建webpack.config.js文件:在项目根目录下创建一个名为webpack.config.js的文件。这个文件将包含构建配置。
3.配置文件内容:根据你的项目需求,你可以在webpack.config.js中设置各种配置项,例如入口文件、输出文件、加载器、插件等。下面是一个简单的示例:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [],
};
4.运行构建:在你的项目根目录下,运行以下命令来构建项目:
npx webpack --config webpack.config.js
5.启动开发服务器(可选):如果想在开发过程中自动重新构建项目,可以使用webpack-dev-server。
npm install --save-dev webpack-dev-server
"scripts": {
"start": "webpack serve --open --config webpack.config.js"
}
如果不使用webpack CLI,可直接使用webpack的模块化API来手动构建项目。可以通过Node.js的require()函数来手动导入webpack和webpack配置文件,并使用webpack的API进行构建。
1.首先,确保已经安装了webpack和webpack-core。
npm install webpack webpack-core --save-dev
//在Webpack的构建流程中,Webpack-core扮演着重要的角色,
//它负责解析入口文件,递归读取引入文件所依赖的文件内容,
//生成AST语法树,并根据AST语法树生成浏览器能够运行的代码。
2.然后,在需要使用webpack的JavaScript文件中,使用以下代码导入webpack:
const webpack = require('webpack');
3.接下来,导入webpack配置文件。假设webpack配置文件名为webpack.config.js,使用以下代码将其导入:
const config = require('./webpack.config.js');
4.现在,可以使用webpack的API进行构建。
①以下是一个示例,展示了如何使用webpack进行构建:
// 导入webpack模块,这样我们就可以使用webpack的API进行构建
const webpack = require('webpack');
//【const path = require('path');】
//【const fs = require('fs');】
// 导入我们的webpack配置文件。这个文件包含了webpack如何处理我们的项目的所有信息
const config = require('./webpack.config.js'); //*****读取webpack配置文件*****
//【const config = JSON.parse(fs.readFileSync('webpack.config.js', 'utf8'));】
// 使用webpack的API执行构建过程。这里的config是我们之前导入的webpack配置
const compiler = webpack(config); //*****创建webpack实例*****
// 运行编译实例。编译实例执行后,会调用回调函数,将错误(如果有的话)和构建统计信息作为参数传入
compiler.run((err, stats) => { //*****编译入口文件*****
// 当构建过程完成时,回调函数会被调用。如果构建过程中出现错误,err会被赋值。stats对象包含了构建过程中的各种信息
if (err || stats.hasErrors()) {
// 如果err或stats.hasErrors()返回true,说明构建过程中出现了错误,我们将其打印出来
console.error('编译出错:', err);
} else {
// 如果构建过程没有错误,我们打印一条成功消息
console.log('编译完成:', stats.toString({ colors: true }));
}
});
在这个示例中,我们首先使用require()函数导入webpack和path模块。然后,我们使用fs模块读取webpack.config.js配置文件,并将其解析为JSON对象。接下来,我们创建一个webpack实例,并将配置文件传递给它。最后,我们调用compiler.run()方法来执行编译过程。
②还可以用另一种写法:
// 导入webpack模块,这样我们就可以使用webpack的API进行构建
const webpack = require('webpack');
// 创建一个Webpack配置对象。这个对象包含了webpack如何处理我们的项目的所有信息
const config = {
// 指定入口文件的路径。这里假设入口文件为./src/index.js
entry: './src/index.js',
// 配置输出目录的路径。这里假设输出目录为项目根目录下的dist文件夹
output: {
path: path.resolve(__dirname, 'dist'),
// 指定输出文件的名称。这里假设输出文件名为bundle.js
filename: 'bundle.js'
}
};
// 使用webpack的API执行构建过程。这里的config是我们之前创建的Webpack配置对象
webpack(config, (err, stats) => {
// 当构建过程完成时,回调函数会被调用。如果构建过程中出现错误,err会被赋值。stats对象包含了构建过程中的各种信息
if (err || stats.hasErrors()) {
// 如果err或stats.hasErrors()返回true,说明构建过程中出现了错误,我们将其打印出来
console.error(err);
} else {
// 如果构建过程没有错误,我们打印一条成功消息
console.log('Webpack build completed successfully.');
}
});
在上面的示例中,webpack(config, callback)函数用于执行构建。config参数是要使用的webpack配置对象,callback参数是一个回调函数,在构建完成后被调用。如果构建过程中出现错误或警告,它们将作为回调函数的参数传递给err和stats。我们可以根据需要进行错误处理或输出构建结果。
5.运行Webpack: 在命令行终端中,运行以下命令来执行Webpack:
npx webpack --config webpack.config.js
{
"name": "my-webpack-project",
"version": "1.0.0",
"scripts": {
"build": "webpack --config webpack.config.js" // 添加一个构建脚本
},
"devDependencies": {
"webpack": "^5.0.0", // 指定Webpack的版本号,根据实际情况进行调整
}
}
npm run build // 使用npm命令构建项目
6.启动开发服务器(可选):
npm install --save-dev webpack webpack-dev-server
const path = require('path');
module.exports = {
entry: './src/index.js', // 指定入口文件
output: {
path: path.resolve(__dirname, 'dist'), // 指定输出目录
filename: 'bundle.js', // 指定输出文件名
},
devServer: {
contentBase: './dist', // 指定服务器响应的静态资源目录
port: 3000, // 指定服务器监听的端口号
},
};
{
"name": "my-webpack-project",
"version": "1.0.0",
"scripts": {
"start": "webpack serve --open" // 添加一个启动脚本
},
"devDependencies": {
"webpack": "^5.0.0", // 指定 webpack 的版本号,根据实际情况进行调整
"webpack-dev-server": "^3.11.0" // 指定 webpack-dev-server 的版本号,根据实际情况进行调整
}
}
npm start // 使用 npm 命令启动服务器