Skip to content
This repository has been archived by the owner on Nov 17, 2022. It is now read-only.

[RFC]统一预请求方案 #585

Open
answershuto opened this issue Oct 12, 2022 · 4 comments
Open

[RFC]统一预请求方案 #585

answershuto opened this issue Oct 12, 2022 · 4 comments
Assignees

Comments

@answershuto
Copy link
Member

answershuto commented Oct 12, 2022

现状:

PHA 中,某个 page 下面的 dataPrefetch 会编译到对应的 home-manifast 中。

export function getConfig() {
  return {
    title: 'Home',
    dataPrefetch: [
      {
        key: 'key_1',
        prefetchType: 'mtop',
        api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice',
        v: '1.0',
        data: {
          param1: 'v1',
          param2: 'v2',
        },
        extHeaders: {
          nameAaaa: '112233',
        },
      }
    ],
  };
}

纯 Web 场景,使用 getData。

export function getData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        name: 'Home',
      });
    }, 1 * 100);
  });
}

方案

发送数据

  • 支持 PHA
    • value 为 object 的 prefetch,会被编译到 manifest 中(保持原本数据结构不变)以供在 PHA 环境中使用。
    • value 为 function 的 prefetch,会被编译成 PHA 的 worker 中调用(目前 PHA 还没支持在 worker 中注入运行时方法,需要推进支持,可以考虑先用 libmtop 发送,需要自动补全通过 event 发送到主 JS Context 并存储数据。此外,没有页面级 Worker,需要全部打到一个 Worker 中使用,这里会有个问题需要解,abc三个页面都有 function 的 prefetch,worker 在全局,是否可以保证只跑当前页面所需的 prefetch)。
  • 支持纯 Web
    • value 为 object 的 prefetch,根据类型解析称 mtop 发送。
    • value 为 function 的 preftch,直接运行匿名函数。
    • 跟现有 getData 逻辑类似,打成一个单独的 script 在主 bundle 之前执行并发送预请求。
// 多个情况
// 静态方法会包装 fetcher (默认 window.fetch)
export const dataLoader = defineDataLoader([
  {
    key: 'key_1',
    prefetchType: 'mtop',
    api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice222',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
  },
  async () => {
    // 当然,用户不需要同构的情况下,可以选择不用。
    const data2 = await Mtop.request({
      api: 'mtop.user.getUserSimple2',
      v: '1.0',
      data: { itemNumId: 37194529489 },
    });

    return { data, data2 };
  },
]);

// 单个静态
export const dataLoader = defineDataLoader({
  key: 'key_1',
  prefetchType: 'mtop',
  api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice222',
  v: '1.0',
  data: {
    param1: 'v1',
    param2: 'v2',
  },
  extHeaders: {
    nameAaaa: '112233',
  },
});

// 单个动态
export const dataLoader = defineDataLoader(async () => {
  const data2 = await Mtop.request({
      api: 'mtop.user.getUserSimple2',
      v: '1.0',
      data: { itemNumId: 37194529489 },
    });

  return { data, data2 };
});

获取数据

export default function Home() {
  const data = useData('data-key'); // [null, {}, null]
}

全局的配置在 PHA 下处理不变,纯 Web 下会被构建到每一个页面下面。

PHA 改造

  • 支持 JS Worker 的 prefetch 接口,同时数据会自动同步到业务 JS Context,通过运行时 getData 获取。
  • JS Worker 需要在页面加载(非得到 manifest 以后)时可感知当前加载的是哪个页面(通过事件监听)。
  • PHA 支持 manifest 放在 HTML 中解析(SSR 场景 + 增强型 UI 能力)。
  • 动态 Prefetch(有 JS 代码的 prefetch),getData 可以生成 Fass 函数,PHA 不需要在冷启时等待 JS Engine 可运行,直发送请求到 Node 直接 prefetch,已启动 webview 的情况下,该代码可以在 JSWorker 中执行。
@chenjun1011
Copy link
Contributor

chenjun1011 commented Oct 26, 2022

10-25 讨论结论

常规写法(动态)

export const dataLoaders = defineDataLoaders(() => {
  const res = await fetch('https://api.example.com/...');
  return res.json();
});

使用数据

const data = useData();

静态配置

export const dataLoaders = defineDataLoaders([
  {
    prefetchType: 'mtop',
    api: 'mtop.xxx.xxx.xxx',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
  },
]);

使用数据

const [data] = useData();

混合使用

