前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >玩转webpack(一)下篇:webpack的基本架构和构建流程

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

原创
作者头像
小时光
修改2017-11-13 10:51:44
3K0
修改2017-11-13 10:51:44
举报
文章被收录于专栏:Technology ShareTechnology Share

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

文件生成阶段

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

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

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

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

代码语言:javascript
复制
// 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 方法:

代码语言:javascript
复制
// 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 构建后的文件就可以比较清楚地看到区别:

代码语言:javascript
复制
// 入口 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 会被触发:

代码语言:javascript
复制
// 监听 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 核心的对象,敬请期待。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文件生成阶段
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档