最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Webpack5的事件流和插件机制

    正文概述 掘金(sunnyhuang519626)   2021-01-18   434

    Webpack的事件流和插件机制

    前面2篇我们已经讲了webpack的运行原理和loader的原理,本篇我们来讲一下webpack的事件流和插件的机制。本系列一共分为三篇

    1. Webpack5的打包分析
    2. Webpack的loader的实现
    3. Webpack的事件流和插件的原理

    webpack的事件流

    什么是webpack的事件流,这里引入深入浅出的webpack里面的一段话。

    Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 --吴浩麟《深入浅出webpack》

    webpack的运行机制

    前面第一篇我们自己实现了一个简单的webpack, 我们通过一个图片来了解一下webpack从开始启动到打包资源后的一个整体运行机制。 Webpack5的事件流和插件机制 总体分为这几个关键步骤

    1. 当我们运行webpack的时候,它会读取你的传入的配置文件(webpack.config.js), 然后运行compile.run来初始化本质构建的参数(入口文件等),然后开始分析模块 (environment钩子函数)
    2. 接下来进入了entryOption阶段,webpack开始读取配置的入口entry,然后开始递归的遍历所有的入口文件 (entryOption钩子)
    3. webpack进入入口文件后,开始分析依赖项,进行compilation过程。 (compilation 钩子函数)
      • 使用配置好的loader对文件内容进行编译(buildModule), 我们可以从传入的事件回调的参数module上拿到模块的resource(资源路径) (buildModule 钩子函数)
      compiler.hooks.compilation.tap('compilation', (compilation , compilationParams) => {
        compilation.hooks.buildModule.tap('sourceMap', (module) => {
          console.log(module.resource)  // 资源路径
        })
      })
      
      • 将经过loader处理后的文件,通过acorn解析生成ast语法树(normalModuleLoader)分析文件的依赖关系,然后形成依赖数组,重复上面的模块分析过程 (webpack5 已经废弃了normalModuleLoader 这个钩子)
    4. emit阶段: 所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的compilation.assets 上拿到所需数据,其中包括即将输出的资源、代码块Chunk等等信息。

    下面这张图是整体的运行机制和主要插件的图的对比 Webpack5的事件流和插件机制 还有一张图是基于webpack基于模块的更加详细的运行流程图(虚线代表重复的执行) Webpack5的事件流和插件机制

    webpack的插件开发

    我们主要实战2个插件

    1. 使用md模式列出文件打包资源的列表
    2. 通过插件分析文件依赖的组件

    首先我们需要知道webpack的插件需要是怎么开发的。它需要2个条件

    1. 必须是一个类
    2. 要暴露一个apply方法,webpack会在初始化的时候执行这个方法, 并传入一个compiler, 源码里面是这样的
    if (Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {
            if (typeof plugin === "function") {
                plugin.call(compiler, compiler);
            } else {
                plugin.apply(compiler);
            }
        }
    }
    

    列表打包资源插件

    从上面的图我们可以看出,我们可以在资源打包后对打包后的资源进行获取,然后新增一个文件。

    1. 通过emit 钩子函数,我们可以在回调函数中的的compilation中的参数拿到assets资源列表
    2. 然后新增一项资源,通过webpack进行流程处理
    class FileListPlugin {
        constructor({ filename = 'index.md' }) {
            this.filename = filename;
        }
    
        apply(compiler) {
            compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
                let assets = compilation.assets;
                let content = '## 文件    资源大小\r\n';
                Object.entries(assets).forEach(([key, value]) => {
                    content += `${key}    ${value.size()}\r\n`;
                });
                assets[this.filename] = {
                    size() {
                        return content.length;
                    },
                    source() {
                        return content;
                    }
                };
                cb();
            });
        }
    }
    
    module.exports = FileListPlugin;
    

    组件被使用的列表

    在现在一切皆组件的思想下,一个组件可能被多个页面使用,有时候我们修改一个组件,都不知道会不会影响到其他的页面,所有这里开发了一个插件,用来统计组件被使用的页面情况。在webpack4和webpack5中的使用不一样,我们现在是基于webpack5进行分析。

    1. 从上面的流程图中我们可以看到,每一个资源(asset) 都会模块处理阶段NormalModuleFactory, 所有我们可以在模块经过loader处理完成后的afterResolve的钩子来分析模块
    2. 在分析完模块的依赖后,可以通过done钩子等将分析的内容输出
    class NormalModuleFactory {
      constructor() {
        this.dependencies = {}
        this.entry = ''
        this.workDictory = ""
      }
    
      apply(compiler) {
        compiler.hooks.normalModuleFactory.tap('NormalModuleFactory', (nmf) => {
          nmf.hooks.afterResolve.tapAsync('nmf', (result, callback) => {
            let { request, contextInfo, context } = result
            if (!request.includes('node_modules')) {
              if (!contextInfo.issuer) {
                console.log('entry', path.normalize(request))
                this.entry = request
                this.workDictory = context
              } else if(!['react', 'react-dom'].includes(request)){
                // 有依赖项 contextInfo 是请求资源的上下文
                const requestPath = path.relative(this.workDictory, contextInfo.issuer)
                // './component/Hello   requestPath: src/index.js
                if(!requestPath.includes("node_modules")) {
                  const resourcePath = path.join(context, request)
                  console.log(requestPath, request)
                  request = path.relative(this.workDictory, resourcePath)
                  if(!this.dependencies[request]) {
                    this.dependencies[request] = [requestPath]
                  } else {
                    this.dependencies[request].push(requestPath)
                  }
                }
              }
            }
            callback()
          })
        })
        compiler.hooks.done.tap('NormalModuleFactory', () => {
          console.log(this.dependencies)
        })
      }
    }
    
    module.exports = NormalModuleFactory
    

    执行npx webpack后我们可以得到这个内容, key为被使用的组件的路径,value为使用该组件的页面或者组件,可以根据项目内容再进行路径匹配分析具体的样式和组件依赖。

    {
      'src\\base\\a.js': [ 'src\\index.js' ],
      'src\\component\\Hcc': [ 'src\\index.js' ],
      'src\\component\\Hello': [ 'src\\index.js', 'src\\component\\Hcc.jsx' ],
      'src\\index.less': [ 'src\\index.js' ],
      'src\\base\\b': [ 'src\\base\\a.js' ],
      'src\\pic\\hcc.jpg': [ 'src\\index.less' ]
    }
    
    参考文章
    1. webpack插件机制探索
    2. 揭秘webpack插件工作流程和原理

    起源地下载网 » Webpack5的事件流和插件机制

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元