Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持运行时插件,以及通过 app.js 扩展运行时的配置 #1051

Closed
sorrycc opened this issue Sep 11, 2018 · 14 comments
Closed

支持运行时插件,以及通过 app.js 扩展运行时的配置 #1051

sorrycc opened this issue Sep 11, 2018 · 14 comments

Comments

@sorrycc
Copy link
Member

sorrycc commented Sep 11, 2018

先看下用户怎么用

e.g. src/app.js,

// 运行时修改路由
export function patchRoutes(routes) {
  // Modify routes as you wish
}

// 自定义 render,比如在 render 前做权限校验
export function render(oldRender) {
  fetch('/api/permissionCheck')
    .then(() => {
      oldRender();
    })
    .catch((e) => {
      require('umi/router').redirect('/login');
    });
}

// 自定义 rootContainer,解决之前使用数据流库(比如 unstated、redux)麻烦的问题
export function rootContainer(container) {
  const React = require('react');
  const { Provider } = require('unstated');
  return React.createElement(Provider, null, container);
}

// 扩展 dva 配置,顺手解决之前的 dva.js 不支持额外的 dva plugin 的问题
export const dva = {
  config: {
    onError(err) {},
    initialState: {},
  },
  plugins: [
    { onAction: require('redux-logger').createLogger() },
    require('dva-logger'),
  ],
};

可以做什么?

实现

Notes

  • 用户可通过 src/app.js 配置 umi 运行时
  • 插件可以定义额外的运行时配置
  • 为避免冲突,配置的 key 需在插件中注册后使用

内置的 key

  • patchRoutes
  • render
  • rootContainer

先实现一个简单的运行时插件机制

import assert from 'assert';

let plugins = null;
let validKeys = [];

export function init(opts = {}) {
  plugins = [];
  validKeys = opts.validKeys;
}

export function use(plugin) {
  if (validKeys && validKeys.length) {
    Object.keys(plugin).forEach(key => {
      assert(plugin[key].includes(key), `Invalid key ${key} from plugin`);
    });
  }
  plugins.push(plugin);
}

export function getItem(key) {
  return plugins.filter(plugin => key in plugin).map(plugin => plugin[key]);
}

export function apply(item, { initialValue }) {
  if (typeof item === 'string') item = getItem(item);
  assert(Array.isArray(item), `item must be Array`);
  return item.reduce(item, (memo, fn) => {
    assert(typeof fn === 'function', `applied item must be function`);
    return fn(memo);
  }, initialValue);
}

export function applyForEach(item, { initialValue }) {
  if (typeof item === 'string') item = getItem(item);
  assert(Array.isArray(item), `item must be Array`);
  item.forEach(item, (memo, fn) => {
    assert(typeof fn === 'function', `applied item must be function`);
    fn(initialValue);
  });
}

// shadow merge
export function mergeConfig(item) {
  if (typeof item === 'string') item = getItem(item);
  assert(Array.isArray(item), `item must be Array`);
  return item.reduce(item, (memo, config) => {
    return { ...memo, ...config };
  }, {});
}

注册插件

入口文件,

window.g_plugins = require('path/to/plugins');
window.g_plugins.init({
  validKeys: {{validKeys}}
});

{{#plugins}}
window.g_plugins.use(require('{{path}}'));
{{/plugins}}

try {
  window.g_plugins.use(require('@/app.js'));
} catch(e) {}

在各场景的应用

比如 patchRoutes 的实现,

let routes = {{{ routes }}};
window.g_plugins.applyForEach('patchRoutes', { initialValue: routes });

比如自定义 render,

let render = () => {
  {{{ render }}}
}
render = window.g_plugins.applyForEach('render', { initialValue: render });

比如自定义 rootContainer,

const rootContainer = window.g_plugins.apply('rootContainer', {
  initialValue: React.createElement(require('./router').default),
});
ReactDOM.render(rootContainer, document.getElementById('${api.config.mountElementId || 'root'}'));

比如 dva 的扩展配置,

const { dva: runtimeDva } = require('@/app.js');
const runtimeDva = window.g_plugins.mergeConfig('dva');
let app = dva({
  history: window.g_history,
  ...(runtimeDva.config || {}),
});
(runtimeDva.plugins || []).forEach(plugin => {
  app.use(plugin);
});

比如 dva 自定义 rootContainer,

plugin.js

// 先注册一个运行时的插件
api.addRuntimePlugin(join(__dirname, './runtime'));

// 添加 dva key
api.addRuntimePluginKey('dva');

runtime.js

export function rootContainer(container) {
  const DvaContainer = require('./DvaContainer').default;
  return React.createElement(DvaContainer, null, container);
}
@sorrycc sorrycc changed the title 支持通过 app.js 扩展运行时的配置 支持运行时插件,以及通过 app.js 扩展运行时的配置 Sep 11, 2018
@sorrycc
Copy link
Member Author

sorrycc commented Sep 11, 2018

插件里注册一个运行时插件就好,运行时会和 @/app.js 合并处理。

api.addRuntimePlugin(join(__dirname, './runtime'));

@xiaohuoni
Copy link
Member

完美啊!

@dilidili
Copy link
Contributor

很不错!是不是把移除插件的逻辑也顺便实现一下,当遇到模式转换相关的用例时会方便管理插件。

@yanglibai
Copy link

有and design pro项目自定义插件的完整demo么,新手看得一脸懵圈

@sorrycc
Copy link
Member Author

sorrycc commented Sep 17, 2018

@yanglibai and design pro 项目自定义插件指什么?

@yanglibai
Copy link

@yanglibai and design pro 项目自定义插件指什么?

就是上面举例的那个运行时插件,在and design pro 里面要怎么用,注册插件的代码要写在哪

@sorrycc
Copy link
Member Author

sorrycc commented Sep 17, 2018

这个还没实现,实现后会有文档的。

@wangxiansheng001
Copy link

有实现动态菜单吗?

@xiaohuoni
Copy link
Member

在and design pro 中使用ant-design/ant-design-pro#2928

@Thyiad
Copy link

Thyiad commented Jun 18, 2019

这个render里面能访问model吗

@shangdev
Copy link

@sorrycc

请问,如何在 render 中访问到全局 model 中的state,比如,我有一个 user.js 全局model,其中有用户信息的 state,我想每次渲染页面前判断是否存在这个 state,再去渲染页面。

因为页面只要一刷新,model 中的 state 就会初始化了,也就拿不到用户的信息。

@WangJM001
Copy link

image
如何获取到dispatch?_store是null这时候还没初始化

@ywy94ywy
Copy link

ywy94ywy commented Nov 9, 2019

我也想知道,怎么样在运行时使用dispatch修改全局数据

@qingfengmy
Copy link

qingfengmy commented Jul 20, 2022

cra中这样写

customRender(() =>
  ReactDOM.render(
    <App />,
    document.getElementById("root")
  )
);

umi中要怎么实现呢?

下面的写法貌似不对

// app.tsx
export function render(oldRender: any) {
  customRender(oldRender);
}

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

No branches or pull requests

10 participants