@lwrjs/router 0.6.2 → 0.7.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle/prod/lwr/navigation/navigation.js +1 -1
- package/build/bundle/prod/lwr/router/router.js +1 -1
- package/build/bundle/prod/lwr/routerContainer/routerContainer.js +1 -1
- package/build/cjs/modules/lwr/contextProvider/contextProvider.cjs +45 -0
- package/build/cjs/modules/lwr/contextUtils/contextInfo.cjs +58 -0
- package/build/cjs/modules/lwr/contextUtils/contextUtils.cjs +66 -0
- package/build/cjs/modules/lwr/contextUtils/navigationApiStore.cjs +49 -0
- package/build/cjs/modules/lwr/currentPageReference/currentPageReference.cjs +31 -0
- package/build/cjs/modules/lwr/currentRoute/currentRoute.cjs +31 -0
- package/build/cjs/modules/lwr/currentView/currentView.cjs +74 -0
- package/build/cjs/modules/lwr/domRouter/domRouter.cjs +271 -0
- package/build/cjs/modules/lwr/domRouterUtils/domRouterUtils.cjs +32 -0
- package/build/cjs/modules/lwr/domRouterUtils/historyUtils.cjs +21 -0
- package/build/cjs/modules/lwr/domRouterUtils/types.cjs +5 -0
- package/build/cjs/modules/lwr/domRouterUtils/uriUtils.cjs +66 -0
- package/build/cjs/modules/lwr/historyRouter/historyRouter.cjs +70 -0
- package/build/cjs/modules/lwr/navigation/navigation.cjs +45 -0
- package/build/cjs/modules/lwr/navigation/navigationApi.cjs +38 -0
- package/build/cjs/modules/lwr/navigation/navigationMixin.cjs +70 -0
- package/build/cjs/modules/lwr/navigationContext/navigationContext.cjs +31 -0
- package/build/cjs/modules/lwr/navigationMixinHacks/navigationMixinHacks.cjs +30 -0
- package/build/cjs/modules/lwr/observable/observable.cjs +58 -0
- package/build/cjs/modules/lwr/outlet/outlet.cjs +89 -0
- package/build/cjs/modules/lwr/router/router.cjs +150 -0
- package/build/cjs/modules/lwr/routerBridge/routerBridge.cjs +84 -0
- package/build/cjs/modules/lwr/routerContainer/routerContainer.cjs +99 -0
- package/build/cjs/modules/lwr/routerContainer/utils.cjs +60 -0
- package/build/cjs/modules/lwr/routerErrors/routerErrors.cjs +155 -0
- package/build/cjs/modules/lwr/routerUtils/filterUtils.cjs +50 -0
- package/build/cjs/modules/lwr/routerUtils/parseUtils.cjs +142 -0
- package/build/cjs/modules/lwr/routerUtils/pathToRegexp.cjs +340 -0
- package/build/cjs/modules/lwr/routerUtils/routeDefUtils.cjs +164 -0
- package/build/cjs/modules/lwr/routerUtils/routeUtils.cjs +179 -0
- package/build/cjs/modules/lwr/routerUtils/routerUtils.cjs +55 -0
- package/build/cjs/modules/lwr/routerUtils/typeUtils.cjs +74 -0
- package/build/cjs/modules/lwr/routerUtils/types.cjs +5 -0
- package/build/cjs/modules/lwr/routerUtils/uriUtils.cjs +85 -0
- package/build/cjs/services/index.cjs +121 -0
- package/build/cjs/services/module-provider/index.cjs +133 -0
- package/build/cjs/services/module-provider/utils.cjs +104 -0
- package/build/modules/lwr/domRouter/domRouter.d.ts +1 -0
- package/build/modules/lwr/domRouter/domRouter.js +37 -4
- package/build/modules/lwr/routerUtils/types.d.ts +1 -1
- package/build/services/index.d.ts +27 -0
- package/build/services/index.js +103 -0
- package/build/services/module-provider/index.d.ts +22 -0
- package/build/services/module-provider/index.js +126 -0
- package/build/services/module-provider/utils.d.ts +26 -0
- package/build/services/module-provider/utils.js +109 -0
- package/package.json +47 -11
- package/index.js +0 -1
- package/lwc.config.json +0 -20
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
+
};
|
|
12
|
+
var __exportStar = (target, module2, desc) => {
|
|
13
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(module2))
|
|
15
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
+
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
};
|
|
20
|
+
var __toModule = (module2) => {
|
|
21
|
+
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// packages/@lwrjs/router/src/services/module-provider/index.ts
|
|
25
|
+
__markAsModule(exports);
|
|
26
|
+
__export(exports, {
|
|
27
|
+
default: () => module_provider_default
|
|
28
|
+
});
|
|
29
|
+
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
30
|
+
var import_utils = __toModule(require("./utils.cjs"));
|
|
31
|
+
var import__ = __toModule(require("../index.cjs"));
|
|
32
|
+
var DEFAULT_DIR = "$rootDir/src/routes";
|
|
33
|
+
var RouterModuleProvider = class {
|
|
34
|
+
constructor({routesDir = DEFAULT_DIR}, context) {
|
|
35
|
+
this.name = "router-module-provider";
|
|
36
|
+
this.watchedFileMap = new Map();
|
|
37
|
+
this.routerConfigJsonCache = new Map();
|
|
38
|
+
this.routerModuleCache = new Map();
|
|
39
|
+
const {
|
|
40
|
+
appEmitter,
|
|
41
|
+
config: {rootDir, contentDir, layoutsDir},
|
|
42
|
+
runtimeEnvironment: {lwrVersion, watchFiles}
|
|
43
|
+
} = context;
|
|
44
|
+
this.version = lwrVersion;
|
|
45
|
+
this.routesDir = (0, import_shared_utils.normalizeResourcePath)(routesDir, {
|
|
46
|
+
rootDir,
|
|
47
|
+
assets: [],
|
|
48
|
+
contentDir,
|
|
49
|
+
layoutsDir
|
|
50
|
+
}).replace(/\/$/, "");
|
|
51
|
+
this.routerEmitter = appEmitter;
|
|
52
|
+
this.routerWatcher = watchFiles ? (0, import_utils.setUpWatcher)(this.onRouterModuleChange.bind(this)) : void 0;
|
|
53
|
+
}
|
|
54
|
+
async onRouterModuleChange(configPath, deleted = false) {
|
|
55
|
+
const moduleId = this.watchedFileMap.get(configPath);
|
|
56
|
+
if (!moduleId) {
|
|
57
|
+
throw new Error("We are observing an unprocessed Router Config file, this should not happen...");
|
|
58
|
+
}
|
|
59
|
+
this.routerConfigJsonCache.delete(configPath);
|
|
60
|
+
this.routerModuleCache.delete(configPath);
|
|
61
|
+
if (!deleted) {
|
|
62
|
+
const recompiledModule = await this.getModule(moduleId);
|
|
63
|
+
if (recompiledModule) {
|
|
64
|
+
this.routerEmitter.notifyModuleSourceChanged(recompiledModule);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
watchConfigs(specifier, moduleId) {
|
|
69
|
+
if (this.routerWatcher) {
|
|
70
|
+
const configPath = (0, import_utils.getRouterConfigPath)(this.routesDir, specifier);
|
|
71
|
+
if (!this.watchedFileMap.has(configPath)) {
|
|
72
|
+
this.routerWatcher.add(configPath);
|
|
73
|
+
}
|
|
74
|
+
this.watchedFileMap.set(configPath, moduleId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
getRouterConfig(specifier) {
|
|
78
|
+
let config;
|
|
79
|
+
const routerId = (0, import_utils.parseSpecifier)(specifier);
|
|
80
|
+
if (routerId) {
|
|
81
|
+
const cacheKey = (0, import_utils.getRouterConfigPath)(this.routesDir, specifier);
|
|
82
|
+
if (this.routerConfigJsonCache.has(cacheKey)) {
|
|
83
|
+
config = this.routerConfigJsonCache.get(cacheKey);
|
|
84
|
+
} else {
|
|
85
|
+
config = (0, import__.getClientRoutes)(cacheKey);
|
|
86
|
+
if (config) {
|
|
87
|
+
this.routerConfigJsonCache.set(cacheKey, config);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
async getModuleEntry({specifier}) {
|
|
94
|
+
const config = this.getRouterConfig(specifier);
|
|
95
|
+
if (config) {
|
|
96
|
+
return {
|
|
97
|
+
id: `${specifier}|${this.version}`,
|
|
98
|
+
virtual: true,
|
|
99
|
+
entry: `<virtual>/${specifier}.js`,
|
|
100
|
+
specifier,
|
|
101
|
+
version: this.version
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async getModule(moduleId) {
|
|
106
|
+
const {specifier, namespace, name = specifier} = moduleId;
|
|
107
|
+
const moduleEntry = await this.getModuleEntry({specifier});
|
|
108
|
+
if (!moduleEntry) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const configPath = (0, import_utils.getRouterConfigPath)(this.routesDir, specifier);
|
|
112
|
+
if (this.routerModuleCache.has(configPath)) {
|
|
113
|
+
return this.routerModuleCache.get(configPath);
|
|
114
|
+
}
|
|
115
|
+
const config = this.getRouterConfig(specifier);
|
|
116
|
+
const compiledSource = (0, import_utils.generateModule)(config);
|
|
117
|
+
const moduleCompiled = {
|
|
118
|
+
id: moduleEntry.id,
|
|
119
|
+
specifier,
|
|
120
|
+
namespace,
|
|
121
|
+
name,
|
|
122
|
+
version: this.version,
|
|
123
|
+
originalSource: compiledSource,
|
|
124
|
+
moduleEntry,
|
|
125
|
+
ownHash: (0, import_shared_utils.hashContent)(compiledSource),
|
|
126
|
+
compiledSource
|
|
127
|
+
};
|
|
128
|
+
this.watchConfigs(specifier, moduleId);
|
|
129
|
+
this.routerModuleCache.set(configPath, moduleCompiled);
|
|
130
|
+
return moduleCompiled;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
var module_provider_default = RouterModuleProvider;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {get: all[name], enumerable: true});
|
|
11
|
+
};
|
|
12
|
+
var __exportStar = (target, module2, desc) => {
|
|
13
|
+
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(module2))
|
|
15
|
+
if (!__hasOwnProp.call(target, key) && key !== "default")
|
|
16
|
+
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
|
|
17
|
+
}
|
|
18
|
+
return target;
|
|
19
|
+
};
|
|
20
|
+
var __toModule = (module2) => {
|
|
21
|
+
return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// packages/@lwrjs/router/src/services/module-provider/utils.ts
|
|
25
|
+
__markAsModule(exports);
|
|
26
|
+
__export(exports, {
|
|
27
|
+
SPECIFIER_PREFIX: () => SPECIFIER_PREFIX,
|
|
28
|
+
generateModule: () => generateModule,
|
|
29
|
+
getRouterConfigPath: () => getRouterConfigPath,
|
|
30
|
+
parseSpecifier: () => parseSpecifier,
|
|
31
|
+
setUpWatcher: () => setUpWatcher
|
|
32
|
+
});
|
|
33
|
+
var import_shared_utils = __toModule(require("@lwrjs/shared-utils"));
|
|
34
|
+
var SPECIFIER_PREFIX = "@lwrjs/router/";
|
|
35
|
+
function parseSpecifier(specifier) {
|
|
36
|
+
return specifier.startsWith(SPECIFIER_PREFIX) ? specifier.replace(SPECIFIER_PREFIX, "") : void 0;
|
|
37
|
+
}
|
|
38
|
+
function getRouterConfigPath(dir, specifier) {
|
|
39
|
+
const routerId = parseSpecifier(specifier);
|
|
40
|
+
return `${dir}/${routerId}.json`;
|
|
41
|
+
}
|
|
42
|
+
function setUpWatcher(onModuleChange) {
|
|
43
|
+
const watcher = (0, import_shared_utils.createFileWatcher)();
|
|
44
|
+
watcher.on("change", (0, import_shared_utils.debounce)((file) => onModuleChange(file), 500));
|
|
45
|
+
watcher.on("unlink", (0, import_shared_utils.debounce)((file) => onModuleChange(file, true), 500));
|
|
46
|
+
watcher.on("add", (file) => console.log(`Watching Router Config file at "${file}"`));
|
|
47
|
+
return watcher;
|
|
48
|
+
}
|
|
49
|
+
function getHandlerClassName(specifier) {
|
|
50
|
+
return specifier.replace(/\//g, "_").replace("@", "");
|
|
51
|
+
}
|
|
52
|
+
function generateHandlerClasses(routes) {
|
|
53
|
+
let handlerClasses = "";
|
|
54
|
+
routes.forEach((r) => {
|
|
55
|
+
const component = r.component;
|
|
56
|
+
if (component) {
|
|
57
|
+
handlerClasses += `class ${getHandlerClassName(component)} {
|
|
58
|
+
callback;
|
|
59
|
+
constructor(callback) {
|
|
60
|
+
this.callback = callback;
|
|
61
|
+
}
|
|
62
|
+
dispose() {}
|
|
63
|
+
update() {
|
|
64
|
+
this.callback({
|
|
65
|
+
viewset: {
|
|
66
|
+
default: {
|
|
67
|
+
module: () => import('${component}'),
|
|
68
|
+
specifier: '${component}',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return handlerClasses;
|
|
78
|
+
}
|
|
79
|
+
function generateRouteDefinitions(routes) {
|
|
80
|
+
let routeDefs = "[\n";
|
|
81
|
+
routes.forEach((r) => {
|
|
82
|
+
const {handler, component, ...routeDef} = r;
|
|
83
|
+
const portableProps = JSON.stringify(routeDef);
|
|
84
|
+
if (handler) {
|
|
85
|
+
routeDefs += portableProps.slice(0, portableProps.length - 1) + `, handler: () => import('${handler}') },
|
|
86
|
+
`;
|
|
87
|
+
} else if (component) {
|
|
88
|
+
routeDefs += portableProps.slice(0, portableProps.length - 1) + `, handler: () => Promise.resolve({ default: ${getHandlerClassName(component)} }) },
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return `${routeDefs} ]`;
|
|
93
|
+
}
|
|
94
|
+
function generateModule(config = {routes: []}) {
|
|
95
|
+
const {basePath = "", caseSensitive, routes: jsonRoutes} = config;
|
|
96
|
+
const csString = caseSensitive ? "true" : "false";
|
|
97
|
+
const routes = generateRouteDefinitions(jsonRoutes);
|
|
98
|
+
const handlers = generateHandlerClasses(jsonRoutes);
|
|
99
|
+
return `import { createRouter as createLwrRouter } from 'lwr/router';
|
|
100
|
+
${handlers}
|
|
101
|
+
export function createRouter({ basePath = '${basePath}', caseSensitive = ${csString} } = {}) {
|
|
102
|
+
return createLwrRouter({ basePath, caseSensitive, routes: ${routes} });
|
|
103
|
+
}`;
|
|
104
|
+
}
|
|
@@ -48,6 +48,7 @@ export declare class DomRouterImpl implements DomRouter {
|
|
|
48
48
|
pendingRoute: DomRoutingMatch | null;
|
|
49
49
|
committedRoute: DomRoutingMatch | null;
|
|
50
50
|
routeObservable: Observable<RoutingResult>;
|
|
51
|
+
eventId?: string;
|
|
51
52
|
contextId: ContextId;
|
|
52
53
|
connected: boolean;
|
|
53
54
|
parent?: DomRouter;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Copyright (c) 2019, salesforce.com, inc.
|
|
3
5
|
* All rights reserved.
|
|
@@ -8,7 +10,11 @@ import { CurrentPageReference, CurrentRoute, CurrentView, NavigationContext, reg
|
|
|
8
10
|
import { currentPageReferenceContextualizer, currentRouteContextualizer, currentViewContextualizer, navigationContextContextualizer, provideContext } from 'lwr/contextProvider';
|
|
9
11
|
import { generateMessageObject, invariant, messages } from 'lwr/routerErrors';
|
|
10
12
|
import { createFilterChain, guid } from 'lwr/routerUtils';
|
|
11
|
-
import { createObservable } from 'lwr/observable';
|
|
13
|
+
import { createObservable } from 'lwr/observable'; // @ts-ignore Need to import from client-modules package
|
|
14
|
+
|
|
15
|
+
import { ROUTER_ERROR, ROUTER_NAV, ROUTER_VIEW } from 'lwr/metrics'; // @ts-ignore
|
|
16
|
+
|
|
17
|
+
import { logOperationStart, logOperationEnd } from 'lwr/profiler';
|
|
12
18
|
import { CONTEXT_ID_BACKDOOR } from 'lwr/navigationMixinHacks';
|
|
13
19
|
// Event fired when a component calls navigate()
|
|
14
20
|
export const NAV_EVENT = `universalcontainernavigationevent${guid()}`;
|
|
@@ -17,6 +23,7 @@ export const PARENT_EVENT = `universalcontainerparentevent${guid()}`;
|
|
|
17
23
|
export class DomRouterImpl {
|
|
18
24
|
pendingRoute = null;
|
|
19
25
|
committedRoute = null;
|
|
26
|
+
// used for instrumentation
|
|
20
27
|
contextId = Object.freeze(() => undefined);
|
|
21
28
|
connected = false;
|
|
22
29
|
preNavFilters = createFilterChain();
|
|
@@ -84,9 +91,13 @@ export class DomRouterImpl {
|
|
|
84
91
|
if (this.committedRoute && result.route === this.committedRoute.route) {
|
|
85
92
|
// This nav event has already been processed, DO NOT do it again #957
|
|
86
93
|
return;
|
|
87
|
-
}
|
|
88
|
-
// then provide the result page reference -> url as the default
|
|
94
|
+
}
|
|
89
95
|
|
|
96
|
+
logOperationEnd({
|
|
97
|
+
id: ROUTER_VIEW,
|
|
98
|
+
specifier: this.eventId
|
|
99
|
+
}); // if there's no pending route (the router was called to navigate independently of the DomRouter)
|
|
100
|
+
// then provide the result page reference -> url as the default
|
|
90
101
|
|
|
91
102
|
this.pendingRoute = this.pendingRoute || { ...result,
|
|
92
103
|
url
|
|
@@ -98,6 +109,10 @@ export class DomRouterImpl {
|
|
|
98
109
|
CurrentRoute.setContext(this.target, this.committedRoute.route);
|
|
99
110
|
this.routeObservable.next({ ...this.committedRoute,
|
|
100
111
|
viewset: result.viewset
|
|
112
|
+
});
|
|
113
|
+
logOperationEnd({
|
|
114
|
+
id: ROUTER_NAV,
|
|
115
|
+
specifier: this.eventId
|
|
101
116
|
}); // kick off child node processing (if necessary)
|
|
102
117
|
|
|
103
118
|
if (this.child) {
|
|
@@ -131,6 +146,10 @@ export class DomRouterImpl {
|
|
|
131
146
|
this.router.subscribe(result => {
|
|
132
147
|
const pageReference = result.route.pageReference || {};
|
|
133
148
|
const url = this.router.generateUrl(pageReference) || '';
|
|
149
|
+
logOperationStart({
|
|
150
|
+
id: ROUTER_VIEW,
|
|
151
|
+
specifier: this.eventId
|
|
152
|
+
});
|
|
134
153
|
|
|
135
154
|
if (result.viewset) {
|
|
136
155
|
// If result includes a viewset, wait to emit pageReference until the view is resolved.
|
|
@@ -243,7 +262,18 @@ export class DomRouterImpl {
|
|
|
243
262
|
|
|
244
263
|
|
|
245
264
|
async process(url, replace) {
|
|
246
|
-
//
|
|
265
|
+
// Mark the navigation event here instead of in navigate()
|
|
266
|
+
// This way, we catch ALL navigation events, since they all must go through process():
|
|
267
|
+
// 1. A component calls navigate()
|
|
268
|
+
// 2. The HistoryRouter catches a popstate event
|
|
269
|
+
// 3. Nested router processing
|
|
270
|
+
// Note: The < 1ms worth of logic in navigate() and _handleNavigationEvent() is excluded
|
|
271
|
+
this.eventId = new Date().getTime().toString();
|
|
272
|
+
logOperationStart({
|
|
273
|
+
id: ROUTER_NAV,
|
|
274
|
+
specifier: this.eventId
|
|
275
|
+
}); // Run the root -> leaf chain of pre navigate filters, if this is the root.
|
|
276
|
+
|
|
247
277
|
try {
|
|
248
278
|
if (!this.parent) {
|
|
249
279
|
await this.preProcess(url);
|
|
@@ -312,6 +342,9 @@ export class DomRouterImpl {
|
|
|
312
342
|
|
|
313
343
|
|
|
314
344
|
processError(messageObject) {
|
|
345
|
+
logOperationStart({
|
|
346
|
+
id: ROUTER_ERROR
|
|
347
|
+
});
|
|
315
348
|
this.errorNavFilters.compile(messageObject);
|
|
316
349
|
|
|
317
350
|
if (this.child) {
|
|
@@ -97,7 +97,7 @@ export declare type RoutingResult = RoutingMatch & {
|
|
|
97
97
|
viewset: ViewSet;
|
|
98
98
|
};
|
|
99
99
|
export interface ViewSet {
|
|
100
|
-
[
|
|
100
|
+
[viewName: string]: (() => Promise<Module>) | ViewInfo;
|
|
101
101
|
}
|
|
102
102
|
export interface ViewInfo {
|
|
103
103
|
module: () => Promise<Module>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const DEFAULT_SCHEMA = "pageReference_v1";
|
|
2
|
+
export interface LwrRouterConfig {
|
|
3
|
+
basePath?: string;
|
|
4
|
+
caseSensitive?: boolean;
|
|
5
|
+
routes: LwrConfigRouteDefinition[];
|
|
6
|
+
}
|
|
7
|
+
export interface LwrConfigRouteDefinition {
|
|
8
|
+
id: string;
|
|
9
|
+
uri: string;
|
|
10
|
+
patterns?: Record<string, string>;
|
|
11
|
+
exact?: boolean;
|
|
12
|
+
page: Partial<PageReference>;
|
|
13
|
+
metadata?: Record<string, any>;
|
|
14
|
+
handler?: string;
|
|
15
|
+
component?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PageReference {
|
|
18
|
+
type: string;
|
|
19
|
+
attributes: Record<string, string | null>;
|
|
20
|
+
state: Record<string, string | null>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Given a Routes Config path, return the array of Config Route Definitions
|
|
24
|
+
* @param path - A path to a Route Config files
|
|
25
|
+
*/
|
|
26
|
+
export declare function getClientRoutes(path: string): LwrRouterConfig | undefined;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { readFile } from '@lwrjs/shared-utils';
|
|
4
|
+
import { LwrUnresolvableError, createSingleDiagnosticError, descriptions } from '@lwrjs/diagnostics';
|
|
5
|
+
export const DEFAULT_SCHEMA = 'pageReference_v1';
|
|
6
|
+
// JSON Schema for the interfaces above
|
|
7
|
+
const RouteDefinitionSchema = {
|
|
8
|
+
definitions: {
|
|
9
|
+
map: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
patternProperties: {
|
|
12
|
+
'^.+$': { type: 'string', minLength: 1 },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
nullableMap: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
patternProperties: {
|
|
18
|
+
'^.+$': {
|
|
19
|
+
anyOf: [{ type: 'string', minLength: 1 }, { type: 'null' }],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
specifier: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
pattern: '^@?[a-zA-Z_\\d]+[/]{1}[a-z-A-Z_\\d]+$',
|
|
26
|
+
},
|
|
27
|
+
routeDef: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
id: { type: 'string', minLength: 1 },
|
|
31
|
+
uri: { type: 'string', minLength: 1 },
|
|
32
|
+
patterns: { $ref: '#/definitions/map' },
|
|
33
|
+
exact: { type: 'boolean' },
|
|
34
|
+
page: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
type: { type: 'string', minLength: 1 },
|
|
38
|
+
attributes: { $ref: '#/definitions/nullableMap' },
|
|
39
|
+
state: { $ref: '#/definitions/nullableMap' },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
metadata: {},
|
|
43
|
+
handler: { $ref: '#/definitions/specifier' },
|
|
44
|
+
component: { $ref: '#/definitions/specifier' },
|
|
45
|
+
},
|
|
46
|
+
required: ['id', 'uri', 'page'],
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
schema: { type: 'string', pattern: `^${DEFAULT_SCHEMA}$` },
|
|
53
|
+
basePath: { type: 'string', minLength: 1 },
|
|
54
|
+
caseSensitive: { type: 'boolean' },
|
|
55
|
+
routes: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { $ref: '#/definitions/routeDef' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['routes'],
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
};
|
|
63
|
+
const ajv = new Ajv();
|
|
64
|
+
const validate = ajv.compile(RouteDefinitionSchema);
|
|
65
|
+
/**
|
|
66
|
+
* Given a Routes Config path, return the array of Config Route Definitions
|
|
67
|
+
* @param path - A path to a Route Config files
|
|
68
|
+
*/
|
|
69
|
+
export function getClientRoutes(path) {
|
|
70
|
+
if (!existsSync(path)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
// Validate the JSON schema
|
|
75
|
+
const json = JSON.parse(readFile(path));
|
|
76
|
+
if (!validate(json)) {
|
|
77
|
+
throw new Error(ajv.errorsText(validate.errors, { separator: '\n' }));
|
|
78
|
+
}
|
|
79
|
+
// Additional validation:
|
|
80
|
+
// 1. Ensure each route definition "id" is unique
|
|
81
|
+
// 2. Ensure each route definition has either a "handler" or a "component", but NOT BOTH
|
|
82
|
+
const routes = json.routes;
|
|
83
|
+
routes.forEach((route, index) => {
|
|
84
|
+
const { id, handler, component } = route;
|
|
85
|
+
if (routes.findIndex((r) => r.id === id) !== index) {
|
|
86
|
+
throw new Error(`Duplicate route definition id: ${id}`);
|
|
87
|
+
}
|
|
88
|
+
if (handler && component) {
|
|
89
|
+
throw new Error('A route definition cannot contain both "handler" and "component"');
|
|
90
|
+
}
|
|
91
|
+
if (!handler && !component) {
|
|
92
|
+
throw new Error('A route definition must contain either "handler" or "component"');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return json;
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
throw createSingleDiagnosticError({
|
|
99
|
+
description: descriptions.UNRESOLVABLE.ROUTES_MODULE(path, e.message),
|
|
100
|
+
}, LwrUnresolvableError);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AbstractModuleId, ModuleCompiled, ModuleEntry, ModuleProvider, ProviderContext } from '@lwrjs/types';
|
|
2
|
+
interface RouterProviderOptions {
|
|
3
|
+
routesDir?: string;
|
|
4
|
+
}
|
|
5
|
+
export default class RouterModuleProvider implements ModuleProvider {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
routesDir: string;
|
|
9
|
+
private routerEmitter;
|
|
10
|
+
private routerWatcher?;
|
|
11
|
+
private watchedFileMap;
|
|
12
|
+
private routerConfigJsonCache;
|
|
13
|
+
private routerModuleCache;
|
|
14
|
+
constructor({ routesDir }: RouterProviderOptions, context: ProviderContext);
|
|
15
|
+
onRouterModuleChange(configPath: string, deleted?: boolean): Promise<void>;
|
|
16
|
+
private watchConfigs;
|
|
17
|
+
private getRouterConfig;
|
|
18
|
+
getModuleEntry({ specifier }: AbstractModuleId): Promise<ModuleEntry | undefined>;
|
|
19
|
+
getModule(moduleId: AbstractModuleId): Promise<ModuleCompiled | undefined>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { hashContent, normalizeResourcePath } from '@lwrjs/shared-utils';
|
|
2
|
+
import { generateModule, getRouterConfigPath, parseSpecifier, setUpWatcher } from './utils.js';
|
|
3
|
+
import { getClientRoutes } from '../index.js';
|
|
4
|
+
const DEFAULT_DIR = '$rootDir/src/routes';
|
|
5
|
+
export default class RouterModuleProvider {
|
|
6
|
+
constructor({ routesDir = DEFAULT_DIR }, context) {
|
|
7
|
+
this.name = 'router-module-provider';
|
|
8
|
+
this.watchedFileMap = new Map(); // <config path, module id>
|
|
9
|
+
// Two layers of caching:
|
|
10
|
+
// 1. Cache the Router Config JSON objects read from the file system, by config path
|
|
11
|
+
// 2. Cache the modules generated from the config, by config path
|
|
12
|
+
this.routerConfigJsonCache = new Map();
|
|
13
|
+
this.routerModuleCache = new Map();
|
|
14
|
+
const { appEmitter, config: { rootDir, contentDir, layoutsDir }, runtimeEnvironment: { lwrVersion, watchFiles }, } = context;
|
|
15
|
+
this.version = lwrVersion;
|
|
16
|
+
// Replace aliases and remove trailing slash from the routes directory
|
|
17
|
+
this.routesDir = normalizeResourcePath(routesDir, {
|
|
18
|
+
rootDir,
|
|
19
|
+
assets: [],
|
|
20
|
+
contentDir,
|
|
21
|
+
layoutsDir,
|
|
22
|
+
}).replace(/\/$/, '');
|
|
23
|
+
// Set up watcher on Router Config files
|
|
24
|
+
this.routerEmitter = appEmitter;
|
|
25
|
+
this.routerWatcher = watchFiles ? setUpWatcher(this.onRouterModuleChange.bind(this)) : undefined;
|
|
26
|
+
}
|
|
27
|
+
// When Router Metadata changes on the file system:
|
|
28
|
+
// 1. delete the config data from the routerConfigJsonCache
|
|
29
|
+
// 2. delete the module from the routerModuleCache
|
|
30
|
+
// 3. recompile the module based on the new data and emit
|
|
31
|
+
async onRouterModuleChange(configPath, deleted = false) {
|
|
32
|
+
const moduleId = this.watchedFileMap.get(configPath);
|
|
33
|
+
if (!moduleId) {
|
|
34
|
+
throw new Error('We are observing an unprocessed Router Config file, this should not happen...');
|
|
35
|
+
}
|
|
36
|
+
this.routerConfigJsonCache.delete(configPath);
|
|
37
|
+
this.routerModuleCache.delete(configPath);
|
|
38
|
+
if (!deleted) {
|
|
39
|
+
const recompiledModule = await this.getModule(moduleId);
|
|
40
|
+
if (recompiledModule) {
|
|
41
|
+
this.routerEmitter.notifyModuleSourceChanged(recompiledModule);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Watch Router Config files:
|
|
46
|
+
// 1. Add files to the watcher
|
|
47
|
+
// 2. Track watched files in the watchedFileMap with the associated module ID object
|
|
48
|
+
watchConfigs(specifier, moduleId) {
|
|
49
|
+
if (this.routerWatcher) {
|
|
50
|
+
const configPath = getRouterConfigPath(this.routesDir, specifier);
|
|
51
|
+
if (!this.watchedFileMap.has(configPath)) {
|
|
52
|
+
this.routerWatcher.add(configPath);
|
|
53
|
+
}
|
|
54
|
+
this.watchedFileMap.set(configPath, moduleId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// 1. Check that:
|
|
58
|
+
// a. the specifier is in the correct format: "@lwrjs/router/{routerId}"
|
|
59
|
+
// b. the config JSON addressed by "routerId" is available on the file system
|
|
60
|
+
// 2. Maintain the Router Config cache
|
|
61
|
+
getRouterConfig(specifier) {
|
|
62
|
+
let config;
|
|
63
|
+
const routerId = parseSpecifier(specifier);
|
|
64
|
+
if (routerId) {
|
|
65
|
+
// Fetch the Router Config JSON from the cache or file system
|
|
66
|
+
const cacheKey = getRouterConfigPath(this.routesDir, specifier);
|
|
67
|
+
if (this.routerConfigJsonCache.has(cacheKey)) {
|
|
68
|
+
// Cache hit
|
|
69
|
+
config = this.routerConfigJsonCache.get(cacheKey);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Cache miss, read from file system
|
|
73
|
+
config = getClientRoutes(cacheKey);
|
|
74
|
+
if (config) {
|
|
75
|
+
this.routerConfigJsonCache.set(cacheKey, config);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return config;
|
|
80
|
+
}
|
|
81
|
+
async getModuleEntry({ specifier }) {
|
|
82
|
+
const config = this.getRouterConfig(specifier);
|
|
83
|
+
if (config) {
|
|
84
|
+
return {
|
|
85
|
+
id: `${specifier}|${this.version}`,
|
|
86
|
+
virtual: true,
|
|
87
|
+
entry: `<virtual>/${specifier}.js`,
|
|
88
|
+
specifier,
|
|
89
|
+
version: this.version,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async getModule(moduleId) {
|
|
94
|
+
// Retrieve the Module Entry
|
|
95
|
+
const { specifier, namespace, name = specifier } = moduleId;
|
|
96
|
+
const moduleEntry = await this.getModuleEntry({ specifier });
|
|
97
|
+
if (!moduleEntry) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Check the module cache first
|
|
101
|
+
const configPath = getRouterConfigPath(this.routesDir, specifier);
|
|
102
|
+
if (this.routerModuleCache.has(configPath)) {
|
|
103
|
+
return this.routerModuleCache.get(configPath);
|
|
104
|
+
}
|
|
105
|
+
// Generate code for the requested module
|
|
106
|
+
const config = this.getRouterConfig(specifier);
|
|
107
|
+
const compiledSource = generateModule(config);
|
|
108
|
+
// Construct a Compiled Module
|
|
109
|
+
const moduleCompiled = {
|
|
110
|
+
id: moduleEntry.id,
|
|
111
|
+
specifier,
|
|
112
|
+
namespace,
|
|
113
|
+
name,
|
|
114
|
+
version: this.version,
|
|
115
|
+
originalSource: compiledSource,
|
|
116
|
+
moduleEntry,
|
|
117
|
+
ownHash: hashContent(compiledSource),
|
|
118
|
+
compiledSource,
|
|
119
|
+
};
|
|
120
|
+
// Watch Router Config file, add cache entries, and return
|
|
121
|
+
this.watchConfigs(specifier, moduleId);
|
|
122
|
+
this.routerModuleCache.set(configPath, moduleCompiled);
|
|
123
|
+
return moduleCompiled;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Watcher } from '@lwrjs/types';
|
|
2
|
+
import type { LwrRouterConfig } from '../index.js';
|
|
3
|
+
export declare const SPECIFIER_PREFIX = "@lwrjs/router/";
|
|
4
|
+
/**
|
|
5
|
+
* Parse a specifier into its Router Config ID
|
|
6
|
+
* Return undefined if the specifier is invalid
|
|
7
|
+
* @param specifier - The raw specifier to parse
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseSpecifier(specifier: string): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Given a Router Config ID and directory, return the file path to the router metadata
|
|
12
|
+
* @param dir - Path to directory containing Router Config files
|
|
13
|
+
* @param specifier - Specifier for the Router Config module
|
|
14
|
+
*/
|
|
15
|
+
export declare function getRouterConfigPath(dir: string, specifier: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Set up a file system watcher; used to spy on Router Config file changes
|
|
18
|
+
* @param onModuleChange - File change callback function
|
|
19
|
+
*/
|
|
20
|
+
export declare function setUpWatcher(onModuleChange: (file: string, deleted?: boolean) => void): Watcher;
|
|
21
|
+
/**
|
|
22
|
+
* Generate a module string which fulfills a router request
|
|
23
|
+
* @param routes - The array of route definitions
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateModule(config?: LwrRouterConfig): string;
|
|
26
|
+
//# sourceMappingURL=utils.d.ts.map
|