研究webpack源码的重点在于捋清楚各个hook的执行顺序,而这些hook又隶属于很多对象,最开始需要关注一些比较核心、比较重要的对象: Compiler Compilation NormalModuleFactory MainTemplate Parser 这些对象的构造函数都继承了Tapable Tapable是一个提供多种发布、订阅模式(同步、异步串行、异步并行)的类
Tapable有个非常重要的方法: apply:注册插件,让插件把hook节点的方法订阅到Tapable子类对象(Compiler、Compilation等)上 源码中经常看到的compiler.apply(new XxxxPlugin()),其实就是将XxxxPlugin插件内的hook监听函数订阅到compiler上
Tapable.prototype.apply = util.deprecate(function apply() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].apply(this);
}
}, "Tapable.apply is deprecated. Call apply on the plugin directly instead")
plugin:订阅 applyPlugins:发布
编写插件,本质就是在钩子函数上订阅方法,webpack内部编译时执行到每一步的时候,就会发布这些方法
当执行npm run build指令时,webpack会先订阅很多依赖函数,然后在某个特定时候会按一定的发布方式发布这些订阅的依赖函数
上面这么多对象订阅的方法主要在 Compiler.compile、Compiler.newCompilation、 这些地方发布
compiler->make compilation->addEntry compilation._addModuleChain normalModuleFactory.create normalModuleFactory->beforeResolve
webpack() ->
new WebpackOptionsApply().process(options, compiler)
compiler.apply(
new JsonpTemplatePlugin(options.output),
new FunctionModulePlugin(options.output),
new NodeSourcePlugin(options.node),
new LoaderTargetPlugin(options.target)
);
// EntryOptionPlugin中订阅了entry-option
compiler.apply(new EntryOptionPlugin());
compiler.applyPluginsBailResult("entry-option", options.context, options.entry);
compiler.apply(
new CompatibilityPlugin(),
new HarmonyModulesPlugin(options.module),
new AMDPlugin(options.module, options.amd || {}),
new CommonJsPlugin(options.module),
new LoaderPlugin(),
new NodeStuffPlugin(options.node),
new RequireJsStuffPlugin(),
new APIPlugin(),
new ConstPlugin(),
new UseStrictPlugin(),
new RequireIncludePlugin(),
new RequireEnsurePlugin(),
new RequireContextPlugin(options.resolve.modules, options.resolve.extensions, options.resolve.mainFiles),
new ImportPlugin(options.module),
new SystemPlugin(options.module)
);
compiler.apply(
new EnsureChunkConditionsPlugin(),
new RemoveParentModulesPlugin(),
new RemoveEmptyChunksPlugin(),
new MergeDuplicateChunksPlugin(),
new FlagIncludedChunksPlugin(),
new OccurrenceOrderPlugin(true),
new FlagDependencyExportsPlugin(),
new FlagDependencyUsagePlugin()
);
compiler.apply(new TemplatedPathPlugin());
compiler.apply(new RecordIdsPlugin());
compiler.apply(new WarnCaseSensitiveModulesPlugin());
compiler.applyPlugins("after-plugins", compiler);
if(!compiler.inputFileSystem) throw new Error("No input filesystem provided");
// 实例化resolver,作用是?
compiler.resolvers.normal = ResolverFactory.createResolver(Object.assign({
fileSystem: compiler.inputFileSystem
}, options.resolve));
compiler.resolvers.context = xxx
compiler.resolvers.loader = xxx
compiler.applyPlugins("after-resolvers", compiler);
compiler = new Compiler();
new NodeEnvironmentPlugin().apply(compiler);
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
compiler.options = new WebpackOptionsApply().process(options, compiler);
compiler.run(callback);
await compiler.applyPluginsAsync("before-run", this)
await compiler.applyPluginsAsync("run", this)
await compiler.readRecords()
await compiler.compile(onCompiled);
compiler.newCompilationParams();
normalModuleFactory: this.createNormalModuleFactory(),
this.applyPlugins("normal-module-factory", normalModuleFactory)
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: []
await compiler.applyPluginsAsync("before-compile", params)
this.applyPlugins("compile", params);
const compilation = this.newCompilation(params);
this.applyPlugins("this-compilation", compilation, params);
this.applyPlugins("compilation", compilation, params);
await this.applyPluginsParallel("make", compilation)
compilation.finish();
await compilation.seal()
await this.applyPluginsAsync("after-compile", compilation)
compiler.make ->
SingleEntryPlugin中的make回调 ->
compilation.addEntry ->
compilation._addModuleChain ->
normalModuleFactory.create -> 回调中得到模块对象
compilation.addModule
compilation.buildModule
normalModule.build
normalModule.doBuild
normalModule.createLoaderContext
runLoaders
normalModule.createSource
normalModule.parser.parse
normalModule._initBuildHash
Dependency是什么?如何理解? const moduleFactory = this.dependencyFactories.get(Dep) 从dependencyFactories中可以获取moduleFactory? HarmonyCompatibilityDependency 第一个module的dependency是entry?
dependency.module = module; dependency也会关联module?
副作用是什么? SideEffectsFlagPlugin
webpack_require.e 会加载独立的chunk,在此期间,installedChunks的变化规律如下: 假设chunk的id是1 如果这个chunk从未被加载过,那么 installedChunks[1] === undefined 判断出这个chunk在installedChunk中是undefined的话,会处理成promise的形式: installedChunks[1] = [resolve, reject, promise] 紧接着,动态插入script标签加载对应的chunk,然后会调用window["webpackJsonp"].push,注意这里的调用并不是如我们看到的把模块push到webpackJsonp这个数组中,webpack改写了这个push方法,这个push方法实际上是webpackJsonpCallback 在这个webpackJsonpCallback方法里面,会将chunk的状态置为0 installedChunks[1] = 0
preWalkStatements 只遍历第一层,不会深入到函数里面 ast是为了得到Dependency
Dependency的作用:在module生成的过程中,需要转换哪些东西,添加哪些代码,删除哪些代码
HarmonySideEffect 和优化相关
SurviveJs webpack读书笔记
文件使用哪些loader不仅仅会看文件的扩展名 The way a loader gets matched against a resolved file can be configured in multiple ways including file type and its location in the file system. Webpack provides even more flexible ways to achieve this as you can apply specific transformation against a file based on where it was imported to the project.
css-loader的作用,从文件中发现@import、url(),并且会跳过external resources style-loader的作用,向HTML中插入style标签,并填充css代码
babel: babelrc的两部分内容: presets和plugins 一个plugin就是一条功能或规则,一个presets就是多个plugins功能的集合