@qlover/create-app 0.6.1 → 0.6.3
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/CHANGELOG.md +61 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/react-app/README.en.md +257 -0
- package/dist/templates/react-app/README.md +29 -231
- package/dist/templates/react-app/config/IOCIdentifier.ts +13 -0
- package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/en/development-guide.md +523 -0
- package/dist/templates/react-app/docs/en/env.md +482 -0
- package/dist/templates/react-app/docs/en/global.md +509 -0
- package/dist/templates/react-app/docs/en/i18n.md +268 -0
- package/dist/templates/react-app/docs/en/index.md +173 -0
- package/dist/templates/react-app/docs/en/ioc.md +424 -0
- package/dist/templates/react-app/docs/en/project-structure.md +434 -0
- package/dist/templates/react-app/docs/en/request.md +425 -0
- package/dist/templates/react-app/docs/en/router.md +404 -0
- package/dist/templates/react-app/docs/en/store.md +321 -0
- package/dist/templates/react-app/docs/en/theme.md +424 -0
- package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +562 -0
- package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
- package/dist/templates/react-app/docs/zh/env.md +479 -0
- package/dist/templates/react-app/docs/zh/global.md +511 -0
- package/dist/templates/react-app/docs/zh/i18n.md +268 -0
- package/dist/templates/react-app/docs/zh/index.md +173 -0
- package/dist/templates/react-app/docs/zh/ioc.md +422 -0
- package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
- package/dist/templates/react-app/docs/zh/request.md +429 -0
- package/dist/templates/react-app/docs/zh/router.md +408 -0
- package/dist/templates/react-app/docs/zh/store.md +321 -0
- package/dist/templates/react-app/docs/zh/theme.md +424 -0
- package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
- package/dist/templates/react-app/package.json +2 -2
- package/dist/templates/react-app/src/base/apis/AiApi.ts +10 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -17
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +2 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +7 -5
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -2
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +33 -0
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +1 -1
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +2 -2
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +1 -1
- package/dist/templates/react-app/src/base/services/RouteService.ts +5 -2
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -10
- package/dist/templates/react-app/src/core/IOC.ts +73 -83
- package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +52 -4
- package/dist/templates/react-app/src/core/bootstraps/{index.ts → BootstrapsRegistry.ts} +2 -3
- package/dist/templates/react-app/src/core/registers/IocRegisterImpl.ts +25 -0
- package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +11 -17
- package/dist/templates/react-app/src/core/registers/RegisterControllers.ts +10 -4
- package/dist/templates/react-app/src/core/registers/RegisterGlobals.ts +6 -15
- package/dist/templates/react-app/src/main.tsx +2 -5
- package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
- package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
- package/dist/templates/react-app/src/styles/css/page.css +1 -1
- package/dist/templates/react-app/src/uikit/controllers/JSONStorageController.ts +1 -1
- package/dist/templates/react-app/tsconfig.app.json +2 -1
- package/dist/templates/react-app/tsconfig.node.json +2 -1
- package/package.json +2 -2
- package/dist/templates/react-app/src/base/port/ApiTransactionInterface.ts +0 -7
- package/dist/templates/react-app/src/base/port/RequestCatcherInterface.ts +0 -12
- package/dist/templates/react-app/src/core/bootstrap.ts +0 -58
- package/dist/templates/react-app/src/core/registers/RegisterApi.ts +0 -5
- package/dist/templates/react-app/src/core/registers/index.ts +0 -32
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# Project Bootstrap Guide
|
|
2
|
+
|
|
3
|
+
## What is Bootstrap?
|
|
4
|
+
|
|
5
|
+
Bootstrap is an application starter that helps us manage various initialization logic uniformly when the application starts.
|
|
6
|
+
|
|
7
|
+
**In simple terms**: Just like a computer needs to start various services during boot-up, our application also needs to do some preparation work when starting, such as:
|
|
8
|
+
|
|
9
|
+
- Check if user is logged in
|
|
10
|
+
- Load user information
|
|
11
|
+
- Initialize API configuration
|
|
12
|
+
- Set theme, language, etc.
|
|
13
|
+
|
|
14
|
+
File path: src/core/bootstraps
|
|
15
|
+
|
|
16
|
+
## Implementation in Project
|
|
17
|
+
|
|
18
|
+
This project is based on the AsyncExecutor of `@qlover/fe-corekit`, and `corekit-bridge` implements Bootstrap on this foundation.
|
|
19
|
+
|
|
20
|
+
**File Entry**: `src/core/bootstraps/BootstrapApp.ts`
|
|
21
|
+
|
|
22
|
+
**Main Components**:
|
|
23
|
+
|
|
24
|
+
1. [IOC Container](./ioc.md) - Dependency injection management
|
|
25
|
+
2. [Environment Variable Injection](./env.md) - Configuration management
|
|
26
|
+
3. [Browser Global Variable Injection](./global.md) - Browser global properties
|
|
27
|
+
|
|
28
|
+
## When to Use Bootstrap?
|
|
29
|
+
|
|
30
|
+
Bootstrap comes in handy when you encounter the following situations:
|
|
31
|
+
|
|
32
|
+
### Why Need a "Bootstrapper"
|
|
33
|
+
|
|
34
|
+
**Core Goal**: Make application startup cleaner and business logic clearer.
|
|
35
|
+
|
|
36
|
+
**Problems Solved**: When a page opens, you need authentication or need to request an API before entering the page, you might do something like this:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
export function App() {
|
|
40
|
+
const [loading, setLoading] = useState(false);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setLoading(true);
|
|
44
|
+
|
|
45
|
+
fetchUserInfo()
|
|
46
|
+
.then(() => {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
})
|
|
49
|
+
.catch(() => {
|
|
50
|
+
setLoading(false);
|
|
51
|
+
});
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
if (loading) {
|
|
55
|
+
return <div>loading ...</div>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return <Router></Router>;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This code works fine, but the key issue is that it's tightly coupled with the component! That means when fetchUserInfo succeeds, it updates local state and then renders Router.
|
|
63
|
+
|
|
64
|
+
However, if the logic for entering Router becomes complex and depends on various conditions, the component becomes very bloated and hard to maintain.
|
|
65
|
+
|
|
66
|
+
For example, consider this situation:
|
|
67
|
+
|
|
68
|
+
When the route is /home, request user information, after success, if the user's roles have permission then enter, otherwise redirect to /login
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
export function App() {
|
|
72
|
+
const [loading, setLoading] = useState(false);
|
|
73
|
+
const [userInfo, setUserInfo] = useState(null);
|
|
74
|
+
const [hasPermission, setHasPermission] = useState(false);
|
|
75
|
+
const location = useLocation();
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
setLoading(true);
|
|
79
|
+
|
|
80
|
+
// Check current route
|
|
81
|
+
if (location.pathname === '/home') {
|
|
82
|
+
fetchUserInfo()
|
|
83
|
+
.then((user) => {
|
|
84
|
+
setUserInfo(user);
|
|
85
|
+
|
|
86
|
+
// Check user permissions
|
|
87
|
+
if (user.roles && user.roles.includes('admin')) {
|
|
88
|
+
setHasPermission(true);
|
|
89
|
+
setLoading(false);
|
|
90
|
+
} else {
|
|
91
|
+
// No permission, redirect to login page
|
|
92
|
+
window.location.href = '/login';
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
console.error('Failed to fetch user info:', error);
|
|
97
|
+
// Also redirect to login page on request failure
|
|
98
|
+
window.location.href = '/login';
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
setLoading(false);
|
|
102
|
+
}
|
|
103
|
+
}, [location.pathname]);
|
|
104
|
+
|
|
105
|
+
// If loading, show loading state
|
|
106
|
+
if (loading) {
|
|
107
|
+
return <div>Loading...</div>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If home page but no permission, show error message
|
|
111
|
+
if (location.pathname === '/home' && !hasPermission) {
|
|
112
|
+
return <div>Access denied</div>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return <Router />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This example shows several issues:
|
|
120
|
+
|
|
121
|
+
1. **Component Has Too Many Responsibilities**: App component needs to handle routing, user authentication, permission checking, error handling, etc.
|
|
122
|
+
2. **Complex State Management**: Need to manage multiple states like loading, userInfo, hasPermission
|
|
123
|
+
3. **Logic Coupling**: Authentication logic mixed with component rendering logic
|
|
124
|
+
4. **Hard to Test**: Component contains too much business logic, making unit testing difficult
|
|
125
|
+
5. **Hard to Extend**: If more permission checks or authentication logic need to be added later, the component becomes more bloated
|
|
126
|
+
|
|
127
|
+
These problems will amplify step by step after project iterations until you start refactoring code!
|
|
128
|
+
|
|
129
|
+
## What is a Bootstrapper?
|
|
130
|
+
|
|
131
|
+
A bootstrapper is a front-end logic processor that runs at the UI layer, with core functions:
|
|
132
|
+
|
|
133
|
+
1. **Front-end Logic Processing**: Execute necessary initialization logic before application rendering
|
|
134
|
+
2. **State Management**: Manage application state through store, achieving reactive UI updates
|
|
135
|
+
3. **Separation of Concerns**: Separate business logic from UI components
|
|
136
|
+
|
|
137
|
+
### Core Features
|
|
138
|
+
|
|
139
|
+
- **Asynchronous Execution**: Implement asynchronous logic processing based on AsyncExecutor
|
|
140
|
+
- **State Driven**: Trigger UI updates through store state changes
|
|
141
|
+
- **Modular**: Support IOC container, environment variable injection, global variable injection, and other modules
|
|
142
|
+
|
|
143
|
+
### Workflow
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
App Start → Bootstrap Initialization → Execute Front-end Logic → Update Store → UI Responds to Updates
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Comparison with Traditional Approach
|
|
150
|
+
|
|
151
|
+
**Traditional Approach**:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
// Business logic mixed in component
|
|
155
|
+
function App() {
|
|
156
|
+
const [loading, setLoading] = useState(true);
|
|
157
|
+
const [data, setData] = useState(null);
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
// Business logic mixed in component
|
|
161
|
+
fetchData()
|
|
162
|
+
.then(setData)
|
|
163
|
+
.finally(() => setLoading(false));
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
if (loading) return <Loading />;
|
|
167
|
+
return <MainContent data={data} />;
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Using Bootstrapper**:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
// Component only focuses on rendering
|
|
175
|
+
function App() {
|
|
176
|
+
const { loading, data } = useStore(); // Get state from store
|
|
177
|
+
|
|
178
|
+
if (loading) return <Loading />;
|
|
179
|
+
return <MainContent data={data} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Business logic handled in bootstrapper
|
|
183
|
+
const bootstrap = new Bootstrap({
|
|
184
|
+
root: window,
|
|
185
|
+
logger,
|
|
186
|
+
ioc: {
|
|
187
|
+
manager: IOC,
|
|
188
|
+
register: new IocRegisterImpl({ pathname, appConfig })
|
|
189
|
+
},
|
|
190
|
+
envOptions: {
|
|
191
|
+
target: appConfig,
|
|
192
|
+
source: import.meta.env,
|
|
193
|
+
prefix: 'APP_'
|
|
194
|
+
},
|
|
195
|
+
globalOptions: {
|
|
196
|
+
sources: globals,
|
|
197
|
+
target: 'AppGlobals'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Register business logic plugins
|
|
202
|
+
bootstrap.use([
|
|
203
|
+
IOC(UserService), // User authentication service
|
|
204
|
+
new UserApiBootstarp(), // User API configuration
|
|
205
|
+
new FeApiBootstarp() // Other API configurations
|
|
206
|
+
]);
|
|
207
|
+
|
|
208
|
+
// Start application
|
|
209
|
+
await bootstrap.initialize();
|
|
210
|
+
await bootstrap.start();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Comparison Results**:
|
|
214
|
+
|
|
215
|
+
- ✅ Component becomes cleaner, only responsible for rendering
|
|
216
|
+
- ✅ Business logic separated into bootstrapper
|
|
217
|
+
- ✅ Can independently test business logic
|
|
218
|
+
- ✅ Can reuse business logic in other UI frameworks
|
|
219
|
+
|
|
220
|
+
**Complete User Authentication Example**:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
// 1. Define User API Plugin (Configure API requests)
|
|
224
|
+
export class UserApiBootstarp implements BootstrapExecutorPlugin {
|
|
225
|
+
readonly pluginName = 'UserApiBootstarp';
|
|
226
|
+
|
|
227
|
+
onBefore({ parameters: { ioc } }: BootstrapContext): void {
|
|
228
|
+
// Configure user API plugins
|
|
229
|
+
ioc
|
|
230
|
+
.get<UserApi>(UserApi)
|
|
231
|
+
.usePlugin(new FetchURLPlugin())
|
|
232
|
+
.usePlugin(IOC.get(IOCIdentifier.ApiMockPlugin))
|
|
233
|
+
.usePlugin(IOC.get(RequestLogger));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Define User Service (Handle user authentication logic)
|
|
238
|
+
@injectable()
|
|
239
|
+
export class UserService
|
|
240
|
+
extends UserAuthService<UserInfo>
|
|
241
|
+
implements ExecutorPlugin
|
|
242
|
+
{
|
|
243
|
+
readonly pluginName = 'UserService';
|
|
244
|
+
|
|
245
|
+
constructor(
|
|
246
|
+
@inject(UserApi) userApi: UserAuthApiInterface<UserInfo>,
|
|
247
|
+
@inject(IOCIdentifier.AppConfig) appConfig: AppConfig
|
|
248
|
+
) {
|
|
249
|
+
super(userApi, {
|
|
250
|
+
userStorage: {
|
|
251
|
+
key: appConfig.userInfoStorageKey,
|
|
252
|
+
storage: storage
|
|
253
|
+
},
|
|
254
|
+
credentialStorage: {
|
|
255
|
+
key: appConfig.userTokenStorageKey,
|
|
256
|
+
storage: storage
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check user authentication status before startup
|
|
262
|
+
async onBefore(): Promise<void> {
|
|
263
|
+
if (this.isAuthenticated()) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const userToken = this.getToken();
|
|
268
|
+
if (!userToken) {
|
|
269
|
+
throw new AppError('NO_USER_TOKEN');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
await this.userInfo();
|
|
273
|
+
this.store.authSuccess();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. Register in bootstrapper
|
|
278
|
+
const bootstrap = new Bootstrap({
|
|
279
|
+
root: window,
|
|
280
|
+
logger,
|
|
281
|
+
ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) },
|
|
282
|
+
envOptions: { target: appConfig, source: import.meta.env, prefix: 'APP_' },
|
|
283
|
+
globalOptions: { sources: globals, target: 'AppGlobals' }
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Register user authentication related plugins
|
|
287
|
+
bootstrap.use([
|
|
288
|
+
IOC(UserService), // User authentication service
|
|
289
|
+
new UserApiBootstarp(), // User API configuration
|
|
290
|
+
IOC(I18nService) // Internationalization service
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
// 4. Start application
|
|
294
|
+
await bootstrap.initialize();
|
|
295
|
+
await bootstrap.start();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Key Points**:
|
|
299
|
+
|
|
300
|
+
- `UserApiBootstarp`: Responsible for configuring API requests
|
|
301
|
+
- `UserService`: Responsible for handling user authentication logic
|
|
302
|
+
- `Bootstrap`: Unified management of all plugins
|
|
303
|
+
- Components: Only responsible for UI rendering
|
|
304
|
+
|
|
305
|
+
And most crucially, UI and logic can exist independently, meaning you can implement multiple different UIs based on this logic.
|
|
306
|
+
|
|
307
|
+
**You might think using classes and objects is redundant, but in terms of software design and long-term project considerations, decades of development have shown us that this is the easiest pattern for developers to understand and collaborate on.**
|
|
308
|
+
|
|
309
|
+
## Advantages of Bootstrapper
|
|
310
|
+
|
|
311
|
+
### 1. **Testability**
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
// Can independently test business logic
|
|
315
|
+
describe('UserService', () => {
|
|
316
|
+
it('should authenticate user successfully', async () => {
|
|
317
|
+
const userService = new UserService(mockUserApi, mockAppConfig);
|
|
318
|
+
const result = await userService.onBefore();
|
|
319
|
+
expect(result).toBeDefined();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 2. **Reusability**
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// Same business logic can be used in different UI frameworks
|
|
328
|
+
// React version
|
|
329
|
+
const bootstrap = new Bootstrap({...});
|
|
330
|
+
bootstrap.use([IOC(UserService)]);
|
|
331
|
+
|
|
332
|
+
// Vue version
|
|
333
|
+
const bootstrap = new Bootstrap({...});
|
|
334
|
+
bootstrap.use([IOC(UserService)]); // Same business logic
|
|
335
|
+
|
|
336
|
+
// Native JS version
|
|
337
|
+
const bootstrap = new Bootstrap({...});
|
|
338
|
+
bootstrap.use([IOC(UserService)]); // Same business logic
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 3. **Extensibility**
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
// Easily add new business logic
|
|
345
|
+
bootstrap.use([
|
|
346
|
+
IOC(UserService),
|
|
347
|
+
IOC(PermissionService), // Add permission service
|
|
348
|
+
IOC(NotificationService), // Add notification service
|
|
349
|
+
IOC(AnalyticsService) // Add analytics service
|
|
350
|
+
]);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 4. **Team Collaboration**
|
|
354
|
+
|
|
355
|
+
- **Frontend Developers**: Focus on UI components and user experience
|
|
356
|
+
- **Backend Developers**: Focus on API interfaces and data processing
|
|
357
|
+
- **Architects**: Focus on business logic design and system architecture
|
|
358
|
+
- **Test Engineers**: Can independently test each module
|
|
359
|
+
|
|
360
|
+
## Quick Start
|
|
361
|
+
|
|
362
|
+
If you want to quickly experience Bootstrap, follow these steps:
|
|
363
|
+
|
|
364
|
+
### Step 1: Create Simple Plugin
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
// Create a simple plugin
|
|
368
|
+
export class SimplePlugin implements BootstrapExecutorPlugin {
|
|
369
|
+
readonly pluginName = 'SimplePlugin';
|
|
370
|
+
|
|
371
|
+
onBefore({ parameters: { logger } }: BootstrapContext): void {
|
|
372
|
+
logger.info('SimplePlugin started successfully!');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Step 2: Register in Bootstrapper
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
const bootstrap = new Bootstrap({
|
|
381
|
+
root: window,
|
|
382
|
+
logger,
|
|
383
|
+
ioc: { manager: IOC, register: new IocRegisterImpl({ pathname, appConfig }) }
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Register plugin
|
|
387
|
+
bootstrap.use([new SimplePlugin()]);
|
|
388
|
+
|
|
389
|
+
// Start
|
|
390
|
+
await bootstrap.initialize();
|
|
391
|
+
await bootstrap.start();
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Step 3: Check Results
|
|
395
|
+
|
|
396
|
+
Open browser console, you'll see the log "SimplePlugin started successfully!".
|
|
397
|
+
|
|
398
|
+
**Tip**: This simple example shows the basic usage of Bootstrap. As you understand the project better, you can gradually add more complex business logic.
|
|
399
|
+
|
|
400
|
+
## Real Project Application Scenarios
|
|
401
|
+
|
|
402
|
+
### Scenario 1: Multi-platform Applications
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
// Web
|
|
406
|
+
const webBootstrap = new Bootstrap({
|
|
407
|
+
root: window
|
|
408
|
+
// Web-specific configuration
|
|
409
|
+
});
|
|
410
|
+
webBootstrap.use([IOC(UserService), IOC(WebSpecificService)]);
|
|
411
|
+
|
|
412
|
+
// Mobile H5
|
|
413
|
+
const mobileBootstrap = new Bootstrap({
|
|
414
|
+
root: window
|
|
415
|
+
// Mobile-specific configuration
|
|
416
|
+
});
|
|
417
|
+
mobileBootstrap.use([IOC(UserService), IOC(MobileSpecificService)]);
|
|
418
|
+
|
|
419
|
+
// Mini Program
|
|
420
|
+
const miniprogramBootstrap = new Bootstrap({
|
|
421
|
+
root: globalThis
|
|
422
|
+
// Mini program specific configuration
|
|
423
|
+
});
|
|
424
|
+
miniprogramBootstrap.use([IOC(UserService), IOC(MiniprogramSpecificService)]);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Scenario 2: Micro-frontend Architecture
|
|
428
|
+
|
|
429
|
+
```tsx
|
|
430
|
+
// Main application
|
|
431
|
+
const mainBootstrap = new Bootstrap({
|
|
432
|
+
root: window
|
|
433
|
+
// Main application configuration
|
|
434
|
+
});
|
|
435
|
+
mainBootstrap.use([IOC(GlobalUserService), IOC(RouterService)]);
|
|
436
|
+
|
|
437
|
+
// Sub-application A
|
|
438
|
+
const appABootstrap = new Bootstrap({
|
|
439
|
+
root: window
|
|
440
|
+
// Sub-application A configuration
|
|
441
|
+
});
|
|
442
|
+
appABootstrap.use([IOC(UserService), IOC(AppASpecificService)]);
|
|
443
|
+
|
|
444
|
+
// Sub-application B
|
|
445
|
+
const appBBootstrap = new Bootstrap({
|
|
446
|
+
root: window
|
|
447
|
+
// Sub-application B configuration
|
|
448
|
+
});
|
|
449
|
+
appBBootstrap.use([IOC(UserService), IOC(AppBSpecificService)]);
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Scenario 3: Progressive Upgrade
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
// Old version: Handle business logic directly in component
|
|
456
|
+
function OldApp() {
|
|
457
|
+
const [user, setUser] = useState(null);
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
fetchUser().then(setUser);
|
|
460
|
+
}, []);
|
|
461
|
+
return <div>{user?.name}</div>;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// New version: Use bootstrapper to separate concerns
|
|
465
|
+
function NewApp() {
|
|
466
|
+
const { user } = useStore(); // Get from store managed by bootstrapper
|
|
467
|
+
return <div>{user?.name}</div>;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Can coexist, migrate gradually
|
|
471
|
+
const bootstrap = new Bootstrap({...});
|
|
472
|
+
bootstrap.use([IOC(UserService)]); // New logic
|
|
473
|
+
// Old logic can still continue to be used
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Best Practices
|
|
477
|
+
|
|
478
|
+
### 1. **Plugin Design Principles**
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
// ✅ Good plugin design
|
|
482
|
+
export class GoodPlugin implements BootstrapExecutorPlugin {
|
|
483
|
+
readonly pluginName = 'GoodPlugin';
|
|
484
|
+
|
|
485
|
+
// Single responsibility
|
|
486
|
+
onBefore({ parameters: { ioc } }: BootstrapContext): void {
|
|
487
|
+
// Do only one thing: configure API
|
|
488
|
+
ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ❌ Bad plugin design
|
|
493
|
+
export class BadPlugin implements BootstrapExecutorPlugin {
|
|
494
|
+
readonly pluginName = 'BadPlugin';
|
|
495
|
+
|
|
496
|
+
// Does too many things
|
|
497
|
+
onBefore({ parameters: { ioc } }: BootstrapContext): void {
|
|
498
|
+
// Configure API
|
|
499
|
+
ioc.get<UserApi>(UserApi).usePlugin(new FetchURLPlugin());
|
|
500
|
+
// Handle routing
|
|
501
|
+
ioc.get<RouterService>(RouterService).configure();
|
|
502
|
+
// Handle theme
|
|
503
|
+
ioc.get<ThemeService>(ThemeService).init();
|
|
504
|
+
// Handle internationalization
|
|
505
|
+
ioc.get<I18nService>(I18nService).load();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### 2. **Error Handling**
|
|
511
|
+
|
|
512
|
+
```tsx
|
|
513
|
+
export class UserService implements ExecutorPlugin {
|
|
514
|
+
readonly pluginName = 'UserService';
|
|
515
|
+
|
|
516
|
+
async onBefore(): Promise<void> {
|
|
517
|
+
try {
|
|
518
|
+
if (this.isAuthenticated()) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const userToken = this.getToken();
|
|
523
|
+
if (!userToken) {
|
|
524
|
+
throw new AppError('NO_USER_TOKEN');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
await this.userInfo();
|
|
528
|
+
this.store.authSuccess();
|
|
529
|
+
} catch (error) {
|
|
530
|
+
// Graceful error handling
|
|
531
|
+
this.store.authFailed(error);
|
|
532
|
+
this.routerService.gotoLogin();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### 3. **Performance Optimization**
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
// Conditional plugin loading
|
|
542
|
+
if (process.env.NODE_ENV === 'development') {
|
|
543
|
+
bootstrap.use([IOC(DevToolsService)]);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Load plugins on demand
|
|
547
|
+
if (appConfig.features.analytics) {
|
|
548
|
+
bootstrap.use([IOC(AnalyticsService)]);
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Summary
|
|
553
|
+
|
|
554
|
+
The Bootstrap starter is not just a technical implementation, but an architectural concept. It helps us:
|
|
555
|
+
|
|
556
|
+
1. **Separate Concerns**: Separate UI and business logic
|
|
557
|
+
2. **Improve Maintainability**: Modular design, easy to understand and modify
|
|
558
|
+
3. **Enhance Testability**: Each module can be tested independently
|
|
559
|
+
4. **Support Team Collaboration**: Different roles can focus on their domains
|
|
560
|
+
5. **Adapt to Changes**: Business logic changes don't affect UI, UI changes don't affect business logic
|
|
561
|
+
|
|
562
|
+
This design pattern is becoming increasingly important in modern frontend development, especially in large projects and team collaboration.
|