美文网首页前端
浅谈 Webpack 原理

浅谈 Webpack 原理

作者: 古月丶 | 来源:发表于2019-08-01 15:28 被阅读0次

    基本概念


    本质上,webpack是JavaScript的静态模块打包工具。在webpack处理程序的时候,它会递归的构建一张依赖关系图,这张关系图为程序所需的全部模块,然后将它们打包成一个或者多个bundle。在学习webpack前必须掌握以下几个核心概念:

    • entry:入口,webpack执行构建的第一步;
    • output:出口,webpack所创建bundles的输出位置,默认为 ./dist
    • plugins:插件,打包过程中所依赖的插件,通过 new 来实例化创建;
    • loader:模块转换器,webpack是JavaScript的模块打包工具,它只认识JavaScript,所以需要通过loader对其他类型的文件进行转换,在执行webpack操作。

    运行流程


    Webpack运行流程是一个串行的流程,从开始到结束会依次执行以下流程:

    1. 获取配置参数:从配置文件和shell读取配置参数,实例化插件;
    2. 开始编译:将上一步获取的参数用来初始化compiler对象,加载插件(依次调用插件的apply方法),执行compiler对象run方法;
    3. 确定入口:从配置文件的 entry 获取全部的入口文件;
    4. 开始编译:根据入口文件获取全部依赖的 loader 并依次执行对文件进行转换;
    5. 完成编译:执行完上一步之后,得到各个插件之间的依赖关系;
    6. 输出资源:根据依赖关系生成一个个包含多个模块的 chunk,生成文件输出列表;
    7. 输出文件:根据配置文件中的 output 生成文件系统,完成构建。

    tapable介绍


    tapable

    webpack本质上是一种事件流的机制,它的工作流程是通过将一个个插件串联起来,而实现这一切的就是tapable。tapable库中有很多的类,他们提供不同的事件流执行机制,简称“钩子”。

    钩子名称 执行方式 说明
    SyncHook 同步串行 不关心监听函数的返回值,按照事件注册顺序依次执行
    SyncBailHook 同步串行 只要有一个监听函数返回不为空,则跳过之后剩下的所有监听函数
    SyncWaterfallHook 同步串行 前一个监听函数的返回值会传递给下一个监听函数
    SyncLoopHook, 同步循环 执行监听函数,当返回值为true时循环执行监听函数,直到返回值为undefined则退出循环执行下一个
    AsyncParallelHook 异步并行 哪个函数先执行完就先执行哪个函数,不关心监听函数的返回值
    AsyncParallelBailHook 异步并行 只要有一个监听函数返回不为空,则跳过之后剩下的所有监听函数直至 callAsync,调用它的回调函数
    AsyncSeriesHook 异步串行 不关心监听函数的返回值,但是必须执行回调函数,等所有函数执行完毕之后调用 callAsync 的回调函数
    AsyncSeriesBailHook 异步串行 异步执行监听函数,当有一个函数返回不为空时,则跳过后面的所有监听函数,直接调用 callAsync 的回调函数
    AsyncSeriesWaterfallHook 异步串行 上一个监听函数的返回值可传递给下一个监听函数

    compiler & compilation


    compiler 对象包含了webpack的全部配置信息(options、loader、plugins等等),它在webpack启动的时候就被实例化,且是全局唯一的,可以简单理解为webpack的实例化
    compilation 对象包含当前模块的全部资源信息,webpack运行时每检测到一个文件发生变化就会重新生成一个compilation,它提供了很多回调函数供插件扩展。

    一些重要的钩子函数

    compiler

    事件钩子 说明 参数 类型
    entry-option 初始化option - SyncBailHook
    run 开始读取records之前,钩入compiler compiler AsyncSeriesHook
    compile 一个新的编译(compilation)创建之后,钩入compiler compilation SyncHook
    compilation compilation创建之后,开始执行插件 compilation SyncHook
    make 递归分析依赖,加载依赖模块 compilation AsyncParallelHook
    after-compile 编译过程结束 compilation AsyncSeriesHook
    emit 生成的资源写入到output之前 compilation AsyncSeriesHook
    after-emit 生成的资源写入到output之后 compilation AsyncSeriesHook
    done 完成编译 stats AsyncSeriesHook
    failed 编译失败 error SyncHook

    compilation

    事件钩子 说明 参数 类型
    normal-module-loader 普通模块loader,一个接一个的加载模块图中所有模块的函数 loaderContext、module SyncHook
    seal 编译(compilation)停止接收新的模块 - SyncHook
    optimize 优化开始 - SyncHook
    optimize-modules 优化模块 modules SyncBailHook
    optimize-chunks 优化chunk chunks SyncBailHook
    additional-assets 为编译创建时附加资源 - AsyncSeriesHook
    optimize-chunk-assets 优化所有的chunk资源,资源会被存储在compilation.assets chunks AsyncSeriesHook
    optimize-assets 优化存储在compilation.assets中的所有资源 assets AsyncSeriesHook

    实现一个简单的loader


    本质上来说loader是一个node模块。它可以加载资源文件,并对这些文件进行解析等操作,它支持链式调用、依次调用。
    loader的配置一般在 webpack.base.confi.jsmodule 中。

    module: {
        rules: [
            {
                test: '/\.txt$/',
                loader: 'my-loader'
            }
            ...
        ]
    }
    

    下面是一个简单loader例子:

    // my-loader.js
    module.exports = function(source) {
        source = source.split('').join('-');    
        return `module.exports = '${source}'`
    }
    
    //在vue项目中引入
    <template>
        <div id="app">
            <div>{{txt}}</div>
        </div>
    </template>
    <script>
    import txt from './assets/mytxt.txt'
    
    export default {
        name: 'App',
        data() {
            return {
                txt: txt
            }
        },
        ...
    }
    </script>
    

    实现一个简单的plugin


    一个插件由以下构成

    • 一个具名 JavaScript 函数。
    • 在它的原型上定义 apply 方法。
    • 指定一个触及到 webpack 本身的 事件钩子(compiler)
    • 操作 webpack 内部的实例特定数据。
    • 在实现功能后调用 webpack 提供的 callback。

    plugin的配置一般在 webpack.dev.conf.jsplugins 中。

    const hcxPlugin = require('./hcx.webpack.js');
    
    plugins: [
        new hcxPlugin({data: 'hcx'}),
        ...
    ]
    

    插件代码:

    function hcxPlugin() {}
    
    hcxPlugin.prototype.apply = function(compiler) {
        compiler.plugin('emit', function(compilation, callback) {
            compilation.chunks.forEach(function(chunk) {
                chunk.modules.forEach(function(module) {
                })
    
                chunk.files.forEach(function(filename) {
                    var source = compilation.assets[filename].source();
                    // do something
                    ...
                })
            })
    
            callback();
        })
    }
    module.exports = hcxPlugin;
    

    相关文章

      网友评论

        本文标题:浅谈 Webpack 原理

        本文链接:https://www.haomeiwen.com/subject/yqjklctx.html