polylith 0.1.52 → 0.1.55
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/README.md +759 -0
- package/bin/apps.js +3 -2
- package/bin/instructions.txt +2 -0
- package/bin/polylith.js +2 -0
- package/package.json +2 -2
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) is used 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 dependencies
|
|
15
|
+
shared between files that can be loaded dynamically. It also does tree shaking
|
|
16
|
+
to remove unused code.
|
|
17
|
+
|
|
18
|
+
The configuration of a build is high-level, focused on how the application is
|
|
19
|
+
constructed rather than the technology used. A build can be specified as either
|
|
20
|
+
a .json file, or though code.
|
|
21
|
+
|
|
22
|
+
## Command Line Interface
|
|
23
|
+
|
|
24
|
+
There is a command line interface for building and running a polylith
|
|
25
|
+
application. This can be installed globally to be run from the command line, and
|
|
26
|
+
locally 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 polylith, and the code necessary to implement and use
|
|
65
|
+
them is defined in the module @polylith/core. This module exports four things:
|
|
66
|
+
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
|
+
A ServiceObject is at its heart 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/bin/apps.js
CHANGED
|
@@ -143,7 +143,7 @@ export async function build(name, config, options, watch) {
|
|
|
143
143
|
async function server(options) {
|
|
144
144
|
var server = new PolylithServer(options, options.dest);
|
|
145
145
|
await server.create(options.apps);
|
|
146
|
-
|
|
146
|
+
server.start(options.port || '8081');
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
export async function watch(name, config, options) {
|
|
@@ -153,7 +153,8 @@ export async function watch(name, config, options) {
|
|
|
153
153
|
await app.watch();
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
if (options.serve)
|
|
157
|
+
await server({...options, apps: apps});
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
export async function run(name, config, options) {
|
package/bin/instructions.txt
CHANGED
package/bin/polylith.js
CHANGED
|
@@ -24,6 +24,7 @@ var clOptions = {
|
|
|
24
24
|
index: true,
|
|
25
25
|
react: true,
|
|
26
26
|
watch: false,
|
|
27
|
+
serve: true,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
var thisDir = path.dirname(forceToPosix(import.meta.url));
|
|
@@ -170,6 +171,7 @@ async function getOptions() {
|
|
|
170
171
|
clOptions.index = getOption('index', 'i')
|
|
171
172
|
clOptions.react = getOption('react', 'r')
|
|
172
173
|
clOptions.watch = getOption('watch', 'w')
|
|
174
|
+
clOptions.serve = getOption('serve', 'v')
|
|
173
175
|
|
|
174
176
|
// force multiple if we already have more than one app;
|
|
175
177
|
if (config.apps && config.apps.length > 1) clOptions.multiple = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polylith",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "cli for the polylith environment",
|
|
5
5
|
"bin": {
|
|
6
6
|
"polylith": "bin/polylith.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"author": "Glenn Anderson",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@polylith/builder": "0.2.
|
|
20
|
+
"@polylith/builder": "0.2.10",
|
|
21
21
|
"@polylith/server": "0.1.17",
|
|
22
22
|
"fs-extra": "^10.1.0",
|
|
23
23
|
"minimist": "^1.2.7"
|