玩转webpack(一)下篇:webpack的基本架构和构建流程

欢迎你们前往腾讯云社区,获取更多腾讯海量技术实践干货哦~javascript

做者:QQ会员技术团队vue

玩转webpack(一)上篇:webpack的基本架构和构建流程java

文件生成阶段

这个阶段的主要内容,是根据 chunks 生成最终文件。主要有三个步骤:模板 hash 更新,模板渲染 chunk,生成文件webpack

Compilation 在实例化的时候,就会同时实例化三个对象:MainTemplate, ChunkTemplateModuleTemplate。这三个对象是用来渲染 chunk 对象,获得最终代码的模板。第一个对应了在 entry 配置的入口 chunk 的渲染模板,第二个是动态引入的非入口 chunk 的渲染模板,最后是 chunk 中的 module 的渲染模板。git

在开始渲染以前,Compilation 实例会调用 createHash 方法来生成此次构建的 hash。在 webpack 的配置中,咱们能够在 output.filename 中配置 [hash] 占位符,最终就会替换成这个 hash。一样,createHash 也会为每个 chunk 也建立一个 hash,对应 output.filename[chunkhash] 占位符。github

每一个 hash 的影响因素比较多,首先三个模板对象会调用 updateHash 方法来更新 hash,在内部还会触发任务点 hash,传递 hash 到其余插件。 chunkhash 也是相似的原理:web

// https://github.com/webpack/webpack/blob/master/lib/Compilation.js

class Compilation extends Tapable {
    // 其余代码..
    createHash() {
        // 其余代码..
        const hash = crypto.createHash(hashFunction);
        if(outputOptions.hashSalt)
        hash.update(outputOptions.hashSalt);
        this.mainTemplate.updateHash(hash);
        this.chunkTemplate.updateHash(hash);
        this.moduleTemplate.updateHash(hash);
        // 其余代码..
        for(let i = 0; i < chunks.length; i++) {
            const chunk = chunks[i];
            const chunkHash = crypto.createHash(hashFunction);
            if(outputOptions.hashSalt)
            chunkHash.update(outputOptions.hashSalt);
            chunk.updateHash(chunkHash);
            if(chunk.hasRuntime()) {
                this.mainTemplate.updateHashForChunk(chunkHash, chunk);
            } else {
                this.chunkTemplate.updateHashForChunk(chunkHash, chunk);
            }
            this.applyPlugins2("chunk-hash", chunk, chunkHash);
            chunk.hash = chunkHash.digest(hashDigest);
            hash.update(chunk.hash);
            chunk.renderedHash = chunk.hash.substr(0, hashDigestLength);
        }
        this.fullHash = hash.digest(hashDigest);
        this.hash = this.fullHash.substr(0, hashDigestLength);
    }
}

当 hash 都建立完成以后,下一步就会遍历 compilation.chunks 来渲染每个 chunk。若是一个 chunk 是入口 chunk,那么就会调用 MainTemplate 实例的 render 方法,不然调用 ChunkTemplate 的 render 方法:微信

// https://github.com/webpack/webpack/blob/master/lib/Compilation.js

class Compilation extends Tapable {
    // 其余代码..
    createChunkAssets() {
        // 其余代码..
        for(let i = 0; i < this.chunks.length; i++) {
            const chunk = this.chunks[i];
            // 其余代码..
            if(chunk.hasRuntime()) {
                source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates);
            } else {
                source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates);
            }
            file = this.getPath(filenameTemplate, {
                noChunkHash: !useChunkHash,
                chunk
            });
            this.assets[file] = source;
            // 其余代码..
        }
    }
}

这里注意到 ModuleTemplate 实例会被传递下去,在实际渲染时将会用 ModuleTemplate 来渲染每个 module,其实更可能是往 module 先后添加一些"包装"代码,由于 module 的源码其实是已经渲染完毕的(还记得前面的 loaders 应用吗?)。架构

MainTemplate 的渲染跟 ChunkTemplate 的不一样点在于,入口 chunk 的源码中会带有启动 webpack 的代码,而非入口 chunk 的源码是不须要的。这个只要查看 webpack 构建后的文件就能够比较清楚地看到区别:app

// 入口 chunk
/******/ (function(modules) { // webpackBootstrap
/******/     // install a JSONP callback for chunk loading
/******/     var parentJsonpFunction = window["webpackJsonp"];
/******/     window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/         // add "moreModules" to the modules object,
/******/         // then flag all "chunkIds" as loaded and fire callback
/******/         var moduleId, chunkId, i = 0, resolves = [], result;
/******/         for(;i < chunkIds.length; i++) {
/******/             chunkId = chunkIds[i];
/******/             if(installedChunks[chunkId]) {
/******/                 resolves.push(installedChunks[chunkId][0]);
/******/             }
/******/             installedChunks[chunkId] = 0;
/******/         }
/******/         for(moduleId in moreModules) {
/******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/                 modules[moduleId] = moreModules[moduleId];
/******/             }
/******/         }
/******/         if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/         while(resolves.length) {
/******/             resolves.shift()();
/******/         }
/******/         
/******/     };
/******/     // 其余代码..
/******/ })(/* modules代码 */);

// 动态引入的 chunk
webpackJsonp([0],[
    /* modules代码.. */
]);

当每一个 chunk 的源码生成以后,就会添加在 Compilation 实例的 assets 属性中。

assets 对象的 key 是最终要生成的文件名称,所以这里要用到前面建立的 hash。调用 Compilation 实例内部的 getPath 方法会根据配置中的 output.filename 来生成文件名称。

assets 对象的 value 是一个对象,对象须要包含两个方法,sourcesize 分别返回文件内容和文件大小。

当全部的 chunk 都渲染完成以后,assets 就是最终更要生成的文件列表。此时 Compilation 实例还会触发几个任务点,例如 addtional-chunk-assetsaddintial-assets等,在这些任务点能够修改 assets 属性来改变最终要生成的文件。

完成上面的操做以后,Compilation 实例的 seal 方法结束,进入到 Compiler 实例的 emitAssets 方法。Compilation 实例的全部工做到此也所有结束,意味着一次构建过程已经结束,接下来只有文件生成的步骤。

Compiler 实例开始生成文件前,最后一个修改最终文件生成的任务点 emit 会被触发:

// 监听 emit 任务点,修改最终文件的最后机会
compiler.plugin("emit", (compilation, callback) => {
    let data = "abcd"
    compilation.assets["newFile.js"] = {
        source() {
            return data
        }
        size() {
            return data.length
        }
    }
})

当任务点 emit 被触发以后,接下来 webpack 会直接遍历 compilation.assets 生成全部文件,而后触发任务点 done,结束构建流程。

总结

通过全文的讨论,咱们将 webpack 的基本架构以及核心的构建流程都过了一遍,但愿在阅读彻底文以后,对你们了解 webpack 原理有所帮助。
最后再次说明,本文内容是由我的理解和整理,若是有不正确的地方欢迎你们指正。若是须要转载,请注明出处。

下一篇文章将会讲解 webpack 核心的对象,敬请期待。

本文来源于 小时光茶社 微信公众号

相关阅读

玩转webpack(一)上篇:webpack的基本架构和构建流程
Webpack + vue 之抽离 CSS 的正确姿式
使用Yeoman generator来规范工程的初始化

此文已由做者受权腾讯云技术社区发布,转载请注明原文出处
原文连接:https://cloud.tencent.com/com...

相关文章
相关标签/搜索