diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 214da10534dc..33cad9688012 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -69,6 +69,9 @@ jobs:
- name: Unit testing client
run: yarn test:client
+ - name: Unit testing libs
+ run: yarn test:libs
+
- name: Build and start server
id: server
env:
diff --git a/build/check-images.ts b/build/check-images.ts
index d7f1f3ec081c..806a6c8571e0 100644
--- a/build/check-images.ts
+++ b/build/check-images.ts
@@ -6,7 +6,7 @@ import path from "node:path";
import imagesize from "image-size";
-import { Document, Image } from "../content/index.js";
+import { Document, FileAttachment } from "../content/index.js";
import { FLAW_LEVELS, DEFAULT_LOCALE } from "../libs/constants/index.js";
import { findMatchesInText } from "./matches-in-text.js";
import * as cheerio from "cheerio";
@@ -140,12 +140,12 @@ export function checkImageReferences(
// but all our images are going to be static.
finalSrc = absoluteURL.pathname;
// We can use the `finalSrc` to look up and find the image independent
- // of the correct case because `Image.findByURL` operates case
+ // of the correct case because `FileAttachment.findByURL` operates case
// insensitively.
- // What follows uses the same algorithm as Image.findByURLWithFallback
+ // What follows uses the same algorithm as FileAttachment.findByURLWithFallback
// but only adds a filePath if it exists for the DEFAULT_LOCALE
- const filePath = Image.findByURL(finalSrc);
+ const filePath = FileAttachment.findByURL(finalSrc);
let enUSFallback = false;
if (
!filePath &&
@@ -156,7 +156,7 @@ export function checkImageReferences(
new RegExp(`^/${doc.locale}/`, "i"),
`/${DEFAULT_LOCALE}/`
);
- if (Image.findByURL(enUSFinalSrc)) {
+ if (FileAttachment.findByURL(enUSFinalSrc)) {
// Use the en-US src instead
finalSrc = enUSFinalSrc;
// Note that this `` value can work if you use the
@@ -366,7 +366,7 @@ export function checkImageWidths(
);
}
} else if (!imgSrc.includes("://") && imgSrc.startsWith("/")) {
- const filePath = Image.findByURLWithFallback(imgSrc);
+ const filePath = FileAttachment.findByURLWithFallback(imgSrc);
if (filePath) {
const dimensions = sizeOf(filePath);
img.attr("width", `${dimensions.width}`);
diff --git a/build/flaws/broken-links.ts b/build/flaws/broken-links.ts
index edfecb1ab97b..f95ed9a60963 100644
--- a/build/flaws/broken-links.ts
+++ b/build/flaws/broken-links.ts
@@ -3,7 +3,7 @@ import fs from "node:fs";
import { fromMarkdown } from "mdast-util-from-markdown";
import { visit } from "unist-util-visit";
-import { Document, Redirect, Image } from "../../content/index.js";
+import { Document, Redirect, FileAttachment } from "../../content/index.js";
import { findMatchesInText } from "../matches-in-text.js";
import {
DEFAULT_LOCALE,
@@ -277,8 +277,8 @@ export function getBrokenLinksFlaws(
const absoluteURL = new URL(href, "http://www.example.com");
const found = Document.findByURL(hrefNormalized);
if (!found) {
- // Before we give up, check if it's an image.
- if (!Image.findByURLWithFallback(hrefNormalized)) {
+ // Before we give up, check if it's an attachment.
+ if (!FileAttachment.findByURLWithFallback(hrefNormalized)) {
// Even if it's a redirect, it's still a flaw, but it'll be nice to
// know what it *should* be.
const resolved = Redirect.resolve(hrefNormalized);
diff --git a/build/index.ts b/build/index.ts
index b30f9918cae9..01f9880fc257 100644
--- a/build/index.ts
+++ b/build/index.ts
@@ -33,7 +33,7 @@ import LANGUAGES_RAW from "../libs/languages/index.js";
import { safeDecodeURIComponent } from "../kumascript/src/api/util.js";
import { wrapTables } from "./wrap-tables.js";
import {
- getAdjacentImages,
+ getAdjacentFileAttachments,
injectLoadingLazyAttributes,
injectNoTranslate,
makeTOC,
@@ -382,8 +382,8 @@ export async function buildDocument(
// The checkImageReferences() does 2 things. Checks image *references* and
// it returns which images it checked. But we'll need to complement any
// other images in the folder.
- getAdjacentImages(path.dirname(document.fileInfo.path)).forEach((fp) =>
- fileAttachments.add(fp)
+ getAdjacentFileAttachments(path.dirname(document.fileInfo.path)).forEach(
+ (fp) => fileAttachments.add(fp)
);
// Check the img tags for possible flaws and possible build-time rewrites
diff --git a/build/utils.ts b/build/utils.ts
index 9b76c2b8e266..f1d6151bfc53 100644
--- a/build/utils.ts
+++ b/build/utils.ts
@@ -12,8 +12,11 @@ import imageminSvgo from "imagemin-svgo";
import { rgPath } from "@vscode/ripgrep";
import sanitizeFilename from "sanitize-filename";
-import { VALID_MIME_TYPES } from "../libs/constants/index.js";
-import { Image } from "../content/index.js";
+import {
+ ANY_ATTACHMENT_REGEXP,
+ VALID_MIME_TYPES,
+} from "../libs/constants/index.js";
+import { FileAttachment } from "../content/index.js";
import { spawnSync } from "node:child_process";
import { BLOG_ROOT } from "../libs/env/index.js";
@@ -184,15 +187,12 @@ export function splitSections(rawHTML) {
*
* @param {Document} document
*/
-export function getAdjacentImages(documentDirectory) {
+export function getAdjacentFileAttachments(documentDirectory: string) {
const dirents = fs.readdirSync(documentDirectory, { withFileTypes: true });
return dirents
.filter((dirent) => {
// This needs to match what we do in filecheck/checker.py
- return (
- !dirent.isDirectory() &&
- /\.(png|jpeg|jpg|gif|svg|webp)$/i.test(dirent.name)
- );
+ return !dirent.isDirectory() && ANY_ATTACHMENT_REGEXP.test(dirent.name);
})
.map((dirent) => path.join(documentDirectory, dirent.name));
}
@@ -249,9 +249,9 @@ export function postLocalFileLinks($, doc) {
const href = element.attribs.href;
// This test is merely here to quickly bail if there's no hope to find the
- // image as a local file link. There are a LOT of hyperlinks throughout
- // the content and this simple if statement means we can skip 99% of the
- // links, so it's presumed to be worth it.
+ // file attachment as a local file link. There are a LOT of hyperlinks
+ // throughout the content and this simple if statement means we can skip 99%
+ // of the links, so it's presumed to be worth it.
if (
!href ||
/^(\/|\.\.|http|#|mailto:|about:|ftp:|news:|irc:|ftp:)/i.test(href)
@@ -259,11 +259,11 @@ export function postLocalFileLinks($, doc) {
return;
}
// There are a lot of links that don't match. E.g. ``
- // So we'll look-up a lot "false positives" that are not images.
+ // So we'll look-up a lot "false positives" that are not file attachments.
// Thankfully, this lookup is fast.
const url = `${doc.mdn_url}/${href}`;
- const image = Image.findByURLWithFallback(url);
- if (image) {
+ const fileAttachment = FileAttachment.findByURLWithFallback(url);
+ if (fileAttachment) {
$(element).attr("href", url);
}
});
diff --git a/client/src/react-app.d.ts b/client/src/react-app.d.ts
index 4a3ff36d684d..6084dd416356 100644
--- a/client/src/react-app.d.ts
+++ b/client/src/react-app.d.ts
@@ -34,16 +34,41 @@ declare module "*.jpeg" {
export default src;
}
+declare module "*.mp3" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.mp4" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.ogg" {
+ const src: string;
+ export default src;
+}
+
declare module "*.png" {
const src: string;
export default src;
}
+declare module "*.webm" {
+ const src: string;
+ export default src;
+}
+
declare module "*.webp" {
const src: string;
export default src;
}
+declare module "*.woff2" {
+ const src: string;
+ export default src;
+}
+
declare module "*.svg" {
import * as React from "react";
diff --git a/client/src/setupProxy.js b/client/src/setupProxy.js
index dba0bbd277c7..8ab9ed5a2c2b 100644
--- a/client/src/setupProxy.js
+++ b/client/src/setupProxy.js
@@ -16,8 +16,8 @@ function config(app) {
app.use("/_+(flaws|translations|open|document)", proxy);
// E.g. search-index.json or index.json
app.use("**/*.json", proxy);
- // This has to match what we do in server/index.js in the catchall handler
- app.use("**/*.(png|webp|gif|jpe?g|svg)", proxy);
+ // Always update libs/constant/index.js when adding/removing extensions!
+ app.use(`**/*.(gif|jpeg|jpg|mp3|mp4|ogg|png|svg|webm|webp|woff2)`, proxy);
// All those root-level images like /favicon-48x48.png
app.use("/*.(png|webp|gif|jpe?g|svg)", proxy);
}
diff --git a/cloud-function/src/app.ts b/cloud-function/src/app.ts
index 9197425dd227..3507fce925da 100644
--- a/cloud-function/src/app.ts
+++ b/cloud-function/src/app.ts
@@ -1,6 +1,8 @@
import express, { Request, Response } from "express";
import { Router } from "express";
+import { ANY_ATTACHMENT_EXT } from "./internal/constants/index.js";
+
import { Origin } from "./env.js";
import { proxyContent } from "./handlers/proxy-content.js";
import { proxyKevel } from "./handlers/proxy-kevel.js";
@@ -48,7 +50,7 @@ router.get(
proxyContent
);
router.get(
- "/[^/]+/docs/*/*.(png|jpeg|jpg|gif|svg|webp)",
+ `/[^/]+/docs/*/*.(${ANY_ATTACHMENT_EXT.join("|")})`,
requireOrigin(Origin.main, Origin.liveSamples),
resolveIndexHTML,
proxyContent
diff --git a/cloud-function/src/utils.ts b/cloud-function/src/utils.ts
index 9479c18168a8..34f0060d6608 100644
--- a/cloud-function/src/utils.ts
+++ b/cloud-function/src/utils.ts
@@ -1,5 +1,10 @@
import { Request, Response } from "express";
+import {
+ ANY_ATTACHMENT_EXT,
+ createRegExpFromExtensions,
+} from "./internal/constants/index.js";
+
import { DEFAULT_COUNTRY } from "./constants.js";
export function getRequestCountry(req: Request): string {
@@ -45,8 +50,12 @@ export function isLiveSampleURL(url: string) {
// These are the only extensions in client/build/*/docs/*.
// `find client/build -type f | grep docs | xargs basename | sed 's/.*\.\([^.]*\)$/\1/' | sort | uniq`
-const ASSET_REGEXP = /\.(gif|html|jpeg|jpg|json|png|svg|txt|xml)$/i;
+const TEXT_EXT = ["html", "json", "svg", "txt", "xml"];
+const ANY_ATTACHMENT_REGEXP = createRegExpFromExtensions(
+ ...ANY_ATTACHMENT_EXT,
+ ...TEXT_EXT
+);
export function isAsset(url: string) {
- return ASSET_REGEXP.test(url);
+ return ANY_ATTACHMENT_REGEXP.test(url);
}
diff --git a/content/image.ts b/content/file-attachment.ts
similarity index 61%
rename from content/image.ts
rename to content/file-attachment.ts
index e977b05ca9ce..152c66298639 100644
--- a/content/image.ts
+++ b/content/file-attachment.ts
@@ -5,14 +5,52 @@ import { readChunkSync } from "read-chunk";
import imageType from "image-type";
import isSvg from "is-svg";
-import { DEFAULT_LOCALE } from "../libs/constants/index.js";
+import {
+ ANY_IMAGE_EXT,
+ AUDIO_EXT,
+ DEFAULT_LOCALE,
+ FONT_EXT,
+ VIDEO_EXT,
+ createRegExpFromExtensions,
+} from "../libs/constants/index.js";
import { ROOTS } from "../libs/env/index.js";
import { memoize, slugToFolder } from "./utils.js";
-function isImage(filePath: string) {
+function isFileAttachment(filePath: string) {
if (fs.statSync(filePath).isDirectory()) {
return false;
}
+
+ return (
+ isAudio(filePath) ||
+ isFont(filePath) ||
+ isVideo(filePath) ||
+ isImage(filePath)
+ );
+}
+
+const AUDIO_FILE_REGEXP = createRegExpFromExtensions(...AUDIO_EXT);
+const FONT_FILE_REGEXP = createRegExpFromExtensions(...FONT_EXT);
+const VIDEO_FILE_REGEXP = createRegExpFromExtensions(...VIDEO_EXT);
+const IMAGE_FILE_REGEXP = createRegExpFromExtensions(...ANY_IMAGE_EXT);
+
+function isAudio(filePath: string) {
+ return AUDIO_FILE_REGEXP.test(filePath);
+}
+
+function isFont(filePath: string) {
+ return FONT_FILE_REGEXP.test(filePath);
+}
+
+function isVideo(filePath: string) {
+ return VIDEO_FILE_REGEXP.test(filePath);
+}
+
+function isImage(filePath: string) {
+ if (!IMAGE_FILE_REGEXP.test(filePath)) {
+ return false;
+ }
+
if (filePath.toLowerCase().endsWith(".svg")) {
return isSvg(fs.readFileSync(filePath, "utf-8"));
}
@@ -37,7 +75,7 @@ function urlToFilePath(url: string) {
const find = memoize((relativePath: string) => {
return ROOTS.map((root) => path.join(root, relativePath)).find(
- (filePath) => fs.existsSync(filePath) && isImage(filePath)
+ (filePath) => fs.existsSync(filePath) && isFileAttachment(filePath)
);
});
diff --git a/content/index.ts b/content/index.ts
index 0c41d320022c..18f2b8a4476f 100644
--- a/content/index.ts
+++ b/content/index.ts
@@ -2,7 +2,7 @@ export * as Document from "./document.js";
export * as Translation from "./translation.js";
export { getPopularities } from "./popularities.js";
export * as Redirect from "./redirect.js";
-export * as Image from "./image.js";
+export * as FileAttachment from "./file-attachment.js";
export {
buildURL,
memoize,
diff --git a/filecheck/checker.ts b/filecheck/checker.ts
index 81a71498535b..742bf51baff7 100644
--- a/filecheck/checker.ts
+++ b/filecheck/checker.ts
@@ -20,10 +20,20 @@ import { MAX_FILE_SIZE } from "../libs/env/index.js";
import {
VALID_MIME_TYPES,
MAX_COMPRESSION_DIFFERENCE_PERCENTAGE,
+ createRegExpFromExtensions,
+ AUDIO_EXT,
+ VIDEO_EXT,
+ FONT_EXT,
} from "../libs/constants/index.js";
const { default: imageminPngquant } = imageminPngquantPkg;
+const BINARY_NON_IMAGE_FILE_REGEXP = createRegExpFromExtensions(
+ ...AUDIO_EXT,
+ ...VIDEO_EXT,
+ ...FONT_EXT
+);
+
function formatSize(bytes: number): string {
if (bytes > 1024 * 1024) {
return `${(bytes / 1024.0 / 1024.0).toFixed(1)}MB`;
@@ -78,6 +88,21 @@ export async function checkFile(
throw new Error(`${filePath} is 0 bytes`);
}
+ // Ensure that binary files contain what their extension indicates.
+ // Exclude images, as they're checked separately in checkCompression().
+ if (BINARY_NON_IMAGE_FILE_REGEXP.test(filePath)) {
+ const ext = filePath.split(".").pop();
+ const type = await fileTypeFromFile(filePath);
+ if (!type) {
+ throw new Error(`Failed to detect type of file attachment: ${filePath}`);
+ }
+ if (ext.toLowerCase() !== type.ext) {
+ throw new Error(
+ `Unexpected type '${type.mime}' (*.${type.ext}) detected for file attachment: ${filePath}.`
+ );
+ }
+ }
+
// FileType can't check for .svg files.
// So use special case for files called '*.svg'
if (path.extname(filePath) === ".svg") {
@@ -177,6 +202,10 @@ export async function checkFile(
);
}
+ await checkCompression(filePath, options);
+}
+
+async function checkCompression(filePath: string, options: CheckerOptions) {
const tempdir = temporaryDirectory();
const extension = path.extname(filePath).toLowerCase();
try {
@@ -189,9 +218,12 @@ export async function checkFile(
plugins.push(imageminGifsicle());
} else if (extension === ".svg") {
plugins.push(imageminSvgo());
- } else {
- throw new Error(`No plugin for ${extension}`);
}
+
+ if (!plugins.length) {
+ return;
+ }
+
const files = await imagemin([filePath], {
destination: tempdir,
plugins,
@@ -225,7 +257,7 @@ export async function checkFile(
filePath
)} is too large (${formattedBefore} > ${formattedMax}), even after compressing to ${formattedAfter}.`
);
- } else if (!options.saveCompression && stat.size > MAX_FILE_SIZE) {
+ } else if (!options.saveCompression && sizeBefore > MAX_FILE_SIZE) {
throw new FixableError(
`${getRelativePath(
filePath
diff --git a/libs/constants/index.d.ts b/libs/constants/index.d.ts
index 296aa927d7b6..04e2deaad14b 100644
--- a/libs/constants/index.d.ts
+++ b/libs/constants/index.d.ts
@@ -6,6 +6,16 @@ export const LOCALE_ALIASES: Map;
export const PREFERRED_LOCALE_COOKIE_NAME: string;
export const CSP_SCRIPT_SRC_VALUES: string[];
export const CSP_VALUE: string;
+export const AUDIO_EXT: string[];
+export const FONT_EXT: string[];
+export const BINARY_IMAGE_EXT: string[];
+export const ANY_IMAGE_EXT: string[];
+export const VIDEO_EXT: string[];
+export const ANY_ATTACHMENT_EXT: string[];
+export const BINARY_ATTACHMENT_EXT: string[];
+export const createRegExpFromExtensions: (...extensions: string[]) => RegExp;
+export const ANY_ATTACHMENT_REGEXP: RegExp;
+export const BINARY_ATTACHMENT_REGEXP: RegExp;
export const FLAW_LEVELS: Readonly>;
export const VALID_FLAW_CHECKS: Set;
export const MDN_PLUS_TITLE: string;
diff --git a/libs/constants/index.js b/libs/constants/index.js
index bf209a9e56bd..d670023c3ee2 100644
--- a/libs/constants/index.js
+++ b/libs/constants/index.js
@@ -153,6 +153,38 @@ export const cspToString = (csp) =>
export const CSP_VALUE = cspToString(CSP_DIRECTIVES);
+// Always update client/src/setupProxy.js when adding/removing extensions, or it won't work on the dev server!
+export const AUDIO_EXT = ["mp3", "ogg"];
+export const FONT_EXT = ["woff2"];
+export const BINARY_IMAGE_EXT = ["gif", "jpeg", "jpg", "png", "webp"];
+export const ANY_IMAGE_EXT = ["svg", ...BINARY_IMAGE_EXT];
+export const VIDEO_EXT = ["mp4", "webm"];
+
+export const BINARY_ATTACHMENT_EXT = [
+ ...AUDIO_EXT,
+ ...FONT_EXT,
+ ...BINARY_IMAGE_EXT,
+ ...VIDEO_EXT,
+].sort();
+
+export const ANY_ATTACHMENT_EXT = [
+ ...AUDIO_EXT,
+ ...FONT_EXT,
+ ...ANY_IMAGE_EXT,
+ ...VIDEO_EXT,
+].sort();
+
+export function createRegExpFromExtensions(...extensions) {
+ return new RegExp(`\\.(${extensions.join("|")})$`, "i");
+}
+
+export const ANY_ATTACHMENT_REGEXP = createRegExpFromExtensions(
+ ...ANY_ATTACHMENT_EXT
+);
+export const BINARY_ATTACHMENT_REGEXP = createRegExpFromExtensions(
+ ...BINARY_ATTACHMENT_EXT
+);
+
// -----
// build
// -----
@@ -202,9 +234,18 @@ export const MARKDOWN_FILENAME = "index.md";
// ---------
export const VALID_MIME_TYPES = new Set([
+ "audio/mp4",
+ "audio/mpeg",
+ "audio/ogg",
+ "audio/webm",
+ "font/woff2",
"image/png",
"image/jpeg", // this is what you get for .jpeg *and* .jpg file extensions
"image/gif",
+ "image/webp",
+ "video/mp4",
+ "video/ogg",
+ "video/webm",
]);
export const MAX_COMPRESSION_DIFFERENCE_PERCENTAGE = 25; // percent
diff --git a/libs/constants/index.test.ts b/libs/constants/index.test.ts
new file mode 100644
index 000000000000..f3d309e5d3e7
--- /dev/null
+++ b/libs/constants/index.test.ts
@@ -0,0 +1,62 @@
+import {
+ createRegExpFromExtensions,
+ ANY_ATTACHMENT_REGEXP,
+ BINARY_ATTACHMENT_REGEXP,
+} from "./index.js";
+
+describe("createRegExpFromExt", () => {
+ const regexp = createRegExpFromExtensions("foo");
+
+ it("accepts the extension", () => {
+ expect(regexp.test("test.foo")).toEqual(true);
+ });
+
+ it("accepts uppercase", () => {
+ expect(regexp.test("test.FOO")).toEqual(true);
+ });
+
+ it("rejects intermediate extensions", () => {
+ expect(regexp.test("test.foo.bar")).toEqual(false);
+ });
+
+ it("rejects other extensions", () => {
+ expect(regexp.test("test.bar")).toEqual(false);
+ });
+
+ it("rejects extensions starting with it", () => {
+ expect(regexp.test("test.foob")).toEqual(false);
+ });
+
+ it("rejects extensions ending with it", () => {
+ expect(regexp.test("test.afoo")).toEqual(false);
+ });
+});
+
+describe("ANY_ATTACHMENT_REGEXP", () => {
+ const regexp = ANY_ATTACHMENT_REGEXP;
+ it("accepts audio files", () => {
+ expect(regexp.test("audio.mp3")).toEqual(true);
+ });
+
+ it("accepts video files", () => {
+ expect(regexp.test("video.mp4")).toEqual(true);
+ });
+
+ it("accepts font files", () => {
+ expect(regexp.test("diagram.svg")).toEqual(true);
+ });
+
+ ["index.html", "index.json", "index.md", "contributors.txt"].forEach(
+ (filename) =>
+ it(`rejects ${filename}`, () => {
+ expect(regexp.test(filename)).toEqual(false);
+ })
+ );
+});
+
+describe("BINARY_ATTACHMENT_REGEXP", () => {
+ const regexp = BINARY_ATTACHMENT_REGEXP;
+ it("rejects svg files", () => {
+ expect(regexp.test("diagram.svg")).toEqual(false);
+ });
+});
diff --git a/package.json b/package.json
index e653c15e6cfa..562874c46fe9 100644
--- a/package.json
+++ b/package.json
@@ -42,12 +42,13 @@
"start:static-server": "ts-node server/static.ts",
"style-dictionary": "style-dictionary build -c sd-config.js",
"stylelint": "stylelint \"**/*.scss\"",
- "test": "yarn prettier-check && yarn test:client && yarn test:kumascript && yarn test:content && yarn test:testing",
+ "test": "yarn prettier-check && yarn test:client && yarn test:kumascript && yarn test:libs && yarn test:content && yarn test:testing",
"test:client": "cd client && tsc --noEmit && cross-env NODE_ENV=test BABEL_ENV=test PUBLIC_URL='' node scripts/test.js --env=jsdom",
"test:content": "yarn jest --rootDir content",
"test:developing": "cross-env CONTENT_ROOT=mdn/content/files TESTING_DEVELOPING=true playwright test developing",
"test:headless": "playwright test headless",
"test:kumascript": "yarn jest --rootDir kumascript --env=node",
+ "test:libs": "yarn jest --rootDir libs --env=node",
"test:prepare": "yarn build:prepare && yarn build && yarn start:static-server",
"test:testing": "yarn jest --rootDir testing",
"tool": "ts-node tool/cli.ts",
diff --git a/server/index.ts b/server/index.ts
index 2751bd9f4f1c..463585520b00 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -17,8 +17,12 @@ import {
renderContributorsTxt,
} from "../build/index.js";
import { findTranslations } from "../content/translations.js";
-import { Document, Redirect, Image } from "../content/index.js";
-import { CSP_VALUE, DEFAULT_LOCALE } from "../libs/constants/index.js";
+import { Document, Redirect, FileAttachment } from "../content/index.js";
+import {
+ ANY_ATTACHMENT_REGEXP,
+ CSP_VALUE,
+ DEFAULT_LOCALE,
+} from "../libs/constants/index.js";
import {
STATIC_ROOT,
PROXY_HOSTNAME,
@@ -292,14 +296,12 @@ app.get("/*", async (req, res, ...args) => {
.sendFile(path.join(STATIC_ROOT, "en-us", "_spas", "404.html"));
}
- // TODO: Would be nice to have a list of all supported file extensions
- // in a constants file.
- if (/\.(png|webp|gif|jpe?g|svg)$/.test(req.path)) {
- // Remember, Image.findByURLWithFallback() will return the absolute file path
+ if (ANY_ATTACHMENT_REGEXP.test(req.path)) {
+ // Remember, FileAttachment.findByURLWithFallback() will return the absolute file path
// iff it exists on disk.
// Using a "fallback" strategy here so that images embedded in live samples
// are resolved if they exist in en-US but not in
- const filePath = Image.findByURLWithFallback(req.path);
+ const filePath = FileAttachment.findByURLWithFallback(req.path);
if (filePath) {
// The second parameter to `send()` has to be either a full absolute
// path or a path that doesn't start with `../` otherwise you'd
diff --git a/server/react-app.d.ts b/server/react-app.d.ts
index bf4ab9180050..ba232bc32748 100644
--- a/server/react-app.d.ts
+++ b/server/react-app.d.ts
@@ -30,16 +30,41 @@ declare module "*.jpeg" {
export default src;
}
+declare module "*.mp3" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.mp4" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.ogg" {
+ const src: string;
+ export default src;
+}
+
declare module "*.png" {
const src: string;
export default src;
}
+declare module "*.webm" {
+ const src: string;
+ export default src;
+}
+
declare module "*.webp" {
const src: string;
export default src;
}
+declare module "*.woff2" {
+ const src: string;
+ export default src;
+}
+
declare module "*.svg" {
import * as React from "react";
diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts
index bf4ab9180050..ba232bc32748 100644
--- a/ssr/react-app.d.ts
+++ b/ssr/react-app.d.ts
@@ -30,16 +30,41 @@ declare module "*.jpeg" {
export default src;
}
+declare module "*.mp3" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.mp4" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.ogg" {
+ const src: string;
+ export default src;
+}
+
declare module "*.png" {
const src: string;
export default src;
}
+declare module "*.webm" {
+ const src: string;
+ export default src;
+}
+
declare module "*.webp" {
const src: string;
export default src;
}
+declare module "*.woff2" {
+ const src: string;
+ export default src;
+}
+
declare module "*.svg" {
import * as React from "react";