@khester/create-dynamics-app 2.1.0 ā 2.2.0
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/dist/artifacts/registry.d.ts +4 -3
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +121 -11
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +1 -1
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/injectDevTools.d.ts.map +1 -1
- package/dist/injectDevTools.js +4 -2
- package/dist/injectDevTools.js.map +1 -1
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +3 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +3 -2
- package/templates/grid-starter/ARCHITECTURE.md +66 -0
- package/templates/grid-starter/README.md +122 -0
- package/templates/grid-starter/env.example +16 -0
- package/templates/grid-starter/gitignore +6 -0
- package/templates/grid-starter/index.html +16 -0
- package/templates/grid-starter/package.json +39 -0
- package/templates/grid-starter/src/App.tsx +23 -0
- package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
- package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
- package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
- package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
- package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
- package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
- package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
- package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
- package/templates/grid-starter/src/index.tsx +18 -0
- package/templates/grid-starter/src/vite-env.d.ts +15 -0
- package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/grid-starter/tsconfig.json +19 -0
- package/templates/grid-starter/vite.config.ts +76 -0
- package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
- package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
- package/templates/pcf-field/index.ts +1 -1
- package/templates/pcf-field/package.json +3 -1
- package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
- package/templates/react-custom-page/ARCHITECTURE.md +75 -0
- package/templates/react-custom-page/README.md +74 -568
- package/templates/react-custom-page/env.example +16 -0
- package/templates/react-custom-page/gitignore +1 -0
- package/templates/react-custom-page/index.html +16 -0
- package/templates/react-custom-page/package.json +21 -49
- package/templates/react-custom-page/src/App.tsx +26 -0
- package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
- package/templates/react-custom-page/src/core/recordContext.ts +51 -0
- package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
- package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
- package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
- package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
- package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
- package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
- package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
- package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
- package/templates/react-custom-page/src/domain/diff.ts +38 -0
- package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
- package/templates/react-custom-page/src/example/exampleError.ts +36 -0
- package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
- package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
- package/templates/react-custom-page/src/example/models/Account.ts +74 -0
- package/templates/react-custom-page/src/index.tsx +18 -128
- package/templates/react-custom-page/src/vite-env.d.ts +15 -0
- package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/react-custom-page/tsconfig.json +12 -22
- package/templates/react-custom-page/vite.config.ts +76 -0
- package/templates/starter-page/README.md +38 -0
- package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
- package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
- package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
- package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
- package/templates/starter-page/gitignore +5 -0
- package/templates/starter-page/package.json +27 -0
- package/templates/starter-page/public/index.html +11 -0
- package/templates/starter-page/src/index.tsx +10 -0
- package/templates/starter-page/src/services/dataverse.ts +30 -0
- package/templates/starter-page/tsconfig.json +15 -0
- package/templates/starter-page/webpack.config.js +17 -0
- package/templates/react-custom-page/deployment/README.md +0 -484
- package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
- package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
- package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
- package/templates/react-custom-page/public/index.html +0 -15
- package/templates/react-custom-page/scripts/custom-build.js +0 -255
- package/templates/react-custom-page/src/components/AccountForm.css +0 -71
- package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
- package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
- package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
- package/templates/react-custom-page/src/components/ContactForm.css +0 -48
- package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
- package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
- package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
- package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
- package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
- package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
- package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
- package/templates/react-custom-page/src/constants/account.ts +0 -410
- package/templates/react-custom-page/src/constants/contact.ts +0 -362
- package/templates/react-custom-page/src/models/Account.ts +0 -480
- package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
- package/templates/react-custom-page/src/models/Contact.ts +0 -580
- package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
- package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
- package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
- package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
- package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
- package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
- package/templates/react-custom-page/src/styles/index.css +0 -171
- package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
- package/templates/react-custom-page/webpack.config.js +0 -57
- /package/templates/_shared/dev-tools/auth/{get-token.js ā get-token.cjs} +0 -0
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
# Migration Guide - Dynamics 365 Template
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This guide helps you migrate existing Dynamics 365 projects to use the enhanced template patterns
|
|
6
|
-
and architecture.
|
|
7
|
-
|
|
8
|
-
## Migration Scenarios
|
|
9
|
-
|
|
10
|
-
### 1. From Basic React D365 Apps
|
|
11
|
-
|
|
12
|
-
If you have a basic React application for Dynamics 365:
|
|
13
|
-
|
|
14
|
-
#### Before (Basic Pattern)
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// Direct API calls without abstraction
|
|
18
|
-
function createAccount(data) {
|
|
19
|
-
return Xrm.WebApi.createRecord('account', data);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Component with inline API logic
|
|
23
|
-
function AccountForm() {
|
|
24
|
-
const handleSubmit = async () => {
|
|
25
|
-
try {
|
|
26
|
-
await Xrm.WebApi.createRecord('account', formData);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error(error);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
#### After (Enhanced Pattern)
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
// Entity model with validation and logging
|
|
38
|
-
export class Account extends BaseEntity {
|
|
39
|
-
public static async create(apiService: IApiService, account: Account): Promise<Account> {
|
|
40
|
-
return await this.createEntity<Account>(
|
|
41
|
-
apiService,
|
|
42
|
-
account,
|
|
43
|
-
AccountConstants.EntityCollectionName,
|
|
44
|
-
'Account.create'
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Component using service layer
|
|
50
|
-
function AccountForm() {
|
|
51
|
-
const { apiService } = useDynamicsApi();
|
|
52
|
-
|
|
53
|
-
const handleSubmit = async () => {
|
|
54
|
-
try {
|
|
55
|
-
const account = new Account(formData);
|
|
56
|
-
await Account.create(apiService, account);
|
|
57
|
-
Logger.userAction('Account created', { name: formData.name });
|
|
58
|
-
} catch (error) {
|
|
59
|
-
Logger.error('Failed to create account', 'AccountForm', error);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### Migration Steps
|
|
66
|
-
|
|
67
|
-
1. **Install the Template Dependencies**
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
npm install @khester/dynamics-ui-components @khester/dynamics-ui-api-client
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
2. **Create Entity Models**
|
|
74
|
-
- Copy `src/models/BaseEntity.ts` to your project
|
|
75
|
-
- Create entity models extending BaseEntity
|
|
76
|
-
- Add constants files for field definitions
|
|
77
|
-
|
|
78
|
-
3. **Implement Service Layer**
|
|
79
|
-
- Copy `src/services/ServiceFactory.ts`
|
|
80
|
-
- Copy API service implementations
|
|
81
|
-
- Update components to use the service layer
|
|
82
|
-
|
|
83
|
-
4. **Add Logging System**
|
|
84
|
-
- Copy `src/components/Logging/` directory
|
|
85
|
-
- Wrap your app with `LoggingProvider`
|
|
86
|
-
- Replace console calls with Logger methods
|
|
87
|
-
|
|
88
|
-
### 2. From PCF Controls to Template
|
|
89
|
-
|
|
90
|
-
If you're building PCF controls:
|
|
91
|
-
|
|
92
|
-
#### Before (Basic PCF)
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
export class MyControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
|
|
96
|
-
public init(context: ComponentFramework.Context<IInputs>): void {
|
|
97
|
-
// Direct DOM manipulation or basic React
|
|
98
|
-
this.container.innerHTML = '<div>Basic Content</div>';
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
#### After (Template PCF)
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
export class MyControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
|
|
107
|
-
public init(context: ComponentFramework.Context<IInputs>): void {
|
|
108
|
-
// Use sophisticated wrapper components
|
|
109
|
-
ReactDOM.render(
|
|
110
|
-
React.createElement(CustomPCFWrapper, {
|
|
111
|
-
context,
|
|
112
|
-
entityType: 'contact',
|
|
113
|
-
enableLogging: true,
|
|
114
|
-
showTabs: true,
|
|
115
|
-
}),
|
|
116
|
-
this.container
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### Migration Steps
|
|
123
|
-
|
|
124
|
-
1. **Copy PCF Wrapper Components**
|
|
125
|
-
- Copy `src/pcf/` directory to your PCF project
|
|
126
|
-
- Update your PCF control to use the wrappers
|
|
127
|
-
|
|
128
|
-
2. **Integrate with Entity Models**
|
|
129
|
-
- Copy entity models and constants
|
|
130
|
-
- Use the service factory for API access
|
|
131
|
-
|
|
132
|
-
3. **Update PCF Configuration**
|
|
133
|
-
- Update your `ControlManifest.Input.xml`
|
|
134
|
-
- Ensure React dependencies are included
|
|
135
|
-
|
|
136
|
-
### 3. From Web Resources to Template
|
|
137
|
-
|
|
138
|
-
If you're using traditional web resources:
|
|
139
|
-
|
|
140
|
-
#### Before (Traditional Web Resources)
|
|
141
|
-
|
|
142
|
-
```html
|
|
143
|
-
<!-- HTML Web Resource -->
|
|
144
|
-
<script src="/scripts/jquery.min.js"></script>
|
|
145
|
-
<script src="/scripts/custom.js"></script>
|
|
146
|
-
<div id="myApp">
|
|
147
|
-
<!-- Static HTML content -->
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<script>
|
|
151
|
-
// Direct Xrm calls
|
|
152
|
-
function loadAccounts() {
|
|
153
|
-
Xrm.WebApi.retrieveMultipleRecords('account', '?$select=name,accountid').then(
|
|
154
|
-
function (result) {
|
|
155
|
-
// Manual DOM manipulation
|
|
156
|
-
var html = '';
|
|
157
|
-
result.entities.forEach(function (account) {
|
|
158
|
-
html += '<div>' + account.name + '</div>';
|
|
159
|
-
});
|
|
160
|
-
document.getElementById('accountList').innerHTML = html;
|
|
161
|
-
}
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
</script>
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### After (Template Web Resources)
|
|
168
|
-
|
|
169
|
-
```html
|
|
170
|
-
<!-- Modern React Web Resource -->
|
|
171
|
-
<div id="root"></div>
|
|
172
|
-
<script src="/scripts/main.js"></script>
|
|
173
|
-
|
|
174
|
-
<!-- main.js contains the compiled React app -->
|
|
175
|
-
<script>
|
|
176
|
-
// Sophisticated React application with:
|
|
177
|
-
// - Entity models and validation
|
|
178
|
-
// - Service layer abstraction
|
|
179
|
-
// - Comprehensive logging
|
|
180
|
-
// - Error handling
|
|
181
|
-
// - Type safety
|
|
182
|
-
</script>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
#### Migration Steps
|
|
186
|
-
|
|
187
|
-
1. **Generate Template Application**
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
npx @khester/create-dynamics-app my-app --template dynamics-365
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
2. **Copy Existing Business Logic**
|
|
194
|
-
- Convert existing functions to entity model methods
|
|
195
|
-
- Move validation to model classes
|
|
196
|
-
- Replace direct DOM manipulation with React components
|
|
197
|
-
|
|
198
|
-
3. **Build and Deploy**
|
|
199
|
-
```bash
|
|
200
|
-
npm run build:prod
|
|
201
|
-
# Upload dist/main.js as Script (JScript) web resource
|
|
202
|
-
# Upload dist/index.html as Web Page (HTML) web resource
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## Best Practices for Migration
|
|
206
|
-
|
|
207
|
-
### 1. Incremental Migration
|
|
208
|
-
|
|
209
|
-
Don't migrate everything at once. Start with:
|
|
210
|
-
|
|
211
|
-
1. **Core Infrastructure**
|
|
212
|
-
- Service factory and API services
|
|
213
|
-
- Logging system
|
|
214
|
-
- Base entity class
|
|
215
|
-
|
|
216
|
-
2. **One Entity at a Time**
|
|
217
|
-
- Pick the most important entity
|
|
218
|
-
- Create model, constants, and components
|
|
219
|
-
- Test thoroughly before moving to next entity
|
|
220
|
-
|
|
221
|
-
3. **Component by Component**
|
|
222
|
-
- Replace existing forms with template components
|
|
223
|
-
- Update list views to use DetailsList patterns
|
|
224
|
-
- Add proper error handling and validation
|
|
225
|
-
|
|
226
|
-
### 2. Data Validation Migration
|
|
227
|
-
|
|
228
|
-
#### Before
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
function validateAccount(data) {
|
|
232
|
-
var errors = [];
|
|
233
|
-
if (!data.name) {
|
|
234
|
-
errors.push('Name is required');
|
|
235
|
-
}
|
|
236
|
-
return errors;
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
#### After
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
export class Account extends BaseEntity {
|
|
244
|
-
public validate(): boolean {
|
|
245
|
-
const errors: string[] = [];
|
|
246
|
-
|
|
247
|
-
if (!this.name?.trim()) {
|
|
248
|
-
errors.push('Account name is required');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (this.name && this.name.length > 160) {
|
|
252
|
-
errors.push('Account name cannot exceed 160 characters');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (errors.length > 0) {
|
|
256
|
-
Logger.validation('Account', errors, 'Account.validate');
|
|
257
|
-
throw new Error(`Validation failed: ${errors.join(', ')}`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### 3. Error Handling Migration
|
|
266
|
-
|
|
267
|
-
#### Before
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
try {
|
|
271
|
-
await Xrm.WebApi.createRecord('account', data);
|
|
272
|
-
alert('Success');
|
|
273
|
-
} catch (error) {
|
|
274
|
-
alert('Error: ' + error.message);
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
#### After
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
try {
|
|
282
|
-
const account = new Account(data);
|
|
283
|
-
const result = await Account.create(apiService, account);
|
|
284
|
-
Logger.userAction('Account created successfully', { accountId: result.accountid });
|
|
285
|
-
// Use proper UI notifications instead of alerts
|
|
286
|
-
} catch (error) {
|
|
287
|
-
Logger.error('Failed to create account', 'AccountForm.handleSubmit', error);
|
|
288
|
-
// Show user-friendly error message
|
|
289
|
-
setErrorMessage('Unable to create account. Please try again.');
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### 4. Environment Detection Migration
|
|
294
|
-
|
|
295
|
-
#### Before
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
// Hard-coded environment logic
|
|
299
|
-
if (window.location.hostname === 'localhost') {
|
|
300
|
-
// Use mock data
|
|
301
|
-
} else {
|
|
302
|
-
// Use Xrm.WebApi
|
|
303
|
-
}
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
#### After
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
// Sophisticated environment detection
|
|
310
|
-
const apiService = ServiceFactory.createApiService();
|
|
311
|
-
// Automatically detects environment and provides appropriate service
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## Common Migration Challenges
|
|
315
|
-
|
|
316
|
-
### 1. TypeScript Adoption
|
|
317
|
-
|
|
318
|
-
**Challenge**: Existing JavaScript code needs TypeScript conversion
|
|
319
|
-
|
|
320
|
-
**Solution**:
|
|
321
|
-
|
|
322
|
-
1. Start with `.ts` files and `any` types
|
|
323
|
-
2. Gradually add proper type definitions
|
|
324
|
-
3. Use the template's interface definitions as examples
|
|
325
|
-
|
|
326
|
-
### 2. Bundle Size Concerns
|
|
327
|
-
|
|
328
|
-
**Challenge**: React app may be larger than traditional web resources
|
|
329
|
-
|
|
330
|
-
**Solution**:
|
|
331
|
-
|
|
332
|
-
- Use the template's optimized build configuration
|
|
333
|
-
- Production build is only ~726KB
|
|
334
|
-
- Enable compression in IIS for web resources
|
|
335
|
-
- Consider code splitting for large applications
|
|
336
|
-
|
|
337
|
-
### 3. PCF Framework Limitations
|
|
338
|
-
|
|
339
|
-
**Challenge**: PCF has restrictions on external dependencies
|
|
340
|
-
|
|
341
|
-
**Solution**:
|
|
342
|
-
|
|
343
|
-
- Use the template's PCF wrapper patterns
|
|
344
|
-
- Bundle dependencies in the control
|
|
345
|
-
- Use the provided ComponentFramework type definitions
|
|
346
|
-
|
|
347
|
-
### 4. Dynamics 365 API Differences
|
|
348
|
-
|
|
349
|
-
**Challenge**: Different API behaviors between environments
|
|
350
|
-
|
|
351
|
-
**Solution**:
|
|
352
|
-
|
|
353
|
-
- Use the ServiceFactory to abstract differences
|
|
354
|
-
- Implement environment-specific logic in service classes
|
|
355
|
-
- Test thoroughly in both development and production
|
|
356
|
-
|
|
357
|
-
## Migration Checklist
|
|
358
|
-
|
|
359
|
-
### Pre-Migration Assessment
|
|
360
|
-
|
|
361
|
-
- [ ] Inventory existing entities and operations
|
|
362
|
-
- [ ] Identify current validation logic
|
|
363
|
-
- [ ] Document existing API usage patterns
|
|
364
|
-
- [ ] Assess current error handling approaches
|
|
365
|
-
|
|
366
|
-
### Migration Implementation
|
|
367
|
-
|
|
368
|
-
- [ ] Set up development environment with template
|
|
369
|
-
- [ ] Create entity models for core entities
|
|
370
|
-
- [ ] Implement service layer abstraction
|
|
371
|
-
- [ ] Add logging throughout application
|
|
372
|
-
- [ ] Create management components
|
|
373
|
-
- [ ] Implement proper error handling
|
|
374
|
-
- [ ] Add comprehensive validation
|
|
375
|
-
|
|
376
|
-
### Testing and Validation
|
|
377
|
-
|
|
378
|
-
- [ ] Test in development environment
|
|
379
|
-
- [ ] Validate all CRUD operations
|
|
380
|
-
- [ ] Test error scenarios
|
|
381
|
-
- [ ] Verify logging functionality
|
|
382
|
-
- [ ] Test build and deployment process
|
|
383
|
-
- [ ] User acceptance testing
|
|
384
|
-
|
|
385
|
-
### Production Deployment
|
|
386
|
-
|
|
387
|
-
- [ ] Build production bundle
|
|
388
|
-
- [ ] Upload web resources to D365
|
|
389
|
-
- [ ] Configure security roles
|
|
390
|
-
- [ ] Train users on new interface
|
|
391
|
-
- [ ] Monitor for issues
|
|
392
|
-
|
|
393
|
-
## Support and Resources
|
|
394
|
-
|
|
395
|
-
### Template Resources
|
|
396
|
-
|
|
397
|
-
- **Documentation**: README.md in template root
|
|
398
|
-
- **Examples**: `/src/examples/` directory
|
|
399
|
-
- **Integration Tests**: INTEGRATION_TEST_RESULTS.md
|
|
400
|
-
|
|
401
|
-
### Development Tools
|
|
402
|
-
|
|
403
|
-
- **Quality Scripts**: `npm run quality`
|
|
404
|
-
- **Build Validation**: `npm run validate`
|
|
405
|
-
- **Type Checking**: `npm run typecheck`
|
|
406
|
-
|
|
407
|
-
### Community and Support
|
|
408
|
-
|
|
409
|
-
- **GitHub Issues**: Report problems or ask questions
|
|
410
|
-
- **Documentation**: Comprehensive inline documentation
|
|
411
|
-
- **Examples**: Real-world implementation patterns
|
|
412
|
-
|
|
413
|
-
## Version Compatibility
|
|
414
|
-
|
|
415
|
-
### Dynamics 365 Versions
|
|
416
|
-
|
|
417
|
-
- **Online**: Fully supported
|
|
418
|
-
- **On-Premises**: Compatible with modern browsers
|
|
419
|
-
- **Government Cloud**: Supported
|
|
420
|
-
|
|
421
|
-
### Browser Support
|
|
422
|
-
|
|
423
|
-
- **Chrome**: 70+
|
|
424
|
-
- **Edge**: 79+
|
|
425
|
-
- **Firefox**: 65+
|
|
426
|
-
- **Safari**: 12+
|
|
427
|
-
|
|
428
|
-
### Node.js Versions
|
|
429
|
-
|
|
430
|
-
- **Minimum**: Node 16+
|
|
431
|
-
- **Recommended**: Node 18+
|
|
432
|
-
- **Latest**: Node 20+
|
|
433
|
-
|
|
434
|
-
## Conclusion
|
|
435
|
-
|
|
436
|
-
Migrating to the enhanced Dynamics 365 template provides significant benefits:
|
|
437
|
-
|
|
438
|
-
- **Type Safety**: Full TypeScript support
|
|
439
|
-
- **Error Handling**: Comprehensive error management
|
|
440
|
-
- **Logging**: Detailed operation tracking
|
|
441
|
-
- **Validation**: Client-side and business rule validation
|
|
442
|
-
- **Performance**: Optimized build process
|
|
443
|
-
- **Maintainability**: Clean architecture patterns
|
|
444
|
-
- **Developer Experience**: Modern tooling and documentation
|
|
445
|
-
|
|
446
|
-
The migration process, while requiring initial effort, results in more robust, maintainable, and
|
|
447
|
-
scalable Dynamics 365 applications.
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<link rel="icon" href="data:," />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
-
<meta name="theme-color" content="#000000" />
|
|
8
|
-
<meta name="description" content="Dynamics 365 Application built with Dynamics UI Kit" />
|
|
9
|
-
<title>Dynamics 365 App - Dynamics UI Kit</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
13
|
-
<div id="root"></div>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
const webpack = require('webpack');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
// Get webpack config
|
|
6
|
-
const webpackConfigPath = path.resolve(__dirname, '../webpack.config.js');
|
|
7
|
-
const getWebpackConfig = require(webpackConfigPath);
|
|
8
|
-
|
|
9
|
-
// Check command line arguments for dev mode
|
|
10
|
-
const isDev = process.argv.includes('--dev') || process.argv.includes('-dev');
|
|
11
|
-
const mode = isDev ? 'development' : 'production';
|
|
12
|
-
|
|
13
|
-
console.log(`Custom Build Script - Mode: ${mode}`);
|
|
14
|
-
|
|
15
|
-
// Create custom webpack config for D365 deployment
|
|
16
|
-
function createD365WebpackConfig() {
|
|
17
|
-
// Get base config
|
|
18
|
-
const baseConfig = getWebpackConfig({}, { mode });
|
|
19
|
-
|
|
20
|
-
// D365-specific optimizations
|
|
21
|
-
const d365Config = {
|
|
22
|
-
...baseConfig,
|
|
23
|
-
mode,
|
|
24
|
-
optimization: {
|
|
25
|
-
// Disable code splitting for single file deployment
|
|
26
|
-
splitChunks: {
|
|
27
|
-
cacheGroups: {
|
|
28
|
-
default: false,
|
|
29
|
-
vendors: false,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
// Disable runtime chunk
|
|
33
|
-
runtimeChunk: false,
|
|
34
|
-
// Control minimization based on mode
|
|
35
|
-
minimize: !isDev,
|
|
36
|
-
},
|
|
37
|
-
output: {
|
|
38
|
-
...baseConfig.output,
|
|
39
|
-
// Remove hashes for consistent D365 web resource naming
|
|
40
|
-
filename: '[name].js',
|
|
41
|
-
chunkFilename: '[name].chunk.js',
|
|
42
|
-
assetModuleFilename: '[name][ext]',
|
|
43
|
-
// Set public path for D365 context
|
|
44
|
-
publicPath: './',
|
|
45
|
-
},
|
|
46
|
-
// Add performance hints for large bundles
|
|
47
|
-
performance: {
|
|
48
|
-
hints: isDev ? false : 'warning',
|
|
49
|
-
maxEntrypointSize: 2000000, // 2MB - reasonable for D365
|
|
50
|
-
maxAssetSize: 2000000,
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Modify CSS handling for single file output
|
|
55
|
-
const rules = d365Config.module.rules.map((rule) => {
|
|
56
|
-
if (rule.test && rule.test.toString().includes('css')) {
|
|
57
|
-
return {
|
|
58
|
-
...rule,
|
|
59
|
-
use: [
|
|
60
|
-
'style-loader', // Inline CSS instead of separate files
|
|
61
|
-
'css-loader',
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return rule;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
d365Config.module.rules = rules;
|
|
69
|
-
|
|
70
|
-
// Modify HTML plugin for D365
|
|
71
|
-
const htmlPluginIndex = d365Config.plugins.findIndex(
|
|
72
|
-
(plugin) => plugin.constructor.name === 'HtmlWebpackPlugin'
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
if (htmlPluginIndex !== -1) {
|
|
76
|
-
d365Config.plugins[htmlPluginIndex] = new (require('html-webpack-plugin'))({
|
|
77
|
-
template: './public/index.html',
|
|
78
|
-
filename: 'index.html',
|
|
79
|
-
inject: 'body',
|
|
80
|
-
// Inline all assets for single-file deployment
|
|
81
|
-
inlineSource: isDev ? false : '.(js|css)$',
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Add banner for D365 deployment info
|
|
86
|
-
d365Config.plugins.push(
|
|
87
|
-
new webpack.BannerPlugin({
|
|
88
|
-
banner: `
|
|
89
|
-
Dynamics 365 Custom Page Bundle
|
|
90
|
-
Built: ${new Date().toISOString()}
|
|
91
|
-
Mode: ${mode}
|
|
92
|
-
Environment: ${isDev ? 'Development' : 'Production'}
|
|
93
|
-
Generated with Dynamics UI Kit
|
|
94
|
-
`.trim(),
|
|
95
|
-
raw: false,
|
|
96
|
-
})
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Add define plugin for environment variables
|
|
100
|
-
d365Config.plugins.push(
|
|
101
|
-
new webpack.DefinePlugin({
|
|
102
|
-
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
103
|
-
'process.env.D365_BUILD': JSON.stringify(true),
|
|
104
|
-
'process.env.BUILD_MODE': JSON.stringify(mode),
|
|
105
|
-
})
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
return d365Config;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Build function
|
|
112
|
-
function build() {
|
|
113
|
-
const config = createD365WebpackConfig();
|
|
114
|
-
|
|
115
|
-
console.log('Starting D365-optimized build...');
|
|
116
|
-
console.log(`Output directory: ${config.output.path}`);
|
|
117
|
-
console.log(
|
|
118
|
-
`Bundle splitting: ${config.optimization.splitChunks ? 'enabled' : 'disabled'}`
|
|
119
|
-
);
|
|
120
|
-
console.log(`Minimization: ${config.optimization.minimize}`);
|
|
121
|
-
|
|
122
|
-
const compiler = webpack(config);
|
|
123
|
-
|
|
124
|
-
compiler.run((err, stats) => {
|
|
125
|
-
if (err) {
|
|
126
|
-
console.error('Build failed with error:', err);
|
|
127
|
-
process.exit(1);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (stats.hasErrors()) {
|
|
131
|
-
console.error('Build completed with errors:');
|
|
132
|
-
console.error(
|
|
133
|
-
stats.toString({
|
|
134
|
-
colors: true,
|
|
135
|
-
chunks: false,
|
|
136
|
-
modules: false,
|
|
137
|
-
errorDetails: true,
|
|
138
|
-
})
|
|
139
|
-
);
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (stats.hasWarnings()) {
|
|
144
|
-
console.warn('Build completed with warnings:');
|
|
145
|
-
console.warn(
|
|
146
|
-
stats.toString({
|
|
147
|
-
colors: true,
|
|
148
|
-
chunks: false,
|
|
149
|
-
modules: false,
|
|
150
|
-
warningsFilter: /export.*was not found in/,
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
console.log('Build completed successfully!');
|
|
156
|
-
console.log(
|
|
157
|
-
stats.toString({
|
|
158
|
-
colors: true,
|
|
159
|
-
chunks: false,
|
|
160
|
-
modules: false,
|
|
161
|
-
assets: true,
|
|
162
|
-
performance: true,
|
|
163
|
-
})
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
// Generate deployment info
|
|
167
|
-
generateDeploymentInfo(stats, config);
|
|
168
|
-
|
|
169
|
-
compiler.close((closeErr) => {
|
|
170
|
-
if (closeErr) {
|
|
171
|
-
console.error('Error closing compiler:', closeErr);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Generate deployment information file
|
|
178
|
-
function generateDeploymentInfo(stats, config) {
|
|
179
|
-
const distPath = config.output.path;
|
|
180
|
-
const deploymentInfo = {
|
|
181
|
-
buildTime: new Date().toISOString(),
|
|
182
|
-
mode: config.mode,
|
|
183
|
-
isDevelopment: isDev,
|
|
184
|
-
assets: [],
|
|
185
|
-
entrypoints: {},
|
|
186
|
-
performance: {
|
|
187
|
-
totalSize: 0,
|
|
188
|
-
maxAssetSize: config.performance?.maxAssetSize || 'unlimited',
|
|
189
|
-
maxEntrypointSize: config.performance?.maxEntrypointSize || 'unlimited',
|
|
190
|
-
},
|
|
191
|
-
d365Integration: {
|
|
192
|
-
singleFileDeployment: !config.optimization.splitChunks,
|
|
193
|
-
webResourceReady: true,
|
|
194
|
-
customPageReady: true,
|
|
195
|
-
pcfReady: true,
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const compilation = stats.compilation;
|
|
200
|
-
|
|
201
|
-
// Collect asset information
|
|
202
|
-
Object.keys(compilation.assets).forEach((assetName) => {
|
|
203
|
-
const asset = compilation.assets[assetName];
|
|
204
|
-
const assetInfo = {
|
|
205
|
-
name: assetName,
|
|
206
|
-
size: asset.size(),
|
|
207
|
-
type: path.extname(assetName) || 'unknown',
|
|
208
|
-
};
|
|
209
|
-
deploymentInfo.assets.push(assetInfo);
|
|
210
|
-
deploymentInfo.performance.totalSize += assetInfo.size;
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Collect entrypoint information
|
|
214
|
-
if (compilation.entrypoints) {
|
|
215
|
-
compilation.entrypoints.forEach((entrypoint, entryName) => {
|
|
216
|
-
const files = entrypoint.getFiles ? entrypoint.getFiles() : [];
|
|
217
|
-
deploymentInfo.entrypoints[entryName] = {
|
|
218
|
-
assets: files,
|
|
219
|
-
size: files.reduce((total, fileName) => {
|
|
220
|
-
const asset = compilation.assets[fileName];
|
|
221
|
-
return total + (asset ? asset.size() : 0);
|
|
222
|
-
}, 0),
|
|
223
|
-
};
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Write deployment info file
|
|
228
|
-
const deploymentInfoPath = path.join(distPath, 'deployment-info.json');
|
|
229
|
-
fs.writeFileSync(deploymentInfoPath, JSON.stringify(deploymentInfo, null, 2));
|
|
230
|
-
|
|
231
|
-
console.log('\nš Deployment Information:');
|
|
232
|
-
console.log(
|
|
233
|
-
` Total bundle size: ${(deploymentInfo.performance.totalSize / 1024).toFixed(2)} KB`
|
|
234
|
-
);
|
|
235
|
-
console.log(` Assets generated: ${deploymentInfo.assets.length}`);
|
|
236
|
-
console.log(` Deployment info saved: ${deploymentInfoPath}`);
|
|
237
|
-
|
|
238
|
-
// D365 deployment recommendations
|
|
239
|
-
console.log('\nš D365 Deployment Guidelines:');
|
|
240
|
-
console.log(' 1. Upload JS files as Script (JScript) web resources');
|
|
241
|
-
console.log(' 2. Upload CSS files as Style Sheet (CSS) web resources');
|
|
242
|
-
console.log(' 3. Upload HTML file as Web Page (HTML) web resource');
|
|
243
|
-
console.log(' 4. Set "Available for Dynamics 365 mobile" if needed');
|
|
244
|
-
console.log(' 5. Publish all web resources after upload');
|
|
245
|
-
|
|
246
|
-
if (deploymentInfo.performance.totalSize > 1000000) {
|
|
247
|
-
console.log('\nā ļø Warning: Bundle size is large (>1MB). Consider:');
|
|
248
|
-
console.log(' - Using code splitting for better performance');
|
|
249
|
-
console.log(' - Removing unused dependencies');
|
|
250
|
-
console.log(' - Enabling compression in D365');
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Start the build
|
|
255
|
-
build();
|