@prairielearn/compiled-assets 4.1.9 → 4.1.11

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @prairielearn/compiled-assets
2
2
 
3
+ ## 4.1.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 5fcb568: Migrate esbuild target to es2022
8
+
9
+ ## 4.1.10
10
+
11
+ ### Patch Changes
12
+
13
+ - df28e68: Bump dependencies
14
+
3
15
  ## 4.1.9
4
16
 
5
17
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAQ5E,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,oBAAoB,CAAC;AAS/D,MAAM,MAAM,cAAc,GAAG,MAAM,CACjC,MAAM,EACN;IACE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CACF,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8BD,wBAAsB,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EpF;AAED;;GAEG;AACH,wBAAsB,KAAK,kBAG1B;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED,wBAAgB,OAAO,sDA6BC,eAAe,OAAO,cAAc,WAkC3D;AAwCD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAGpE;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAExE;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAG1E;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAavE;AAyGD,wBAAsB,KAAK,CACzB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CAUzB","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'path';\n\nimport esbuild, { type Metafile } from 'esbuild';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { type HtmlSafeString, html } 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\nexport type AssetsManifest = Record<\n string,\n {\n assetPath: string;\n preloads: string[];\n }\n>;\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 };\n\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\n\nlet splitEsbuildContext: esbuild.BuildContext | null = null;\nlet splitEsbuildServer: esbuild.ServeResult | null = null;\n\nlet relativeSourcePaths: string[] | null = null;\n\n/**\n * Node built-in modules to mark as external.\n *\n * See https://github.com/tree-sitter/tree-sitter/pull/5546.\n */\nconst NODE_ONLY_EXTERNALS = ['fs/promises', 'module'];\n\nasync function serveEsbuildContext(context: esbuild.BuildContext): Promise<esbuild.ServeResult> {\n // This must stay in sync with the Host header workaround in `handler()`.\n // esbuild 0.25+ validates proxied requests against the server's listening host.\n return context.serve({\n host: '0.0.0.0',\n // Use an OS-assigned port to avoid esbuild's default port 8000, which\n // conflicts with mkdocs when running `make dev-docs`.\n port: 0,\n });\n}\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,jsx,tsx,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: 'es2017',\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 define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n esbuildServer = await serveEsbuildContext(esbuildContext);\n\n const splitSourceGlob = path.join(\n options.sourceDirectory,\n 'scripts',\n 'esm-bundles',\n '**',\n '*.{js,ts,jsx,tsx}',\n );\n const splitSourcePaths = await globby(splitSourceGlob);\n\n relativeSourcePaths.push(\n ...splitSourcePaths.map((p) => path.relative(options.sourceDirectory, p)),\n );\n\n splitEsbuildContext = await esbuild.context({\n entryPoints: splitSourcePaths,\n target: 'es2017',\n format: 'esm',\n sourcemap: 'inline',\n bundle: true,\n splitting: true,\n write: false,\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n splitEsbuildServer = await serveEsbuildContext(splitEsbuildContext);\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n splitEsbuildContext?.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() {\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 // 404 immediately if the file is not found.\n fallthrough: false,\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer || !splitEsbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { port } = esbuildServer;\n const { port: splitPort } = splitEsbuildServer;\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: IncomingMessage, res: ServerResponse) {\n const isSplitBundle =\n req.url?.startsWith('/scripts/esm-bundles') ||\n // Chunked assets must be served by the split server.\n req.url?.startsWith('/chunk-');\n\n // esbuild will reject requests that come from hosts other than the host on\n // which the esbuild dev server is listening:\n // https://github.com/evanw/esbuild/commit/de85afd65edec9ebc44a11e245fd9e9a2e99760d\n // https://github.com/evanw/esbuild/releases/tag/v0.25.0\n // We work around this by modifying the request headers to make it look like\n // the request is coming from localhost, which esbuild won't reject.\n const headers = structuredClone(req.headers);\n headers.host = 'localhost';\n delete headers['x-forwarded-for'];\n delete headers['x-forwarded-host'];\n delete headers['x-forwarded-proto'];\n delete headers['referer'];\n\n const proxyReq = http.request(\n {\n hostname: '127.0.0.1',\n port: isSplitBundle ? splitPort : port,\n path: req.url,\n method: req.method,\n 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;\n\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 asset = manifest[sourceFilePath];\n if (!asset) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return options.publicPath + asset.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 // Creates a script tag for an IIFE bundle.\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\nexport function compiledScriptModuleTag(sourceFile: string): HtmlSafeString {\n // Creates a module script tag for an ESM bundle.\n return html`<script type=\"module\" src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledScriptPreloadPaths(sourceFile: string): string[] {\n assertConfigured();\n\n // In dev mode, we don't have a manifest, so we can't preload anything.\n if (options.dev) return [];\n\n const manifest = readManifest();\n const asset = manifest[`scripts/${sourceFile}`];\n if (!asset) {\n throw new Error(`Unknown script asset: ${sourceFile}`);\n }\n\n return asset.preloads.map((preload) => options.publicPath + preload);\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string): Promise<Metafile> {\n await fs.ensureDir(buildDirectory);\n\n const scriptFiles = await globby(path.join(sourceDirectory, 'scripts', '*.{js,jsx,ts,tsx}'));\n const styleFiles = await globby(path.join(sourceDirectory, 'stylesheets', '*.css'));\n const files = [...scriptFiles, ...styleFiles];\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es2017',\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 define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true, // Write metadata about the build\n });\n\n // We only build ESM bundles for scripts that are split into chunks (i.e. React components). The classic\n // (IIFE) bundles can't yet be switched to `type=module` due to load-ordering constraints; see the blockers\n // tracked in https://github.com/PrairieLearn/PrairieLearn/issues/15151.\n const scriptBundleFiles = await globby(\n path.join(sourceDirectory, 'scripts', 'esm-bundles', '**/*.{js,jsx,ts,tsx}'),\n );\n const esmBundleBuildResult = await esbuild.build({\n entryPoints: scriptBundleFiles,\n target: 'es2017',\n format: 'esm',\n sourcemap: 'linked',\n bundle: true,\n splitting: true,\n minify: true,\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true,\n });\n\n // Merge the resulting metafiles.\n const metafile: Metafile = {\n inputs: { ...buildResult.metafile.inputs, ...esmBundleBuildResult.metafile.inputs },\n outputs: { ...buildResult.metafile.outputs, ...esmBundleBuildResult.metafile.outputs },\n };\n\n return metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): AssetsManifest {\n const manifest: AssetsManifest = {};\n\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n // Compute all the necessary preloads for each entrypoint. This includes\n // any code-split chunks, as well as any files that are dynamically imported.\n const preloads = new Set<string>();\n\n // Recursively walk the `imports` dependency tree\n const visit = (entry: (typeof meta)['imports'][number]) => {\n if (!['import-statement', 'dynamic-import'].includes(entry.kind)) return;\n // External imports aren't emitted to disk,\n // so they shouldn't be added to preloads.\n if (entry.external) return;\n const preloadPath = path.relative(buildDirectory, entry.path);\n if (preloads.has(preloadPath)) return;\n preloads.add(preloadPath);\n for (const imp of metafile.inputs[entry.path]?.imports ?? []) {\n visit(imp);\n }\n };\n\n for (const imp of meta.imports) {\n visit(imp);\n }\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = {\n assetPath,\n preloads: [...preloads],\n };\n });\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.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAQ5E,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,oBAAoB,CAAC;AAS/D,MAAM,MAAM,cAAc,GAAG,MAAM,CACjC,MAAM,EACN;IACE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CACF,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8BD,wBAAsB,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EpF;AAED;;GAEG;AACH,wBAAsB,KAAK,kBAG1B;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC;AAED,wBAAgB,OAAO,sDA6BC,eAAe,OAAO,cAAc,WAkC3D;AAwCD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAGpE;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAExE;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAG1E;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAavE;AAyGD,wBAAsB,KAAK,CACzB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,CAAC,CAUzB","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'path';\n\nimport esbuild, { type Metafile } from 'esbuild';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { type HtmlSafeString, html } 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\nexport type AssetsManifest = Record<\n string,\n {\n assetPath: string;\n preloads: string[];\n }\n>;\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 };\n\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\n\nlet splitEsbuildContext: esbuild.BuildContext | null = null;\nlet splitEsbuildServer: esbuild.ServeResult | null = null;\n\nlet relativeSourcePaths: string[] | null = null;\n\n/**\n * Node built-in modules to mark as external.\n *\n * See https://github.com/tree-sitter/tree-sitter/pull/5546.\n */\nconst NODE_ONLY_EXTERNALS = ['fs/promises', 'module'];\n\nasync function serveEsbuildContext(context: esbuild.BuildContext): Promise<esbuild.ServeResult> {\n // This must stay in sync with the Host header workaround in `handler()`.\n // esbuild 0.25+ validates proxied requests against the server's listening host.\n return context.serve({\n host: '0.0.0.0',\n // Use an OS-assigned port to avoid esbuild's default port 8000, which\n // conflicts with mkdocs when running `make dev-docs`.\n port: 0,\n });\n}\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,jsx,tsx,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: 'es2022',\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 define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n esbuildServer = await serveEsbuildContext(esbuildContext);\n\n const splitSourceGlob = path.join(\n options.sourceDirectory,\n 'scripts',\n 'esm-bundles',\n '**',\n '*.{js,ts,jsx,tsx}',\n );\n const splitSourcePaths = await globby(splitSourceGlob);\n\n relativeSourcePaths.push(\n ...splitSourcePaths.map((p) => path.relative(options.sourceDirectory, p)),\n );\n\n splitEsbuildContext = await esbuild.context({\n entryPoints: splitSourcePaths,\n target: 'es2022',\n format: 'esm',\n sourcemap: 'inline',\n bundle: true,\n splitting: true,\n write: false,\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n splitEsbuildServer = await serveEsbuildContext(splitEsbuildContext);\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n splitEsbuildContext?.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() {\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 // 404 immediately if the file is not found.\n fallthrough: false,\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer || !splitEsbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { port } = esbuildServer;\n const { port: splitPort } = splitEsbuildServer;\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: IncomingMessage, res: ServerResponse) {\n const isSplitBundle =\n req.url?.startsWith('/scripts/esm-bundles') ||\n // Chunked assets must be served by the split server.\n req.url?.startsWith('/chunk-');\n\n // esbuild will reject requests that come from hosts other than the host on\n // which the esbuild dev server is listening:\n // https://github.com/evanw/esbuild/commit/de85afd65edec9ebc44a11e245fd9e9a2e99760d\n // https://github.com/evanw/esbuild/releases/tag/v0.25.0\n // We work around this by modifying the request headers to make it look like\n // the request is coming from localhost, which esbuild won't reject.\n const headers = structuredClone(req.headers);\n headers.host = 'localhost';\n delete headers['x-forwarded-for'];\n delete headers['x-forwarded-host'];\n delete headers['x-forwarded-proto'];\n delete headers['referer'];\n\n const proxyReq = http.request(\n {\n hostname: '127.0.0.1',\n port: isSplitBundle ? splitPort : port,\n path: req.url,\n method: req.method,\n 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;\n\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 asset = manifest[sourceFilePath];\n if (!asset) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return options.publicPath + asset.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 // Creates a script tag for an IIFE bundle.\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\nexport function compiledScriptModuleTag(sourceFile: string): HtmlSafeString {\n // Creates a module script tag for an ESM bundle.\n return html`<script type=\"module\" src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledScriptPreloadPaths(sourceFile: string): string[] {\n assertConfigured();\n\n // In dev mode, we don't have a manifest, so we can't preload anything.\n if (options.dev) return [];\n\n const manifest = readManifest();\n const asset = manifest[`scripts/${sourceFile}`];\n if (!asset) {\n throw new Error(`Unknown script asset: ${sourceFile}`);\n }\n\n return asset.preloads.map((preload) => options.publicPath + preload);\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string): Promise<Metafile> {\n await fs.ensureDir(buildDirectory);\n\n const scriptFiles = await globby(path.join(sourceDirectory, 'scripts', '*.{js,jsx,ts,tsx}'));\n const styleFiles = await globby(path.join(sourceDirectory, 'stylesheets', '*.css'));\n const files = [...scriptFiles, ...styleFiles];\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es2022',\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 define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true, // Write metadata about the build\n });\n\n // We only build ESM bundles for scripts that are split into chunks (i.e. React components). The classic\n // (IIFE) bundles can't yet be switched to `type=module` due to load-ordering constraints; see the blockers\n // tracked in https://github.com/PrairieLearn/PrairieLearn/issues/15151.\n const scriptBundleFiles = await globby(\n path.join(sourceDirectory, 'scripts', 'esm-bundles', '**/*.{js,jsx,ts,tsx}'),\n );\n const esmBundleBuildResult = await esbuild.build({\n entryPoints: scriptBundleFiles,\n target: 'es2022',\n format: 'esm',\n sourcemap: 'linked',\n bundle: true,\n splitting: true,\n minify: true,\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true,\n });\n\n // Merge the resulting metafiles.\n const metafile: Metafile = {\n inputs: { ...buildResult.metafile.inputs, ...esmBundleBuildResult.metafile.inputs },\n outputs: { ...buildResult.metafile.outputs, ...esmBundleBuildResult.metafile.outputs },\n };\n\n return metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): AssetsManifest {\n const manifest: AssetsManifest = {};\n\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n // Compute all the necessary preloads for each entrypoint. This includes\n // any code-split chunks, as well as any files that are dynamically imported.\n const preloads = new Set<string>();\n\n // Recursively walk the `imports` dependency tree\n const visit = (entry: (typeof meta)['imports'][number]) => {\n if (!['import-statement', 'dynamic-import'].includes(entry.kind)) return;\n // External imports aren't emitted to disk,\n // so they shouldn't be added to preloads.\n if (entry.external) return;\n const preloadPath = path.relative(buildDirectory, entry.path);\n if (preloads.has(preloadPath)) return;\n preloads.add(preloadPath);\n for (const imp of metafile.inputs[entry.path]?.imports ?? []) {\n visit(imp);\n }\n };\n\n for (const imp of meta.imports) {\n visit(imp);\n }\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = {\n assetPath,\n preloads: [...preloads],\n };\n });\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.js CHANGED
@@ -53,7 +53,7 @@ export async function init(newOptions) {
53
53
  relativeSourcePaths = sourcePaths.map((p) => path.relative(options.sourceDirectory, p));
54
54
  esbuildContext = await esbuild.context({
55
55
  entryPoints: sourcePaths,
56
- target: 'es2017',
56
+ target: 'es2022',
57
57
  format: 'iife',
58
58
  sourcemap: 'inline',
59
59
  bundle: true,
@@ -76,7 +76,7 @@ export async function init(newOptions) {
76
76
  relativeSourcePaths.push(...splitSourcePaths.map((p) => path.relative(options.sourceDirectory, p)));
77
77
  splitEsbuildContext = await esbuild.context({
78
78
  entryPoints: splitSourcePaths,
79
- target: 'es2017',
79
+ target: 'es2022',
80
80
  format: 'esm',
81
81
  sourcemap: 'inline',
82
82
  bundle: true,
@@ -224,7 +224,7 @@ async function buildAssets(sourceDirectory, buildDirectory) {
224
224
  const files = [...scriptFiles, ...styleFiles];
225
225
  const buildResult = await esbuild.build({
226
226
  entryPoints: files,
227
- target: 'es2017',
227
+ target: 'es2022',
228
228
  format: 'iife',
229
229
  sourcemap: 'linked',
230
230
  bundle: true,
@@ -248,7 +248,7 @@ async function buildAssets(sourceDirectory, buildDirectory) {
248
248
  const scriptBundleFiles = await globby(path.join(sourceDirectory, 'scripts', 'esm-bundles', '**/*.{js,jsx,ts,tsx}'));
249
249
  const esmBundleBuildResult = await esbuild.build({
250
250
  entryPoints: scriptBundleFiles,
251
- target: 'es2017',
251
+ target: 'es2022',
252
252
  format: 'esm',
253
253
  sourcemap: 'linked',
254
254
  bundle: true,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'path';\n\nimport esbuild, { type Metafile } from 'esbuild';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { type HtmlSafeString, html } 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\nexport type AssetsManifest = Record<\n string,\n {\n assetPath: string;\n preloads: string[];\n }\n>;\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 };\n\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\n\nlet splitEsbuildContext: esbuild.BuildContext | null = null;\nlet splitEsbuildServer: esbuild.ServeResult | null = null;\n\nlet relativeSourcePaths: string[] | null = null;\n\n/**\n * Node built-in modules to mark as external.\n *\n * See https://github.com/tree-sitter/tree-sitter/pull/5546.\n */\nconst NODE_ONLY_EXTERNALS = ['fs/promises', 'module'];\n\nasync function serveEsbuildContext(context: esbuild.BuildContext): Promise<esbuild.ServeResult> {\n // This must stay in sync with the Host header workaround in `handler()`.\n // esbuild 0.25+ validates proxied requests against the server's listening host.\n return context.serve({\n host: '0.0.0.0',\n // Use an OS-assigned port to avoid esbuild's default port 8000, which\n // conflicts with mkdocs when running `make dev-docs`.\n port: 0,\n });\n}\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,jsx,tsx,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: 'es2017',\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 define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n esbuildServer = await serveEsbuildContext(esbuildContext);\n\n const splitSourceGlob = path.join(\n options.sourceDirectory,\n 'scripts',\n 'esm-bundles',\n '**',\n '*.{js,ts,jsx,tsx}',\n );\n const splitSourcePaths = await globby(splitSourceGlob);\n\n relativeSourcePaths.push(\n ...splitSourcePaths.map((p) => path.relative(options.sourceDirectory, p)),\n );\n\n splitEsbuildContext = await esbuild.context({\n entryPoints: splitSourcePaths,\n target: 'es2017',\n format: 'esm',\n sourcemap: 'inline',\n bundle: true,\n splitting: true,\n write: false,\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n splitEsbuildServer = await serveEsbuildContext(splitEsbuildContext);\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n splitEsbuildContext?.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() {\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 // 404 immediately if the file is not found.\n fallthrough: false,\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer || !splitEsbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { port } = esbuildServer;\n const { port: splitPort } = splitEsbuildServer;\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: IncomingMessage, res: ServerResponse) {\n const isSplitBundle =\n req.url?.startsWith('/scripts/esm-bundles') ||\n // Chunked assets must be served by the split server.\n req.url?.startsWith('/chunk-');\n\n // esbuild will reject requests that come from hosts other than the host on\n // which the esbuild dev server is listening:\n // https://github.com/evanw/esbuild/commit/de85afd65edec9ebc44a11e245fd9e9a2e99760d\n // https://github.com/evanw/esbuild/releases/tag/v0.25.0\n // We work around this by modifying the request headers to make it look like\n // the request is coming from localhost, which esbuild won't reject.\n const headers = structuredClone(req.headers);\n headers.host = 'localhost';\n delete headers['x-forwarded-for'];\n delete headers['x-forwarded-host'];\n delete headers['x-forwarded-proto'];\n delete headers['referer'];\n\n const proxyReq = http.request(\n {\n hostname: '127.0.0.1',\n port: isSplitBundle ? splitPort : port,\n path: req.url,\n method: req.method,\n 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;\n\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 asset = manifest[sourceFilePath];\n if (!asset) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return options.publicPath + asset.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 // Creates a script tag for an IIFE bundle.\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\nexport function compiledScriptModuleTag(sourceFile: string): HtmlSafeString {\n // Creates a module script tag for an ESM bundle.\n return html`<script type=\"module\" src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledScriptPreloadPaths(sourceFile: string): string[] {\n assertConfigured();\n\n // In dev mode, we don't have a manifest, so we can't preload anything.\n if (options.dev) return [];\n\n const manifest = readManifest();\n const asset = manifest[`scripts/${sourceFile}`];\n if (!asset) {\n throw new Error(`Unknown script asset: ${sourceFile}`);\n }\n\n return asset.preloads.map((preload) => options.publicPath + preload);\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string): Promise<Metafile> {\n await fs.ensureDir(buildDirectory);\n\n const scriptFiles = await globby(path.join(sourceDirectory, 'scripts', '*.{js,jsx,ts,tsx}'));\n const styleFiles = await globby(path.join(sourceDirectory, 'stylesheets', '*.css'));\n const files = [...scriptFiles, ...styleFiles];\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es2017',\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 define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true, // Write metadata about the build\n });\n\n // We only build ESM bundles for scripts that are split into chunks (i.e. React components). The classic\n // (IIFE) bundles can't yet be switched to `type=module` due to load-ordering constraints; see the blockers\n // tracked in https://github.com/PrairieLearn/PrairieLearn/issues/15151.\n const scriptBundleFiles = await globby(\n path.join(sourceDirectory, 'scripts', 'esm-bundles', '**/*.{js,jsx,ts,tsx}'),\n );\n const esmBundleBuildResult = await esbuild.build({\n entryPoints: scriptBundleFiles,\n target: 'es2017',\n format: 'esm',\n sourcemap: 'linked',\n bundle: true,\n splitting: true,\n minify: true,\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true,\n });\n\n // Merge the resulting metafiles.\n const metafile: Metafile = {\n inputs: { ...buildResult.metafile.inputs, ...esmBundleBuildResult.metafile.inputs },\n outputs: { ...buildResult.metafile.outputs, ...esmBundleBuildResult.metafile.outputs },\n };\n\n return metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): AssetsManifest {\n const manifest: AssetsManifest = {};\n\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n // Compute all the necessary preloads for each entrypoint. This includes\n // any code-split chunks, as well as any files that are dynamically imported.\n const preloads = new Set<string>();\n\n // Recursively walk the `imports` dependency tree\n const visit = (entry: (typeof meta)['imports'][number]) => {\n if (!['import-statement', 'dynamic-import'].includes(entry.kind)) return;\n // External imports aren't emitted to disk,\n // so they shouldn't be added to preloads.\n if (entry.external) return;\n const preloadPath = path.relative(buildDirectory, entry.path);\n if (preloads.has(preloadPath)) return;\n preloads.add(preloadPath);\n for (const imp of metafile.inputs[entry.path]?.imports ?? []) {\n visit(imp);\n }\n };\n\n for (const imp of meta.imports) {\n visit(imp);\n }\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = {\n assetPath,\n preloads: [...preloads],\n };\n });\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, { type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'path';\n\nimport esbuild, { type Metafile } from 'esbuild';\nimport expressStaticGzip from 'express-static-gzip';\nimport fs from 'fs-extra';\nimport { globby } from 'globby';\n\nimport { type HtmlSafeString, html } 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\nexport type AssetsManifest = Record<\n string,\n {\n assetPath: string;\n preloads: string[];\n }\n>;\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 };\n\nlet esbuildContext: esbuild.BuildContext | null = null;\nlet esbuildServer: esbuild.ServeResult | null = null;\n\nlet splitEsbuildContext: esbuild.BuildContext | null = null;\nlet splitEsbuildServer: esbuild.ServeResult | null = null;\n\nlet relativeSourcePaths: string[] | null = null;\n\n/**\n * Node built-in modules to mark as external.\n *\n * See https://github.com/tree-sitter/tree-sitter/pull/5546.\n */\nconst NODE_ONLY_EXTERNALS = ['fs/promises', 'module'];\n\nasync function serveEsbuildContext(context: esbuild.BuildContext): Promise<esbuild.ServeResult> {\n // This must stay in sync with the Host header workaround in `handler()`.\n // esbuild 0.25+ validates proxied requests against the server's listening host.\n return context.serve({\n host: '0.0.0.0',\n // Use an OS-assigned port to avoid esbuild's default port 8000, which\n // conflicts with mkdocs when running `make dev-docs`.\n port: 0,\n });\n}\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,jsx,tsx,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: 'es2022',\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 define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n esbuildServer = await serveEsbuildContext(esbuildContext);\n\n const splitSourceGlob = path.join(\n options.sourceDirectory,\n 'scripts',\n 'esm-bundles',\n '**',\n '*.{js,ts,jsx,tsx}',\n );\n const splitSourcePaths = await globby(splitSourceGlob);\n\n relativeSourcePaths.push(\n ...splitSourcePaths.map((p) => path.relative(options.sourceDirectory, p)),\n );\n\n splitEsbuildContext = await esbuild.context({\n entryPoints: splitSourcePaths,\n target: 'es2022',\n format: 'esm',\n sourcemap: 'inline',\n bundle: true,\n splitting: true,\n write: false,\n outbase: options.sourceDirectory,\n outdir: options.buildDirectory,\n entryNames: '[dir]/[name]',\n define: {\n 'process.env.NODE_ENV': '\"development\"',\n },\n external: NODE_ONLY_EXTERNALS,\n });\n splitEsbuildServer = await serveEsbuildContext(splitEsbuildContext);\n }\n}\n\n/**\n * Shuts down the development assets compiler if it is running.\n */\nexport async function close() {\n esbuildContext?.dispose();\n splitEsbuildContext?.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() {\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 // 404 immediately if the file is not found.\n fallthrough: false,\n maxAge: '31557600',\n immutable: true,\n },\n });\n }\n\n if (!esbuildServer || !splitEsbuildServer) {\n throw new Error('esbuild server not initialized');\n }\n\n const { port } = esbuildServer;\n const { port: splitPort } = splitEsbuildServer;\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: IncomingMessage, res: ServerResponse) {\n const isSplitBundle =\n req.url?.startsWith('/scripts/esm-bundles') ||\n // Chunked assets must be served by the split server.\n req.url?.startsWith('/chunk-');\n\n // esbuild will reject requests that come from hosts other than the host on\n // which the esbuild dev server is listening:\n // https://github.com/evanw/esbuild/commit/de85afd65edec9ebc44a11e245fd9e9a2e99760d\n // https://github.com/evanw/esbuild/releases/tag/v0.25.0\n // We work around this by modifying the request headers to make it look like\n // the request is coming from localhost, which esbuild won't reject.\n const headers = structuredClone(req.headers);\n headers.host = 'localhost';\n delete headers['x-forwarded-for'];\n delete headers['x-forwarded-host'];\n delete headers['x-forwarded-proto'];\n delete headers['referer'];\n\n const proxyReq = http.request(\n {\n hostname: '127.0.0.1',\n port: isSplitBundle ? splitPort : port,\n path: req.url,\n method: req.method,\n 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;\n\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 asset = manifest[sourceFilePath];\n if (!asset) {\n throw new Error(`Unknown ${type} asset: ${sourceFile}`);\n }\n\n return options.publicPath + asset.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 // Creates a script tag for an IIFE bundle.\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\nexport function compiledScriptModuleTag(sourceFile: string): HtmlSafeString {\n // Creates a module script tag for an ESM bundle.\n return html`<script type=\"module\" src=\"${compiledScriptPath(sourceFile)}\"></script>`;\n}\n\nexport function compiledScriptPreloadPaths(sourceFile: string): string[] {\n assertConfigured();\n\n // In dev mode, we don't have a manifest, so we can't preload anything.\n if (options.dev) return [];\n\n const manifest = readManifest();\n const asset = manifest[`scripts/${sourceFile}`];\n if (!asset) {\n throw new Error(`Unknown script asset: ${sourceFile}`);\n }\n\n return asset.preloads.map((preload) => options.publicPath + preload);\n}\n\nasync function buildAssets(sourceDirectory: string, buildDirectory: string): Promise<Metafile> {\n await fs.ensureDir(buildDirectory);\n\n const scriptFiles = await globby(path.join(sourceDirectory, 'scripts', '*.{js,jsx,ts,tsx}'));\n const styleFiles = await globby(path.join(sourceDirectory, 'stylesheets', '*.css'));\n const files = [...scriptFiles, ...styleFiles];\n const buildResult = await esbuild.build({\n entryPoints: files,\n target: 'es2022',\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 define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true, // Write metadata about the build\n });\n\n // We only build ESM bundles for scripts that are split into chunks (i.e. React components). The classic\n // (IIFE) bundles can't yet be switched to `type=module` due to load-ordering constraints; see the blockers\n // tracked in https://github.com/PrairieLearn/PrairieLearn/issues/15151.\n const scriptBundleFiles = await globby(\n path.join(sourceDirectory, 'scripts', 'esm-bundles', '**/*.{js,jsx,ts,tsx}'),\n );\n const esmBundleBuildResult = await esbuild.build({\n entryPoints: scriptBundleFiles,\n target: 'es2022',\n format: 'esm',\n sourcemap: 'linked',\n bundle: true,\n splitting: true,\n minify: true,\n entryNames: '[dir]/[name]-[hash]',\n outbase: sourceDirectory,\n outdir: buildDirectory,\n define: {\n 'process.env.NODE_ENV': '\"production\"',\n },\n external: NODE_ONLY_EXTERNALS,\n metafile: true,\n });\n\n // Merge the resulting metafiles.\n const metafile: Metafile = {\n inputs: { ...buildResult.metafile.inputs, ...esmBundleBuildResult.metafile.inputs },\n outputs: { ...buildResult.metafile.outputs, ...esmBundleBuildResult.metafile.outputs },\n };\n\n return metafile;\n}\n\nfunction makeManifest(\n metafile: Metafile,\n sourceDirectory: string,\n buildDirectory: string,\n): AssetsManifest {\n const manifest: AssetsManifest = {};\n\n Object.entries(metafile.outputs).forEach(([outputPath, meta]) => {\n if (!meta.entryPoint) return;\n\n // Compute all the necessary preloads for each entrypoint. This includes\n // any code-split chunks, as well as any files that are dynamically imported.\n const preloads = new Set<string>();\n\n // Recursively walk the `imports` dependency tree\n const visit = (entry: (typeof meta)['imports'][number]) => {\n if (!['import-statement', 'dynamic-import'].includes(entry.kind)) return;\n // External imports aren't emitted to disk,\n // so they shouldn't be added to preloads.\n if (entry.external) return;\n const preloadPath = path.relative(buildDirectory, entry.path);\n if (preloads.has(preloadPath)) return;\n preloads.add(preloadPath);\n for (const imp of metafile.inputs[entry.path]?.imports ?? []) {\n visit(imp);\n }\n };\n\n for (const imp of meta.imports) {\n visit(imp);\n }\n\n const entryPath = path.relative(sourceDirectory, meta.entryPoint);\n const assetPath = path.relative(buildDirectory, outputPath);\n manifest[entryPath] = {\n assetPath,\n preloads: [...preloads],\n };\n });\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/compiled-assets",
3
- "version": "4.1.9",
3
+ "version": "4.1.11",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,21 +15,21 @@
15
15
  "commander": "^15.0.0",
16
16
  "esbuild": "^0.28.1",
17
17
  "express-static-gzip": "^2.2.0 <3",
18
- "fs-extra": "^11.3.6",
19
- "globby": "^16.2.0",
20
- "pretty-bytes": "^7.1.0",
18
+ "fs-extra": "^11.4.0",
19
+ "globby": "^16.2.2",
20
+ "pretty-bytes": "^7.1.1",
21
21
  "tmp-promise": "^3.0.3",
22
- "@prairielearn/html": "^5.0.7"
22
+ "@prairielearn/html": "^5.0.8"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/express": "^4.17.25 <5",
26
26
  "@types/fs-extra": "^11.0.4",
27
- "@types/node": "^24.13.2",
27
+ "@types/node": "^24.13.3",
28
28
  "@typescript/native-preview": "beta",
29
- "@vitest/coverage-v8": "^4.1.9",
29
+ "@vitest/coverage-v8": "^4.1.10",
30
30
  "express": "^4.22.2 <5",
31
31
  "get-port": "^7.2.0",
32
- "vitest": "^4.1.9",
32
+ "vitest": "^4.1.10",
33
33
  "@prairielearn/tsconfig": "^2.1.0"
34
34
  },
35
35
  "scripts": {
package/src/index.ts CHANGED
@@ -90,7 +90,7 @@ export async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<
90
90
 
91
91
  esbuildContext = await esbuild.context({
92
92
  entryPoints: sourcePaths,
93
- target: 'es2017',
93
+ target: 'es2022',
94
94
  format: 'iife',
95
95
  sourcemap: 'inline',
96
96
  bundle: true,
@@ -124,7 +124,7 @@ export async function init(newOptions: Partial<CompiledAssetsOptions>): Promise<
124
124
 
125
125
  splitEsbuildContext = await esbuild.context({
126
126
  entryPoints: splitSourcePaths,
127
- target: 'es2017',
127
+ target: 'es2022',
128
128
  format: 'esm',
129
129
  sourcemap: 'inline',
130
130
  bundle: true,
@@ -304,7 +304,7 @@ async function buildAssets(sourceDirectory: string, buildDirectory: string): Pro
304
304
  const files = [...scriptFiles, ...styleFiles];
305
305
  const buildResult = await esbuild.build({
306
306
  entryPoints: files,
307
- target: 'es2017',
307
+ target: 'es2022',
308
308
  format: 'iife',
309
309
  sourcemap: 'linked',
310
310
  bundle: true,
@@ -331,7 +331,7 @@ async function buildAssets(sourceDirectory: string, buildDirectory: string): Pro
331
331
  );
332
332
  const esmBundleBuildResult = await esbuild.build({
333
333
  entryPoints: scriptBundleFiles,
334
- target: 'es2017',
334
+ target: 'es2022',
335
335
  format: 'esm',
336
336
  sourcemap: 'linked',
337
337
  bundle: true,
@@ -1 +0,0 @@
1
- $ tsgo