@polylith/builder 0.0.37 → 0.0.39

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/App.js CHANGED
@@ -7,33 +7,18 @@ import * as resolve from '@rollup/plugin-node-resolve';
7
7
  import commonjs from '@rollup/plugin-commonjs';
8
8
  import html from 'rollup-plugin-html';
9
9
  import livereload from 'rollup-plugin-livereload';
10
+ import styles from "rollup-plugin-styles";
10
11
 
11
12
  import loader from './plugin-loader.js';
12
13
  import mainHTML from './plugin-main-html.js';
13
14
  import features from './plugin-features.js';
14
- import styles from "rollup-plugin-styles";
15
15
  import resources from "./plugin-copy-resources.js";
16
16
  import jsconfig from "./plugin-jsconfig.js";
17
17
 
18
+ import {forceToPosix, fileExists} from './utils.js'
18
19
  import ConfigFeature from './ConfigFeature.js';
19
20
  import Files from './Files.js';
20
21
 
21
- /**
22
- * call this function to check if the given file exists
23
- *
24
- * @param {String} path the name of the file
25
- * @returns {Promise<Boolean>} true if the file exists
26
- */
27
-
28
- async function fileExists(path) {
29
- try {
30
- await stat(path)
31
- return true;
32
- } catch (e) {
33
- return false;
34
- }
35
- }
36
-
37
22
  /**
38
23
  * The base class for applications. Applications inherit from this class
39
24
  */
