先上一張流程圖
通常webpack打包文件是經過cli調用node
webpack.js --config=webpack.build.js
這實際上等同於經過node調用webpack
const Webpack = require('./node_modules/webpack'); const config = require('./config1.js'); const compiler = Webpack(config); compiler.run();
Webpack(config)源碼以下:git
const webpack = (options, callback) => { //將用戶本地的配置文件拼接上webpack內置的參數 options = new WebpackOptionsDefaulter().process(options); //初始化compiler對象(webpack編輯器對象,包含全部webpack主環境相關內容) compiler = new Compiler(options.context); compiler.options = options; //註冊NodeEnvironmentPlugin插件和用戶配置的插件 new NodeEnvironmentPlugin().apply(compiler); if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { plugin.apply(compiler); } } //觸發environment和afterEnvironment上註冊的事件 compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); //註冊webpack內置插件,源碼以下 compiler.options = new WebpackOptionsApply().process(options, compiler); return compiler; }) class WebpackOptionsApply extends OptionsApply { process(options, compiler) { //註冊EntryOptionPlugin new EntryOptionPlugin().apply(compiler); //觸發entryOption鉤子 var a = compiler.hooks.entryOption.call(options.context, options.entry); //觸發afterPlugins鉤子 compiler.hooks.afterPlugins.call(compiler); //觸發afterResolvers鉤子 compiler.hooks.afterResolvers.call(compiler); } }
主要是初始化compiler對象和註冊插件,下面介紹下EntryOptionPlugin插件github
EntryOptionPlugin.apply方法 apply(compiler) { //將回調函數註冊到hooks.entryOption上 //上文調用compiler.hooks.entryOption.call(options.context, options.entry)時觸發 compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => { //取出entry文件入口配置,判斷是否數組,調用對應的插件 for (const name of Object.keys(entry)) { itemToPlugin(context, entry[name], name).apply(compiler); } } } const itemToPlugin = (context, item, name) => { if (Array.isArray(item)) { return new MultiEntryPlugin(context, item, name); } return new SingleEntryPlugin(context, item, name); } //本文介紹entry[name]爲字符串的狀況,調用new SingleEntryPlugin().apply方法,源碼以下 apply(compiler) { //在compilation鉤子上註冊回調,compilation.call時觸發 compiler.hooks.compilation.tap( "SingleEntryPlugin", (compilation, { normalModuleFactory }) => { //設置SingleEntryDependency使用normalModuleFactory建立Module compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); } ); compiler.hooks.make.tapAsync( "SingleEntryPlugin", (compilation, callback) => { const { entry, name, context } = this; const dep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); } ); }
通過上一步的分析能夠對webpack的插件機制有必定的瞭解,插件主要是掛載一些回調函數在compiler的生命週期上,當執行到該階段時觸發(事件的發佈訂閱,繼承自tapable)。
compiler的生命週期可參考:webpack hooks,下面再看下compiler.run()方法web
run(callback) { this.compile(onCompiled); } compile(callback) { //初始化compilation,compilation對象表明了一次單一的版本構建和生成資源過程 const compilation = this.newCompilation(params); // 觸發註冊在make上的事件函數, this.hooks.make.callAsync(compilation, err => { //make上註冊的事件執行完畢後觸發回調,源碼後面給出 } } //觸發上文提到的SingleEntryPlugin註冊事件 compiler.hooks.make.tapAsync( "SingleEntryPlugin", (compilation, callback) => { const { entry, name, context } = this; // 入口文件的依賴對象, const dep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); } ); addEntry(context, entry, name, callback) { this._addModuleChain(context, dep, ...) } _addModuleChain(context, dependency, onModule, callback) { //獲取dependency const Dep = /** @type {DepConstructor} */ (dependency.constructor); //獲取moduleFactory,根據上文的介紹此處是normalModuleFactory const moduleFactory = this.dependencyFactories.get(Dep); //獲取module moduleFactory.create((err, module) => { dependency.module = module; this.buildModule(module, false, null, null, err => { //初始化moudle後生成ast對象,計算依賴,後面介紹 }) ) } //獲取module的實現 //normalModuleFactory.create create(data, callback) { // 獲取在constructor中註冊的factory方法 const factory = this.hooks.factory.call(null); factory(result, (err, module) => {}) } class NormalModuleFactory extends Tapable { constructor(context, resolverFactory, options) { this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => { //返回初始的module對象 callback(null, { context: context, request: loaders .map(loaderToIdent) .concat([resource]) .join("!"), dependencies: data.dependencies, ... }); } } }
buildModule回調api
this.buildModule(module, false, null, null, err => { // 根據js代碼獲取ast語法樹對象 ast = acorn.parse(code, parserOptions); // 根據ast加載模塊的依賴 this.prewalkStatements(ast.body); this.walkStatements(ast.body);
make主要是以entry爲入口,生成一個modoule對象,其中的關鍵是根據js代碼生成ast語法樹對象,同時分析語法樹加載須要使用到的依賴(dependency),若是存在import依賴,就會生成新的modoule,知道全部依賴加在完畢,下圖是部分dependency示例數組
make階段完成以後會進入seal階段app
this.hooks.make.callAsync(compilation, err => { compilation.seal(err => {}) }) seal() { for (const preparedEntrypoint of this._preparedEntrypoints) { const module = preparedEntrypoint.module; const name = preparedEntrypoint.name; const chunk = this.addChunk(name); chunk.entryModule = module; } this.createChunkAssets(); } createChunkAssets(){ const manifest = template.getRenderManifest({ chunk, hash: this.hash, fullHash: this.fullHash, outputOptions, moduleTemplates: this.moduleTemplates, dependencyTemplates: this.dependencyTemplates }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }] for (const fileManifest of manifest) { source = fileManifest.render(); } }
compile結束後調用compiler.emitAssets編輯器
emitAssets() { const targetPath = this.outputFileSystem.join( outputPath, targetFile ); let content = source.source(); //this.writeFile = fs.writeFile.bind(fs); this.outputFileSystem.writeFile(targetPath, content, callback); }