coralite-scripts 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,16 +4,6 @@ Welcome to **Coralite starter script**, a lightweight Static Site Generator (SSG
4
4
 
5
5
  ---
6
6
 
7
- ## Prerequisites
8
-
9
- Ensure you have:
10
- - Node.js ≥ 20.x
11
- - npm or pnpm installed
12
-
13
- > Coralite uses experimental ES modules (`--experimental-vm-modules`) — make sure your Node version supports it.
14
-
15
- ---
16
-
17
7
  ## Project Structure
18
8
 
19
9
  Coralite expects a standard folder layout:
package/bin/index.js CHANGED
@@ -1,15 +1,17 @@
1
- #!/usr/bin/env -S node --experimental-vm-modules --experimental-import-meta-resolve
1
+ #!/usr/bin/env node
2
2
 
3
3
  import loadConfig from '../libs/load-config.js'
4
4
  import { Command, Argument } from 'commander'
5
5
  import server from '../libs/server.js'
6
6
  import colours from 'kleur'
7
- import buildHTML from '../libs/build-html.js'
8
- import pkg from '../package.json' with { type: 'json'}
7
+ import pkg from '../package.json' with { type: 'json' }
9
8
  import buildSass from '../libs/build-sass.js'
10
- import { join } from 'node:path'
11
- import { deleteDirectoryRecursive, copyDirectory, toMS, toTime } from '../libs/build-utils.js'
9
+ import { join, relative } from 'node:path'
10
+ import { deleteDirectoryRecursive, copyDirectory, toMS, toTime, displayError } from '../libs/build-utils.js'
12
11
  import buildCSS from '../libs/build-css.js'
12
+ import { Coralite } from 'coralite'
13
+ import { mkdir, writeFile } from 'node:fs/promises'
14
+ import ora from 'ora'
13
15
 
14
16
  // remove all Node warnings before doing anything else
15
17
  process.removeAllListeners('warning')
@@ -37,56 +39,117 @@ if (mode === 'dev') {
37
39
  } else if (mode === 'build') {
38
40
  const PAD = ' '
39
41
  const border = '─'.repeat(Math.min(process.stdout.columns, 36) / 2)
42
+ const dash = colours.gray(' ─ ')
43
+
44
+ if (options.verbose) {
45
+ // log the response time and status code
46
+ process.stdout.write('\n' + PAD + colours.yellow('Compiling Coralite... \n\n'))
47
+ process.stdout.write(border + colours.inverse(` LOGS `) + border + '\n\n')
48
+ } else {
49
+ process.stdout.write('\n' + PAD + colours.yellow('Compiling Coralite... \n\n'))
50
+ }
40
51
 
41
- // log the response time and status code
42
- process.stdout.write('\n' + PAD + colours.yellow('Compiling Coralite... \n\n'))
43
- process.stdout.write(border + colours.inverse(` LOGS `) + border + '\n\n')
44
52
  // delete old output files
45
53
  deleteDirectoryRecursive(config.output)
46
54
 
47
55
  const start = process.hrtime()
48
- const documents = await buildHTML(config)
49
- const dash = colours.gray(' ─ ')
56
+ // start coralite
57
+ const coralite = new Coralite({
58
+ templates: config.templates,
59
+ pages: config.pages,
60
+ plugins: config.plugins
61
+ })
62
+ await coralite.initialise()
63
+
64
+ let spinner
65
+ let pageCount = 0
66
+
67
+ try {
68
+ if (!options.verbose) {
69
+ spinner = ora('Building pages...').start()
70
+ }
50
71
 
51
- for (let i = 0; i < documents.length; i++) {
52
- const document = documents[i]
72
+ // compile website
73
+ await coralite.build(async (result) => {
74
+ const relDir = relative(config.pages, result.path.dirname)
75
+ const outDir = join(config.output, relDir)
76
+ const outFile = join(outDir, result.path.filename)
53
77
 
54
- process.stdout.write(toTime() + toMS(document.duration) + dash + document.item.path.pathname + '\n')
55
- }
78
+ await mkdir(outDir, { recursive: true })
79
+ await writeFile(outFile, result.html)
56
80
 
57
- const publicDir = config.public
81
+ if (options.verbose) {
82
+ process.stdout.write(toTime() + toMS(result.duration) + dash + result.path.pathname + '\n')
83
+ } else {
84
+ pageCount++
85
+ spinner.text = `Building pages... (${pageCount} completed)`
86
+ }
58
87
 
59
- if (publicDir) {
60
- copyDirectory(publicDir, config.output)
61
- }
88
+ return outFile
89
+ })
62
90
 
63
- if (config.styles) {
64
- if (config.styles.type === 'sass' || config.styles.type === 'scss') {
65
- const results = await buildSass({
66
- input: config.styles.input,
67
- output: join(config.output, 'css'),
68
- options: config.sassOptions,
69
- start
70
- })
91
+ if (!options.verbose) {
92
+ spinner.succeed(`Pages built (${pageCount} completed)`)
93
+ }
71
94
 
72
- for (let i = 0; i < results.length; i++) {
73
- const result = results[i]
95
+ const publicDir = config.public
74
96
 
75
- process.stdout.write(toTime() + toMS(result.duration) + dash + result.output + '\n')
97
+ if (publicDir) {
98
+ if (!options.verbose) {
99
+ spinner = ora('Copying public directory...').start()
76
100
  }
77
- } else if (config.styles.type === 'css') {
78
- const results = await buildCSS({
79
- input: config.styles.input,
80
- output: join(config.output, 'css'),
81
- plugins: config.cssPlugins,
82
- start
83
- })
84
-
85
- for (let i = 0; i < results.length; i++) {
86
- const result = results[i]
87
-
88
- process.stdout.write(toTime() + toMS(result.duration) + dash + result.output + '\n')
101
+ await copyDirectory(publicDir, config.output)
102
+ if (!options.verbose) {
103
+ spinner.succeed('Public directory copied')
89
104
  }
90
105
  }
106
+
107
+ if (config.styles) {
108
+ if (!options.verbose) {
109
+ spinner = ora('Building styles...').start()
110
+ }
111
+
112
+ if (config.styles.type === 'sass' || config.styles.type === 'scss') {
113
+ const results = await buildSass({
114
+ input: config.styles.input,
115
+ output: join(config.output, 'css'),
116
+ options: config.sassOptions,
117
+ start
118
+ })
119
+
120
+ for (let i = 0; i < results.length; i++) {
121
+ const result = results[i]
122
+
123
+ if (options.verbose) {
124
+ process.stdout.write(toTime() + toMS(result.duration) + dash + result.output + '\n')
125
+ }
126
+ }
127
+ } else if (config.styles.type === 'css') {
128
+ const results = await buildCSS({
129
+ input: config.styles.input,
130
+ output: join(config.output, 'css'),
131
+ plugins: config.cssPlugins,
132
+ start
133
+ })
134
+
135
+ for (let i = 0; i < results.length; i++) {
136
+ const result = results[i]
137
+
138
+ if (options.verbose) {
139
+ process.stdout.write(toTime() + toMS(result.duration) + dash + result.output + '\n')
140
+ }
141
+ }
142
+ }
143
+
144
+ if (!options.verbose) {
145
+ spinner.succeed('Styles built')
146
+ }
147
+ }
148
+ } catch (error) {
149
+ if (spinner) {
150
+ spinner.fail('Build failed')
151
+ }
152
+ displayError('Build failed', error)
153
+ process.exit(1)
91
154
  }
92
155
  }
@@ -1,6 +1,7 @@
1
1
  import colours from 'kleur'
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
+ import { cp } from 'node:fs/promises'
4
5
 
5
6
  /**
6
7
  * Creates current time in format [HH:MM:SS].mmm (milliseconds), colored with ANSI colors, and formatted as bold white string for better readability of logs or console output
@@ -53,26 +54,8 @@ export function toCode (code) {
53
54
  * @param {string} src - The source directory path to copy from
54
55
  * @param {string} dest - The destination directory path to copy to
55
56
  */
56
- export function copyDirectory (src, dest) {
57
- // Create destination directory if it doesn't exist
58
- if (!fs.existsSync(dest)) {
59
- fs.mkdirSync(dest, { recursive: true })
60
- }
61
-
62
- // Read source directory contents
63
- const entries = fs.readdirSync(src)
64
-
65
- for (const entry of entries) {
66
- const srcPath = path.join(src, entry)
67
- const destPath = path.join(dest, entry)
68
-
69
- // Check if it's a file or directory and copy accordingly
70
- if (fs.statSync(srcPath).isDirectory()) {
71
- copyDirectory(srcPath, destPath) // Recursive call for subdirectories
72
- } else {
73
- fs.copyFileSync(srcPath, destPath) // Copy files
74
- }
75
- }
57
+ export async function copyDirectory (src, dest) {
58
+ await cp(src, dest, { recursive: true })
76
59
  }
77
60
 
78
61
  /**
package/libs/server.js CHANGED
@@ -4,7 +4,7 @@ import localAccess from 'local-access'
4
4
  import chokidar from 'chokidar'
5
5
  import buildSass from './build-sass.js'
6
6
  import { displayError, displayInfo, displaySuccess, toCode, toMS, toTime } from './build-utils.js'
7
- import { extname, join, normalize } from 'path'
7
+ import { extname, join, normalize, relative, sep } from 'path'
8
8
  import { readFile, access, constants } from 'fs/promises'
9
9
  import Coralite from 'coralite'
10
10
  import buildCSS from './build-css.js'
@@ -28,6 +28,8 @@ async function server (config, options) {
28
28
 
29
29
  // track active connections.
30
30
  const clients = new Set()
31
+ const pageCache = new Map()
32
+ const memoryPageSource = new Map()
31
33
 
32
34
  // start coralite
33
35
  displayInfo('Initializing Coralite...')
@@ -165,6 +167,13 @@ async function server (config, options) {
165
167
  }
166
168
  }
167
169
 
170
+ const cacheKey = path.startsWith('/') ? path.slice(1) : path
171
+
172
+ if (pageCache.has(cacheKey)) {
173
+ res.send(pageCache.get(cacheKey))
174
+ return
175
+ }
176
+
168
177
  try {
169
178
  // first attempt to read the file directly.
170
179
  await access(path)
@@ -175,7 +184,7 @@ async function server (config, options) {
175
184
  if (!path.endsWith('.html')) {
176
185
  res.sendStatus(404)
177
186
  } else {
178
- const pathname = join(config.pages, path)
187
+ let pathname = join(config.pages, path)
179
188
 
180
189
  try {
181
190
  // if that fails, try reading from pages directory.
@@ -183,31 +192,79 @@ async function server (config, options) {
183
192
  // check if page source file exists and is readable
184
193
  await access(pathname, constants.R_OK)
185
194
  } catch {
186
- res.sendStatus(404)
195
+ // check if it is a known in memory page source
196
+ const cacheKey = path.startsWith('/') ? path.slice(1) : path
197
+ if (memoryPageSource.has(cacheKey)) {
198
+ pathname = memoryPageSource.get(cacheKey)
199
+ } else {
200
+ res.sendStatus(404)
201
+ return
202
+ }
187
203
  }
188
204
 
189
- const start = process.hrtime()
190
- let duration, dash = colours.gray(' ─ ')
191
-
192
- await coralite.pages.setItem(pathname)
193
- // build the HTML for this page using the built-in compiler.
194
- const documents = await coralite.compile(pathname)
195
- // inject a script to enable live reload via Server-Sent Events
196
- const injectedHtml = documents[0].html.replace(/<\/body>/i, `\n
197
- <script>
198
- const eventSource = new EventSource('/_/rebuild');
199
- eventSource.onmessage = function(event) {
200
- if (event.data === 'connected') return;
201
- // Reload page when file changes
202
- location.reload()
203
- }
204
- </script>
205
- </body>`)
206
-
207
- // prints time and path to the file that has been changed or added.
208
- duration = process.hrtime(start)
209
- process.stdout.write(toTime() + colours.bgGreen(' Compiled HTML ') + dash + toMS(duration) + dash + path + '\n')
210
- res.send(injectedHtml)
205
+ try {
206
+ const start = process.hrtime()
207
+ let duration, dash = colours.gray(' ─ ')
208
+
209
+ let rebuildScript = '\n<script>\n'
210
+ rebuildScript += " const eventSource = new EventSource('/_/rebuild');\n"
211
+ rebuildScript += ' eventSource.onmessage = function(event) {\n'
212
+ rebuildScript += " if (event.data === 'connected') return;\n"
213
+ rebuildScript += ' // Reload page when file changes\n'
214
+ rebuildScript += ' location.reload()\n'
215
+ rebuildScript += ' }\n'
216
+ rebuildScript += ' </script>\n'
217
+ rebuildScript += '</body>\n'
218
+
219
+ await coralite.pages.setItem(pathname)
220
+ // build the HTML for this page using the built-in compiler.
221
+ const documents = await coralite.build(pathname, (result) => {
222
+ // inject a script to enable live reload via Server-Sent Events
223
+ const injectedHtml = result.html.replace(/<\/body>/i, rebuildScript)
224
+
225
+ const relPath = relative(config.pages, result.path.pathname)
226
+ const normalizedKey = relPath.split(sep).join('/')
227
+
228
+ // map in memory page to source
229
+ if (normalizedKey !== pathname) {
230
+ memoryPageSource.set(normalizedKey, pathname)
231
+ }
232
+
233
+ // only cache pages that were out of scope of the initial page request
234
+ if (normalizedKey !== cacheKey) {
235
+ pageCache.set(normalizedKey, injectedHtml)
236
+ }
237
+
238
+ return {
239
+ path: result.path,
240
+ html: injectedHtml,
241
+ duration: result.duration
242
+ }
243
+ })
244
+
245
+ // prints time and path to the file that has been changed or added.
246
+ duration = process.hrtime(start)
247
+ process.stdout.write(toTime() + colours.bgGreen(' Compiled HTML ') + dash + toMS(duration) + dash + path + '\n')
248
+
249
+ // find the document that matches the request path
250
+ const doc = documents.find(doc => {
251
+ const relPath = relative(config.pages, doc.path.pathname)
252
+ const normalizedKey = relPath.split(sep).join('/')
253
+ return normalizedKey === cacheKey
254
+ })
255
+
256
+ if (doc) {
257
+ res.send(doc.html)
258
+ } else {
259
+ res.sendStatus(404)
260
+ }
261
+ } catch (error) {
262
+ // If headers haven't been sent, send 500
263
+ if (!res.headersSent) {
264
+ res.status(500).send(error.message)
265
+ }
266
+ displayError('Request processing failed', error)
267
+ }
211
268
  }
212
269
  }
213
270
  })
@@ -235,6 +292,8 @@ async function server (config, options) {
235
292
  compileTimeout = setTimeout(async () => {
236
293
  if (isCompiling || pendingChanges.size === 0) return
237
294
 
295
+ pageCache.clear()
296
+
238
297
  isCompiling = true
239
298
  const start = process.hrtime()
240
299
  let dash = colours.gray(' ─ ')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-scripts",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Configuration and scripts for Create Coralite.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,10 +53,11 @@
53
53
  "express": "^5.1.0",
54
54
  "kleur": "^4.1.5",
55
55
  "local-access": "^1.1.0",
56
+ "ora": "^9.1.0",
56
57
  "portfinder": "^1.0.38",
57
58
  "postcss": "^8.5.6",
58
59
  "sass": "^1.91.0",
59
- "coralite": "0.19.0"
60
+ "coralite": "0.21.0"
60
61
  },
61
62
  "scripts": {
62
63
  "build": "premove dist && pnpm build-types",