diff --git a/ReadMe.md b/ReadMe.md index bdbbc53..e242261 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -22,6 +22,8 @@ https://web-cell.dev/scaffold/ - [x] **Path Mode**: `location.hash` (default) & `history.pushState()` +- [x] **Async Loading** (recommend to use with `import()` ECMAScript proposal) + - [x] (experimental) [Nested Router][5] support ## Installation @@ -46,6 +48,8 @@ npm install parcel-bundler -D ## Usage +### Sync Pages + `source/model/index.ts` ```typescript @@ -56,7 +60,7 @@ export const history = new History(); `source/page/PageRouter.tsx` -```jsx +```javascript import { createCell, component } from 'web-cell'; import { observer } from 'mobx-web-cell'; import { HTMLRouter } from 'cell-router/source'; @@ -78,6 +82,10 @@ function Example({ path }) { }) export default class PageRouter extends HTMLRouter { protected history = history; + protected routes = [ + { paths: ['test'], component: Test }, + { paths: ['example'], component: Example } + ]; render() { return ( @@ -90,15 +98,73 @@ export default class PageRouter extends HTMLRouter { Example -
- {matchRoutes( - [ - { paths: ['test'], component: Test }, - { paths: ['example'], component: Example } - ], - history.path - )} -
+
{super.render()}
+ + ); + } +} +``` + +### Async Pages + +`tsconfig.json` + +```json +{ + "compilerOptions": { + "module": "ESNext" + } +} +``` + +`source/page/index.ts` + +```javascript +export default [ + { + paths: ['', 'home'], + component: async () => (await import('./Home.tsx')).default, + async: true + }, + { + paths: ['list'], + component: async () => (await import('./List.tsx')).default, + async: true + } +]; +``` + +`source/component/PageRouter.tsx` + +```javascript +import { component, createCell } from 'web-cell'; +import { observer } from 'mobx-web-cell'; +import { HTMLRouter } from 'cell-router/source'; + +import { history } from '../model'; +import routes from '../page'; + +@observer +@component({ + tagName: 'page-router', + renderTarget: 'children' +}) +export default class PageRouter extends HTMLRouter { + protected history = history; + protected routes = routes; + + render() { + return ( +
+ +
{super.render()}
); } diff --git a/package.json b/package.json index 5db57a4..f881a57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cell-router", - "version": "2.0.0-rc.3", + "version": "2.0.0-rc.4", "license": "LGPL-3.0", "description": "Web Component Router based on WebCell & MobX", "keywords": [ @@ -26,7 +26,7 @@ "peerDependencies": { "mobx": "^5.15.1", "mobx-web-cell": "^0.2.5", - "web-cell": "^2.0.0-rc.12" + "web-cell": "^2.0.0-rc.13" }, "devDependencies": { "@types/jest": "^24.0.25", @@ -48,10 +48,10 @@ "ts-jest": "^24.2.0", "typedoc": "^0.15.6", "typescript": "^3.7.4", - "web-cell": "^2.0.0-rc.12" + "web-cell": "^2.0.0-rc.13" }, "scripts": { - "debug": "cd test/ && parcel source/index.html", + "start": "cd test/ && parcel source/index.html --open", "lint": "lint-staged", "pack-test": "cd test/ && parcel build source/index.html", "set-chrome": "app-find chrome -c", diff --git a/source/HTMLRouter.ts b/source/HTMLRouter.tsx similarity index 65% rename from source/HTMLRouter.ts rename to source/HTMLRouter.tsx index dfe3558..3e3a581 100644 --- a/source/HTMLRouter.ts +++ b/source/HTMLRouter.tsx @@ -1,7 +1,7 @@ -import { mixin, delegate } from 'web-cell'; +import { mixin, delegate, createCell } from 'web-cell'; import { History, HistoryMode } from './History'; -import { scrollTo } from './utility'; +import { Route, scrollTo, matchRoutes } from './utility'; type LinkElement = HTMLAnchorElement | HTMLAreaElement; @@ -25,6 +25,8 @@ export abstract class HTMLRouter extends mixin() { } protected abstract history: History; + protected abstract routes: Route[]; + private currentPage: Function; handleLink = delegate('a[href]', (event: MouseEvent) => { const link = event.target as LinkElement; @@ -63,6 +65,15 @@ export abstract class HTMLRouter extends mixin() { this.history.reset(!!this.parentRouter); + this.routes = this.routes + .map(({ paths, ...rest }) => + paths.map(path => ({ paths: [path], ...rest })) + ) + .flat() + .sort(({ paths: [A] }, { paths: [B] }) => + (B as string).localeCompare(A as string) + ); + this.addEventListener('click', this.handleLink); window.addEventListener('popstate', this.handleBack); @@ -77,4 +88,28 @@ export abstract class HTMLRouter extends mixin() { if (this.history.mode === HistoryMode.hash) window.removeEventListener('hashchange', this.handleHash); } + + render() { + const { component, async, path, params } = + matchRoutes(this.routes, this.history.path) || {}; + + if (!component) return; + + if (async) + component().then((func: Function) => { + const route = this.routes.find( + route => route.component === component + ); + if (!route) return; + + route.component = func; + delete route.async; + // @ts-ignore + this.update(); + }); + else this.currentPage = component; + + if (this.currentPage) + return ; + } } diff --git a/source/utility.tsx b/source/utility.ts similarity index 78% rename from source/utility.tsx rename to source/utility.ts index 10daf64..c19c417 100644 --- a/source/utility.tsx +++ b/source/utility.ts @@ -1,5 +1,3 @@ -import { createCell } from 'web-cell'; - export function parsePathData(URI: string) { const params = {}, [path, data] = URI.split('?'); @@ -31,21 +29,19 @@ export function scrollTo(selector: string, root?: Element) { if (anchor) anchor.scrollIntoView({ behavior: 'smooth' }); } -interface Route { +export interface Route { paths: (string | RegExp)[]; - component: Function; + component: Function | (() => Promise); + async?: boolean; } export function matchRoutes(list: Route[], path: string) { - for (const { paths, component: Component } of list) + for (const { paths, ...rest } of list) for (const item of paths) if ( typeof item === 'string' ? path.startsWith(item) : item.exec(path) - ) { - const data = parsePathData(path); - - return ; - } + ) + return { ...rest, ...parsePathData(path) }; } diff --git a/test/source/page/SubRouter.tsx b/test/source/page/SubRouter.tsx index eac8c57..f1f83bf 100644 --- a/test/source/page/SubRouter.tsx +++ b/test/source/page/SubRouter.tsx @@ -1,6 +1,6 @@ import { createCell, component } from 'web-cell'; import { observer } from 'mobx-web-cell'; -import { HTMLRouter, matchRoutes } from '../../../source'; +import { HTMLRouter } from '../../../source'; import { subHistory } from '../model'; @@ -19,6 +19,10 @@ function Temp({ path }) { }) export default class SubRouter extends HTMLRouter { protected history = subHistory; + protected routes = [ + { paths: ['sample'], component: Sample }, + { paths: ['temp'], component: Temp } + ]; render() { return ( @@ -31,15 +35,7 @@ export default class SubRouter extends HTMLRouter { Temp -
- {matchRoutes( - [ - { paths: ['sample'], component: Sample }, - { paths: ['temp'], component: Temp } - ], - subHistory.path - )} -
+
{super.render()}
); } diff --git a/test/source/page/TopRouter.tsx b/test/source/page/TopRouter.tsx index 6ed913d..442e276 100644 --- a/test/source/page/TopRouter.tsx +++ b/test/source/page/TopRouter.tsx @@ -1,6 +1,6 @@ import { createCell, component } from 'web-cell'; import { observer } from 'mobx-web-cell'; -import { HTMLRouter, matchRoutes } from '../../../source'; +import { HTMLRouter } from '../../../source'; import { topHistory } from '../model'; import SubRouter from './SubRouter'; @@ -25,6 +25,14 @@ function Example({ path }) { }) export default class TopRouter extends HTMLRouter { protected history = topHistory; + protected routes = [ + { paths: ['test'], component: Test }, + { + paths: ['example'], + component: () => Promise.resolve(Example), + async: true + } + ]; render() { return ( @@ -37,15 +45,7 @@ export default class TopRouter extends HTMLRouter { Example -
- {matchRoutes( - [ - { paths: ['test'], component: Test }, - { paths: ['example'], component: Example } - ], - topHistory.path - )} -
+
{super.render()}
); } diff --git a/tsconfig.json b/tsconfig.json index 61c17b8..adea1eb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,11 +2,12 @@ "compilerOptions": { "module": "ES6", "moduleResolution": "Node", + "esModuleInterop": true, "downlevelIteration": true, "experimentalDecorators": true, "jsx": "react", "jsxFactory": "createCell", - "lib": ["DOM", "DOM.Iterable"], + "lib": ["ES2019", "DOM", "DOM.Iterable"], "target": "ES5" }, "include": ["source/**/*"]