mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 17:48:13 +00:00
fix: router change
This commit is contained in:
parent
29ea5f77d6
commit
920e584d7e
@ -1,18 +1,42 @@
|
||||
{
|
||||
"componentName": "Page",
|
||||
"fileName": "home",
|
||||
"props": {},
|
||||
"children": [
|
||||
"componentsTree": [
|
||||
{
|
||||
"componentName": "Container",
|
||||
"componentName": "Page",
|
||||
"fileName": "home1",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"type": "primary"
|
||||
},
|
||||
"children": ["Welcome to Your Rax App!"]
|
||||
"componentName": "Container",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"type": "primary",
|
||||
"content": "Page 1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"componentName": "Page",
|
||||
"fileName": "home2",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Container",
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"componentName": "Text",
|
||||
"props": {
|
||||
"type": "primary",
|
||||
"content": "Page 2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -23,8 +23,10 @@
|
||||
"@recore/obx": "^1.0.8",
|
||||
"classnames": "^2.2.6",
|
||||
"driver-universal": "^3.1.3",
|
||||
"history": "^5.0.0",
|
||||
"lodash": "^4.17.19",
|
||||
"rax-find-dom-node": "^1.0.0",
|
||||
"rax-use-router": "^3.0.0",
|
||||
"react": "^16",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
|
||||
259
packages/rax-simulator-renderer/src/rax-use-router.js
Normal file
259
packages/rax-simulator-renderer/src/rax-use-router.js
Normal file
@ -0,0 +1,259 @@
|
||||
// Inspired by react-router and universal-router
|
||||
import { useState, useEffect, useLayoutEffect, createElement } from 'rax';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
|
||||
const cache = {};
|
||||
function decodeParam(val) {
|
||||
try {
|
||||
return decodeURIComponent(val);
|
||||
} catch (err) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function matchPath(route, pathname, parentParams) {
|
||||
let { path, routes, exact: end = true, strict = false, sensitive = false } = route;
|
||||
// If not has path or has routes that should do not exact match
|
||||
if (path == null || routes) {
|
||||
end = false;
|
||||
}
|
||||
|
||||
// Default path is empty
|
||||
path = path || '';
|
||||
|
||||
const regexpCacheKey = `${path}|${end}|${strict}|${sensitive}`;
|
||||
const keysCacheKey = regexpCacheKey + '|';
|
||||
|
||||
let regexp = cache[regexpCacheKey];
|
||||
let keys = cache[keysCacheKey] || [];
|
||||
|
||||
if (!regexp) {
|
||||
regexp = pathToRegexp(path, keys, {
|
||||
end,
|
||||
strict,
|
||||
sensitive
|
||||
});
|
||||
cache[regexpCacheKey] = regexp;
|
||||
cache[keysCacheKey] = keys;
|
||||
}
|
||||
|
||||
const result = regexp.exec(pathname);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = result[0];
|
||||
const params = { ...parentParams, history: router.history, location: router.history.location };
|
||||
|
||||
for (let i = 1; i < result.length; i++) {
|
||||
const key = keys[i - 1];
|
||||
const prop = key.name;
|
||||
const value = result[i];
|
||||
if (value !== undefined || !Object.prototype.hasOwnProperty.call(params, prop)) {
|
||||
if (key.repeat) {
|
||||
params[prop] = value ? value.split(key.delimiter).map(decodeParam) : [];
|
||||
} else {
|
||||
params[prop] = value ? decodeParam(value) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
path: !end && url.charAt(url.length - 1) === '/' ? url.substr(1) : url,
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
function matchRoute(route, baseUrl, pathname, parentParams) {
|
||||
let matched;
|
||||
let childMatches;
|
||||
let childIndex = 0;
|
||||
|
||||
return {
|
||||
next() {
|
||||
if (!matched) {
|
||||
matched = matchPath(route, pathname, parentParams);
|
||||
|
||||
if (matched) {
|
||||
return {
|
||||
done: false,
|
||||
$: {
|
||||
route,
|
||||
baseUrl,
|
||||
path: matched.path,
|
||||
params: matched.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (matched && route.routes) {
|
||||
while (childIndex < route.routes.length) {
|
||||
if (!childMatches) {
|
||||
const childRoute = route.routes[childIndex];
|
||||
childRoute.parent = route;
|
||||
|
||||
childMatches = matchRoute(
|
||||
childRoute,
|
||||
baseUrl + matched.path,
|
||||
pathname.substr(matched.path.length),
|
||||
matched.params,
|
||||
);
|
||||
}
|
||||
|
||||
const childMatch = childMatches.next();
|
||||
if (!childMatch.done) {
|
||||
return {
|
||||
done: false,
|
||||
$: childMatch.$,
|
||||
};
|
||||
}
|
||||
|
||||
childMatches = null;
|
||||
childIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return { done: true };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let _initialized = false;
|
||||
let _routerConfig = null;
|
||||
const router = {
|
||||
history: null,
|
||||
handles: [],
|
||||
errorHandler() { },
|
||||
addHandle(handle) {
|
||||
return router.handles.push(handle);
|
||||
},
|
||||
removeHandle(handleId) {
|
||||
router.handles[handleId - 1] = null;
|
||||
},
|
||||
triggerHandles(component) {
|
||||
router.handles.map((handle) => {
|
||||
handle && handle(component);
|
||||
});
|
||||
},
|
||||
match(fullpath) {
|
||||
if (fullpath == null) return;
|
||||
|
||||
router.fullpath = fullpath;
|
||||
|
||||
const parent = router.root;
|
||||
const matched = matchRoute(
|
||||
parent,
|
||||
parent.path,
|
||||
fullpath
|
||||
);
|
||||
|
||||
function next(parent) {
|
||||
const current = matched.next();
|
||||
|
||||
if (current.done) {
|
||||
const error = new Error(`No match for ${fullpath}`);
|
||||
return router.errorHandler(error, router.history.location);
|
||||
}
|
||||
|
||||
let component = current.$.route.component;
|
||||
if (typeof component === 'function') {
|
||||
component = component(current.$.params, router.history.location);
|
||||
}
|
||||
|
||||
if (component instanceof Promise) {
|
||||
// Lazy loading component by import('./Foo')
|
||||
return component.then((component) => {
|
||||
// Check current fullpath avoid router has changed before lazy loading complete
|
||||
if (fullpath === router.fullpath) {
|
||||
router.triggerHandles(component);
|
||||
}
|
||||
});
|
||||
} else if (component != null) {
|
||||
router.triggerHandles(component);
|
||||
return component;
|
||||
} else {
|
||||
return next(parent);
|
||||
}
|
||||
}
|
||||
|
||||
return next(parent);
|
||||
}
|
||||
};
|
||||
|
||||
function matchLocation({ pathname }) {
|
||||
router.match(pathname);
|
||||
}
|
||||
|
||||
|
||||
function getInitialComponent(routerConfig) {
|
||||
let InitialComponent = [];
|
||||
|
||||
if (_routerConfig === null) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!routerConfig) {
|
||||
throw new Error('Error: useRouter should have routerConfig, see: https://www.npmjs.com/package/rax-use-router.');
|
||||
}
|
||||
if (!routerConfig.history || !routerConfig.routes) {
|
||||
throw new Error('Error: routerConfig should contain history and routes, see: https://www.npmjs.com/package/rax-use-router.');
|
||||
}
|
||||
}
|
||||
_routerConfig = routerConfig;
|
||||
}
|
||||
if (_routerConfig.InitialComponent) {
|
||||
InitialComponent = _routerConfig.InitialComponent;
|
||||
}
|
||||
router.history = _routerConfig.history;
|
||||
|
||||
return InitialComponent;
|
||||
}
|
||||
|
||||
export function useRouter(routerConfig) {
|
||||
const [component, setComponent] = useState(getInitialComponent(routerConfig));
|
||||
|
||||
if (routerConfig) {
|
||||
_routerConfig = routerConfig;
|
||||
const routes = _routerConfig.routes;
|
||||
router.root = Array.isArray(routes) ? { routes } : routes;
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (_initialized) throw new Error('Error: useRouter can only be called once.');
|
||||
_initialized = true;
|
||||
const history = _routerConfig.history;
|
||||
const routes = _routerConfig.routes;
|
||||
|
||||
router.root = Array.isArray(routes) ? { routes } : routes;
|
||||
|
||||
const handleId = router.addHandle((component) => {
|
||||
setComponent(component);
|
||||
});
|
||||
|
||||
// Init path match
|
||||
if (!_routerConfig.InitialComponent) {
|
||||
matchLocation(history.location);
|
||||
}
|
||||
|
||||
const unlisten = history.listen(({ location }) => {
|
||||
matchLocation(location);
|
||||
});
|
||||
|
||||
return () => {
|
||||
router.removeHandle(handleId);
|
||||
unlisten();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { component };
|
||||
}
|
||||
|
||||
export function withRouter(Component) {
|
||||
function Wrapper(props) {
|
||||
const history = router.history;
|
||||
return createElement(Component, { ...props, history, location: history.location });
|
||||
};
|
||||
|
||||
Wrapper.displayName = 'withRouter(' + (Component.displayName || Component.name) + ')';
|
||||
Wrapper.WrappedComponent = Component;
|
||||
return Wrapper;
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
import { Fragment, Component, createElement } from 'rax';
|
||||
// import { observer } from './obx-rax/observer';
|
||||
import RaxEngine from '@ali/lowcode-rax-renderer/lib/index';
|
||||
import { History } from 'history';
|
||||
import { Component, createElement, Fragment, useState } from 'rax';
|
||||
import { useRouter } from './rax-use-router';
|
||||
// import RaxEngine from '../../rax-render/lib/index';
|
||||
import { SimulatorRendererContainer, DocumentInstance } from './renderer';
|
||||
import { host } from './host';
|
||||
|
||||
import { DocumentInstance, SimulatorRendererContainer } from './renderer';
|
||||
import './renderer.less';
|
||||
|
||||
|
||||
// patch cloneElement avoid lost keyProps
|
||||
const originCloneElement = (window as any).Rax.cloneElement;
|
||||
(window as any).Rax.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => {
|
||||
@ -40,27 +41,38 @@ const originCloneElement = (window as any).Rax.cloneElement;
|
||||
export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||
render() {
|
||||
const { rendererContainer } = this.props;
|
||||
rendererContainer.onDocumentChange(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
return (
|
||||
<Layout rendererContainer={rendererContainer}>
|
||||
<Routes rendererContainer={rendererContainer} />
|
||||
<Routes rendererContainer={rendererContainer} history={rendererContainer.history} />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Routes extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||
render() {
|
||||
const { rendererContainer } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
{rendererContainer.documentInstances.map((instance, index) => {
|
||||
console.log('Routes');
|
||||
return <Renderer key={index} rendererContainer={rendererContainer} documentInstance={instance} />;
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
export const Routes = (props: {
|
||||
rendererContainer: SimulatorRendererContainer,
|
||||
history: History
|
||||
}) => {
|
||||
const { rendererContainer, history } = props;
|
||||
const { documentInstances } = rendererContainer;
|
||||
|
||||
const routes = {
|
||||
history,
|
||||
routes: documentInstances.map(instance => {
|
||||
return {
|
||||
path: instance.path,
|
||||
component: (props: any) => <Renderer rendererContainer={rendererContainer} documentInstance={instance} {...props} />
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
const { component } = useRouter(routes);
|
||||
return component;
|
||||
}
|
||||
|
||||
function ucfirst(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.substring(1);
|
||||
}
|
||||
@ -83,7 +95,7 @@ function getDeviceView(view: any, device: string, mode: string) {
|
||||
|
||||
class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
render() {
|
||||
const { rendererContainer, children } = this.props;
|
||||
@ -109,14 +121,13 @@ class Renderer extends Component<{
|
||||
});
|
||||
}
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
render() {
|
||||
const { documentInstance } = this.props;
|
||||
const { container } = documentInstance;
|
||||
const { designMode, device } = container;
|
||||
const { rendererContainer: renderer } = this.props;
|
||||
// const { device, designMode } = renderer;
|
||||
|
||||
return (
|
||||
<RaxEngine
|
||||
|
||||
@ -13,6 +13,9 @@ import SimulatorRendererView from './renderer-view';
|
||||
import { raxFindDOMNodes } from './utils/find-dom-nodes';
|
||||
import { getClientRects } from './utils/get-client-rects';
|
||||
import loader from './utils/loader';
|
||||
// @ts-ignore
|
||||
// import { createMemoryHistory, MemoryHistory } from 'history/umd/history.development';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
|
||||
const { Instance } = shared;
|
||||
|
||||
@ -223,8 +226,9 @@ export class DocumentInstance {
|
||||
export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
||||
readonly isSimulatorRenderer = true;
|
||||
private dispose?: () => void;
|
||||
// TODO: history
|
||||
readonly history: any;
|
||||
readonly history: MemoryHistory;
|
||||
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
@obx.ref private _documentInstances: DocumentInstance[] = [];
|
||||
get documentInstances() {
|
||||
@ -258,47 +262,48 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
||||
}
|
||||
return inst;
|
||||
});
|
||||
this.emitter.emit('documentChange');
|
||||
const path = host.project.currentDocument ? documentInstanceMap.get(host.project.currentDocument.id)!.path : '/';
|
||||
if (firstRun) {
|
||||
initialEntry = path;
|
||||
} else {
|
||||
// if (this.history.location.pathname !== path) {
|
||||
// this.history.replace(path);
|
||||
// }
|
||||
if (this.history.location.pathname !== path) {
|
||||
this.history.replace(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
// const history = createMemoryHistory({
|
||||
// initialEntries: [initialEntry],
|
||||
// });
|
||||
// this.history = history;
|
||||
// history.listen((location, action) => {
|
||||
// host.project.open(location.pathname.substr(1));
|
||||
// });
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [initialEntry],
|
||||
});
|
||||
this.history = history;
|
||||
history.listen(({ location }) => {
|
||||
host.project.open(location.pathname.substr(1));
|
||||
});
|
||||
host.componentsConsumer.consume(async (componentsAsset) => {
|
||||
if (componentsAsset) {
|
||||
await this.load(componentsAsset);
|
||||
this.buildComponents();
|
||||
}
|
||||
});
|
||||
// this._appContext = {
|
||||
// utils: {
|
||||
// router: {
|
||||
// push(path: string, params?: object) {
|
||||
// history.push(withQueryParams(path, params));
|
||||
// },
|
||||
// replace(path: string, params?: object) {
|
||||
// history.replace(withQueryParams(path, params));
|
||||
// },
|
||||
// },
|
||||
// legaoBuiltins: {
|
||||
// getUrlParams() {
|
||||
// const search = history.location.search;
|
||||
// return parseQuery(search);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// constants: {},
|
||||
// };
|
||||
this._appContext = {
|
||||
utils: {
|
||||
router: {
|
||||
push(path: string, params?: object) {
|
||||
// history.push(withQueryParams(path, params));
|
||||
},
|
||||
replace(path: string, params?: object) {
|
||||
// history.replace(withQueryParams(path, params));
|
||||
},
|
||||
},
|
||||
legaoBuiltins: {
|
||||
getUrlParams() {
|
||||
const search = history.location.search;
|
||||
// return parseQuery(search);
|
||||
}
|
||||
}
|
||||
},
|
||||
constants: {},
|
||||
};
|
||||
host.injectionConsumer.consume((data) => {
|
||||
// sync utils, i18n, contants,... config
|
||||
});
|
||||
@ -420,6 +425,13 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
|
||||
cursor.release();
|
||||
}
|
||||
|
||||
onDocumentChange(cb: () => void) {
|
||||
this.emitter.on('documentChange', cb);
|
||||
return () => {
|
||||
this.emitter.removeListener('renderer', fn);
|
||||
};
|
||||
}
|
||||
|
||||
createComponent(schema: ComponentSchema): Component | null {
|
||||
return null;
|
||||
// TODO: use ComponentEngine refactor
|
||||
|
||||
@ -53,5 +53,5 @@
|
||||
"publishConfig": {
|
||||
"registry": "http://registry.npm.alibaba-inc.com"
|
||||
},
|
||||
"homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-react-renderer@0.8.13/build/index.html"
|
||||
"homepage": "https:/unpkg.alibaba-inc.com/@ali/lowcode-react-renderer@0.8.18/build/index.html"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user