coralite-scripts 0.31.5 → 0.31.6

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/libs/server.js +143 -29
  2. package/package.json +2 -2
package/libs/server.js CHANGED
@@ -5,7 +5,7 @@ import chokidar from 'chokidar'
5
5
  import buildSass from './build-sass.js'
6
6
  import { displayError, displayInfo, displayWarning, displaySuccess, toCode, toMS, toTime, deleteDirectoryRecursive } from './build-utils.js'
7
7
  import { extname, join, normalize, relative, sep } from 'path'
8
- import { access, constants, mkdir, writeFile } from 'fs/promises'
8
+ import { access, constants, mkdir, readFile, writeFile } from 'fs/promises'
9
9
  import Coralite from 'coralite'
10
10
  import buildCSS from './build-css.js'
11
11
  import { existsSync, mkdirSync } from 'fs'
@@ -32,39 +32,125 @@ async function server (config, options) {
32
32
  const memoryPageSource = new Map()
33
33
 
34
34
  // start coralite
35
- displayInfo('Initializing Coralite...')
36
- const coralite = new Coralite({
37
- components: config.components,
38
- pages: config.pages,
39
- plugins: config.plugins,
40
- assets: config.assets,
41
- baseURL: config.baseURL,
42
- ignoreByAttribute: config.ignoreByAttribute,
43
- skipRenderByAttribute: config.skipRenderByAttribute,
44
- mode: 'development',
45
- output: config.output,
46
- onError: ({ level, message, error }) => {
47
- if (level === 'ERR') {
48
- displayError(message, error)
49
- } else if (level === 'WARN') {
50
- displayWarning(message)
51
- } else {
52
- displayInfo(message)
35
+ let coralite
36
+ let currentConfig = config
37
+ const pluginPaths = new Set()
38
+
39
+ /**
40
+ * Asynchronously extracts relative plugin paths from the `coralite.config.js` file.
41
+ * This function clears the existing `pluginPaths` Set, reads the configuration file
42
+ * (if it exists), and parses it line-by-line to find `import ... from '...'` statements.
43
+ * Any relative paths found in these imports are resolved against the current working
44
+ * directory and added to the `pluginPaths` Set.
45
+ *
46
+ * @returns {Promise<void>} A promise that resolves when the file has been processed.
47
+ * Fails silently if the file does not exist or cannot be read.
48
+ */
49
+ const extractPluginPaths = async () => {
50
+ pluginPaths.clear()
51
+
52
+ try {
53
+ const configPath = join(process.cwd(), 'coralite.config.js')
54
+
55
+ try {
56
+ await access(configPath, constants.F_OK)
57
+ } catch (err) {
58
+ return
59
+ }
60
+
61
+ const configContent = await readFile(configPath, 'utf-8')
62
+ const lines = configContent.split('\n')
63
+
64
+ for (const line of lines) {
65
+ if (line.trim().startsWith('import ') && line.includes(' from ')) {
66
+ const match = line.match(/from\s+['"]([^'"]+)['"]/)
67
+ if (match && match[1]) {
68
+ const importPath = match[1]
69
+
70
+ // If it's a relative path, resolve it
71
+ if (importPath.startsWith('.')) {
72
+ pluginPaths.add(join(process.cwd(), importPath))
73
+ }
74
+ }
75
+ }
53
76
  }
77
+ } catch (error) {
78
+ // ignore any other unexpected errors during reading/parsing
54
79
  }
55
- })
56
- await coralite.initialise()
57
- displaySuccess('Coralite initialized successfully')
80
+ }
58
81
 
59
- if (config.plugins) {
60
- for (const plugin of config.plugins) {
61
- if (typeof plugin.server === 'function') {
62
- await plugin.server(app, coralite)
82
+ let originalAppRouter
83
+ /**
84
+ * Asynchronously initializes or re-initializes the Coralite instance.
85
+ * This function performs a complete setup/teardown cycle, making it suitable
86
+ * for Hot Module Replacement (HMR) or development live-reloading. Its key operations include:
87
+ * 1. Backing up the original Express application router state.
88
+ * 2. Clearing the page cache.
89
+ * 3. Creating and initializing a new `Coralite` instance with the current configuration.
90
+ * 4. Resetting the Express router stack to remove stale plugin routes.
91
+ * 5. Re-registering server-side plugin routes.
92
+ * 6. Broadcasting a 'reload' signal to all connected clients via Server-Sent Events (SSE).
93
+ *
94
+ * @returns {Promise<void>} Resolves when the Coralite instance, plugins, and routing have been fully initialized.
95
+ */
96
+ const initCoralite = async () => {
97
+ if (!originalAppRouter && app._router) {
98
+ originalAppRouter = Object.assign({}, app._router)
99
+ originalAppRouter.stack = [...app._router.stack]
100
+ }
101
+ displayInfo('Initializing Coralite...')
102
+
103
+ pageCache.clear()
104
+
105
+ coralite = new Coralite({
106
+ components: currentConfig.components,
107
+ pages: currentConfig.pages,
108
+ plugins: currentConfig.plugins,
109
+ assets: currentConfig.assets,
110
+ baseURL: currentConfig.baseURL,
111
+ ignoreByAttribute: currentConfig.ignoreByAttribute,
112
+ skipRenderByAttribute: currentConfig.skipRenderByAttribute,
113
+ mode: 'development',
114
+ output: currentConfig.output,
115
+ onError: ({ level, message, error }) => {
116
+ if (level === 'ERR') {
117
+ displayError(message, error)
118
+ } else if (level === 'WARN') {
119
+ displayWarning(message)
120
+ } else {
121
+ displayInfo(message)
122
+ }
123
+ }
124
+ })
125
+
126
+ await coralite.initialise()
127
+
128
+ displaySuccess('Coralite initialized successfully')
129
+
130
+ // Reset express routing to remove old plugin routes before adding new ones
131
+ if (originalAppRouter && originalAppRouter.stack) {
132
+ // Splice the router stack back to its original length to remove newly added routes
133
+ app._router.stack.splice(originalAppRouter.stack.length)
134
+ }
135
+
136
+ if (currentConfig.plugins) {
137
+ extractPluginPaths()
138
+ for (const plugin of currentConfig.plugins) {
139
+ if (typeof plugin.server === 'function') {
140
+ await plugin.server(app, coralite)
141
+ }
63
142
  }
64
143
  }
144
+
145
+ clients.forEach(client => {
146
+ client.write(`data: reload\n\n`)
147
+ })
65
148
  }
66
149
 
150
+ await initCoralite()
151
+
67
152
  const watchPath = [
153
+ process.cwd() + '/coralite.config.js',
68
154
  config.public,
69
155
  config.pages,
70
156
  config.components
@@ -360,6 +446,8 @@ async function server (config, options) {
360
446
  let compileTimeout = null
361
447
  let isCompiling = false
362
448
  const pendingChanges = new Set()
449
+ const configPathStr = join(process.cwd(), 'coralite.config.js')
450
+
363
451
 
364
452
  // Helper function to debounce compilations
365
453
  const debounceCompile = () => {
@@ -384,8 +472,33 @@ async function server (config, options) {
384
472
  const componentChanges = changes.filter(p => p.startsWith(componentPath))
385
473
  const sassChanges = changes.filter(p => p.endsWith('.scss') || p.endsWith('.sass'))
386
474
  const cssChanges = changes.filter(p => p.endsWith('.css'))
475
+ const configChanges = changes.filter(p => p === configPathStr || Array.from(pluginPaths).some(pluginPath => p === pluginPath))
387
476
 
388
477
  try {
478
+ // Handle config changes
479
+ if (configChanges.length > 0) {
480
+ displayInfo('Configuration changed, reloading Coralite...')
481
+ // Append cache busting param to reload properly
482
+ const { pathToFileURL } = await import('url')
483
+ const bust = '?t=' + Date.now()
484
+
485
+ try {
486
+ const freshConfigModule = await import(pathToFileURL(configPathStr).toString() + bust)
487
+ const { defineConfig } = await import('./config.js')
488
+
489
+ if (freshConfigModule.default) {
490
+ currentConfig = defineConfig(freshConfigModule.default)
491
+ currentConfig.output = config.output
492
+ currentConfig.server = config.server
493
+
494
+ // Re-initialize Coralite with new config
495
+ await initCoralite()
496
+ }
497
+ } catch (err) {
498
+ displayError('Failed to reload configuration', err)
499
+ }
500
+ }
501
+
389
502
  // Handle component changes
390
503
  for (const path of componentChanges) {
391
504
  await coralite.components.setItem(path)
@@ -423,7 +536,8 @@ async function server (config, options) {
423
536
  if (pagesChanges.length > 0
424
537
  || componentChanges.length > 0
425
538
  || sassChanges.length > 0
426
- || cssChanges.length > 0) {
539
+ || cssChanges.length > 0
540
+ || configChanges.length > 0) {
427
541
  clients.forEach(client => {
428
542
  client.write(`data: reload\n\n`)
429
543
  })
@@ -449,7 +563,7 @@ async function server (config, options) {
449
563
  }
450
564
  })
451
565
  .on('change', async (path) => {
452
- // Add to pending changes and trigger debounced compilation
566
+ // We only want to trigger for things we care about or are in watchPath (but sometimes chokidar watches the whole dir)
453
567
  pendingChanges.add(path)
454
568
  debounceCompile()
455
569
  })
@@ -458,7 +572,7 @@ async function server (config, options) {
458
572
  if (path.startsWith(componentPath)) {
459
573
  // set component item
460
574
  coralite.components.setItem(path)
461
- } else if (path.endsWith('.scss') || path.endsWith('.sass')) {
575
+ } else if (path.endsWith('.scss') || path.endsWith('.sass') || path === configPathStr || Array.from(pluginPaths).some(pluginPath => path === pluginPath)) {
462
576
  // Add to pending changes and trigger debounced compilation
463
577
  pendingChanges.add(path)
464
578
  debounceCompile()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-scripts",
3
- "version": "0.31.5",
3
+ "version": "0.31.6",
4
4
  "description": "Configuration and scripts for Create Coralite.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,7 +58,7 @@
58
58
  "portfinder": "^1.0.38",
59
59
  "postcss": "^8.5.6",
60
60
  "sass": "^1.91.0",
61
- "coralite": "0.31.5"
61
+ "coralite": "0.31.6"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "premove dist && pnpm build-types",