polylith 0.1.52 → 0.1.53

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 (2) hide show
  1. package/README.md +759 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,759 @@
1
+ # Polylith
2
+
3
+ Polylith is a lightweight framework that focuses on the overall shape of
4
+ the application and the building and serving of it. This framework does not
5
+ provide any UI code, or even mandate any particular architecture. It just
6
+ facilitates extreme decoupling of code through services and features, which
7
+ will be explained below.
8
+
9
+ ## Rollup vs Webpack
10
+
11
+ Because of the specific requirements of building the application,
12
+ [Rollup](https://www.npmjs.com/package/rollup) was chosen as the core of the
13
+ build system rather than Webpack. These requirements include the creation of
14
+ synthetic modules and the automatic creation of chunks based on
15
+ dependencies shared between files that can be loaded dynamically. It also
16
+ does tree shaking to remove unused code.
17
+
18
+ The configuration of a build is very high-level, focused on how the
19
+ application is constructed more than the technology it uses. A build can be
20
+ specified as either a .json file, or though code.
21
+
22
+ ## Command Line Interface
23
+
24
+ There is a command line interface for building and running the application.
25
+ This can be installed globally to be run from the command line, and locally
26
+ to be accessed through npm scripts. To access this from the command line
27
+ install the module polylith globally, like this:
28
+
29
+ `npm install -g polylith`
30
+
31
+ Running the command polylith with no parameters will provide a description
32
+ of how to use it. However, most of the most common commands needed for
33
+ developing and running can be defined as scripts in the package.json file.
34
+
35
+ ## jsconfig.json
36
+
37
+ Polylith will use the jsconfig.json file to find the location of imported
38
+ files. This is directly compatible with the format VS Code uses. This is
39
+ a commong definition of the jsconfig.json file.
40
+
41
+ ```json
42
+ {
43
+ "compilerOptions": {
44
+ "module": "commonjs",
45
+ "target": "es6",
46
+ "baseUrl": "src/",
47
+ "paths": {
48
+ "*": ["*", "features/*", "components/*", "images/*", "icons/*",
49
+ "common/*"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+ ## polylith.json
55
+
56
+ A single repository can define multiple applications that will be built
57
+ into the distribution and accessed with unique URLs once deployed. This
58
+ information and the base location of various directories such as the source
59
+ and distribution directory are set up in the polylith.json file. This file
60
+ should not need to be updated.
61
+
62
+ ## Services
63
+
64
+ Services are the core of the new architecture, and the code necessary to
65
+ implement and use them is defined in the module @polylith/core. This module
66
+ exports four things: Service, Registry, registry, makeEventable.
67
+
68
+ ### ServiceObject
69
+
70
+ An instance of ServiceObject is the external representation of a service as
71
+ retrieved from the registry. An object of this type is a different than the
72
+ implementation of the service, which extends the class Service.
73
+ ServiceObject is not exported from the core and cannot be directly created.
74
+ The base of a ServiceObject is an EventBus. Use the methods, listen,
75
+ unlisten and firefor handling events from the service object. In principle,
76
+ all methods on a service are implemented as events, although for
77
+ performance reasons, under most circumstances, they will also be added as
78
+ function references on the service object. These methods will be described
79
+ below.
80
+
81
+ ### Service
82
+
83
+ Implementations of a service extend the class Service. This class only has
84
+ two methods, the constructor and the method implement. However, the methods
85
+ listen, unlisten and fire will be added to the instance when the service is
86
+ constructed.
87
+
88
+ #### constructor
89
+ `constructor (name, overrideRegistry)`
90
+
91
+ - `name` is the name that will be used to retrieve this service from the
92
+ registry. If there is no name, then the service will not be added to the
93
+ registry. To get the service object from an unregistered service use the
94
+ member this.serviceObject. The name of this service is available in the
95
+ member this.serviceName.
96
+ - `overrideRegistry` is optional. If passed, this parameter will be used as
97
+ the registry associated with this instance. Otherwise, it will default to
98
+ the global registry instance. Accessing the registry should always be done
99
+ with the member this.registry.
100
+
101
+ ### implement
102
+
103
+ `implement (names)`
104
+
105
+ Call this method to add methods to the service object.
106
+
107
+ - `names` This parameter is an array of strings. These strings are the names
108
+ of methods on this service that should be exposed as functions on the
109
+ service object. These functions will be added as members on the service
110
+ object bound to this. These functions will also be added as events.
111
+
112
+ ### listen
113
+
114
+ `listen (eventName, cb)`
115
+
116
+ Call this method to listen for events on the service object. This method is
117
+ added to this service instance and is a reference to the listen method on
118
+ the service object. There can be multiple calls to listen, and each
119
+ listener will be called in the order it was added.
120
+
121
+ - `eventName` this is a string that is the name of the event to listen for.
122
+ - `cb` this is the function to call when the event is fired.
123
+
124
+ **returns**
125
+
126
+ This returns an opaque value which is a reference to this particular
127
+ listener. This reference will be used to when calling unlisten.
128
+
129
+ ### unlisten
130
+
131
+ `unlisten(eventName, listenerId)`
132
+
133
+ Call this method to remove an event listener. This method is added to this
134
+ service instance and is a reference to the listen method on the service
135
+ object.
136
+
137
+ - `eventName` the name of the event to stop listening to.
138
+ - `listenerId` the value returned from the listen method.
139
+
140
+ ### fire
141
+
142
+ `fire(eventName, ...args)`
143
+
144
+ Call this method to fire an event on the service object. This method is
145
+ added to this service instance and a is a reference to the fire method of
146
+ the service object.
147
+
148
+ - `eventName` this string is the name of the event to fire.
149
+ - `...args` all other parameters passed to the fire method are passed to the
150
+ event listeners.
151
+
152
+ **returns**
153
+
154
+ If any of the listeners return a promise, so will the fire method. This
155
+ promise will resolve when all promises returned from all listeners have
156
+ resolved. If there are no promise results from any of the listeners, it
157
+ will return with the value of the first listener that returned a result
158
+ other than undefined. If it returns a promise, that promise will be
159
+ resolved with the value of the first listener that returned or resolved
160
+ with a result. This value will be checked in the order the listeners were
161
+ added.
162
+
163
+ ## Registry
164
+
165
+ The registry class manages a list of service objects. There is a global
166
+ instance of this class called registry, but it can also be instantiated for
167
+ use in unit tests.
168
+
169
+ ### constructor
170
+
171
+ `constructor ()`
172
+
173
+ The constructor for the registry, it takes no parameters.
174
+
175
+ ### subscribe
176
+
177
+ `subscribe(name)`
178
+
179
+ Call this method to get a service. This will be an instance of the
180
+ ServiceObject class.
181
+
182
+ - `name` the name of the service.
183
+
184
+ **returns**
185
+
186
+ The service object, or false if it was not found.
187
+
188
+ ### start
189
+
190
+ `async start(prefix = '')`
191
+
192
+ Call this method to initiate the startup life cycle of the registered
193
+ services. This is an asynchronous method and should be called with await.
194
+
195
+ - `prefix` if defined it will only run the lifecycle methods on services with
196
+ names that start with that prefix.
197
+
198
+ ## registry
199
+
200
+ This variable is exported from the @polylith/core module and is a reference
201
+ to the global registry. In most cases, access to the registry will be done
202
+ through the this.registry member of a service. Accessing the registry in a
203
+ component will be done using a [React Context object](https://reactjs.org/docs/context.html).
204
+
205
+ ## makeEventable
206
+
207
+ `export function makeEventable(obj)`
208
+
209
+ Use this function to add the event methods listen, unlisten and fire to any
210
+ object. The implementation of this function creates a new EventBus object
211
+ and adds it to the object as eventBus. It then adds the event methods from
212
+ that bus to the object. This function has these parameters:
213
+
214
+ - `obj` the object to add the event methods to.
215
+
216
+ ## Service Lifecycle
217
+
218
+ Two methods will be called on every service when the start method has been
219
+ called on a Registry instance.
220
+
221
+ ### start
222
+
223
+ `start()`
224
+
225
+ If this method is implemented on the service, it will be called first. This
226
+ can be an asynchronous method. The start method will be called on all
227
+ services asynchronously and all must complete before the next lifecycle
228
+ step.
229
+
230
+ **returns**
231
+
232
+ If this method returns a promise, then the start step of the lifecycle will
233
+ wait until that promise has been resolved or rejected. This method will be
234
+ called on each service in the order they were registered.
235
+
236
+ ### ready
237
+
238
+ `ready()`
239
+
240
+ If this method is implemented on the service, it will be called
241
+ synchronously after the start methods have completed on all registered
242
+ services. When this method is called all other services must be ready to be
243
+ used. This method will be called on each service in the order they were
244
+ registered.
245
+
246
+ ## Example
247
+
248
+ This is an example of a service from
249
+ [Polylith Game Center](https://github.com/Ondoher/poly-gc-react) application.
250
+
251
+ ```javascript
252
+ import {Service} from '@polylith/core';
253
+ import {load} from '@polylith/loader';
254
+
255
+ export default class LoaderService extends Service {
256
+ constructor (service) {
257
+ super('mahjongg-loader', service);
258
+ this.implement(['ready', 'clicked']);
259
+ }
260
+
261
+ ready() {
262
+ this.directory = this.registry.subscribe('directory');
263
+ this.directory.add({image: 'images/mj/mj-tile.jpg', name: 'Mah Jongg', serviceName: 'mahjongg-loader'});
264
+ this.pagesService = this.registry.subscribe('main-pages');
265
+ }
266
+
267
+ async clicked() {
268
+ await load('mahjongg');
269
+ this.pagesService.add('mahjongg', 'mj:controller');
270
+ this.pagesService.show('mahjongg');
271
+ }
272
+ }
273
+
274
+ new LoaderService();
275
+ ```
276
+
277
+ ## Application
278
+
279
+ The definition of an application can be setup by either extending an
280
+ instance of the Application class, or within a config file. The parts of an
281
+ application that can specified and the methods to access them are defined below.
282
+
283
+ ### Basic Setup
284
+
285
+ The basic setup of an application contains this information: name, index,
286
+ application template and various directories. This information is provided
287
+ in the constructor of an application.
288
+
289
+ `constructor(name, config)`
290
+
291
+ The constructor of the Application class takes these parameters:
292
+
293
+ - `name` this is a string that identifies this application. It does not
294
+ represent a human readable name, just a short code to distinguish it from
295
+ other potential apps.
296
+ - `config` this is an object that specifies the rest of the basic information
297
+ for an application. This is the only required information for an
298
+ application, all other settings are optional.
299
+ - `root` this the absolute path to the project directory. All other paths
300
+ are relative to this one, or relative to another path derived from this
301
+ path. So, for instance, the name of the index file might be specified as
302
+ "src/index.js". When deriving this in code, it is important not to hardcode
303
+ this path to a specific developer's machine but derive it relative to the
304
+ location of the build file. For example:
305
+ ```javascript
306
+ var projectPath = path.join(path.dirname(import.meta.url), '../');
307
+ ```
308
+ - `index` this is the entry point of the application. All the code that is
309
+ bunded for the application follows from the imports on this entry point, as
310
+ well as the features, which are described below.
311
+ - `spec` this optional string is the entry point for the tests. Like the
312
+ index, all the code that will be bundled for the tests follows from the
313
+ imports here, and the tests defined for features.
314
+ - `dest` this specifies the base directory where the bundled files are
315
+ copied. When running the application this will be the root directory.
316
+ - `testDest` this optional string specifies the directory where the bundled
317
+ files for testing are placed. When running the tests, this will be the root
318
+ directory.
319
+ - `template` this object specifies the html file for the application. This
320
+ contains placeholders for where in the html file to place certain generated
321
+ html elements, such as the scripts and css files. This template will be
322
+ expanded on below. This object has these two fields:
323
+ - `source` this is the location of the source template html file.
324
+ - `destination` this is where the html file should be placed in the
325
+ destination directory.
326
+
327
+ ### Templates
328
+
329
+ A template defines the html file that will be served to the browser. This
330
+ file contains placeholders for content that is generated during the build
331
+ process. These placeholders take the form ${\<name\>}. There are currently
332
+ two placeholders defined:
333
+
334
+ - `mainCss` this is where the link tags for the style sheets will be placed.
335
+ This should be placed inside the document head. The urls for these links
336
+ are specified by the resource definition for the CSS files. This is
337
+ described below.
338
+ - `scripts` this is where the top-level chunks for the application are placed
339
+ as script tags.
340
+
341
+ This is a sample html template:
342
+ ```html
343
+ <html>
344
+ <head>
345
+ <meta http-equiv="Content-Type" content="text/html;UTF-8">
346
+ <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=false"/>
347
+ ${mainCss}
348
+ </head>
349
+ <body>
350
+ <div id="dialog-overlay"></div>
351
+ <div id="dialogs"></div>
352
+ <div id="main-content" class="main-content"></div>
353
+ ${scripts}
354
+ </body>
355
+ </html>
356
+ ```
357
+ ### Resources
358
+
359
+ A resource specifier defines a set of files that are not code that should
360
+ be to be moved to the application's destination directory, how to find
361
+ them, and where to put them. This is an object that contains these fields:
362
+
363
+ - `dest` this is a relative path in the destination folder where the
364
+ specified resourced should be copied.
365
+ - `cwd` this specifies the root directory where the searching should begin.
366
+ The directory here will be the basis for the directory for the copied files.
367
+ - `glob` this is a [glob](https://www.npmjs.com/package/fast-glob) that is
368
+ used to search for matching files.
369
+ - `keepNest` if true, the files found here will be moved to the destination
370
+ folder maintaining their relative path from the directory specified by cwd.
371
+ Otherwise, they are each copied directly to the path specified by dest.
372
+
373
+ An application maintains an array of these resource specifiers.
374
+
375
+ #### addResources
376
+
377
+ `addResources(root, resourceSpecs)`
378
+
379
+ Call this method of the Application object to add an array of resources to
380
+ the application. This method can be called multiple times. This method
381
+ takes two parameters:
382
+
383
+ - `root` this specifies the root of the resource spec. The cwd fields given
384
+ in the resource specifiers will be relative to this path. For the
385
+ application, this would usually be an empty string. For a feature it would
386
+ be the root of the feature. Features are described below.
387
+ - `resourceSpecs` this is an array of resource specifiers.
388
+
389
+ ### CSS
390
+
391
+ CSS files are a special type of resource. These files will be loaded into
392
+ the html file with a link tag of type "stylesheet". When CSS files are
393
+ added to the application they will also be added as resources to be copied
394
+ to the destination directory.
395
+
396
+ #### addMainCss
397
+
398
+ `addMainCss(root, specs)`
399
+
400
+ Call this method of the Application object to add CSS files to be loaded
401
+ with the application. This method can be called multiple times. This method
402
+ takes two parameters:
403
+
404
+ - `root` this specifies the root of the resource spec. The cwd fields given
405
+ in the resource specifiers will be relative to this path. For the
406
+ application, this would usually be an empty string. For a feature it would
407
+ be the root of the feature. Features are described below.
408
+ - `resourceSpecs` this is an array of resource specifiers.
409
+
410
+ ### Config
411
+
412
+ An application build can add data that will become accessible to the
413
+ application code using the get function exported from the @polylith/config
414
+ module.
415
+
416
+ #### addConfig
417
+
418
+ `addConfig(config)`
419
+
420
+ Call this method on the Application object to add an object that can be
421
+ accessed in the application JavaScript. It takes one parameter:
422
+
423
+ - `config` this is an object that will be [deepmerged](
424
+ https://www.npmjs.com/package/deepmerge) into the config data available to
425
+ the application code.
426
+
427
+ #### get
428
+
429
+ `function get(key)`
430
+
431
+ This method is not part of the application object but is exported by the
432
+ module @polylith/config and used by the application code which calls it to
433
+ get configuration data that has been added as part of the application
434
+ build. It takes one parameter:
435
+
436
+ - `key` the key is a dot separated string that specifies the nested object
437
+ path to the config data to get.
438
+
439
+ **Returns**
440
+
441
+ The result of this function is the value of the configuration data at that
442
+ key. If the key does not specify configuration data, the function will
443
+ return undefined and a warning will be output to the console.
444
+
445
+ ### Features
446
+
447
+ A feature is an isolated set of functionality that the main application has
448
+ no dependencies on. It is described in more detail below. An application
449
+ adds features by referencing the base directory of the feature. The files
450
+ in that directory will specify the details of building it.
451
+
452
+ #### addFeature
453
+
454
+ `addFeature(feature)`
455
+
456
+ Call this method of the Application object to add a feature. It takes one
457
+ parameter:
458
+
459
+ - `feature` this string is the path to the feature, which is relative to the
460
+ application source directory.
461
+
462
+ #### Including Features
463
+
464
+ To include all the features specified in the application build, the primary
465
+ index file of the application has to import the module @polylith/features.
466
+
467
+ ### Loadables
468
+
469
+ A loadable is a module that can be imported dynamically at runtime. A
470
+ loadable is specified with an index file and referenced with a name. In
471
+ addition to importing the module, a loadable can define CSS files that
472
+ should be loaded with it, and a service prefix. The prefix will be used to
473
+ run the service lifecycle on all the registered services that start with
474
+ that prefix.
475
+
476
+ #### addLoadable
477
+
478
+ `async addLoadable(root, name, index, prefix, css)`
479
+
480
+ Call this method of the Application object to add a new loadable module to
481
+ the application. It takes these parameters:
482
+
483
+ - `root` a relative path from the source root to the other directories
484
+ specified here.
485
+ - `name` the name that will be used to reference this loadable.
486
+ - `index` the path to the entry point of this loadable. It will be the
487
+ imports that follow from this entry point that defines the code that will
488
+ be loaded at runtime.
489
+ - `prefix` if specified, then after loading the module, all the services that
490
+ start with this string will go through their lifecycle methods.
491
+ - `css` if specified is an array of resource specs for the css files to load
492
+ with this loadable. Paths here will be relative to the specified root.
493
+
494
+ #### load
495
+
496
+ `export async function load(name)`
497
+
498
+ This function is not part of the application object but is exported from
499
+ the module @polylith/loader. From the application code, call this method
500
+ asynchronously to load the module. It takes one parameter:
501
+
502
+ - `name` this string is the name specified in the call to addLoadable.
503
+
504
+ **Returns**
505
+
506
+ This method returns a [module namespace object](
507
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#module_namespace_object).
508
+ This can be used to access the exports of the imported module.
509
+
510
+ ### Chunks
511
+
512
+ Polylith uses [Rollup](https://rollupjs.org/) as its bundling system.
513
+ [Chunks](https://rollupjs.org/tutorial/#code-splitting) are divisions of
514
+ code that have been split out into individual entry points. Rollup will
515
+ automatically create chunks based on dynamic imports but can also be given
516
+ rules to manually split code.
517
+
518
+ `addManualChunk(spec)`
519
+
520
+ Call this method of the Application object to add a specifier for how to
521
+ split code. If no manual chunks specifiers are added, then the default is
522
+ to split any module that includes "node\_modules" in its path into a chunk
523
+ named "vendor".
524
+
525
+ - `spec` the type of this parameter can be one of object, function or array.
526
+ All manual chunk specifiers added with this method have to be of the same
527
+ type. If not, the previously added chunk specifiers will be removed and a
528
+ warning output to the console. The following is how polylith handles the
529
+ different types of chunk specifiers.
530
+ - `array` - each element of the given array is an object that contains two
531
+ fields, includes and name.
532
+ - `includes` this is a string. If the filename of the module being tested
533
+ includes this value it will be a match
534
+ - `name` if there is a match, then this is the name of the chunk.
535
+ - `function` - this is a function that will be passed the module name. If
536
+ it returns a value, that will be the name of the chunk.
537
+ - `object` - this object will be shallow merged into the current object.
538
+ This object is passed directly to the [manualChunks](
539
+ https://rollupjs.org/configuration-options/#output-manualchunks) field in
540
+ the rollup configuration.
541
+
542
+ Chunks specifiers that have been added are evaluated in the opposite order
543
+ they were added, so that the most recent specifiers are evaluated first.
544
+
545
+ ## Routing
546
+
547
+ When the application is run, Polylith instantiates an instance of an
548
+ [Express](https://expressjs.com/) server to deliver the application to a
549
+ browser. An application can add its own routes to this server.
550
+
551
+ ### setRouterModule
552
+
553
+ `setRouterModule(modulePath)`
554
+
555
+ Call this method of the Application object to set a module that will be
556
+ loaded to add routes to the Polylith server.
557
+
558
+ - `modulePath` this is a path to a JavaScript file that will be imported to
559
+ add routes. This path is relative to the project root.
560
+
561
+ This module should export a default function that takes a router as a
562
+ parameter and returns that same router. This function will be called
563
+ asynchronously.
564
+
565
+ ## Feature
566
+
567
+ A feature represents a discreet, high-level, set of functionality. In the Game
568
+ Center application each game is implemented as a feature. The code for a feature
569
+ should be isolated from all other features and interact with the application
570
+ only through services. The application itself should have no dependencies on a
571
+ feature, and there should be no repercussions from adding or removing one.
572
+
573
+ Polylith contains a number of facilities to assist this isolation.
574
+ Specifically, a feature can independently specify code than can be
575
+ dynamically imported at runtime, called a "loadable". It can specify its
576
+ own configuration data, its own resource files that will be copied into the
577
+ distribution folder, and CSS files that will be loaded with the application.
578
+
579
+ The code for a feature will exist in its own directory, usually nested
580
+ under a directory named 'src/features/'
581
+
582
+ The specifier for a feature can be set up in one of three ways. The first
583
+ is simply the entry point of the feature--usually index.js. All the code
584
+ for the feature will be determined from the imports of this file. The
585
+ second is through a configuration file, and last is code that will be
586
+ passed the application object and can call methods of that object to add
587
+ its requirements.
588
+
589
+ ### build.json
590
+
591
+ The configuration file for a feature should be named build.json and be in
592
+ the root directory for that feature. This configuration can have these
593
+ fields:
594
+
595
+ - `index` this is the entry point for the feature, which is usually index.js.
596
+ The imports from this entry point will define the code included in the
597
+ feature.
598
+ - `config` this specifies an object that has feature specific configuration
599
+ data that will be merged into the main application's config data. This can
600
+ be accessed through the get function exported from @polylith/config
601
+ - `resources` this is an array of resource specifiers. This is documented in
602
+ the section above on the application.
603
+ - `css` this is also an array of resource specifiers but must be css files.
604
+ These files will be added to the application list of css files and loaded
605
+ with the application.
606
+ - `loadables` this is an array of loadable specifiers. A loadable is a module
607
+ that can be dynamically imported into the application at runtime. This is
608
+ documented in the section above on the application.
609
+
610
+ ## Unit Testing
611
+
612
+ In addition to building an application, Polylith can also builds tests.
613
+ Tests are an alternate path through the source with a different entry point
614
+ and a different destination. The primary entry point for tests is specified
615
+ in the application setup. The code for which tests to run will follow from
616
+ the imports that result from this file. Features do not need to be included
617
+ here, as features can specify their own tests. The tests to run are not
618
+ determined using a file search but are done directly though imports.
619
+
620
+ ### Application
621
+
622
+ The entry point and build destination for tests is defined in the
623
+ constructor of the application with these fields in the config parameters:
624
+
625
+ - `spec` this string is the entry point for the tests. Like the index, all
626
+ the code that will be bundled for the tests follows from the imports here,
627
+ and the tests defined for features.
628
+ - `testDest` this string specifies the directory where the bundled files for
629
+ testing are placed. When running the tests, this will be the root directory.
630
+
631
+ Both of these must be defined for tests to work.
632
+
633
+ ### Features
634
+
635
+ As features are independent from the application, the tests for features
636
+ are defined independently. The features to test are the same features that
637
+ are included in the application build.
638
+
639
+ When building the tests for a feature, rather than looking for the files
640
+ build.js, build.json and index.js, to build a feature, it instead looks for
641
+ spec.js, test.js and test.json. if only spec.js is defined this will be the
642
+ entry point for the feature tests. A test config file has the same fields
643
+ as a build config file, except that the index refers to the testing entry
644
+ point. All the code that will be bundled for the tests follows from the
645
+ imports from the test entry point.
646
+
647
+ ### Loadables
648
+
649
+ Loadables are ignored during testing, the code in a loadable should built
650
+ into the tests defined by the feature.
651
+
652
+ ### Unit Testing Services
653
+
654
+ Because the basic unit of implementation is a service, Polylith has
655
+ provided a mechanism to test a service in isolation without needing to
656
+ import other dependencies. Doing this requires a little cooperation from
657
+ the implementation of a service. The module that implements a service
658
+ should export as the default the class that it defines it. A unit test can
659
+ construct an instance of that service and pass to it an alternate registry
660
+ that it seeds with mocks of all the service's dependencies. To create a new
661
+ registry, make a new instance of the Registry class.
662
+
663
+ ### Jasmine/Karma
664
+
665
+ Although Jasmine is not a requirement, this is the testing framework that
666
+ has been used in all the Polylith. testing. And it is Karma that has been
667
+ used as a test runner.
668
+
669
+ Because the code to build the tests is handled by Polylith, there is no
670
+ need to configure Jasmine. This is a sample configuration that can be used
671
+ to run Karma during development testing. It is assumed here that the test
672
+ destination directory is "tests". Because the application is built using ES
673
+ modules, and karma is expecting its configuration as a CommonJS module, the
674
+ file name for this configuration must be karma.conf.cjs.
675
+
676
+ ```javascript
677
+ module.exports = function(config) {
678
+ config.set({
679
+ basePath: '',
680
+ frameworks: ['jasmine'],
681
+ files: [
682
+ {pattern: 'tests/\*\*/\*.css', included: false},
683
+ 'tests/\*\*/\*.js'
684
+ ],
685
+ plugins: [
686
+ require('karma-jasmine'),
687
+ require('karma-chrome-launcher'),
688
+ require('karma-spec-reporter'),
689
+ require('karma-jasmine-html-reporter')
690
+ ],
691
+ reporters: ['spec'],
692
+ port: 9876,
693
+ colors: true,
694
+ client: {
695
+ captureConsole: true,
696
+ },
697
+ autoWatch: true,
698
+ browsers: ['ChromeHeadless'],
699
+ singleRun: false,
700
+ concurrency: Infinity,
701
+ })
702
+ }
703
+ ```
704
+ To build tests from the Polylith command line use this command.
705
+
706
+ `polylith test -a -w`
707
+
708
+ The -a flag is used to specify that tests should be run for all
709
+ applications. The -w flag will watch the source for changes and rerun the
710
+ build. To run Karma, enter this command.
711
+
712
+ `karma start --browsers ChromeHeadless karma.conf.cjs`
713
+
714
+ The test script defined in the package.json file will run these two
715
+ commands in a separate window. So, to run them use the command:
716
+
717
+ `npm test`
718
+
719
+ ## Synthetic Modules
720
+
721
+ A synthetic module does not exist as an actual module that has its own
722
+ source but is generated at build time by Polylith. Although they do not
723
+ exist as standalone source, synthetic modules are imported like any other
724
+ module.
725
+
726
+ Because many of the features of the Polylith build systems are specified in
727
+ configuration rather than code, it uses synthetic modules to access them.
728
+
729
+ - @polylith/config
730
+ All the configuration data that has been created during the build process
731
+ is collected by this synthetic module and added to the configuration store.
732
+ The function get is exported to access this data.
733
+ - @polylith/loader
734
+ In order to properly generate the dependencies of a loadable module,
735
+ Rollup needs to know the exact location of the source. During build, Rollup
736
+ will modify the filename specified by a dynamic import(), to the file that
737
+ is copied the destination directory.
738
+
739
+ Polylith generates the source for this synthetic module to add the
740
+ `import()` code for each loadable added to the build with the filename
741
+ provided in the specification of the loadable so that Rollup can find it.
742
+ It will also generate the code to load the CSS, and if a prefix is defined
743
+ run the lifecycle on any service added by this loadable.
744
+
745
+ This module exports a single function named load that takes as it's only
746
+ parameter the name of the loadable that was giving during its setup. This
747
+ method should be called asynchronously:
748
+
749
+ ```javascript
750
+ import {load} from '@polylith/loader';`
751
+
752
+ await load('mahjongg');
753
+ ```
754
+
755
+ - @polylith/features
756
+ To import the code for all the features added in the build, this synthetic
757
+ module should be imported in the entry point for the application. The code
758
+ generated for this module consists of imports to the entry points of all
759
+ the features added.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polylith",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "cli for the polylith environment",
5
5
  "bin": {
6
6
  "polylith": "bin/polylith.js"