@@ -42,16 +27,15 @@ export default class App {
42
27
  * Construct the app object.
43
28
  *
44
29
  * @param {String} name a name for the app
45
- * @param {String} root the root directory of the project. All other
46
- * paths will be relative to this path.
30
+ * @param {String} root the full path to the root directory of the project.
31
+ * All other paths will be relative to this path.
47
32
  * @param {String} index the relative path to the main source file from the
48
33
  * root. All source paths will be assumed to be relative to this path.
49
34
  * @param {String} dest the relative path to the destination folder from the
50
35
  * root for rolled up files
51
36
  */
52
-
53
37
  constructor(name, root, index, dest) {
54
- root = App.fixPath(root);
38
+ root = forceToPosix(root);
55
39
  this.root = root;
56
40
 
57
41
  var filename = path.posix.join(root, index);
@@ -69,30 +53,62 @@ export default class App {
69
53
  this.manualChunkType = 'function';
70
54
  this.manualChunks = [];
71
55
  this.files = new Files(this.sourcePath, this.destPath);
56
+ this.cssSpecs = [];
72
57
  this.cssFiles = [];
73
58
  this.liveReload = true;
59
+ this.templateVariables = {};
60
+ this.ns = name.toUpperCase();
61
+ this.ns = this.ns.replace(/[- ]*?/g, '_');
62
+ this.codeVariables = {};
74
63
  }
75
64
 
76
- static fileToPath(filename) {
77
- filename = App.fixPath(filename);
78
- return path.posix.dirname(filename);
65
+ /**
66
+ * Call this method to set the code variable name space. An object with this
67
+ * name will be attached to the window object with the code variables as
68
+ * members. The default value for the name space is the app name in upper
69
+ * snake case.
70
+ *
71
+ * @param {String} ns the name space name for added code variables. This
72
+ * must be a valid JavaScript identifier.
73
+ */
74
+ setNamespace(ns) {
75
+ this.ns = ns;
79
76
  }
80
77
 
81
78
  /**
82
- * call this method to force the file path to use posix notation and remove
83
- * all drive information.
79
+ * Call this method to add a code variable to the output html file. This
80
+ * variable will be added the namsespace for the app.
84
81
  *
82
+ * @param {String} name the name of tyhe variable. This must be a valid
83
+ * JavaScript identifier.
84
+ * @param {*} value the value of the variable to set. This can be any type
85
+ * that can be serialized through JSON.
86
+ */
87
+ addCodeVariable(name, value) {
88
+ this.codeVariables[name] = value;
89
+ }
90
+
91
+ /**
92
+ * Call this method to get the replacement value for ${codeVariables}. This
93
+ * will be the code that adds all the codeVariables to the namespace.
85
94
  *
86
- * @param {String} src the filename wqe
87
- * @returns {String} the new path
95
+ * @returns {String} the replacement value for the codeVariables template
96
+ * variable;
88
97
  */
89
- static fixPath(src) {
90
- src = src.replace('file:', '');
91
- src = src.replace('///', '');
92
- src = src.replace(/.:/, '');
93
- src = src.replace(/\\/g, '/');
98
+ getCodeVariablesValue() {
99
+ var names = Object.keys(this.codeVariables);
100
+ if (names.length === 0) return '';
101
+
102
+ var members = names.map(function(name) {
103
+ return ` ${name}: ${JSON.stringify(this.codeVariables[name])},`
104
+ }, this);
94
105
 
95
- return src;
106
+ var codeBlock =
107
+ ` ${this.ns} = {
108
+ ${members.join('\n')}
109
+ }
110
+ `;
111
+ return codeBlock;
96
112
  }
97
113
 
98
114
  /**
@@ -109,23 +125,67 @@ export default class App {
109
125
  return './' + path;
110
126
  }
111
127
 
128
+ /**
129
+ * Call this method with true to reload the browser when any files in the
130
+ * destination folder have changed.
131
+ *
132
+ * @param {Bollean} on set to true to turn on destination watching
133
+ */
112
134
  setLiveReload(on) {
113
135
  this.liveReload = on;
114
136
  }
137
+
138
+ /**
139
+ * Call this method to set the template for the main application html. This
140
+ * template file must have the required replacement strings for basic
141
+ * functionality. Call setTemplateVariable to create application specific
142
+ * repalcement values.
143
+ *
144
+ * @param {String} source the relative path from the application root to the
145
+ * html template
146
+ * @param {String} destination the relative path to the destination file
147
+ */
115
148
  setHtmlTemplate(source, destination) {
116
149
  this.htmlTemplate = {source: source, destination: destination};
117
150
  }
118
151
 
152
+ /**
153
+ * Call this method to set the value of a template variable. Template
154
+ * variables specify a location in the html template where the value will be
155
+ * inserted. To specify where the value should be inserted in the template
156
+ * file add the string "${variableName}" in the location.
157
+ *
158
+ * @param {*} name
159
+ * @param {*} value
160
+ */
161
+ setTemplateVariable(name, value) {
162
+ this.templateVariables[name] = value;
163
+ }
164
+
119
165
  addConfig(config, root) {
120
166
  this.configs[root] = config;
121
167
  }
122
168
 
123
- async addMainCss(specs) {
169
+ /**
170
+ * Call this method to add specifications for application css files These
171
+ * css files will be included in the html template
172
+ *
173
+ * @param {ResourceSpecList} specs the specification for the css files. These
174
+ * will also be added as resources to be copied.
175
+ */
176
+ addMainCss(specs) {
177
+ this.cssSpecs = [...this.cssSpecs, ...specs];
178
+ }
179
+
180
+ /**
181
+ * Call this method to find all the css files specified by the css specs
182
+ */
183
+ async findMainCss() {
124
184
  var files = new Files(this.sourcePath, this.destPath)
125
185
 
126
- // get the file paths
186
+ // Find all the files frem the added css speces
127
187
  specs.forEach(function(spec) {
128
- files.addCopySpec('', spec);
188
+ files.addResourceSpec('', spec);
129
189
  }, this)
130
190
 
131
191
  var expanded = await files.findAllFiles();
@@ -141,30 +201,32 @@ export default class App {
141
201
 
142
202
  /**
143
203
  * Call this method to add a list of resources that will be moved to the
144
- * destination path when the application is built. This will either be a
145
- * feature root, or the source path
204
+ * destination path when the application is built.
146
205
  *
147
- * @param {Array<import('./Files.js').CopySpec} resourceSpecs the copy specs
206
+ * @param {String} root the relative path from the source root to the path
207
+ * to the origin of the caller. This will either be a feature root, or
208
+ * empty for the main applicatio the source pathPaths specified in the
209
+ * resource spec are assumed to be relative to this.-
210
+ * @param {Array<ReourceSpec>} resourceSpecs the copy specs
148
211
  * for all the resources being added.
149
- * @param {String} root the path to the origin of the caller. Paths in
150
- * the spec are assumed to be relative to this.
151
212
  */
152
213
  addResources(root, resourceSpecs) {
153
214
  resourceSpecs.forEach(function(spec) {
154
- this.files.addCopySpec(root, spec);
215
+ this.files.addResourceSpec(root, spec);
155
216
  }, this)
156
217
  }
157
218
 
158
219
  /**
159
220
  * Call this method to add a feature to the application. This method is
160
221
  * given a path to the root of the feature. At build time the builder will
161
- * look directory for a file named build.js and if found import it and
162
- * call the default function passing it a pointner to this object.
222
+ * look in the feature directory for a file named build.js and if found
223
+ * import it and call the default function passing it a pointner to this
224
+ * object.
163
225
  *
164
226
  * If there is no build.js file the builder will look for a build.json file.
165
227
  * If present that json will be loaded and used to build the feature.
166
228
  *
167
- * If that is not found it will look for an index.js file. and if found it
229
+ * If that is not found it will look for an index.js file, and if found it
168
230
  * will add that to the list of feature index files which will be
169
231
  * automatically imported when the built code imports the @polylith/features
170
232
  * module
@@ -223,54 +285,53 @@ export default class App {
223
285
  }
224
286
 
225
287
  /**
226
- * If no manual chunk specifiers are added, then this will be used as the
227
- * default.
288
+ * This is called by rollup if no manual chunk specifiers are added,
228
289
  *
229
- * @param {String} id this is the filename of the current file being
290
+ * @param {String} filename this is the filename of the current file being
230
291
  * processed by rollup
231
292
  *
232
293
  * @returns {String} the chunk name if there is a match
233
294
  */
234
- defaultManualChunks(id) {
235
- if (id.includes('node_modules')) {
295
+ defaultManualChunks(filename) {
296
+ if (filename.includes('node_modules')) {
236
297
  return 'vendor';
237
298
  }
238
299
  }
239
300
 
240
301
  /**
241
- * This is called when manual chunk specifications have been added as an
242
- * array.
302
+ * This is called by rollup when manual chunk specifications have been added
303
+ * as an array.
243
304
  *
244
- * @param {String} id this is the filename of the current file being
305
+ * @param {String} filename this is the filename of the current file being
245
306
  * processed by rollup
246
- * @returns {String} the name fo the chunk if there is a matching chunk
307
+ * @returns {String} the name oo the chunk if there is a matching chunk
247
308
  * name specifier
248
309
  */
249
- handleManualChunksArray(id) {
250
- var fixId = App.fixPath(id);
310
+ handleManualChunksArray(filename) {
311
+ var posixFilename = forceToPosix(filename);
251
312
  var result = this.manualChunks.find(function(spec) {
252
- return fixId.includes(spec.includes);
313
+ return posixFilename.includes(spec.includes);
253
314
  })
254
315
 
255
316
  if (result) return result.name;
256
317
  }
257
318
 
258
319
  /**
259
- * This is called when the manual chunk specifiers are functions. Each
260
- * registered function is called in the reverse order to how they were
320
+ * This is called by rollup when the manual chunk specifiers are functions.
321
+ * Each registered function is called in the reverse order to how they were
261
322
  * added.
262
323
  *
263
- * @param {String} id this is the filename of the current file being
324
+ * @param {String} filename this is the filename of the current file being
264
325
  * processed by rollup
265
326
  * @returns the chunk name if any of the callbacks return one
266
327
  */
267
- handleManualChunksCallbacks(id) {
268
- var fixId = App.fixPath(id);
328
+ handleManualChunksCallbacks(filename) {
329
+ var posixFilename = forceToPosix(filename);
269
330
  if (this.manualChunks.length === 0) {
270
- return this.defaultManualChunks(fixId);
331
+ return this.defaultManualChunks(posixFilename);
271
332
  } else {
272
- for (let idx = 0; idx < this.manualChunks.length; idx++) {
273
- var result = this.manualChunks[idx](fixId);
333
+ for (chunk of this.manualChunks) {
334
+ var result = chunk(posixFilename);
274
335
  if (result) return result;
275
336
  }
276
337
  }
@@ -343,40 +404,62 @@ export default class App {
343
404
 
344
405
  /**
345
406
  *
407
+ * @param {String} root the relative path from the source root to the
408
+ * feature directory. Loadable paths are assumed to be relative to this
409
+ * directory
346
410
  * @param {String} name unique name of the loadable that will be passed to
347
411
  * the load method
348
- * @param {String} main the relative path from the source folder to the entry
349
- * point of the loadable.
412
+ * @param {String} index the relative path from the source folder to the
413
+ * entry point of the loadable.
350
414
  * @param {String} [prefix] if given, the prefix on services created in
351
415
  * this loadable. When the loadable has been loaded, the start and
352
416
  * ready methods will be called on all services starting with this
353
417
  * prefix.
354
- * @param {Array<CopySpec>} [css] if supplied it will be a copy spec of the
355
- * css files that will included when the module is loaded
418
+ * @param {ResourceSpecList} [css] if give, it will be a list of resource
419
+ * specifications for the css files that will be included when the
420
+ * module is loaded
356
421
  */
357
- async addLoadable(root, name, main, prefix, css) {
358
- // expand css specs
359
- var cssUris = css ? [] : undefined;
360
- if (css) {
422
+ async addLoadable(root, name, index, prefix, css) {
423
+ var indexPath = path.posix.join(this.sourcePath, index);
424
+ this.loadables.push({name, index: indexPath, prefix, root, css});
425
+ }
426
+
427
+ /**
428
+ * Call this method to locate all the css files for a loadable. These css
429
+ * files will be loaded into the browser when this loadable has been loaded
430
+ *
431
+ * @param {Loadable} loadable
432
+ */
433
+ async findLoadableCss(loadable) {
434
+ if (loadable.css) {
361
435
  var files = new Files(this.sourcePath, this.destPath)
362
- css.forEach(function(spec) {
363
- files.addCopySpec(root, spec);
436
+ loadable.css.forEach(function(spec) {
437
+ files.addResourceSpec(loadable.root, spec);
364
438
  }, this)
439
+
365
440
  var expanded = await files.findAllFiles();
366
441
  var keys = Object.keys(expanded);
367
- keys.forEach(function(key){
442
+
443
+ var cssUris = keys.map(function(key){
368
444
  var file = expanded[key];
369
- cssUris.push(file.uri)
445
+ return file.uri
370
446
  }, this)
371
- }
372
447
 
373
- var dest = path.posix.join(this.sourcePath, main);
374
- this.loadables.push({name, path: dest, prefix, css: cssUris});
448
+ loadable.css = cssUris;
449
+ }
375
450
  }
376
451
 
377
-
378
- buildMainCss() {
452
+ /**
453
+ * Call this method to build the template variable for the main html css
454
+ * files
455
+ *
456
+ * @returns {Promise<String>} the value of the mainCss template variable
457
+ */
458
+ async buildMainCss() {
379
459
  var cssTags = '';
460
+
461
+ await this.findMainCss();
462
+
380
463
  this.cssFiles.forEach(function(uri) {
381
464
  cssTags += ` <link rel="stylesheet" href="${uri}"></link>`
382
465
  }, this);
@@ -385,25 +468,28 @@ export default class App {
385
468
  }
386
469
 
387
470
  /**
388
- * The build method calls this method to create the rollup configuration
389
- * object
471
+ * The build method calls this to create the rollup configuration object
390
472
  *
391
473
  * @returns {Object} rollup configuration object
392
474
  */
393
475
 
394
- buildConfiguration() {
476
+ async buildConfiguration() {
395
477
  var input = [this.fullIndexPath];
396
- this.loadables.forEach(function(spec) {
397
- input.push(spec.path);
398
- });
399
478
 
400
- this.variables = {
401
- pattern: 'main-js',
402
- replacement: `<script type="module" src="${this.index}"></script>`
479
+ // using a for loop because we are making an async call
480
+ for (let loadable of loadables) {
481
+ // find all the css files for the loadables
482
+ await this.findLoadableCss(loadable);
483
+ input.push(loadable.index);
403
484
  }
404
485
 
405
486
  var manualChunks = this.getManualChunks();
406
- var mainCss = this.buildMainCss();
487
+ var mainCss = await this.buildMainCss();
488
+ var codeVariables = this.getCodeVariablesValue();
489
+
490
+ this.templateVariables['mainCss'] = mainCss;
491
+ this.templateVariables['codeVariables'] = codeVariables;
492
+
407
493
  var plugins = [
408
494
  resolve.nodeResolve({
409
495
  extensions: ['.js', '.jsx']
@@ -424,7 +510,7 @@ export default class App {
424
510
  root: this.root,
425
511
  source: this.htmlTemplate.source,
426
512
  destination: this.htmlTemplate.destination,
427
- replaceVars: {mainCss: mainCss},
513
+ templateVars: this.templateVariables,
428
514
  }),
429
515
  resources(this.name, this.files)
430
516
  ];
@@ -449,9 +535,9 @@ export default class App {
449
535
  return '[name]-[hash][extname]';
450
536
  },
451
537
  entryFileNames: function(chunkInfo) {
452
- var entry = App.fixPath(chunkInfo.facadeModuleId);
538
+ var entry = forceToPosix(chunkInfo.facadeModuleId);
453
539
  var found = this.loadables.find(function(loadable) {
454
- return loadable.path === entry;
540
+ return loadable.index === entry;
455
541
  }, this);
456
542
  var exists = Boolean(found);
457
543
 
@@ -478,7 +564,7 @@ export default class App {
478
564
 
479
565
  async build() {
480
566
  await this.buildFeatures();
481
- this.config = this.buildConfiguration();
567
+ this.config = await this.buildConfiguration();
482
568
 
483
569
  const bundle = await rollup.rollup(this.config.input);
484
570
  await bundle.generate(this.config.output);
package/ConfigApp.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import App from './App.js';
2
2
  import path from 'node:path/posix';
3
+ import {forceToPosix} from './utils.js'
3
4
 
5
+ /**
6
+ * This class is used to build an application from a configuration file.
7
+ */
4
8
  export default class ConfigApp extends App {
5
9
  constructor (config, root) {
6
- root = App.fixPath(root);
10
+ root = forceToPosix(root);
7
11
  var name = config.name || 'unnamed';
8
- var index = config.index || path.join(root, 'src', 'index.js');
9
- var dest = config.dest || path.join(root, 'dist');
12
+ var index = config.index || 'src/index.js';
13
+ var dest = config.dest || 'dist';
10
14
 
11
15
  super(name, root, index, dest);
12
16
  this.config = config;
@@ -14,20 +18,30 @@ export default class ConfigApp extends App {
14
18
  if (!index.template || !index.template.source) throw new Error('html source not defined in config file');
15
19
  var source = index.template.source;
16
20
  var sourceFilename = path.basename(source);
17
- var destination = index.template.destination || path.join(dest, sourceFilename)
21
+ var destination = index.template.destination || path.join(dest, sourceFilename);
18
22
  this.setHtmlTemplate(source, destination);
19
23
 
20
- if (config.manualChunks) this.addManualChunks(config.manualChunks);
24
+ if (config.manualChunks) {
25
+ config.manualChunks.forEach(function(chunk) {
26
+ this.addManualChunks(chunk);
27
+ }, this)
28
+ }
29
+
21
30
  if (this.config.features) {
22
31
  this.config.features.forEach(function(feature) {
23
32
  this.addFeature(feature);
24
33
  }.bind(this))
25
34
  }
26
35
 
27
- if (config.resources) ;
28
- }
36
+ if (config.resources && Array.isArray(config.resources)) this.addResources('', config.resources);
37
+ if (config.css) this.addMainCss(css);
29
38
 
30
- async getFeatures() {
31
- return this.config.features || [];
39
+ if (config.variables) {
40
+ var names = Object.keys(config.variables);
41
+
42
+ names.forEach(function(name) {
43
+ this.addCodeVariable(name, config.variables[name]);
44
+ }, this)
45
+ }
32
46
  }
33
47
  }
package/ConfigFeature.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import Feature from './Feature.js';
2
2
  import path from 'node:path/posix';
3
+ import App from './App.js';
3
4
 
4
5
  export default class ConfigFeature extends Feature {
5
6
  /**
package/Files.js CHANGED
@@ -3,27 +3,8 @@ import path from 'node:path/posix'
3
3
  import {ensureDir} from 'fs-extra';
4
4
  import { copyFile } from 'node:fs/promises';
5
5
  import App from './App.js';
6
-
7
- /**
8
- * @typedef {Object} CopySpec
9
- * @property {String} dest the relative path of the copy destination from the
10
- * application's destination path.
11
- * @property {String} cwd the relative path from the spec root to search for
12
- * files
13
- * @property {String} glob the search expression for the files to copy, in glob
14
- * format
15
- * @property {Boolean} [keepNest] if true then the nesting of the found file
16
- * relative to the cwd property will be retained when the file is copied.
17
- * Defaults to false
18
- */
19
-
20
- /**
21
- * @typedef {Object} FileSpec
22
- * @property {String} name the full path to the file being copied
23
- * @property {String} searchRoot the absolute path to where the search started
24
- * @property {CopySpec} spec the specifier for how to find and copy files
25
- */
26
-
6
+ import {forceToPosix} from './utils.js'
7
+ import './types.js'
27
8
  /**
28
9
  * create an instance of this class to specify and and copy files from source
29
10
  * to destination.
@@ -40,6 +21,7 @@ export default class Files {
40
21
  constructor(src, dest) {
41
22
  this.dest = dest;
42
23
  this.src = src;
24
+ /** @type {CopyInfoList} */
43
25
  this.files = {};
44
26
  this.specs = [];
45
27
  }
@@ -52,20 +34,23 @@ export default class Files {
52
34
  * @param {String} root the originating path of the specifier. This is a
53
35
  * relative path from the project src path. This is probably the location
54
36
  * of the feature, or empty for the src path itself.
55
- * @param {CopySpec} spec the specification for how to find and copy files
37
+ * @param {ResourceSpec} spec the specification for how to find and copy files
56
38
  */
57
- addCopySpec(root, spec) {
39
+ addResourceSpec(root, spec) {
58
40
  spec.root = root;
59
41
  this.specs.push(spec);
60
42
  }
61
43
 
62
44
  /**
63
- * call this methid to generate the destination filename from the spec and
45
+ * call this method to generate the destination filename from the spec and
64
46
  * the source filename
65
47
  *
66
- * @param {CopySpec} spec the spec that generate the filename
67
- * @param {String} srcFilename the source filename
68
- * @returns
48
+ * @param {String} searchRoot the full path to the directory where the
49
+ * search started that found this file
50
+ * @param {ResourceSpec} spec the spec that found the file
51
+ * @param {String} srcFilename the full path to the source file
52
+ *
53
+ * @returns {String} the full path to the destination file
69
54
  */
70
55
  makeDestination(searchRoot, spec, srcFilename) {
71
56
  var fullPath = searchRoot;
@@ -87,35 +72,84 @@ export default class Files {
87
72
  * different specs then the spec with the longest search root will take
88
73
  * presidence, since it is the spec with the greatest specificity
89
74
  *
90
- * @param {Array<String>} files absolute filepaths of files that have been found
91
- * @param {String} searchRoot the absolute path of the spec root
92
- * @param {CopySpec} spec the spec that was used to find these files.
75
+ * @param {String} searchRoot the full path to the directory where the
76
+ * search started that found this file
77
+ * @param {Array.<String>} files full paths to files that have been found
78
+ * @param {ResourceSpec} spec the spec that was used to find these files.
79
+ *
80
+ * @returns {CopyInfoList} the list of files found for this spec
93
81
  */
94
82
  addFiles(searchRoot, files, spec) {
83
+ /** @type {CopyInfoList} */
84
+ var foundFiles = {};
85
+
95
86
  // the rule here is that files that are found by multiple specs will be
96
87
  // controlled according to the spec with the deepest nested search path.
97
- // Since file paths here are absolute tthis will always be based on the
88
+ // Since file paths here are absolute this will always be based on the
98
89
  // string length.
99
90
  files.forEach(function(file) {
100
- file = App.fixPath(file);
91
+ file = forceToPosix(file);
92
+
101
93
  // reconcile conflicts
102
94
  if (this.files[file]) {
103
95
  var copyInfo = this.files[file];
104
96
  if (copyInfo.searchRoot.length > searchRoot.length) return;
105
97
  }
98
+
106
99
  var destination = this.makeDestination(searchRoot, spec, file);
107
100
  var uri = destination.slice(this.dest.length + 1);
108
101
 
109
- this.files[file] = {
102
+ foundFiles[file] = {
110
103
  name: file,
104
+ searchRoot: searchRoot,
111
105
  destFilename: destination,
112
106
  uri: uri,
113
- searchRoot: searchRoot,
114
107
  spec: spec,
115
108
  }
116
- }, this)
109
+ }, this);
110
+
111
+ this.files = {...this.files, ...foundFiles}
112
+
113
+ return foundFiles;
117
114
  }
118
115
 
116
+ /**
117
+ * Call this method to get all the folders where files can copied from
118
+ *
119
+ * @returns {Array.<String>}
120
+ */
121
+ getAllFolders() {
122
+ var folders = this.specs.map(function(spec){
123
+ return path.join(this.src, spec.root, spec.cwd);
124
+ }, this);
125
+
126
+ return folders;
127
+ }
128
+
129
+
130
+ /**
131
+ * Call this method to copy a single file into its destination location
132
+ *
133
+ * @param {String} file the full path of the file to be copied
134
+ */
135
+ async copyOneFile(file, updated) {
136
+ file = forceToPosix(file);
137
+
138
+ var destination = this.files[file] && this.files[file].destFilename;
139
+
140
+ if (destination && updated) {
141
+ console.log(`file ${file} updated, copied to ${destination}`);
142
+ await copyFile(file, destination);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Call this method to find all the files specified by a spec.
148
+ *
149
+ * @param {ResourceSpec} spec the specification for the files to find
150
+ *
151
+ * @returns {Promise.<CopyInfoList>} all the files that have been found so far.
152
+ */
119
153
  async findFiles(spec) {
120
154
  var searchRoot = path.join(this.src, spec.root, spec.cwd);
121
155
  var options = {
@@ -136,17 +170,16 @@ export default class Files {
136
170
 
137
171
  /**
138
172
  * Call this method to locate all the files to be found from all the copy
139
- * specs that have been added
173
+ * specs that have been added.
140
174
  *
141
- * @returns {Promise<Array<FileSpec>>} the list of all files. This is also
142
- * stored in the object variable this.files
175
+ * @returns {Promise<CopyInfoList>} the list of all files that have
176
+ * been found. This is also stored in the object variable this.files
143
177
  */
144
178
  async findAllFiles() {
145
179
  if (this.filesFound) return this.files;
146
180
 
147
181
  // using a for loop here because we are making async calls
148
- for (let idx = 0; idx < this.specs.length; idx++) {
149
- let spec = this.specs[idx];
182
+ for (let spec of this.specs) {
150
183
  await this.findFiles(spec);
151
184
  }
152
185
 
@@ -156,22 +189,20 @@ export default class Files {
156
189
  }
157
190
 
158
191
  /**
159
- * Call this method to copy all the files that have been specified through
160
- * addCopySpec
192
+ * Call this method to copy all the files that have been specified by
193
+ * calling addResourceSpec to their destination directories
161
194
  */
162
195
  async copyFiles() {
163
196
  await this.findAllFiles();
164
197
 
165
198
  var filenames = Object.keys(this.files);
199
+
166
200
  // using a for loop because we are making async calls
167
- for (let idx = 0 ; idx < filenames.length; idx++) {
168
- let srcFilename = filenames[idx];
201
+ for (let srcFilename of filenames) {
169
202
  let destFilename = this.files[srcFilename].destFilename;
170
- var destFilePath = path.dirname(destFilename);
171
- // we will override existing destination files. This could have
172
- // unintended consequences
203
+ let destFilePath = path.dirname(destFilename);
204
+
173
205
  try {
174
- // console.log(`copying ${srcFilename} to ${destFilename}`);
175
206
  await ensureDir(destFilePath);
176
207
  await copyFile(srcFilename, destFilename);
177
208
  } catch (e) {
package/index.js CHANGED
@@ -2,5 +2,12 @@ import App from './App.js'
2
2
  import Feature from './Feature.js';
3
3
  import ConfigApp from './ConfigApp.js';
4
4
  import ConfigFeature from './ConfigFeature.js';
5
+ import utils from './utils.js';
5
6
 
6
- export {App, Feature, ConfigApp, ConfigFeature};
7
+ var utilsExport = {
8
+ forceToPosix: utils.forceToPosix,
9
+ fileExists: utils.fileExists,
10
+ fileToPath: utils.fileToPath,
11
+ }
12
+
13
+ export {App, Feature, ConfigApp, ConfigFeature, utilsExport as utils};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polylith/builder",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
4
4
  "description": "The polylith builder",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -10,13 +10,15 @@ export default function(name, files) {
10
10
  return {
11
11
  name: "copy-resources",
12
12
 
13
- async buildStart(){
14
- var foundFiles = files.findAllFiles();
15
- var filenames = Object.keys(foundFiles);
16
- filenames.forEach(function(name) {
17
- var destFilename = foundFiles[name].destFilename;
18
- this.addWatchFile(destFilename);
19
- });
13
+ buildStart() {
14
+ var folders = files.getAllFolders();
15
+ folders.forEach(function(name) {
16
+ this.addWatchFile(name);
17
+ }, this);
18
+ },
19
+
20
+ watchChange(file, event) {
21
+ files.copyOneFile(file);
20
22
  },
21
23
 
22
24
  async generateBundle(outputOptions, bundleInfo) {
@@ -7,12 +7,9 @@ function makeSource(features) {
7
7
 
8
8
  var source = `${importStatements}`
9
9
 
10
- console.log('@polylith/features');
11
- console.log(source);
12
10
  return source;
13
11
  }
14
12
 
15
-
16
13
  export default function features(features) {
17
14
  return {
18
15
  name: 'features',
@@ -1,25 +1,6 @@
1
1
  import path from 'node:path/posix';
2
2
  import {readFile, writeFile, stat} from 'node:fs/promises';
3
-
4
- async function fileExists(path) {
5
- try {
6
- await stat(path)
7
- return true;
8
- } catch (e) {
9
- return false;
10
- }
11
- }
12
-
13
- function fixPath(src) {
14
- src = src.replace('file:', '');
15
- src = src.replace('///', '');
16
- src = src.replace(/.:/, '');
17
- src = src.replace(/\\/g, '/');
18
-
19
- return src;
20
- }
21
-
22
-
3
+ import {forceToPosix, fileExists} from './utils.js'
23
4
 
24
5
  /**
25
6
  * Matches pattern with a single star against search.
@@ -58,20 +39,19 @@ function matchStar(pattern, search) {
58
39
 
59
40
  async function findPathMatch(base, source, paths) {
60
41
  var patterns = Object.keys(paths);
61
- var source = fixPath(source);
42
+ var source = forceToPosix(source);
62
43
 
63
44
  if (source.indexOf(base) === 0) return
64
- // source = source.slice(base.length);
65
45
 
66
- for(let patternIdx = 0; patternIdx < patterns.length; patternIdx++) {
67
- let pattern = patterns[patternIdx];
46
+ for (let pattern of patterns) {
68
47
  let searches = paths[pattern];
69
48
  let capture = matchStar(pattern, source);
70
- if (!capture) continue;
71
49
 
50
+ if (!capture) continue;
72
51
  if (!Array.isArray(searches)) continue;
73
- for (let searchIdx = 0; searchIdx < searches.length; searchIdx++) {
74
- var tryName = path.join(base, searches[searchIdx].replace('*', capture));
52
+
53
+ for (let search of searches) {
54
+ var tryName = path.join(base, search.replace('*', capture));
75
55
 
76
56
  if (await fileExists(tryName)) {
77
57
  return tryName;
@@ -111,7 +91,7 @@ export default function jsconfig(root) {
111
91
  name: 'jsconfig',
112
92
 
113
93
  async resolveId (source, importer, options) {
114
- source = fixPath(source);
94
+ source = forceToPosix(source);
115
95
 
116
96
  if (previouslyMatched[source] !== undefined) return previouslyMatched[source];
117
97
  previouslyMatched[source] === null;
package/plugin-loader.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path/posix';
2
2
  import {readFile, writeFile, stat} from 'node:fs/promises';
3
- import {fixPath} from './utils.js';
3
+ import {forceToPosix} from './utils.js';
4
4
  var templateSource;
5
5
 
6
6
  function makeSource(loadables) {
@@ -38,7 +38,7 @@ ${serviceStr}
38
38
 
39
39
  var switchStr =
40
40
  ` case '${loadable.name}':
41
- promise = import('${loadable.path}')${prefixStr}
41
+ promise = import('${loadable.index}')${prefixStr}
42
42
  break;
43
43
  `
44
44
  loadableSwitches += switchStr;
@@ -64,7 +64,7 @@ export default function loader(loadables) {
64
64
  },
65
65
 
66
66
  async load (id) {
67
- var root = path.dirname(fixPath(import.meta.url));
67
+ var root = path.dirname(forceToPosix(import.meta.url));
68
68
  if (!templateSource) {
69
69
  templateSource = await readFile(path.join(root, 'loaderTemplate.txt'), 'utf-8');
70
70
  }
@@ -6,12 +6,6 @@ import {readFile, writeFile} from 'node:fs/promises';
6
6
  const INVALID_ARGS_ERROR =
7
7
  "[plugin-main-html] You did not provide a template or target!";
8
8
 
9
- /**
10
- * Takes an HTML file as a template and replaces variables.
11
- * @param {Object} options The options object.
12
- * @return {Object} The rollup code object.
13
- */
14
-
15
9
  function createScriptTags(scripts) {
16
10
  var tags = '';
17
11
  scripts.forEach(function(script) {
@@ -22,54 +16,58 @@ function createScriptTags(scripts) {
22
16
  return tags;
23
17
  }
24
18
 
25
- export default function(options = {}) {
26
- var { root, source, destination, replaceVars } = options;
19
+ /**
20
+ * Takes an HTML file as a template and replaces template variables with
21
+ * assigned values
22
+ *
23
+ * @param {Object} options The options object.
24
+ * @return {Object} The rollup plugin object.
25
+ */
26
+ export default function(options = {}) {
27
+
28
+ var { root, source, destination, templateVars } = options;
27
29
 
28
- return {
29
- name: "main-html-template",
30
+ return {
31
+ name: "main-html-template",
30
32
 
31
- async generateBundle(outputOptions, bundleInfo) {
32
- return new Promise(async function (resolve, reject) {
33
- try {
34
- var includes = [];
35
- var names = Object.keys(bundleInfo);
36
- var scripts;
33
+ async generateBundle(outputOptions, bundleInfo) {
34
+ var includes = [];
35
+ var names = Object.keys(bundleInfo);
36
+ var scripts;
37
37
 
38
- if (!destination && !source) throw new Error(INVALID_ARGS_ERROR);
38
+ if (!destination && !source) throw new Error(INVALID_ARGS_ERROR);
39
39
 
40
- names.forEach(function(name) {
41
- var entry = bundleInfo[name];
42
- if (!entry.isDynamicEntry) {
43
- includes.push(name);
44
- }
45
- });
40
+ names.forEach(function(name) {
41
+ var entry = bundleInfo[name];
42
+ if (!entry.isDynamicEntry) {
43
+ includes.push(name);
44
+ }
45
+ });
46
46
 
47
- scripts = createScriptTags(includes);
48
- replaceVars["scripts"] = scripts;
47
+ scripts = createScriptTags(includes);
48
+ templateVars["scripts"] = scripts;
49
49
 
50
- var sourceFilePath = path.join(root, source);
51
- var destinationFilePath = path.join(root, destination);
50
+ var sourceFilePath = path.join(root, source);
51
+ var destinationFilePath = path.join(root, destination);
52
52
 
53
- // Read the file
54
- var content = await readFile(sourceFilePath, 'utf-8');
55
- if (replaceVars) {
56
- var varNames = Object.keys(replaceVars);
57
- varNames.forEach(function(name) {
58
- var replacement = replaceVars[name]
59
- var escapedName = escapeStringRegexp('${' + name + '}');
60
- var regex = new RegExp(escapedName, 'g');
61
- content = content.replace(regex, replacement);
62
- });
63
- }
53
+ var content = await readFile(sourceFilePath, 'utf-8');
64
54
 
65
- // write the injected template to a file
66
- await ensureFile(destinationFilePath);
67
- writeFile(destinationFilePath, content, 'utf-8');
68
- resolve();
69
- } catch (e) {
70
- reject(e);
55
+ if (templateVars) {
56
+ var varNames = Object.keys(templateVars);
57
+ varNames.forEach(function(name) {
58
+ var replacement = templateVars[name]
59
+ var escapedName = escapeStringRegexp('${' + name + '}');
60
+ var regex = new RegExp(escapedName, 'g');
61
+ content = content.replace(regex, replacement);
62
+ });
71
63
  }
72
- });
73
- },
74
- };
64
+
65
+ // remove template vars that were not replaced
66
+ content = content.replace(/\$\{.*?\}/, '');
67
+
68
+ // write the injected template to a file
69
+ await ensureFile(destinationFilePath);
70
+ writeFile(destinationFilePath, content, 'utf-8');
71
+ },
72
+ };
75
73
  }
package/types.js ADDED
@@ -0,0 +1,60 @@
1
+ /** @module types */
2
+
3
+ /**
4
+ * This type defines how to locate a set of files that will be copied to the
5
+ * dest folder.
6
+ *
7
+ * @typedef {Object} ResourceSpec
8
+ * @property {String} dest the relative path of the copy destination from the
9
+ * application's destination path.
10
+ * @property {String} cwd the relative path from the spec root to search for
11
+ * files
12
+ * @property {String} glob the search expression for the files to copy, in glob
13
+ * format
14
+ * @property {Boolean} [keepNest] if true then the nesting of the found file
15
+ * relative to the cwd property will be retained when the file is copied.
16
+ * Defaults to false
17
+ */
18
+
19
+ /**
20
+ * This type defines a list of ResourceSpec
21
+ *
22
+ * @typedef {Array.<ResourceSpec} ResourceSpecList
23
+ */
24
+
25
+ /**
26
+ * This defines the specification for a file that has been found from a
27
+ * ResourceSpec
28
+ *
29
+ * @typedef {Object} CopyInfo
30
+ * @property {String} name the full path to the file found
31
+ * @property {String} searchRoot the absolute path to where the search started.
32
+ * @property {String} destFilename the absolute path to where the file be copied
33
+ * @property {String} uri the path to the file relative to the destination
34
+ * directory. This can be used to reference the file from the output
35
+ * application.
36
+ * @property {ResourceSpec} spec the specifier used to locate this file
37
+ */
38
+
39
+ /**
40
+ * This type defines a list of CopyInfos
41
+ *
42
+ * @typedef {Object.<String, CopyInfo>} CopyInfoList
43
+ */
44
+
45
+ /**
46
+ * This specifies what is in a loadable object
47
+ *
48
+ * @typedef {Object} Loadable
49
+ * @property {String} name this is the name the loadable will be known as. This
50
+ * will be the string passed to the load function
51
+ * @property {String} root the relative path from the source directory to the
52
+ * root of the feature. All feature paths are assumed to be relative to
53
+ * this
54
+ * @property {String} index the absolute path to the loadables index file
55
+ * @property {String} prefix if given then all services that begin with this
56
+ * prefix will go through the start sequence when loaded
57
+ * @property {ResourceSpecList|Array<String>} css is either the specification for
58
+ * where to find the css files to load, or during build, an array of the
59
+ * uris for each css file.
60
+ */
package/utils.js CHANGED
@@ -1,16 +1,37 @@
1
1
  import path from 'node:path/posix';
2
2
  import {readFile, writeFile, stat} from 'node:fs/promises';
3
3
 
4
- export function fixPath(src) {
4
+ /**
5
+ * call this function to force the file path to use posix notation and remove
6
+ * all drive information.
7
+ *
8
+ *
9
+ * @param {String} src the filename wqe
10
+ * @returns {String} the new path
11
+ */
12
+ export function forceToPosix(src) {
5
13
  src = src.replace('file:', '');
6
14
  src = src.replace('///', '');
7
- src = src.replace(/.:/, '');
15
+ src = src.replace(/.*?:/, '');
8
16
  src = src.replace(/\\/g, '/');
9
17
 
10
18
  return src;
11
19
  }
12
20
 
13
- async function fileExists(path) {
21
+ export function fileToPath(filename) {
22
+ filename = fixPath(filename);
23
+ return path.dirname(filename);
24
+ }
25
+
26
+
27
+ /**
28
+ * call this function to check if the given file exists
29
+ *
30
+ * @param {String} path the name of the file
31
+ * @returns {Promise<Boolean>} true if the file exists
32
+ */
33
+
34
+ export async function fileExists(path) {
14
35
  try {
15
36
  await stat(path)
16
37
  return true;