-
Notifications
You must be signed in to change notification settings - Fork 214
Expand file tree
/
Copy pathApplicationExtension.ts
More file actions
141 lines (128 loc) · 5.21 KB
/
ApplicationExtension.ts
File metadata and controls
141 lines (128 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
// Third-Party
import {RouteProps} from 'react-router-dom'
// Local
import {ApplicationExtension as ApplicationExtensionBase} from '../../shared/classes/application-extension-base'
// Types
import {ApplicationExtensionConfig, Modules, SerializedRoute} from '../../types'
export type ReactApplicationExtensionConfig = ApplicationExtensionConfig
/**
* An abstract class representing an Application Extension. This class provides
* foundational methods and properties for extending an application with additional
* configuration and routing capabilities. It is designed to be subclassed
* by other Application Extensions that need to augment the base application, particularly
* during server and client-side rendering.
*
* @abstract
*/
export class ApplicationExtension<
Config extends ReactApplicationExtensionConfig
> extends ApplicationExtensionBase<Config> {
constructor(config: Config) {
super(config)
this.extendRoutes = this.extendRoutes.bind(this)
}
/**
* Called during the rendering of the base application on the server and the client.
* It is predominantly used to enhance the "base" application by wrapping it with React providers.
*
* @protected
* @param App - The base application component.
* @returns EnhancedApp - The enhanced application component.
*/
public extendApp<T extends React.ComponentType<T>>(
App: React.ComponentType<T>
): React.ComponentType<T> {
return App
}
/**
* Called during server rendering and client application initialization. This method allows
* you to modify the routes of the base application, typically used to add new routes pointing
* at page components added by your application extension.
*
* @protected
* @param routes - The list application routes currently loaded.
* @returns routes - The modified application routes.
*/
public extendRoutes(routes: RouteProps[]): Promise<RouteProps[]> {
return Promise.resolve(routes)
}
public getRoutes(): Promise<RouteProps[]> {
return Promise.resolve([])
}
/**
* Called before route matching is evaluated. This method gives each extension the opportunity
* to modify the routes knowing that the list of routes passed-in is complete.
*
* @protected
* @param routes - All the application routes from both extensions and base application.
* @returns routes - The modified application routes.
*/
public beforeRouteMatch(routes: RouteProps[]): RouteProps[] {
return routes
}
public async serialize(): Promise<SerializedRoute[]> {
const routes = await this.getRoutes()
return routes.map((route) => {
if (!route.component?.displayName) {
throw new Error(`Component for route with path "${route.path}" is missing a displayName`)
}
return {
path: route.path,
componentName: route.component?.displayName,
// TODO: do we need to serialize props?
//componentProps: route.props
}
})
}
public deserialize(serializedRoutes: SerializedRoute[], componentMap: Modules): any[] {
return serializedRoutes.map(
({path, componentName}) => {
let component = componentMap[componentName]
if (!component) {
throw new Error(`${componentName} component could not be deserialized for route with path: ${path}`)
}
// if (componentProps) {
// const Component = component
// component = () => <Component {...componentProps} />
// }
return {
path,
exact: true,
component
}
}
)
}
/**
* Protected method to get the component map. This method should be called
* by subclasses to provide the correct path for importing modules.
*
* @protected
* @param path - The path to the modules directory that contains the page components.
* @returns A promise that resolves to the component map.
*/
protected async generateComponentMapFromModules(modules: Modules): Promise<Modules> {
const componentMap = Object.keys(modules).reduce((acc: Modules, key: string) => {
const module = modules[key]
// TODO: will displayName always exist or do we need error handling?
acc[module.displayName] = module
return acc
}, {})
return componentMap
}
// TODO: should we make an abstract class for getComponentMap to enforce
// subclasses to implement it?
/**
* Default method to get the component map using the default path './pages'.
* Subclasses can override this method if they need to use a different path.
*
* @returns A promise that resolves to the component map.
*/
// public abstract getComponentMap(): Promise<Modules>
}