packaton 0.0.6 → 0.0.7
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 +1 -1
- package/TODO.md +1 -0
- package/index.d.ts +1 -1
- package/index.js +5 -5
- package/package.json +1 -1
- package/src/app-dev.js +2 -2
- package/src/app-prod.js +18 -55
- package/src/app.js +1 -1
- package/src/config.js +6 -6
- package/src/{WatcherDevClient.js → plugins-dev/WatcherDevClient.js} +1 -1
- package/src/{HtmlCompiler.js → plugins-prod/HtmlCompiler.js} +1 -1
- package/src/plugins-prod/cspNginxMapPlugin.js +9 -0
- package/src/{media-remaper.js → plugins-prod/media-remaper.js} +1 -1
- package/src/plugins-prod/netiflyAndCloudflareHeadersPlugin.js +19 -0
- package/src/{reportSizes.js → plugins-prod/reportSizesPlugin.js} +5 -2
- package/src/plugins-prod/sitemapPlugin.js +20 -0
- package/src/{app-router.js → router.js} +20 -20
- /package/src/{openInBrowser.js → plugins-dev/openInBrowser.js} +0 -0
- /package/src/{watcherDev.js → plugins-dev/watcherDev.js} +0 -0
- /package/src/{HtmlCompiler.test.js → plugins-prod/HtmlCompiler.test.js} +0 -0
- /package/src/{media-remaper.test.js → plugins-prod/media-remaper.test.js} +0 -0
- /package/src/{minifyCSS.js → plugins-prod/minifyCSS.js} +0 -0
- /package/src/{minifyCSS.test.js → plugins-prod/minifyCSS.test.js} +0 -0
- /package/src/{minifyHTML.js → plugins-prod/minifyHTML.js} +0 -0
- /package/src/{minifyHTML.test.js → plugins-prod/minifyHTML.test.js} +0 -0
- /package/src/{minifyJS.js → plugins-prod/minifyJS.js} +0 -0
- /package/src/{fs-utils.js → utils/fs-utils.js} +0 -0
- /package/src/{fs-utils.test.js → utils/fs-utils.test.js} +0 -0
- /package/src/{http-response.js → utils/http-response.js} +0 -0
- /package/src/{mimes.js → utils/mimes.js} +0 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ computes their corresponding CSP nonce and injects it as well.
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
## Images and Videos (immutable naming)
|
|
15
|
-
For long-term caching, [media-remaper.js](src/media-remaper.js) appends a SHA-1 hash
|
|
15
|
+
For long-term caching, [media-remaper.js](src/plugins-prod/media-remaper.js) appends a SHA-1 hash
|
|
16
16
|
to the filenames and takes care of rewriting their `src` in HTML (**only in HTML**).
|
|
17
17
|
|
|
18
18
|
If you want to use media files in CSS, create a similar function to
|
package/TODO.md
CHANGED
package/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface Config {
|
|
|
12
12
|
hotReload?: boolean // For UI dev purposes only
|
|
13
13
|
|
|
14
14
|
// Production
|
|
15
|
-
|
|
15
|
+
outputDir?: string
|
|
16
16
|
outputExtension?: string
|
|
17
17
|
minifyJS?: (js: string) => Promise<string>
|
|
18
18
|
minifyCSS?: (css: string) => Promise<string>
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Packaton } from './src/app.js'
|
|
2
|
-
export { HtmlCompiler } from './src/HtmlCompiler.js'
|
|
3
|
-
export { minifyHTML } from './src/minifyHTML.js'
|
|
4
|
-
export { minifyCSS } from './src/minifyCSS.js'
|
|
5
|
-
export { minifyJS } from './src/minifyJS.js'
|
|
6
|
-
export {
|
|
2
|
+
export { HtmlCompiler } from './src/plugins-prod/HtmlCompiler.js'
|
|
3
|
+
export { minifyHTML } from './src/plugins-prod/minifyHTML.js'
|
|
4
|
+
export { minifyCSS } from './src/plugins-prod/minifyCSS.js'
|
|
5
|
+
export { minifyJS } from './src/plugins-prod/minifyJS.js'
|
|
6
|
+
export { reportSizesPlugin } from './src/plugins-prod/reportSizesPlugin.js'
|
package/package.json
CHANGED
package/src/app-dev.js
CHANGED
package/src/app-prod.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import http from 'node:http'
|
|
2
1
|
import { cpSync } from 'node:fs'
|
|
2
|
+
import { createServer } from 'node:http'
|
|
3
3
|
import { basename, join, dirname } from 'node:path'
|
|
4
4
|
|
|
5
5
|
import { docs } from './app.js'
|
|
6
|
-
import { router } from './
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { router } from './router.js'
|
|
7
|
+
import { HtmlCompiler } from './plugins-prod/HtmlCompiler.js'
|
|
8
|
+
import { sitemapPlugin } from './plugins-prod/sitemapPlugin.js'
|
|
9
|
+
import { reportSizesPlugin } from './plugins-prod/reportSizesPlugin.js'
|
|
10
|
+
import { write, removeDir, } from './utils/fs-utils.js'
|
|
11
|
+
import { cspNginxMapPlugin } from './plugins-prod/cspNginxMapPlugin.js'
|
|
12
|
+
import { renameMediaWithHashes } from './plugins-prod/media-remaper.js'
|
|
13
|
+
import { netiflyAndCloudflareHeadersPlugin } from './plugins-prod/netiflyAndCloudflareHeadersPlugin.js'
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
/**
|
|
@@ -19,25 +22,15 @@ export async function buildStaticPages(config) {
|
|
|
19
22
|
const MEDIA_REL_URL = join(config.staticDir, 'media')
|
|
20
23
|
|
|
21
24
|
const pSource = config.srcPath
|
|
22
|
-
const pDist = config.
|
|
23
|
-
const pDistStatic = join(config.
|
|
25
|
+
const pDist = config.outputDir
|
|
26
|
+
const pDistStatic = join(config.outputDir, config.staticDir)
|
|
24
27
|
const pDistMedia = join(pDist, MEDIA_REL_URL)
|
|
25
|
-
const pDistSitemap = join(pDist, 'sitemap.txt')
|
|
26
|
-
const pDistRobots = join(pDist, 'robots.txt')
|
|
27
|
-
const pSizesReport = 'packed-sizes.json'
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const server = http.createServer(router(config))
|
|
33
|
-
server.listen(0, '127.0.0.1', async error => {
|
|
29
|
+
const server = createServer(router(config))
|
|
30
|
+
server.on('error', reject)
|
|
31
|
+
server.listen(0, '127.0.0.1', async () => {
|
|
34
32
|
docs.init(config.srcPath, config.ignore)
|
|
35
33
|
try {
|
|
36
|
-
if (error) {
|
|
37
|
-
reject(error)
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
removeDir(pDist)
|
|
42
35
|
cpSync(join(pSource, config.staticDir), pDistStatic, {
|
|
43
36
|
recursive: true,
|
|
@@ -69,27 +62,10 @@ export async function buildStaticPages(config) {
|
|
|
69
62
|
cspByRoute.push([route, doc.csp()])
|
|
70
63
|
}
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.join('\n'))
|
|
77
|
-
|
|
78
|
-
if (!isFile(pDistRobots))
|
|
79
|
-
write(pDistRobots,
|
|
80
|
-
`Sitemap: https://${config.sitemapDomain}/sitemap.txt`)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (config.cspMapEnabled) {
|
|
84
|
-
write(pDistCspNginxMap, cspByRoute.map(([route, csp]) =>
|
|
85
|
-
`${route} "${csp}";`).join('\n'))
|
|
86
|
-
|
|
87
|
-
write(CLOUDFLARE_HEADERS_FILE,
|
|
88
|
-
makeHeadersFile(cspByRoute, MEDIA_REL_URL))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
reportSizes(pSizesReport, pDist,
|
|
92
|
-
docs.routes.map(f => f + config.outputExtension))
|
|
65
|
+
sitemapPlugin(config, docs.routes)
|
|
66
|
+
reportSizesPlugin(config, docs.routes)
|
|
67
|
+
cspNginxMapPlugin(config, cspByRoute)
|
|
68
|
+
netiflyAndCloudflareHeadersPlugin(config, cspByRoute, MEDIA_REL_URL)
|
|
93
69
|
}
|
|
94
70
|
catch (error) {
|
|
95
71
|
reject(error)
|
|
@@ -104,19 +80,6 @@ export async function buildStaticPages(config) {
|
|
|
104
80
|
})
|
|
105
81
|
}
|
|
106
82
|
|
|
107
|
-
// TODO @ThinkAbout in Nginx we need to use `/index` but in CF `/`
|
|
108
|
-
function makeHeadersFile(cspByRoute, MEDIA_URL) {
|
|
109
|
-
const cspHeaders = cspByRoute.map(([route, csp]) => {
|
|
110
|
-
const r = route === '/index'
|
|
111
|
-
? '/'
|
|
112
|
-
: route
|
|
113
|
-
return `${r}\n Content-Security-Policy: ${csp}`
|
|
114
|
-
})
|
|
115
|
-
cspHeaders.push(`${MEDIA_URL}/*`)
|
|
116
|
-
cspHeaders.push(' Cache-Control: public,max-age=31536000,immutable')
|
|
117
|
-
return cspHeaders.join('\n')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
83
|
|
|
121
84
|
async function crawlRoutes({ address, port }, routes) {
|
|
122
85
|
const pages = []
|
package/src/app.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readdirSync } from 'node:fs'
|
|
|
2
2
|
import { basename, join, dirname } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { setup } from './config.js'
|
|
5
|
-
import { isFile } from './fs-utils.js'
|
|
5
|
+
import { isFile } from './utils/fs-utils.js'
|
|
6
6
|
import { devStaticPages } from './app-dev.js'
|
|
7
7
|
import { buildStaticPages } from './app-prod.js'
|
|
8
8
|
|
package/src/config.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
|
|
3
|
-
import { isDirectory } from './fs-utils.js'
|
|
4
|
-
import { openInBrowser } from './openInBrowser.js'
|
|
3
|
+
import { isDirectory } from './utils/fs-utils.js'
|
|
4
|
+
import { openInBrowser } from './plugins-dev/openInBrowser.js'
|
|
5
5
|
|
|
6
|
-
import { minifyJS } from './minifyJS.js'
|
|
7
|
-
import { minifyCSS } from './minifyCSS.js'
|
|
8
|
-
import { minifyHTML } from './minifyHTML.js'
|
|
6
|
+
import { minifyJS } from './plugins-prod/minifyJS.js'
|
|
7
|
+
import { minifyCSS } from './plugins-prod/minifyCSS.js'
|
|
8
|
+
import { minifyHTML } from './plugins-prod/minifyHTML.js'
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/** @type {{
|
|
@@ -28,7 +28,7 @@ const schema = {
|
|
|
28
28
|
|
|
29
29
|
// Production
|
|
30
30
|
outputExtension: ['.html', optional(String)],
|
|
31
|
-
|
|
31
|
+
outputDir: ['dist', optional(String)], // TODO resolve
|
|
32
32
|
minifyJS: [minifyJS, optional(Function)],
|
|
33
33
|
minifyCSS: [minifyCSS, optional(Function)],
|
|
34
34
|
minifyHTML: [minifyHTML, optional(Function)],
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { write } from '../utils/fs-utils.js'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function cspNginxMapPlugin(config, cspByRoute) {
|
|
6
|
+
const out = join(config.outputDir, '.csp-map.nginx')
|
|
7
|
+
write(out, cspByRoute.map(([route, csp]) =>
|
|
8
|
+
`${route} "${csp}";`).join('\n'))
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { write } from '../utils/fs-utils.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function netiflyAndCloudflareHeadersPlugin(config, cspByRoute, MEDIA_URL) {
|
|
6
|
+
const out = join(join(config.outputDir, config.staticDir), '_headers')
|
|
7
|
+
|
|
8
|
+
const cspHeaders = cspByRoute.map(([route, csp]) => {
|
|
9
|
+
const r = route === '/index'
|
|
10
|
+
? '/'
|
|
11
|
+
: route
|
|
12
|
+
return `${r}\n Content-Security-Policy: ${csp}`
|
|
13
|
+
})
|
|
14
|
+
cspHeaders.push(`${MEDIA_URL}/*`)
|
|
15
|
+
cspHeaders.push(' Cache-Control: public,max-age=31536000,immutable')
|
|
16
|
+
|
|
17
|
+
write(out, cspHeaders.join('\n'))
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
|
-
import { read, sizeOf, sha1, saveAsJSON, isFile } from '
|
|
2
|
+
import { read, sizeOf, sha1, saveAsJSON, isFile } from '../utils/fs-utils.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function reportSizesPlugin(config, routes) {
|
|
6
|
+
const files = routes.map(f => f + config.outputExtension)
|
|
7
|
+
const baseDir = config.outputDir
|
|
8
|
+
const reportFilename = 'packed-sizes.json'
|
|
6
9
|
const oldReport = isFile(reportFilename)
|
|
7
10
|
? JSON.parse(read(reportFilename))
|
|
8
11
|
: {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { write, isFile } from '../utils/fs-utils.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function sitemapPlugin(config, routes) {
|
|
6
|
+
if (!config.sitemapDomain)
|
|
7
|
+
return
|
|
8
|
+
|
|
9
|
+
const outMap = join(config.outputDir, 'sitemap.txt')
|
|
10
|
+
const outRobots = join(config.outputDir, 'robots.txt')
|
|
11
|
+
|
|
12
|
+
write(outMap, routes
|
|
13
|
+
.filter(r => r !== '/index')
|
|
14
|
+
.map(r => `https://${config.sitemapDomain + r}`)
|
|
15
|
+
.join('\n'))
|
|
16
|
+
|
|
17
|
+
if (!isFile(outRobots))
|
|
18
|
+
write(outRobots,
|
|
19
|
+
`Sitemap: https://${config.sitemapDomain}/sitemap.txt`)
|
|
20
|
+
}
|
|
@@ -2,43 +2,43 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
4
|
import { docs } from './app.js'
|
|
5
|
-
import { mimeFor } from './mimes.js'
|
|
6
|
-
import { devClientWatcher } from './WatcherDevClient.js'
|
|
7
|
-
import { sendError, sendJSON, servePartialContent, serveStaticAsset } from './http-response.js'
|
|
5
|
+
import { mimeFor } from './utils/mimes.js'
|
|
6
|
+
import { devClientWatcher } from './plugins-dev/WatcherDevClient.js'
|
|
7
|
+
import { sendError, sendJSON, servePartialContent, serveStaticAsset } from './utils/http-response.js'
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
const WATCHER_DEV = '/plugins-dev/watcherDev.js'
|
|
11
|
+
|
|
10
12
|
const API = {
|
|
11
13
|
watchDev: '/packaton/watch-dev'
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
const WATCHER_DEV = '/watcherDev.js'
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
/** @param {Config} config */
|
|
18
19
|
export function router({ srcPath, ignore, mode }) {
|
|
19
20
|
docs.init(srcPath, ignore)
|
|
21
|
+
const isDev = mode === 'development'
|
|
20
22
|
return async function (req, response) {
|
|
21
23
|
let url = new URL(req.url, 'http://_').pathname
|
|
22
24
|
try {
|
|
23
|
-
if (url === API.watchDev)
|
|
25
|
+
if (url === API.watchDev)
|
|
24
26
|
longPollDevHotReload(req, response)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (url === WATCHER_DEV) {
|
|
27
|
+
|
|
28
|
+
else if (url === WATCHER_DEV)
|
|
28
29
|
serveStaticAsset(response, join(import.meta.dirname, url))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (docs.hasRoute(url))
|
|
37
|
-
await serveDocument(response, docs.fileFor(url), mode === 'development')
|
|
30
|
+
|
|
31
|
+
else if (docs.hasRoute(url))
|
|
32
|
+
await serveDocument(response, docs.fileFor(url), isDev)
|
|
33
|
+
|
|
34
|
+
else if (docs.hasRoute(join(url, 'index')))
|
|
35
|
+
await serveDocument(response, docs.fileFor(join(url, 'index')), isDev)
|
|
36
|
+
|
|
38
37
|
else if (req.headers.range)
|
|
39
|
-
await servePartialContent(response, req.headers,
|
|
38
|
+
await servePartialContent(response, req.headers, join(srcPath, url))
|
|
39
|
+
|
|
40
40
|
else
|
|
41
|
-
serveStaticAsset(response,
|
|
41
|
+
serveStaticAsset(response, join(srcPath, url))
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
44
|
sendError(response, error)
|
|
@@ -51,7 +51,7 @@ async function serveDocument(response, file, isDev) {
|
|
|
51
51
|
? await readFile(file, 'utf8')
|
|
52
52
|
: (await import(file + '?' + Date.now())).default()
|
|
53
53
|
if (isDev)
|
|
54
|
-
html += `<script type="module" src="${WATCHER_DEV}"></script>`
|
|
54
|
+
html += `<script type="module" src="${WATCHER_DEV}"></script>`
|
|
55
55
|
response.setHeader('Content-Type', mimeFor('html'))
|
|
56
56
|
response.end(html)
|
|
57
57
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|