export const dataLoaders = defineDataLoaders([
  () => {
    const res = await fetch('https://api.example.com/...');
    return res.json();
  },
  {
    prefetchType: 'mtop',
    api: 'mtop.xxx.xxx.xxx',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
  },
]);

使用数据

const [data1, data2] = useData();

SSR

默认情况下,SSR 下复用 dataLoaders 中定义的数据请求,当需要特殊定制 Server 端逻辑时,可以使用 defineServerDataLoaders 来定义。

export const serverDataLoaders = defineServerDataLoaders(() => {
  const res = await fetch('https://api.example.com/...');
  return res.json();
});

定义了 defineServerDataLoaders 后,SSR 下只执行 serverDataLoaders, 不再调用 dataLoaders。仅在降级为 CSR 或路由跳转时调用 dataLoaders

SSG

默认情况下,SSG 下的 useData 获取的数据为 null,可通过 defineStaticDataLoaders 定义 SSG 场景下的数据

export const staticDataLoaders = defineStaticDataLoaders(() => {
  const res = await fetch('https://api.example.com/...');
  return res.json();
});

@chenjun1011
Copy link
Contributor

chenjun1011 commented Oct 26, 2022

如果不考虑一个路由组件需要发多个请求的情况,我倾向于保留现有 getData, getServerData, getStaticData 的设计,相比👆🏻的调用方式会简单很多

针对 PHA 静态配置的场景,额外提供 defineDataLoader 的能力,只支持传静态配置

export const dataLoader = defineDataLoaders({
    prefetchType: 'mtop',
    api: 'mtop.xxx.xxx.xxx',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
});

一个页面有多个数据请求的情况,通过嵌套路由来实现

@answershuto
Copy link
Member Author

收敛 getStaticData 以及 getServerData 的抽象,目前来看核心的同构能力需要抽象 fetcher interface(uni-fetcher) 以解决在不同平台(端)下的发送请求问题。

defineDataLoaders 保持保持之前讨论的设计:

// 多个情况
// 静态方法会包装 fetcher (默认 window.fetch)
// 动态方法会传入 fetcher (默认 window.fetch)
export const dataLoader = defineDataLoader([
  {
    key: 'key_1',
    prefetchType: 'mtop',
    api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice222',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
  },
  {
    key: 'key_1',
    prefetchType: 'mtop',
    api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice222',
    v: '1.0',
    data: {
      param1: 'v1',
      param2: 'v2',
    },
    extHeaders: {
      nameAaaa: '112233',
    },
  },
  async (fetcher) => {
    const data = await fetcher({
      api: 'mtop.user.getUserSimple',
      v: '1.0',
      data: { itemNumId: 37194529489 },
    });

    // 当然,用户不需要同构的情况下,可以选择不用。
    const data2 = await Mtop.request({
      api: 'mtop.user.getUserSimple2',
      v: '1.0',
      data: { itemNumId: 37194529489 },
    });

    return { data, data2 };
  },
]);

// 单个静态
export const dataLoader = defineDataLoader({
  key: 'key_1',
  prefetchType: 'mtop',
  api: 'mtop.tmall.kangaroo.core.service.route.pagerecommendservice222',
  v: '1.0',
  data: {
    param1: 'v1',
    param2: 'v2',
  },
  extHeaders: {
    nameAaaa: '112233',
  },
});

// 单个动态
export const dataLoader = defineDataLoader(async (fetcher) => {
  const data = await fetcher({
    api: 'mtop.user.getUserSimple',
    v: '1.0',
    data: { itemNumId: 37194529489 },
  });

  const data2 = await fetcher({
    api: 'mtop.user.getUserSimple2',
    v: '1.0',
    data: { itemNumId: 37194529489 },
  });

  return { data, data2 };
});
const plugin = () => ({
  name: 'plugin-mtop',
  setup: ({ generator }) => {
    generator.addDataLoaderClientFetcher({
      source: '@ali/universal-mtop',// 需要修改时,可以 @/src/customFetcher/fetcher.js
      alias: {
        request: 'fetcher',
      },
      specifier: ['request'],
    });

    generator.addDataLoaderServerFetcher({
      source: '@ali/universal-mtop',
      alias: {
        request: 'fetcher',
      },
      specifier: ['request'],
    });
  },
});

export default plugin;

@chenjun1011
Copy link
Contributor

app 的数据请求方式也需要统一

注册数据请求:统一 getAppDatadataLoader 的声明方式

export const dataLoader = defineDataLoader(() => {
  const res = await fetch('https://api.example.com/...');
  return res.json();
});

消费数据:仍然保留 useAppData

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants