Repository navigation
Expand file tree
/
Copy pathvite.config.ts
More file actions
183 lines (176 loc) · 8.79 KB
/
Copy pathvite.config.ts
File metadata and controls
183 lines (176 loc) · 8.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { readdirSync, readFileSync } from 'node:fs'
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { basename, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { writeBundles } from '@missing-elements/h5p-author'
import { build, defineConfig, type Plugin } from 'vite'
/*
* The site's H5P part, `/h5p/`, which Vite builds into `_site/` once Eleventy has written the pages
* there (`pnpm build`):
*
* - `/h5p/build/build-page.js`, the build page's script, which `src/h5p/build/index.njk` loads. The
* player, its runtime and the library pack name their files relative to their own modules, and
* Vite emits them into `/h5p/assets/` with everything else. The player's Service Worker lands
* there too, so its scope is `/h5p/assets/h5p/`: no other page of the site is under it.
* - `/h5p/build/types/`, the h5p-author skill's type files, which the page copies for the chat
* with a source's problems (see `typeFiles()`).
* - `/h5p/bundles/`, for a chat that reaches no npm registry (see `bundles()`).
*
* `/h5p/skill/`, the h5p-author skill's files and a page of their addresses, is Eleventy's
* (`eleventy.config.js`, `src/h5p/skill/`). The page lists the bundles too, written before them:
* it names them from the author package's `contentTypes`, the list `writeBundles` writes from.
*
* Moved here from embed-my.github.io's `/build` and `/bundles/` (#7).
*/
/** Where the files a package's licence asks to travel with it are emitted, beside them. */
const ASSETS = 'h5p/assets'
/** Where the skill's type files are emitted, beside the build page that copies them. */
const TYPES = 'h5p/build/types'
/**
* Vite writes into Eleventy's `_site/`, so it cannot empty its output folder: the folders only it
* writes are emptied instead, before it writes them again.
*/
function emptyOwnFolders(): Plugin {
let outDir = ''
return {
name: 'missing-elements:empty-own-folders',
apply: 'build',
configResolved(config) {
outDir = resolve(config.root, config.build.outDir)
},
async buildStart() {
for (const folder of [ASSETS, TYPES, 'h5p/bundles']) await rm(join(outDir, folder), { recursive: true, force: true })
}
}
}
/**
* The text files that travel with the code: the runtime's licence and notice, which it asks to go
* with copies of it (GPL-3.0), and the library pack's licence list, which asks the same of its
* libraries. Vite emits the scripts, fonts and parts as hashed assets and would leave them behind.
*/
function licenceFiles(): Plugin {
const runtime = new URL('./node_modules/@missing-elements/h5p-runtime/dist/', import.meta.url)
const libraries = new URL('./node_modules/@missing-elements/h5p-libraries/', import.meta.url)
return {
name: 'missing-elements:licence-files',
apply: 'build',
generateBundle() {
for (const file of ['LICENSE.txt', 'NOTICE.txt']) {
this.emitFile({ type: 'asset', fileName: `${ASSETS}/runtime-${file}`, source: readFileSync(new URL(file, runtime)) })
}
this.emitFile({ type: 'asset', fileName: `${ASSETS}/libraries-LICENSES.txt`, source: readFileSync(new URL('libraries.txt', libraries)) })
}
}
}
/**
* `/h5p/build/types/`, the h5p-author skill's type files, as the pinned author package carries
* them since 0.7.0: `<Type>.md`, a content type's notes, an example and its fields, and
* `index.json`, the author's version and the types there is a file for.
*
* A chat in a browser has the skill's text and none of its files, so it guesses how a content type
* is written, and learns one level's names from each round of problems. The build page copies the
* files of a source's own type, and of the libraries it names, for the chat along with the
* source's problems (h5p-offline-player#172). They are the release's, so they describe the fields
* of the build this page runs.
*/
function typeFiles(): Plugin {
const author = new URL('./node_modules/@missing-elements/h5p-author/', import.meta.url)
const folder = new URL('references/types/', author)
return {
name: 'missing-elements:type-files',
apply: 'build',
generateBundle() {
const names = readdirSync(folder).filter((name) => name.endsWith('.md')).sort()
if (!names.length) throw new Error(`@missing-elements/h5p-author has no type files in ${fileURLToPath(folder)}`)
for (const name of names) this.emitFile({ type: 'asset', fileName: `${TYPES}/${name}`, source: readFileSync(new URL(name, folder)) })
const { version } = JSON.parse(readFileSync(new URL('package.json', author), 'utf8')) as { version: string }
const listing = { author: version, types: names.map((name) => name.slice(0, -'.md'.length)) }
this.emitFile({ type: 'asset', fileName: `${TYPES}/index.json`, source: `${JSON.stringify(listing, null, 2)}\n` })
}
}
}
/**
* `/h5p/bundles/`, for a chat that runs Node and can fetch a file but reaches no npm registry, as
* in ChatGPT's sandbox. The h5p-author skill builds there from two files fetched from here: the
* command line, and the bundle of the source's content type.
*
* - `<type>.zip`, one per content type in the pack (`h5p-author bundle --all`): `parts/`, the
* pack's whole index with the parts a build of that type can read, and `libraries.txt`.
* - `parts/`: every part of the pack under its own name, and its index. A bundle holds no addon,
* so a build whose text calls for one stops and names its part, which the chat fetches from here.
* - `h5p-author.mjs`: the command line as one file, everything it imports bundled in by Vite, so it
* runs with nothing installed. It takes `--libraries`: the npm package's pinned pack is not with
* it, nor the package.json `--version` reads, so `index.json` says the versions.
* - `index.json`: the author's and the pack's versions, and each bundle's file, size and parts.
* - `libraries.txt`: the pack's licence list, which asks to travel with its libraries.
*
* Written after the page, from the packages the lockfile pins: `_site/` holds them, git does not.
*/
function bundles(): Plugin {
const root = fileURLToPath(new URL('.', import.meta.url))
const author = new URL('./node_modules/@missing-elements/h5p-author/', import.meta.url)
const libraries = new URL('./node_modules/@missing-elements/h5p-libraries/', import.meta.url)
const version = async (from: URL) => (JSON.parse(await readFile(new URL('package.json', from), 'utf8')) as { version: string }).version
let outDir = ''
return {
name: 'missing-elements:bundles',
apply: 'build',
configResolved(config) {
outDir = resolve(config.root, config.build.outDir)
},
async writeBundle() {
const folder = join(outDir, 'h5p', 'bundles')
const written = await writeBundles(folder)
const parts = join(folder, 'parts')
await mkdir(parts)
const index = JSON.parse(await readFile(new URL('parts/index.json', libraries), 'utf8')) as { libraries: Array<{ folder: string }> }
for (const { folder: part } of index.libraries) await copyFile(new URL(`parts/${part}.h5p`, libraries), join(parts, `${part}.h5p`))
await copyFile(new URL('parts/index.json', libraries), join(parts, 'index.json'))
await copyFile(new URL('libraries.txt', libraries), join(folder, 'libraries.txt'))
await build({
configFile: false,
root,
logLevel: 'warn',
publicDir: false,
build: {
ssr: fileURLToPath(new URL('bin/h5p-author.mjs', author)),
outDir: folder,
emptyOutDir: false,
copyPublicDir: false,
minify: false,
reportCompressedSize: false,
rolldownOptions: { output: { entryFileNames: 'h5p-author.mjs', format: 'es', codeSplitting: false } }
},
ssr: { noExternal: true, target: 'node' }
})
const listing = {
author: await version(author),
pack: await version(libraries),
tool: 'h5p-author.mjs',
parts: 'parts/',
licences: 'libraries.txt',
bundles: written.map(({ output, type, parts, size }) => {
const [machineName, typeVersion] = type.split(' ')
return { type: machineName, version: typeVersion, file: basename(output), size, parts: parts.length }
})
}
await writeFile(join(folder, 'index.json'), `${JSON.stringify(listing, null, 2)}\n`)
}
}
}
export default defineConfig({
publicDir: false,
// A missing file is a 404, as on GitHub Pages, not the home page: the browser tests read
// `vite preview`'s answers, and an SPA's fallback would answer every address with a page.
appType: 'mpa',
plugins: [emptyOwnFolders(), licenceFiles(), typeFiles(), bundles()],
build: {
outDir: '_site',
emptyOutDir: false,
assetsDir: ASSETS,
rolldownOptions: {
input: { 'build-page': 'src/h5p/build/build-page.ts' },
output: { entryFileNames: 'h5p/build/[name].js' }
}
}
})