Skip to content

Latest commit

 

History

History
163 lines (102 loc) · 8.45 KB

53.精读《插件化思维》.md

File metadata and controls

163 lines (102 loc) · 8.45 KB

53.精读《插件化思维》

构建化工具 Webpack,glup,grunt 等都支持插件化开发,后端框架 koa, egg 等也支持插件化拓展,所有框架都希望自身有高拓展性,所以纷纷选择插件化作为增强框架自身的功能

  • 插件化作为 geek 的一部分,协同开发的程序都离不开插件机制的支撑,没有插件化,核心库的代码就会变得冗余,导致功能耦合相对严重,维护成本增加,插件化就是将不断扩张的功能分散到插件中,内部进行集中维护逻辑。

精读

  • 理想状态下,一个插件应该满足三个拓展性要求:

    1. 社区可以贡献代码,即使代码存在问题,也不影响核心代码的稳定性
    2. 支持二次开发,满足不同的业务场景需求
    3. 让代码以功能为维度聚合,而不是用片面的逻辑结构组成,代码量一旦上来此优势便十分明显
  • 做插件设计,最好先从使用者角度出发,设计舒服的调用方式再去考虑实现。

  • 插件化分类

    • 参考设计模式: 命令模式, 工厂模式,抽象工厂模式等等总结三种:

      • 约定/注入插件化 (2.1)
      • 事件插件化 (2.2)
      • 插槽插件化 (2.3)
    • 2.1 约定一般是入口文件/指定文件名作为插件入口,文件形式.json/.ts 不等,返回对象根据约定名称书写,就会被加载并获取到其上下文

      // e.g. package.json 中的appollo只要存在commands属性,就会自动注册新命令行
      {
        "appollo": {
          "commands": [{"name": "publish", "action": "doPublish"}]
        }
      }
      • 如果功能相对杂乱,没有清晰的功能入口规划,用对象形式会更加简洁,并更倾向使用一个入口,因为主要操作的是上下文,只需要一个入口,内部逻辑种类无法控制:(Temp)
        export default class User {
          // context.sourceFiles.xx
        } // fis, gulp, webpack, egg
    • 2.2 事件插件化就是通过事件的能力提供插件开发的能力:

      • 比如 dom 事件 docment.on("focus", callback) 本质上他可拓展,可以重复定义多个 focus 事件相互独立,事件间的 callback 执行任务也不受影响,可以理解为事件机制在某些阶段释放出一些钩子,允许用户代码拓展整体框架的生命周期,事件机制比较出彩的就数 koa 了,其中间件洋葱模型非常出色,,可以认为是控制执行时机的事件插件化,把想要执行的事件插件放在 next()后即可,终止插件执行也可以不调用 next()
    • 2.3 插槽插件化

      • 这种插件化一般用在 UI 元素的拓展,react 的内置数据流是符合组件物理结构的, 而 redux 数据流是符合用户定义的逻辑结构,对于 html 布局也是一样,html 为物理结构,插槽布局方式就是 html 中的 redux

        // 插槽组织形式
        {
        	position: "root",
            View: <Layout>{insertionPosition("Layout")}</Layout>
        }
      • 重要点: 实现 UI 解耦,父元素不需要知道子元素的具体实现,一般决定一个组件状态的是其父元素而不是子元素,比如一个 button 在中表现为一种组合态的样式,但不能说有了另外一个组件而影响自身逻辑发生变化。

        <Tabs>{insertionPosition(`tabs-${this.state.selectedTabs}`)}</Tabs>
    • 2.4 分型插件化

      • 特点: 插件结构与项目结构分型,组成大项目的小插件,自身结构与项目结构相同
  • 核心代码怎么加载组件,支持插件化的框架核心功能是整合插件以及定义生命周期,与功能相关的代码可以通过插件实现

  • 确定插件加载形式

  • 确定插件注册形式

    • npm 注册,符合每个前缀会自动注册为插件
    • 通过文件名注册, 存在xx.plugin.ts会自动做到插件引用,一般作为辅助方案使用
    • 代码形式注册,在代码中 require 即可,如babel-polyfill不过要求插件执行逻辑在浏览器中运行,场景受限。
    • 通过描述注册: package.json 描述一个属性,表明加载插件,如.babelrc {"presets": ["es2015"]}
    • 自动注册:比较暴力,通过遍历可能存在的位置,主要满足插件约定的自动注册为组件
  • 确定生命周期: 组件注册完成后,第一件事就是加载组件,根据框架业务逻辑不同而执行不同的生命周期,插件在生命周期中扮演不同的功能,通过插件影响这些过程的执行。

  • 插件对生命周期的拦截:一般通过事件,回调函数等方式,支持插件生命周期的拦截

  • 插件之间的依赖和通信,两种方式处理:依赖关系定义在业务项目中,与依赖关系定义在插件中,如依赖关系定义的项目中,webpack 配置举例:

    {
      "use": ["babel-loader", "ts-loader"]
    }
    • 执行逻辑是 ts-loader -> babel-loader, 规则由框架自身决定,但是插件加载规则有顺序,跟配置写法相关。配置得写在插件中。
    • 另一种行为将插件依赖写在插件中,webpack-preload-plugin 依赖 html-webpack-plugin
    • 两种场景一是与业务有关的顺序,插件无法做主的业务逻辑问题 ,需要把殊勋交给业务项目配置;二是插件内部顺序,业务无需关心的顺序问题,插件自身定义执行顺序,框架核心一般可能需要同时支持者两种配置方式,最终决定插件的加载顺序。
    • 插件间通信可以通过 hook 或者 context 支持,hook 主要传递的是时机信息,context 主要传递的是数据信息,是否生效取决于上面说到的插件加载顺序
  • 核心功能的插件化

    • 插件化框架核心代码主要是对插件的加载,生命周期的梳理,以及实现 hook 让插件影响生命周期,最后补充上插件的加载顺序以及通信,就比较完备了。

    • 衡量代码质量点: 是否所有核心业务逻辑都可以由插件完成,因为只有插件实现核心业务逻辑,才能检验插件的能力,进而推导出第三方插件是否拥有足够的拓展能力。

    • 核心逻辑有一部分代码没有通过插件机制编写,第三方插件无法拓展此逻辑,也不利于框架的维护。

    • 从此思想出发,开发者得明确哪些功能应该做成插件,以及将哪些插件固化为内置插件,核心三点:

      • 哪些插件需要内置:开源的,基础功能以及体现核心竞争力的可以内置,如基础功能,缺少基础功能的框架做不了任何事 create-react-app babel

      • 插件是依赖型还是完全正交的:正交插件(不影响其他插件,也不依赖其他任何插件,自身也不需要被任何插件拓展)

        • 非正交插件也是三点:
          • 依赖其他插件,需要补充使用顺序
          • 依赖并拓展其他插件的插件,此插件最好不要减少其他插件的功能,可以新增一个规则单独对插件执行顺序进行控制
      • 可能被其他插件拓展的插件:难点在设计拓展的颗粒度

        • 入口文件举例:

          import * as React from 'react';
          import * as ReactDOM from "react-dom";
          import { App } from "./app";
          
          ReactDOM.render(<App />, document.getElementById("root"));
          
          
        • 潜在的拓展需求:

          1. 某处插入其他代码支持
          2. 如何保住插入代码的顺序
          3. react-lite 替换 react怎么支持
          4. dev 模式用 hot(App)替换 App 作为入口,怎么支持
          5. 模板入口 div 的 id 可能不是 root,怎么支持
          6. 模板入口 div 是自动生成的,怎么支持
          7. 用于 ReactNative,没有 document,怎么支持
          8. 后端渲染时,需要用ReactDOM.hydrate而不是ReactDOM.render怎么支持
          9. 八种场景组合出现,需要保证任意组合正确运行,无法全量替换模板,怎么办?
    • 插件化场景 -- 举例

      1. 前后端框架 react 的生命周期 koa 的中间件, 业务代码用到的 request 处理
      2. 脚手架 功能可插拔
      3. 工具库 redux 提供的中间件机制
      4. 需要多人协同的复杂业务场景

    summary

    • 插件化系统化集成思考引出对框架开发心得分享
    • 插件化带来更好的分工协作,同时框架提供的可拓展性得到了提高。