@wix/origami-site-express 0.1.1 → 0.1.2
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 +2 -0
- package/package.json +2 -2
- package/src/adapter/index.d.ts +1 -1
- package/src/adapter/index.js +3 -1
- package/src/adapter/project.js +19 -0
- package/src/index.d.ts +4 -0
- package/src/index.js +7 -1
- package/src/server/build.js +25 -0
- package/src/server/env.js +1 -1
- package/src/server/manifest.js +37 -0
- package/src/server/prod.js +32 -0
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ The site has no server code. Its scripts call the bin:
|
|
|
8
8
|
| Script | Command | What it does |
|
|
9
9
|
|---|---|---|
|
|
10
10
|
| `yarn dev` | `origami-site-express dev` | Vite in middleware mode + Express + the core in dev mode: hot reload, live routing |
|
|
11
|
+
| `yarn build` | `origami-site-express build` | Vite client build into `dist/client`, the route table into `dist/server/origami-routes.json` |
|
|
12
|
+
| `yarn start` | `origami-site-express start` | Serves `dist/` with the core in prod mode. No Vite at runtime. Refuses to start without a matching build. |
|
|
11
13
|
|
|
12
14
|
`PORT` sets the port (default 3000). `yarn dev` listens on `localhost` only, because it serves the site's source
|
|
13
15
|
through Vite; set `HOST=0.0.0.0` to reach it from another machine or a container. `VITE_WIX_CLIENT_ID` and `VITE_WIX_METASITE_ID` come from `.env`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-site-express",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Run an exported Wix origami site on Node with Express and Vite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
]
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
-
"falconPackageHash": "
|
|
68
|
+
"falconPackageHash": "3470a27832e07e4b9235a2df50d0c09302d980f76639e8987884ee39"
|
|
69
69
|
}
|
package/src/adapter/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { OrigamiAdapter } from '@wix/origami-site/types'
|
|
2
2
|
/** The adapter descriptor the CLI and the conformance suite load from `@wix/origami-site-express/adapter`. */
|
|
3
|
-
declare const adapter:
|
|
3
|
+
declare const adapter: OrigamiAdapter
|
|
4
4
|
export default adapter
|
package/src/adapter/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { driver } from './driver.js'
|
|
3
|
+
import { project } from './project.js'
|
|
3
4
|
|
|
4
5
|
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'))
|
|
5
6
|
|
|
6
|
-
/** @type {
|
|
7
|
+
/** @type {import('@wix/origami-site/types').OrigamiAdapter} */
|
|
7
8
|
export default {
|
|
8
9
|
name: pkg.name,
|
|
9
10
|
version: pkg.version,
|
|
10
11
|
framework: { name: 'express', range: pkg.dependencies.express },
|
|
12
|
+
project: ctx => project(ctx, pkg.version),
|
|
11
13
|
conformance: driver,
|
|
12
14
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** What the CLI writes for an Express site. Pure: no file system or network.
|
|
2
|
+
* @param {import('@wix/origami-site/types').ProjectContext} ctx @param {string} version
|
|
3
|
+
* @returns {import('@wix/origami-site/types').ProjectFiles} */
|
|
4
|
+
export function project(ctx, version) {
|
|
5
|
+
return {
|
|
6
|
+
files: {},
|
|
7
|
+
packageJson: {
|
|
8
|
+
scripts: { dev: 'origami-site-express dev', build: 'origami-site-express build', start: 'origami-site-express start' },
|
|
9
|
+
dependencies: { '@wix/origami-site': ctx.coreVersion, '@wix/origami-site-express': `^${version}` },
|
|
10
|
+
},
|
|
11
|
+
readme: `### Run the site
|
|
12
|
+
- \`yarn dev\`: local server on http://localhost:3000 with hot reload.
|
|
13
|
+
- \`yarn build\`: builds into \`dist/\`.
|
|
14
|
+
- \`yarn start\`: serves the build. Set \`PORT\` to change the port.
|
|
15
|
+
|
|
16
|
+
### Settings
|
|
17
|
+
\`VITE_WIX_CLIENT_ID\` and \`VITE_WIX_METASITE_ID\` in \`.env\` are used by both the browser and the server.`,
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export type CommandOptions = { root: string }
|
|
2
2
|
/** `origami-site-express dev`: Vite in middleware mode + Express + the core, live. */
|
|
3
3
|
export function dev(options: CommandOptions): Promise<void>
|
|
4
|
+
/** `origami-site-express build`: Vite client build into dist/client, route table into dist/server. */
|
|
5
|
+
export function build(options: CommandOptions): Promise<void>
|
|
6
|
+
/** `origami-site-express start`: serves the build with the core in prod mode. */
|
|
7
|
+
export function start(options: CommandOptions): Promise<void>
|
package/src/index.js
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// Each command loads only its own module, so `start` never loads Vite.
|
|
2
|
+
/** @type {typeof import('./index.js').dev} */
|
|
3
|
+
export const dev = async options => (await import('./server/dev.js')).dev(options)
|
|
4
|
+
/** @type {typeof import('./index.js').build} */
|
|
5
|
+
export const build = async options => (await import('./server/build.js')).build(options)
|
|
6
|
+
/** @type {typeof import('./index.js').start} */
|
|
7
|
+
export const start = async options => (await import('./server/prod.js')).start(options)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { build as viteBuild } from 'vite'
|
|
4
|
+
import react from '@vitejs/plugin-react'
|
|
5
|
+
import { importCoreBuild, importCoreServer } from './core.js'
|
|
6
|
+
import { CLIENT_ENTRY } from './manifest.js'
|
|
7
|
+
|
|
8
|
+
/** `origami-site-express build`: the Vite client build, then the route table it was built from.
|
|
9
|
+
* @param {{ root: string }} options */
|
|
10
|
+
export async function build({ root }) {
|
|
11
|
+
const { readSiteTree, writeBuiltTable } = await importCoreServer(root)
|
|
12
|
+
const { origamiVitePlugins } = await importCoreBuild(root)
|
|
13
|
+
// One read: the browser's page list and the server's routes come from the same table.
|
|
14
|
+
const table = await readSiteTree(root)
|
|
15
|
+
// A build that fails after Vite replaced dist/client must not leave the old table for start to accept.
|
|
16
|
+
await rm(join(root, 'dist/server/origami-routes.json'), { force: true })
|
|
17
|
+
const siteTree = { table: async () => table, onTableChange: () => () => {} }
|
|
18
|
+
await viteBuild({
|
|
19
|
+
root, configFile: false, mode: 'production',
|
|
20
|
+
plugins: [...origamiVitePlugins({ root, siteTree }), react()],
|
|
21
|
+
build: { outDir: 'dist/client', emptyOutDir: true, manifest: true, rolldownOptions: { input: CLIENT_ENTRY } },
|
|
22
|
+
})
|
|
23
|
+
// Last, so a failed client build never leaves a table that start would accept.
|
|
24
|
+
await writeBuiltTable(join(root, 'dist/server'), table)
|
|
25
|
+
}
|
package/src/server/env.js
CHANGED
|
@@ -10,6 +10,6 @@ export function siteOptions(env) {
|
|
|
10
10
|
export const port = () => Number(process.env.PORT ?? 3000)
|
|
11
11
|
|
|
12
12
|
/** Dev serves the site's source through Vite, so it stays on this machine unless HOST says otherwise.
|
|
13
|
-
* 127.0.0.1, not localhost: on macOS localhost binds only ::1. Start listens on every interface
|
|
13
|
+
* 127.0.0.1, not localhost: on macOS localhost binds only ::1. Start listens on every interface so the deployed site is reachable.
|
|
14
14
|
* @param {'dev' | 'prod'} mode */
|
|
15
15
|
export const host = mode => process.env.HOST || (mode === 'dev' ? '127.0.0.1' : undefined)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export const CLIENT_ENTRY = 'src/site/_generated/client.jsx'
|
|
5
|
+
|
|
6
|
+
/** Reads dist/client/.vite/manifest.json once, and returns the asset tags for each page.
|
|
7
|
+
* @param {string} root */
|
|
8
|
+
export function readManifest(root) {
|
|
9
|
+
/** @type {Record<string, { file: string, css?: string[], imports?: string[] }>} */
|
|
10
|
+
const manifest = JSON.parse(readFileSync(join(root, 'dist/client/.vite/manifest.json'), 'utf8'))
|
|
11
|
+
const entry = manifest[CLIENT_ENTRY]
|
|
12
|
+
if (!entry) throw new Error(`${CLIENT_ENTRY} is missing from the build manifest: run yarn build again.`)
|
|
13
|
+
/** The chunk, its static imports and all their CSS, imports' CSS first: the order Vite applies it in.
|
|
14
|
+
* @param {string} key */
|
|
15
|
+
const assets = (key, files = new Set(), css = new Set()) => {
|
|
16
|
+
const chunk = manifest[key]
|
|
17
|
+
if (!chunk || files.has(chunk.file)) return { files, css }
|
|
18
|
+
files.add(chunk.file)
|
|
19
|
+
for (const child of chunk.imports ?? []) assets(child, files, css)
|
|
20
|
+
for (const file of chunk.css ?? []) css.add(file)
|
|
21
|
+
return { files, css }
|
|
22
|
+
}
|
|
23
|
+
const shared = assets(CLIENT_ENTRY)
|
|
24
|
+
/** @param {{ itemPath?: string }} page */
|
|
25
|
+
return page => {
|
|
26
|
+
// The page chunk loads in parallel with the entry instead of after it. The built-in 404 has no itemPath: no preload.
|
|
27
|
+
const own = page.itemPath ? assets(`src/${page.itemPath}/_generated/index.jsx`) : { files: new Set(), css: new Set() }
|
|
28
|
+
const preload = [...shared.files, ...own.files].filter(file => file !== entry.file)
|
|
29
|
+
return {
|
|
30
|
+
head: [
|
|
31
|
+
...[...new Set([...shared.css, ...own.css])].map(file => `<link rel="stylesheet" href="/${file}" />`),
|
|
32
|
+
...[...new Set(preload)].map(file => `<link rel="modulepreload" href="/${file}" />`),
|
|
33
|
+
].join('\n '),
|
|
34
|
+
body: `<script type="module" src="/${entry.file}"></script>`,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { createServer } from 'node:http'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import { importCoreNode, importCoreServer } from './core.js'
|
|
6
|
+
import { catchAll } from './bridge.js'
|
|
7
|
+
import { pageHtml } from './shell.js'
|
|
8
|
+
import { readManifest } from './manifest.js'
|
|
9
|
+
import { port, siteOptions } from './env.js'
|
|
10
|
+
import { serve } from './lifecycle.js'
|
|
11
|
+
|
|
12
|
+
/** `origami-site-express start`: the build output and the core in prod mode. No Vite at runtime.
|
|
13
|
+
* @param {{ root: string }} options */
|
|
14
|
+
export async function start({ root }) {
|
|
15
|
+
// Values already in process.env win, so the host's environment overrides .env.
|
|
16
|
+
if (existsSync(join(root, '.env'))) process.loadEnvFile(join(root, '.env'))
|
|
17
|
+
const { readBuiltTable, createSite, errorResponse, serializePageData } = await importCoreServer(root)
|
|
18
|
+
const node = await importCoreNode(root)
|
|
19
|
+
const table = await readBuiltTable(join(root, 'dist/server'))
|
|
20
|
+
const site = await createSite({ root, mode: 'prod', table, ...siteOptions(process.env) })
|
|
21
|
+
const assetsFor = readManifest(root)
|
|
22
|
+
const app = express()
|
|
23
|
+
app.disable('x-powered-by')
|
|
24
|
+
// index: false keeps / from serving a stray index.html; redirect: false lets /somedir reach the core.
|
|
25
|
+
app.use('/assets', express.static(join(root, 'dist/client/assets'), { index: false, redirect: false, maxAge: '1y', immutable: true }))
|
|
26
|
+
app.use(express.static(join(root, 'dist/client'), { index: false, redirect: false }))
|
|
27
|
+
app.use(catchAll({
|
|
28
|
+
mode: 'prod', errorResponse, node, getSite: async () => site,
|
|
29
|
+
renderPage: async resolution => pageHtml(resolution, { serializePageData, ...assetsFor(resolution.page) }),
|
|
30
|
+
}))
|
|
31
|
+
await serve(createServer(app), { port: port(), onClose: () => site.close() })
|
|
32
|
+
}
|