@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.
Files changed (52) hide show
  1. package/build/bundle/prod/lwr/navigation/navigation.js +1 -1
  2. package/build/bundle/prod/lwr/router/router.js +1 -1
  3. package/build/bundle/prod/lwr/routerContainer/routerContainer.js +1 -1
  4. package/build/cjs/modules/lwr/contextProvider/contextProvider.cjs +45 -0
  5. package/build/cjs/modules/lwr/contextUtils/contextInfo.cjs +58 -0
  6. package/build/cjs/modules/lwr/contextUtils/contextUtils.cjs +66 -0
  7. package/build/cjs/modules/lwr/contextUtils/navigationApiStore.cjs +49 -0
  8. package/build/cjs/modules/lwr/currentPageReference/currentPageReference.cjs +31 -0
  9. package/build/cjs/modules/lwr/currentRoute/currentRoute.cjs +31 -0
  10. package/build/cjs/modules/lwr/currentView/currentView.cjs +74 -0
  11. package/build/cjs/modules/lwr/domRouter/domRouter.cjs +271 -0
  12. package/build/cjs/modules/lwr/domRouterUtils/domRouterUtils.cjs +32 -0
  13. package/build/cjs/modules/lwr/domRouterUtils/historyUtils.cjs +21 -0
  14. package/build/cjs/modules/lwr/domRouterUtils/types.cjs +5 -0
  15. package/build/cjs/modules/lwr/domRouterUtils/uriUtils.cjs +66 -0
  16. package/build/cjs/modules/lwr/historyRouter/historyRouter.cjs +70 -0
  17. package/build/cjs/modules/lwr/navigation/navigation.cjs +45 -0
  18. package/build/cjs/modules/lwr/navigation/navigationApi.cjs +38 -0
  19. package/build/cjs/modules/lwr/navigation/navigationMixin.cjs +70 -0
  20. package/build/cjs/modules/lwr/navigationContext/navigationContext.cjs +31 -0
  21. package/build/cjs/modules/lwr/navigationMixinHacks/navigationMixinHacks.cjs +30 -0
  22. package/build/cjs/modules/lwr/observable/observable.cjs +58 -0
  23. package/build/cjs/modules/lwr/outlet/outlet.cjs +89 -0
  24. package/build/cjs/modules/lwr/router/router.cjs +150 -0
  25. package/build/cjs/modules/lwr/routerBridge/routerBridge.cjs +84 -0
  26. package/build/cjs/modules/lwr/routerContainer/routerContainer.cjs +99 -0
  27. package/build/cjs/modules/lwr/routerContainer/utils.cjs +60 -0
  28. package/build/cjs/modules/lwr/routerErrors/routerErrors.cjs +155 -0
  29. package/build/cjs/modules/lwr/routerUtils/filterUtils.cjs +50 -0
  30. package/build/cjs/modules/lwr/routerUtils/parseUtils.cjs +142 -0
  31. package/build/cjs/modules/lwr/routerUtils/pathToRegexp.cjs +340 -0
  32. package/build/cjs/modules/lwr/routerUtils/routeDefUtils.cjs +164 -0
  33. package/build/cjs/modules/lwr/routerUtils/routeUtils.cjs +179 -0
  34. package/build/cjs/modules/lwr/routerUtils/routerUtils.cjs +55 -0
  35. package/build/cjs/modules/lwr/routerUtils/typeUtils.cjs +74 -0
  36. package/build/cjs/modules/lwr/routerUtils/types.cjs +5 -0
  37. package/build/cjs/modules/lwr/routerUtils/uriUtils.cjs +85 -0
  38. package/build/cjs/services/index.cjs +121 -0
  39. package/build/cjs/services/module-provider/index.cjs +133 -0
  40. package/build/cjs/services/module-provider/utils.cjs +104 -0
  41. package/build/modules/lwr/domRouter/domRouter.d.ts +1 -0
  42. package/build/modules/lwr/domRouter/domRouter.js +37 -4
  43. package/build/modules/lwr/routerUtils/types.d.ts +1 -1
  44. package/build/services/index.d.ts +27 -0
  45. package/build/services/index.js +103 -0
  46. package/build/services/module-provider/index.d.ts +22 -0
  47. package/build/services/module-provider/index.js +126 -0
  48. package/build/services/module-provider/utils.d.ts +26 -0
  49. package/build/services/module-provider/utils.js +109 -0
  50. package/package.json +47 -11
  51. package/index.js +0 -1
  52. 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
- } // if there's no pending route (the router was called to navigate independently of the DomRouter)
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
- // Run the root -> leaf chain of pre navigate filters, if this is the root.
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
- [namedView: string]: (() => Promise<Module>) | ViewInfo;
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