@prairielearn/compiled-assets 3.0.1 → 3.0.3
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/CHANGELOG.md +16 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -0
- package/dist/index.test.js.map +1 -1
- package/package.json +21 -9
- package/src/index.test.ts +9 -0
- package/src/index.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @prairielearn/compiled-assets
|
|
2
2
|
|
|
3
|
+
## 3.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 24a93b8: Upgrade all JavaScript dependencies
|
|
8
|
+
- Updated dependencies [24a93b8]
|
|
9
|
+
- @prairielearn/html@4.0.3
|
|
10
|
+
|
|
11
|
+
## 3.0.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 0f7c90f: Upgrade all JavaScript dependencies
|
|
16
|
+
- Updated dependencies [0f7c90f]
|
|
17
|
+
- @prairielearn/html@4.0.2
|
|
18
|
+
|
|
3
19
|
## 3.0.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const DEFAULT_OPTIONS = {
|
|
|
14
14
|
let options = { ...DEFAULT_OPTIONS };
|
|
15
15
|
let esbuildContext = null;
|
|
16
16
|
let esbuildServer = null;
|
|
17
|
+
let relativeSourcePaths = null;
|
|
17
18
|
export async function init(newOptions) {
|
|
18
19
|
options = {
|
|
19
20
|
...DEFAULT_OPTIONS,
|
|
@@ -29,6 +30,9 @@ export async function init(newOptions) {
|
|
|
29
30
|
// new entrypoints that are added while the server is running.
|
|
30
31
|
const sourceGlob = path.join(options.sourceDirectory, '*', '*.{js,ts,css}');
|
|
31
32
|
const sourcePaths = await globby(sourceGlob);
|
|
33
|
+
// Save the result of globbing for the source paths so that we can later
|
|
34
|
+
// check if a given filename exists.
|
|
35
|
+
relativeSourcePaths = sourcePaths.map((p) => path.relative(options.sourceDirectory, p));
|
|
32
36
|
esbuildContext = await esbuild.context({
|
|
33
37
|
entryPoints: sourcePaths,
|
|
34
38
|
target: 'es6',
|
|
@@ -107,6 +111,13 @@ function compiledPath(type, sourceFile) {
|
|
|
107
111
|
assertConfigured();
|
|
108
112
|
const sourceFilePath = `${type}/${sourceFile}`;
|
|
109
113
|
if (options.dev) {
|
|
114
|
+
// To ensure that errors that would be raised in production are also raised
|
|
115
|
+
// in development, we'll check for the existence of the asset file on disk.
|
|
116
|
+
// This mirrors the production check of the file in the manifest: if a file
|
|
117
|
+
// exists on disk, it should be in the manifest.
|
|
118
|
+
if (!relativeSourcePaths?.find((p) => p === sourceFilePath)) {
|
|
119
|
+
throw new Error(`Unknown ${type} asset: ${sourceFile}`);
|
|
120
|
+
}
|
|
110
121
|
return options.publicPath + sourceFilePath.replace(/\.(js|ts)x?$/, '.js');
|
|
111
122
|
}
|
|
112
123
|
const manifest = readManifest();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,OAAqB,MAAM,SAAS,CAAC;AAE5C,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,IAAI,EAAkB,MAAM,oBAAoB,CAAC;AAE1D,MAAM,eAAe,GAAG;IACtB,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;IAC1C,eAAe,EAAE,UAAU;IAC3B,cAAc,EAAE,gBAAgB;IAChC,UAAU,EAAE,SAAS;CACtB,CAAC;AAmBF,IAAI,OAAO,GAAoC,EAAE,GAAG,eAAe,EAAE,CAAC;AACtE,IAAI,cAAc,GAAgC,IAAI,CAAC;AACvD,IAAI,aAAa,GAA+B,IAAI,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,UAA0C;IACnE,OAAO,GAAG;QACR,GAAG,eAAe;QAClB,GAAG,UAAU;KACd,CAAC;IAEF,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAC5B,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,6CAA6C;QAC7C,EAAE;QACF,0EAA0E;QAC1E,8DAA8D;QAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;QAC5E,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,cAAc,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC;YACrC,WAAW,EAAE,WAAW;YACxB,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE;gBACN,OAAO,EAAE,MAAM;gBACf,QAAQ,EAAE,MAAM;aACjB;YACD,OAAO,EAAE,OAAO,CAAC,eAAe;YAChC,MAAM,EAAE,OAAO,CAAC,cAAc;YAC9B,UAAU,EAAE,cAAc;SAC3B,CAAC,CAAC;QAEH,aAAa,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,cAAc,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,gBAAgB,EAAE,CAAC;IAEnB,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;QAClB,0EAA0E;QAC1E,sEAAsE;QACtE,6CAA6C;QAC7C,OAAO,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE;YAChD,YAAY,EAAE,IAAI;YAClB,2CAA2C;YAC3C,eAAe,EAAE,CAAC,IAAI,CAAC;YACvB,WAAW,EAAE;gBACX,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE,IAAI;aAChB;SACF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC;IAErC,6EAA6E;IAC7E,2BAA2B;IAC3B,OAAO,UAAU,GAAG,EAAE,GAAG;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAC3B;YACE,QAAQ,EAAE,IAAI;YACd,IAAI;YACJ,IAAI,EAAE,GAAG,CAAC,GAAG;YACb,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,EACD,CAAC,QAAQ,EAAE,EAAE;YACX,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC,CACF,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC;AAED,IAAI,cAAc,GAA0B,IAAI,CAAC;AACjD,SAAS,YAAY;IACnB,gBAAgB,EAAE,CAAC;IAEnB,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;QACxE,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,CAAmB,CAAC;IACnE,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,IAA+B,EAAE,UAAkB;IACvE,gBAAgB,EAAE,CAAC;IACnB,MAAM,cAAc,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,CAAC;IAE/C,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,OAAO,OAAO,CAAC,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,WAAW,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,GAAG,OAAO,CAAC,UAAU,IAAI,SAAS,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,OAAO,YAAY,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,YAAY,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,IAAI,CAAA,gBAAgB,kBAAkB,CAAC,UAAU,CAAC,aAAa,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,UAAkB;IACtD,OAAO,IAAI,CAAA,gCAAgC,sBAAsB,CAAC,UAAU,CAAC,MAAM,CAAC;AACtF,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,eAAuB,EAAE,cAAsB;IACxE,MAAM,EAAE,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAEnC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAClF,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC;QACtC,WAAW,EAAE,KAAK;QAClB,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,MAAM;QACd,SAAS,EAAE,QAAQ;QACnB,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE;YACN,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,MAAM;SACjB;QACD,UAAU,EAAE,qBAAqB;QACjC,OAAO,EAAE,eAAe;QACxB,MAAM,EAAE,cAAc;QACtB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IAEH,OAAO,WAAW,CAAC,QAAQ,CAAC;AAC9B,CAAC;AAED,SAAS,YAAY,CACnB,QAAkB,EAClB,eAAuB,EACvB,cAAsB;IAEtB,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,EAAE,EAAE;QAC9D,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAClE,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;QAC5D,QAAQ,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,eAAuB,EACvB,cAAsB;IAEtB,yEAAyE;IACzE,MAAM,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;IAChE,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAE3C,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import http from 'node:http';\nimport path from 'path';\n\nimport esbuild, { Metafile } from 'esbuild';\nimport type { RequestHandler } from 'express';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { html, HtmlSafeString } from '@prairielearn/html';\n\nconst DEFAULT_OPTIONS = {\n dev: process.env.NODE_ENV !== 'production',\n sourceDirectory: './assets',\n buildDirectory: './public/build',\n publicPath: '/build/',\n};\n\ntype AssetsManifest = Record<string, string>;\n\nexport interface CompiledAssetsOptions {\n /**\n * Whether the app is running in dev mode. If dev mode is enabled, then\n * assets will be built on the fly as they're requested. Otherwise, assets\n * should have been pre-compiled to the `buildDirectory` directory.\n */\n dev?: boolean;\n /** Root directory of assets. */\n sourceDirectory?: string;\n /** Directory where the built assets will be output to. */\n buildDirectory?: string;\n /** The path that assets will be served from, e.g. `/build/`. */\n publicPath?: string;\n}\n\nlet options: Required<CompiledAssetsOptions> = { ...DEFAULT_OPTIONS };\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\n\nexport async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<void> {\n options = {\n ...DEFAULT_OPTIONS,\n ...newOptions,\n };\n\n if (!options.publicPath.endsWith('/')) {\n options.publicPath += '/';\n }\n\n if (options.dev) {\n // Use esbuild's asset server in development.\n //\n // Note that esbuild doesn't support globs, so the server will not pick up\n // new entrypoints that are added while the server is running.\n const sourceGlob = path.join(options.sourceDirectory, '*', '*.{js,ts,css}');\n const sourcePaths = await globby(sourceGlob);\n esbuildContext = await esbuild.context({\n entryPoints: sourcePaths,\n target: 'es6',\n format: 'iife',\n sourcemap: 'inline',\n bundle: true,\n write: false,\n loader: {\n '.woff': 'file',\n '.woff2': 'file',\n },\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n });\n\n esbuildServer = await esbuildContext.serve();\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n}\n\nexport function assertConfigured(): void {\n if (!options) {\n throw new Error('@prairielearn/compiled-assets was not configured');\n }\n}\n\nexport function handler(): RequestHandler {\n assertConfigured();\n\n if (!options?.dev) {\n // We're running in production: serve all assets from the build directory.\n // Set headers to cache for as long as possible, since the assets will\n // include content hashes in their filenames.\n return expressStaticGzip(options?.buildDirectory, {\n enableBrotli: true,\n // Prefer Brotli if the client supports it.\n orderPreference: ['br'],\n serveStatic: {\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { host, port } = esbuildServer;\n\n // We're running in dev mode, so we need to boot up ESBuild to start building\n // and watching our assets.\n return function (req, res) {\n const proxyReq = http.request(\n {\n hostname: host,\n port,\n path: req.url,\n method: req.method,\n headers: req.headers,\n },\n (proxyRes) => {\n res.writeHead(proxyRes.statusCode ?? 500, proxyRes.headers);\n proxyRes.pipe(res, { end: true });\n },\n );\n req.pipe(proxyReq, { end: true });\n };\n}\n\nlet cachedManifest: AssetsManifest | null = null;\nfunction readManifest(): AssetsManifest {\n assertConfigured();\n\n if (!cachedManifest) {\n const manifestPath = path.join(options.buildDirectory, 'manifest.json');\n cachedManifest = fs.readJSONSync(manifestPath) as AssetsManifest;\n }\n\n return cachedManifest;\n}\n\nfunction compiledPath(type: 'scripts' | 'stylesheets', sourceFile: string): string {\n assertConfigured();\n const sourceFilePath = `${type}/${sourceFile}`;\n\n if (options.dev) {\n return options.publicPath + sourceFilePath.replace(/\\.(js|ts)x?$/, '.js');\n }\n\n const manifest = readManifest();\n const assetPath = manifest[sourceFilePath];\n if (!assetPath) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return `${options.publicPath}/${assetPath}`;\n}\n\nexport function compiledScriptPath(sourceFile: string): string {\n return compiledPath('scripts', sourceFile);\n}\n\nexport function compiledStylesheetPath(sourceFile: string): string {\n return compiledPath('stylesheets', sourceFile);\n}\n\nexport function compiledScriptTag(sourceFile: string): HtmlSafeString {\n return html`<script src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledStylesheetTag(sourceFile: string): HtmlSafeString {\n return html`<link rel=\"stylesheet\" href=\"${compiledStylesheetPath(sourceFile)}\" />`;\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string) {\n await fs.ensureDir(buildDirectory);\n\n const files = await globby(path.join(sourceDirectory, '*/*.{js,jsx,ts,tsx,css}'));\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es6',\n format: 'iife',\n sourcemap: 'linked',\n bundle: true,\n minify: true,\n loader: {\n '.woff': 'file',\n '.woff2': 'file',\n },\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n metafile: true,\n });\n\n return buildResult.metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): Record<string, string> {\n const manifest: Record<string, string> = {};\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = assetPath;\n });\n return manifest;\n}\n\nexport async function build(\n sourceDirectory: string,\n buildDirectory: string,\n): Promise<AssetsManifest> {\n // Remove existing assets to ensure that no stale assets are left behind.\n await fs.remove(buildDirectory);\n\n const metafile = await buildAssets(sourceDirectory, buildDirectory);\n const manifest = makeManifest(metafile, sourceDirectory, buildDirectory);\n const manifestPath = path.join(buildDirectory, 'manifest.json');\n await fs.writeJSON(manifestPath, manifest);\n\n return manifest;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import http from 'node:http';\nimport path from 'path';\n\nimport esbuild, { Metafile } from 'esbuild';\nimport type { RequestHandler } from 'express';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { html, HtmlSafeString } from '@prairielearn/html';\n\nconst DEFAULT_OPTIONS = {\n dev: process.env.NODE_ENV !== 'production',\n sourceDirectory: './assets',\n buildDirectory: './public/build',\n publicPath: '/build/',\n};\n\ntype AssetsManifest = Record<string, string>;\n\nexport interface CompiledAssetsOptions {\n /**\n * Whether the app is running in dev mode. If dev mode is enabled, then\n * assets will be built on the fly as they're requested. Otherwise, assets\n * should have been pre-compiled to the `buildDirectory` directory.\n */\n dev?: boolean;\n /** Root directory of assets. */\n sourceDirectory?: string;\n /** Directory where the built assets will be output to. */\n buildDirectory?: string;\n /** The path that assets will be served from, e.g. `/build/`. */\n publicPath?: string;\n}\n\nlet options: Required<CompiledAssetsOptions> = { ...DEFAULT_OPTIONS };\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\nlet relativeSourcePaths: string[] | null = null;\n\nexport async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<void> {\n options = {\n ...DEFAULT_OPTIONS,\n ...newOptions,\n };\n\n if (!options.publicPath.endsWith('/')) {\n options.publicPath += '/';\n }\n\n if (options.dev) {\n // Use esbuild's asset server in development.\n //\n // Note that esbuild doesn't support globs, so the server will not pick up\n // new entrypoints that are added while the server is running.\n const sourceGlob = path.join(options.sourceDirectory, '*', '*.{js,ts,css}');\n const sourcePaths = await globby(sourceGlob);\n\n // Save the result of globbing for the source paths so that we can later\n // check if a given filename exists.\n relativeSourcePaths = sourcePaths.map((p) => path.relative(options.sourceDirectory, p));\n\n esbuildContext = await esbuild.context({\n entryPoints: sourcePaths,\n target: 'es6',\n format: 'iife',\n sourcemap: 'inline',\n bundle: true,\n write: false,\n loader: {\n '.woff': 'file',\n '.woff2': 'file',\n },\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n });\n\n esbuildServer = await esbuildContext.serve();\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n}\n\nexport function assertConfigured(): void {\n if (!options) {\n throw new Error('@prairielearn/compiled-assets was not configured');\n }\n}\n\nexport function handler(): RequestHandler {\n assertConfigured();\n\n if (!options?.dev) {\n // We're running in production: serve all assets from the build directory.\n // Set headers to cache for as long as possible, since the assets will\n // include content hashes in their filenames.\n return expressStaticGzip(options?.buildDirectory, {\n enableBrotli: true,\n // Prefer Brotli if the client supports it.\n orderPreference: ['br'],\n serveStatic: {\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { host, port } = esbuildServer;\n\n // We're running in dev mode, so we need to boot up ESBuild to start building\n // and watching our assets.\n return function (req, res) {\n const proxyReq = http.request(\n {\n hostname: host,\n port,\n path: req.url,\n method: req.method,\n headers: req.headers,\n },\n (proxyRes) => {\n res.writeHead(proxyRes.statusCode ?? 500, proxyRes.headers);\n proxyRes.pipe(res, { end: true });\n },\n );\n req.pipe(proxyReq, { end: true });\n };\n}\n\nlet cachedManifest: AssetsManifest | null = null;\nfunction readManifest(): AssetsManifest {\n assertConfigured();\n\n if (!cachedManifest) {\n const manifestPath = path.join(options.buildDirectory, 'manifest.json');\n cachedManifest = fs.readJSONSync(manifestPath) as AssetsManifest;\n }\n\n return cachedManifest;\n}\n\nfunction compiledPath(type: 'scripts' | 'stylesheets', sourceFile: string): string {\n assertConfigured();\n const sourceFilePath = `${type}/${sourceFile}`;\n\n if (options.dev) {\n // To ensure that errors that would be raised in production are also raised\n // in development, we'll check for the existence of the asset file on disk.\n // This mirrors the production check of the file in the manifest: if a file\n // exists on disk, it should be in the manifest.\n if (!relativeSourcePaths?.find((p) => p === sourceFilePath)) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return options.publicPath + sourceFilePath.replace(/\\.(js|ts)x?$/, '.js');\n }\n\n const manifest = readManifest();\n const assetPath = manifest[sourceFilePath];\n if (!assetPath) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return `${options.publicPath}/${assetPath}`;\n}\n\nexport function compiledScriptPath(sourceFile: string): string {\n return compiledPath('scripts', sourceFile);\n}\n\nexport function compiledStylesheetPath(sourceFile: string): string {\n return compiledPath('stylesheets', sourceFile);\n}\n\nexport function compiledScriptTag(sourceFile: string): HtmlSafeString {\n return html`<script src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledStylesheetTag(sourceFile: string): HtmlSafeString {\n return html`<link rel=\"stylesheet\" href=\"${compiledStylesheetPath(sourceFile)}\" />`;\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string) {\n await fs.ensureDir(buildDirectory);\n\n const files = await globby(path.join(sourceDirectory, '*/*.{js,jsx,ts,tsx,css}'));\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es6',\n format: 'iife',\n sourcemap: 'linked',\n bundle: true,\n minify: true,\n loader: {\n '.woff': 'file',\n '.woff2': 'file',\n },\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n metafile: true,\n });\n\n return buildResult.metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): Record<string, string> {\n const manifest: Record<string, string> = {};\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = assetPath;\n });\n return manifest;\n}\n\nexport async function build(\n sourceDirectory: string,\n buildDirectory: string,\n): Promise<AssetsManifest> {\n // Remove existing assets to ensure that no stale assets are left behind.\n await fs.remove(buildDirectory);\n\n const metafile = await buildAssets(sourceDirectory, buildDirectory);\n const manifest = makeManifest(metafile, sourceDirectory, buildDirectory);\n const manifestPath = path.join(buildDirectory, 'manifest.json');\n await fs.writeJSON(manifestPath, manifest);\n\n return manifest;\n}\n"]}
|
package/dist/index.test.js
CHANGED
|
@@ -43,6 +43,8 @@ async function testProject(options) {
|
|
|
43
43
|
const cssText = await cssRes.text();
|
|
44
44
|
assert.match(cssText, /body\s*\{/);
|
|
45
45
|
assert.match(cssText, /color:\s*red/);
|
|
46
|
+
assert.throws(() => compiledScriptPath('nonexistent.js'), 'Unknown scripts asset: nonexistent.js');
|
|
47
|
+
assert.throws(() => compiledStylesheetPath('nonexistent.css'), 'Unknown stylesheets asset: nonexistent.css');
|
|
46
48
|
}
|
|
47
49
|
finally {
|
|
48
50
|
server.close();
|
package/dist/index.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'path';\n\nimport { assert } from 'chai';\nimport express from 'express';\nimport fs from 'fs-extra';\nimport getPort from 'get-port';\nimport fetch from 'node-fetch';\nimport tmp from 'tmp-promise';\n\nimport {\n init,\n close,\n handler,\n build,\n compiledScriptPath,\n compiledStylesheetPath,\n type CompiledAssetsOptions,\n} from './index.js';\n\nasync function testProject(options: CompiledAssetsOptions) {\n await tmp.withDir(\n async (dir) => {\n // macOS does weird things with symlinks in its tmp directories. Resolve\n // the real path so that our asset-building machinery doesn't get confused.\n const tmpDir = await fs.realpath(dir.path);\n\n const scriptsRoot = path.join(tmpDir, 'assets', 'scripts');\n await fs.ensureDir(scriptsRoot);\n\n const stylesRoot = path.join(tmpDir, 'assets', 'stylesheets');\n await fs.ensureDir(stylesRoot);\n\n const jsScriptPath = path.join(scriptsRoot, 'foo.js');\n await fs.writeFile(jsScriptPath, 'console.log(\"foo\")');\n\n const tsScriptPath = path.join(scriptsRoot, 'bar.ts');\n await fs.writeFile(tsScriptPath, 'interface Foo {};\\n\\nconsole.log(\"bar\")');\n\n const stylesPath = path.join(stylesRoot, 'baz.css');\n await fs.writeFile(stylesPath, 'body { color: red; }');\n\n if (!options.dev) {\n await build(path.join(tmpDir, 'assets'), path.join(tmpDir, 'public', 'build'));\n }\n\n await init({\n sourceDirectory: path.join(tmpDir, 'assets'),\n buildDirectory: path.join(tmpDir, 'public', 'build'),\n publicPath: '/build',\n ...options,\n });\n\n const port = await getPort();\n const app = express();\n app.use('/build', handler());\n const server = app.listen(port);\n\n try {\n const jsRes = await fetch(`http://localhost:${port}${compiledScriptPath('foo.js')}`);\n assert.isTrue(jsRes.ok);\n assert.match(await jsRes.text(), /console\\.log\\(\"foo\"\\)/);\n\n const cssRes = await fetch(`http://localhost:${port}${compiledStylesheetPath('baz.css')}`);\n assert.isTrue(cssRes.ok);\n const cssText = await cssRes.text();\n assert.match(cssText, /body\\s*\\{/);\n assert.match(cssText, /color:\\s*red/);\n\n assert.throws(\n () => compiledScriptPath('nonexistent.js'),\n 'Unknown scripts asset: nonexistent.js',\n );\n assert.throws(\n () => compiledStylesheetPath('nonexistent.css'),\n 'Unknown stylesheets asset: nonexistent.css',\n );\n } finally {\n server.close();\n }\n },\n {\n unsafeCleanup: true,\n },\n );\n}\n\ndescribe('compiled-assets', () => {\n afterEach(async () => close());\n\n it('works in dev mode', async () => {\n await testProject({ dev: true });\n });\n\n it('works in prod mode', async () => {\n await testProject({ dev: false });\n });\n});\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prairielearn/compiled-assets",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/cli.js",
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"dev": "tsc --watch --preserveWatchOutput",
|
|
15
|
-
"test": "mocha src/**/*.test.ts"
|
|
15
|
+
"test": "c8 mocha src/**/*.test.ts"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@prairielearn/html": "^4.0.
|
|
19
|
-
"commander": "^12.
|
|
20
|
-
"esbuild": "^0.21.
|
|
18
|
+
"@prairielearn/html": "^4.0.3",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"esbuild": "^0.21.5",
|
|
21
21
|
"express": "^4.19.2",
|
|
22
22
|
"express-static-gzip": "^2.1.7",
|
|
23
23
|
"fs-extra": "^11.2.0",
|
|
@@ -27,12 +27,24 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@prairielearn/tsconfig": "^0.0.0",
|
|
30
|
-
"@types/node": "^20.
|
|
30
|
+
"@types/node": "^20.14.9",
|
|
31
|
+
"c8": "^10.1.2",
|
|
31
32
|
"chai": "^5.1.1",
|
|
32
33
|
"get-port": "^7.1.0",
|
|
33
|
-
"mocha": "^10.
|
|
34
|
+
"mocha": "^10.6.0",
|
|
34
35
|
"node-fetch": "^3.3.2",
|
|
35
|
-
"tsx": "^4.
|
|
36
|
-
"typescript": "^5.
|
|
36
|
+
"tsx": "^4.16.2",
|
|
37
|
+
"typescript": "^5.5.3"
|
|
38
|
+
},
|
|
39
|
+
"c8": {
|
|
40
|
+
"reporter": [
|
|
41
|
+
"html",
|
|
42
|
+
"text-summary",
|
|
43
|
+
"cobertura"
|
|
44
|
+
],
|
|
45
|
+
"all": true,
|
|
46
|
+
"include": [
|
|
47
|
+
"src/**"
|
|
48
|
+
]
|
|
37
49
|
}
|
|
38
50
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -65,6 +65,15 @@ async function testProject(options: CompiledAssetsOptions) {
|
|
|
65
65
|
const cssText = await cssRes.text();
|
|
66
66
|
assert.match(cssText, /body\s*\{/);
|
|
67
67
|
assert.match(cssText, /color:\s*red/);
|
|
68
|
+
|
|
69
|
+
assert.throws(
|
|
70
|
+
() => compiledScriptPath('nonexistent.js'),
|
|
71
|
+
'Unknown scripts asset: nonexistent.js',
|
|
72
|
+
);
|
|
73
|
+
assert.throws(
|
|
74
|
+
() => compiledStylesheetPath('nonexistent.css'),
|
|
75
|
+
'Unknown stylesheets asset: nonexistent.css',
|
|
76
|
+
);
|
|
68
77
|
} finally {
|
|
69
78
|
server.close();
|
|
70
79
|
}
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface CompiledAssetsOptions {
|
|
|
36
36
|
let options: Required<CompiledAssetsOptions> = { ...DEFAULT_OPTIONS };
|
|
37
37
|
let esbuildContext: esbuild.BuildContext | null = null;
|
|
38
38
|
let esbuildServer: esbuild.ServeResult | null = null;
|
|
39
|
+
let relativeSourcePaths: string[] | null = null;
|
|
39
40
|
|
|
40
41
|
export async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<void> {
|
|
41
42
|
options = {
|
|
@@ -54,6 +55,11 @@ export async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<
|
|
|
54
55
|
// new entrypoints that are added while the server is running.
|
|
55
56
|
const sourceGlob = path.join(options.sourceDirectory, '*', '*.{js,ts,css}');
|
|
56
57
|
const sourcePaths = await globby(sourceGlob);
|
|
58
|
+
|
|
59
|
+
// Save the result of globbing for the source paths so that we can later
|
|
60
|
+
// check if a given filename exists.
|
|
61
|
+
relativeSourcePaths = sourcePaths.map((p) => path.relative(options.sourceDirectory, p));
|
|
62
|
+
|
|
57
63
|
esbuildContext = await esbuild.context({
|
|
58
64
|
entryPoints: sourcePaths,
|
|
59
65
|
target: 'es6',
|
|
@@ -148,6 +154,14 @@ function compiledPath(type: 'scripts' | 'stylesheets', sourceFile: string): stri
|
|
|
148
154
|
const sourceFilePath = `${type}/${sourceFile}`;
|
|
149
155
|
|
|
150
156
|
if (options.dev) {
|
|
157
|
+
// To ensure that errors that would be raised in production are also raised
|
|
158
|
+
// in development, we'll check for the existence of the asset file on disk.
|
|
159
|
+
// This mirrors the production check of the file in the manifest: if a file
|
|
160
|
+
// exists on disk, it should be in the manifest.
|
|
161
|
+
if (!relativeSourcePaths?.find((p) => p === sourceFilePath)) {
|
|
162
|
+
throw new Error(`Unknown ${type} asset: ${sourceFile}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
151
165
|
return options.publicPath + sourceFilePath.replace(/\.(js|ts)x?$/, '.js');
|
|
152
166
|
}
|
|
153
167
|
|