@khester/create-dynamics-app 2.1.0 → 2.3.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.
Files changed (122) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +122 -12
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-dataset/package.json +3 -1
  38. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  41. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  42. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  43. package/templates/pcf-field/index.ts +1 -1
  44. package/templates/pcf-field/package.json +3 -1
  45. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  46. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  47. package/templates/react-custom-page/README.md +74 -568
  48. package/templates/react-custom-page/env.example +16 -0
  49. package/templates/react-custom-page/gitignore +1 -0
  50. package/templates/react-custom-page/index.html +16 -0
  51. package/templates/react-custom-page/package.json +21 -49
  52. package/templates/react-custom-page/src/App.tsx +26 -0
  53. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  54. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  55. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  56. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  57. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  58. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  59. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  60. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  61. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  62. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  63. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  64. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  65. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  67. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  69. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  70. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  71. package/templates/react-custom-page/src/index.tsx +18 -128
  72. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  73. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  74. package/templates/react-custom-page/tsconfig.json +12 -22
  75. package/templates/react-custom-page/vite.config.ts +76 -0
  76. package/templates/starter-page/README.md +38 -0
  77. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  78. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  79. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  80. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  81. package/templates/starter-page/gitignore +5 -0
  82. package/templates/starter-page/package.json +27 -0
  83. package/templates/starter-page/public/index.html +11 -0
  84. package/templates/starter-page/src/index.tsx +10 -0
  85. package/templates/starter-page/src/services/dataverse.ts +30 -0
  86. package/templates/starter-page/tsconfig.json +15 -0
  87. package/templates/starter-page/webpack.config.js +17 -0
  88. package/templates/react-custom-page/deployment/README.md +0 -484
  89. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  90. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  91. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  92. package/templates/react-custom-page/public/index.html +0 -15
  93. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  94. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  95. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  96. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  97. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  98. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  99. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  100. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  101. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  102. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  103. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  105. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  106. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  107. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  108. package/templates/react-custom-page/src/constants/account.ts +0 -410
  109. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  110. package/templates/react-custom-page/src/models/Account.ts +0 -480
  111. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  112. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  113. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  114. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  115. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  116. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  117. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  118. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  119. package/templates/react-custom-page/src/styles/index.css +0 -171
  120. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  121. package/templates/react-custom-page/webpack.config.js +0 -57
  122. /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();