diff --git a/.eslintrc.js b/.eslintrc.cjs
similarity index 93%
rename from .eslintrc.js
rename to .eslintrc.cjs
index 3ee18d5bcbd2..7fccfd4b81ad 100644
--- a/.eslintrc.js
+++ b/.eslintrc.cjs
@@ -1,5 +1,5 @@
-const { readGitignoreFiles } = require("eslint-gitignore");
const path = require("node:path");
+const { readGitignoreFiles } = require("eslint-gitignore");
const ignores = readGitignoreFiles({
cwd: path.join(".git", "info"),
@@ -40,6 +40,12 @@ module.exports = {
"warn",
{ ignoreRestSiblings: true },
],
+ "n/no-extraneous-import": [
+ "error",
+ {
+ allowModules: ["@jest/globals"],
+ },
+ ],
"n/no-missing-import": "off",
"n/no-unpublished-import": "off",
"n/shebang": "off",
diff --git a/.nvmrc b/.nvmrc
index 6f7f377bf514..8122c281de1d 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v16
+v16.19
diff --git a/build/build-options.ts b/build/build-options.ts
index 7d0c6b8ee563..0c531c2a9f1c 100644
--- a/build/build-options.ts
+++ b/build/build-options.ts
@@ -1,7 +1,7 @@
import dotenv from "dotenv";
dotenv.config();
-import { FLAW_LEVELS, VALID_FLAW_CHECKS } from "../libs/constants";
+import { FLAW_LEVELS, VALID_FLAW_CHECKS } from "../libs/constants/index.js";
import {
DEFAULT_FLAW_LEVELS,
FILES,
@@ -11,7 +11,7 @@ import {
FIX_FLAWS_DRY_RUN,
FIX_FLAWS_TYPES,
FIX_FLAWS_VERBOSE,
-} from "../libs/env";
+} from "../libs/env/index.js";
const options = Object.freeze({
flawLevels: parseFlawLevels(DEFAULT_FLAW_LEVELS),
diff --git a/build/check-images.ts b/build/check-images.ts
index 7e02fc1289a0..00651c017bf1 100644
--- a/build/check-images.ts
+++ b/build/check-images.ts
@@ -4,12 +4,13 @@
import path from "node:path";
-import sizeOf from "image-size";
+import imagesize from "image-size";
-import { Document, Image } from "../content";
-import { FLAW_LEVELS } from "../libs/constants";
-import { findMatchesInText } from "./matches-in-text";
-import { DEFAULT_LOCALE } from "../libs/constants";
+import { Document, Image } from "../content/index.js";
+import { FLAW_LEVELS, DEFAULT_LOCALE } from "../libs/constants/index.js";
+import { findMatchesInText } from "./matches-in-text.js";
+
+const { default: sizeOf } = imagesize;
/**
* Mutate the `$` instance for image reference and if appropriate,
diff --git a/build/cli.ts b/build/cli.ts
index 7d500ab49ed5..fa58797c1ad0 100644
--- a/build/cli.ts
+++ b/build/cli.ts
@@ -6,22 +6,32 @@ import zlib from "node:zlib";
import chalk from "chalk";
import cliProgress from "cli-progress";
-import { program } from "@caporal/core";
-import { prompt } from "inquirer";
-
-import { Document, slugToFolder, translationsOf } from "../content";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { VALID_LOCALES } from "../libs/constants";
+import caporal from "@caporal/core";
+import inquirer from "inquirer";
+
+import { Document, slugToFolder, translationsOf } from "../content/index.js";
+import {
+ CONTENT_ROOT,
+ CONTENT_TRANSLATED_ROOT,
+ BUILD_OUT_ROOT,
+} from "../libs/env/index.js";
+import { VALID_LOCALES } from "../libs/constants/index.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
-import { renderHTML } from "../ssr/dist/main";
-import options from "./build-options";
-import { buildDocument, BuiltDocument, renderContributorsTxt } from ".";
-import { DocMetadata, Flaws } from "../libs/types";
-import SearchIndex from "./search-index";
-import { BUILD_OUT_ROOT } from "../libs/env";
-import { makeSitemapXML, makeSitemapIndexXML } from "./sitemaps";
-import { humanFileSize } from "./utils";
+import { renderHTML } from "../ssr/dist/main.js";
+import options from "./build-options.js";
+import {
+ buildDocument,
+ BuiltDocument,
+ renderContributorsTxt,
+} from "./index.js";
+import { DocMetadata, Flaws } from "../libs/types/document.js";
+import SearchIndex from "./search-index.js";
+import { makeSitemapXML, makeSitemapIndexXML } from "./sitemaps.js";
+import { humanFileSize } from "./utils.js";
+
+const { program } = caporal;
+const { prompt } = inquirer;
export type DocumentBuild = SkippedDocumentBuild | InteractiveDocumentBuild;
diff --git a/build/document-utils.ts b/build/document-utils.ts
index e707bea61d75..ed371c00b3b9 100644
--- a/build/document-utils.ts
+++ b/build/document-utils.ts
@@ -1,4 +1,4 @@
-import { Document } from "../content";
+import { Document } from "../content/index.js";
const TRANSFORM_STRINGS = new Map(
Object.entries({
diff --git a/build/extract-sections.ts b/build/extract-sections.ts
index a24d090f133c..be4f6f8a6d2b 100644
--- a/build/extract-sections.ts
+++ b/build/extract-sections.ts
@@ -1,6 +1,6 @@
import * as cheerio from "cheerio";
-import { ProseSection, Section } from "../libs/types";
-import { extractSpecifications } from "./extract-specifications";
+import { ProseSection, Section } from "../libs/types/document.js";
+import { extractSpecifications } from "./extract-specifications.js";
type SectionsAndFlaws = [Section[], string[]];
diff --git a/build/extract-sidebar.ts b/build/extract-sidebar.ts
index 37774eef39d8..0ff8fa8871fa 100644
--- a/build/extract-sidebar.ts
+++ b/build/extract-sidebar.ts
@@ -1,5 +1,5 @@
import * as cheerio from "cheerio";
-import { Doc } from "../libs/types/document";
+import { Doc } from "../libs/types/document.js";
/** Extract and mutate the $ if it as a "Quick_links" section.
* But only if it exists.
diff --git a/build/extract-specifications.ts b/build/extract-specifications.ts
index 3215db5f0448..479e8386269d 100644
--- a/build/extract-specifications.ts
+++ b/build/extract-specifications.ts
@@ -1,8 +1,8 @@
-import { packageBCD } from "./resolve-bcd";
-import * as bcd from "@mdn/browser-compat-data/types";
-import { Specification } from "../libs/types/document";
-import specs from "web-specs";
-import web from "../kumascript/src/api/web";
+import { packageBCD } from "./resolve-bcd.js";
+import bcd from "@mdn/browser-compat-data/types";
+import { Specification } from "../libs/types/document.js";
+import specs from "web-specs/index.json" assert { type: "json" };
+import web from "../kumascript/src/api/web.js";
export function extractSpecifications(
query: string | undefined,
diff --git a/build/extract-summary.ts b/build/extract-summary.ts
index f35f3cf4f7d2..6bd2ab2cb143 100644
--- a/build/extract-summary.ts
+++ b/build/extract-summary.ts
@@ -1,5 +1,5 @@
import * as cheerio from "cheerio";
-import { ProseSection, Section } from "../libs/types/document";
+import { ProseSection, Section } from "../libs/types/document.js";
/**
* Given an array of sections, return a plain text
diff --git a/build/flaws/bad-bcd-queries.ts b/build/flaws/bad-bcd-queries.ts
index 119257a561b1..d6e3d6f7312d 100644
--- a/build/flaws/bad-bcd-queries.ts
+++ b/build/flaws/bad-bcd-queries.ts
@@ -1,4 +1,4 @@
-import { packageBCD } from "../resolve-bcd";
+import { packageBCD } from "../resolve-bcd.js";
// Bad BCD queries are when the `
` tags have an
// ID (or even lack the `id` attribute) that don't match anything in the
diff --git a/build/flaws/broken-links.ts b/build/flaws/broken-links.ts
index c7ed1cfa0d69..d104bb645c5e 100644
--- a/build/flaws/broken-links.ts
+++ b/build/flaws/broken-links.ts
@@ -1,19 +1,16 @@
import fs from "node:fs";
-import path from "node:path";
import fromMarkdown from "mdast-util-from-markdown";
import visit from "unist-util-visit";
-import { Document, Redirect, Image } from "../../content";
-import { findMatchesInText } from "../matches-in-text";
+import { Document, Redirect, Image } from "../../content/index.js";
+import { findMatchesInText } from "../matches-in-text.js";
import {
DEFAULT_LOCALE,
FLAW_LEVELS,
VALID_LOCALES,
-} from "../../libs/constants";
-import { isValidLocale } from "../../libs/locale-utils";
-
-const dirname = __dirname;
+} from "../../libs/constants/index.js";
+import { isValidLocale } from "../../libs/locale-utils/index.js";
function findMatchesInMarkdown(rawContent, href) {
const matches = [];
@@ -27,10 +24,14 @@ function findMatchesInMarkdown(rawContent, href) {
}
const _safeToHttpsDomains = new Map();
+
function getSafeToHttpDomains() {
if (!_safeToHttpsDomains.size) {
const fileParsed = JSON.parse(
- fs.readFileSync(path.join(dirname, "safe-to-https-domains.json"), "utf-8")
+ fs.readFileSync(
+ new URL("safe-to-https-domains.json", import.meta.url),
+ "utf-8"
+ )
);
Object.entries(fileParsed).forEach(([key, value]) =>
_safeToHttpsDomains.set(key, value)
diff --git a/build/flaws/heading-links.ts b/build/flaws/heading-links.ts
index 9002779a37e2..f9b03619a9d5 100644
--- a/build/flaws/heading-links.ts
+++ b/build/flaws/heading-links.ts
@@ -1,4 +1,4 @@
-import { findMatchesInText } from "../matches-in-text";
+import { findMatchesInText } from "../matches-in-text.js";
// You're not allowed to have `
` elements inside `` or `` elements
// because those will be rendered out as "links to themselves".
diff --git a/build/flaws/index.ts b/build/flaws/index.ts
index e1c48c6ec705..41f5fae78dc8 100644
--- a/build/flaws/index.ts
+++ b/build/flaws/index.ts
@@ -2,21 +2,24 @@ import path from "node:path";
import chalk from "chalk";
import { RequestError } from "got";
-import { Document } from "../../content";
-import { FLAW_LEVELS, VALID_FLAW_CHECKS } from "../../libs/constants";
-import { DEFAULT_LOCALE } from "../../libs/constants";
+import { Document } from "../../content/index.js";
+import {
+ FLAW_LEVELS,
+ VALID_FLAW_CHECKS,
+ DEFAULT_LOCALE,
+} from "../../libs/constants/index.js";
import {
replaceMatchesInText,
replaceMatchingLinksInMarkdown,
-} from "../matches-in-text";
-import { forceExternalURL, downloadAndResizeImage } from "../utils";
-import { getBadBCDQueriesFlaws } from "./bad-bcd-queries";
-import { getBrokenLinksFlaws } from "./broken-links";
-import { getHeadingLinksFlaws } from "./heading-links";
-import { getPreTagFlaws } from "./pre-tags";
-export { injectSectionFlaws } from "./sections";
-import { getUnsafeHTMLFlaws } from "./unsafe-html";
-import { injectTranslationDifferences } from "./translation-differences";
+} from "../matches-in-text.js";
+import { forceExternalURL, downloadAndResizeImage } from "../utils.js";
+import { getBadBCDQueriesFlaws } from "./bad-bcd-queries.js";
+import { getBrokenLinksFlaws } from "./broken-links.js";
+import { getHeadingLinksFlaws } from "./heading-links.js";
+import { getPreTagFlaws } from "./pre-tags.js";
+export { injectSectionFlaws } from "./sections.js";
+import { getUnsafeHTMLFlaws } from "./unsafe-html.js";
+import { injectTranslationDifferences } from "./translation-differences.js";
export interface Flaw {
explanation: any;
diff --git a/build/flaws/pre-tags.ts b/build/flaws/pre-tags.ts
index 5f6d6e198aa9..d7cf274c39c7 100644
--- a/build/flaws/pre-tags.ts
+++ b/build/flaws/pre-tags.ts
@@ -1,6 +1,6 @@
-import { Flaw } from ".";
+import { Flaw } from "./index.js";
-import { getFirstMatchInText } from "../matches-in-text";
+import { getFirstMatchInText } from "../matches-in-text.js";
const escapeHTML = (s) =>
s
.replace(/&/g, "&")
diff --git a/build/flaws/sections.ts b/build/flaws/sections.ts
index 0c1f8b1a1116..3458323d3727 100644
--- a/build/flaws/sections.ts
+++ b/build/flaws/sections.ts
@@ -1,4 +1,4 @@
-import { FLAW_LEVELS } from "../../libs/constants";
+import { FLAW_LEVELS } from "../../libs/constants/index.js";
export function injectSectionFlaws(doc, flaws, options) {
if (!flaws.length) {
diff --git a/build/flaws/translation-differences.ts b/build/flaws/translation-differences.ts
index 2960473400fe..b968bfb2bc0b 100644
--- a/build/flaws/translation-differences.ts
+++ b/build/flaws/translation-differences.ts
@@ -1,7 +1,7 @@
-import { Flaw } from ".";
+import { Flaw } from "./index.js";
-import { Document, Translation } from "../../content";
-import { DEFAULT_LOCALE } from "../../libs/constants";
+import { Document, Translation } from "../../content/index.js";
+import { DEFAULT_LOCALE } from "../../libs/constants/index.js";
export function injectTranslationDifferences(doc, $, document): Flaw[] {
const flaws = [];
diff --git a/build/flaws/unsafe-html.ts b/build/flaws/unsafe-html.ts
index 6ee2a58b41e9..6066b6bd0a2a 100644
--- a/build/flaws/unsafe-html.ts
+++ b/build/flaws/unsafe-html.ts
@@ -1,10 +1,10 @@
-import { Flaw } from ".";
+import { Flaw } from "./index.js";
import {
INTERACTIVE_EXAMPLES_BASE_URL,
LIVE_SAMPLES_BASE_URL,
-} from "../../libs/env";
-import { findMatchesInText } from "../matches-in-text";
+} from "../../libs/env/index.js";
+import { findMatchesInText } from "../matches-in-text.js";
const safeIFrameSrcs = [
// EmbedGHLiveSample.ejs
diff --git a/build/git-history.ts b/build/git-history.ts
index 76c72c933099..8386c43a16f7 100644
--- a/build/git-history.ts
+++ b/build/git-history.ts
@@ -1,8 +1,8 @@
import fs from "node:fs";
import path from "node:path";
-import { execGit } from "../content";
-import { CONTENT_ROOT } from "../libs/env";
+import { execGit } from "../content/index.js";
+import { CONTENT_ROOT } from "../libs/env/index.js";
function getFromGit(contentRoot = CONTENT_ROOT) {
// If `contentRoot` was a symlink, the `repoRoot` won't be. That'll make it
diff --git a/build/index.ts b/build/index.ts
index 0a7f92b61b52..f8979f4e703c 100644
--- a/build/index.ts
+++ b/build/index.ts
@@ -1,4 +1,3 @@
-import { Doc } from "../libs/types";
import fs from "node:fs";
import path from "node:path";
@@ -7,29 +6,34 @@ import {
MacroInvocationError,
MacroLiveSampleError,
MacroRedirectedLinkError,
-} from "../kumascript/src/errors";
-
-import { Document, Image, execGit } from "../content";
-import { CONTENT_ROOT, REPOSITORY_URLS } from "../libs/env";
-import * as kumascript from "../kumascript";
-
-import { FLAW_LEVELS } from "../libs/constants";
-import { extractSections } from "./extract-sections";
-import { extractSidebar } from "./extract-sidebar";
-import { extractSummary } from "./extract-summary";
-export { default as SearchIndex } from "./search-index";
-import { addBreadcrumbData } from "./document-utils";
-import { fixFixableFlaws, injectFlaws, injectSectionFlaws } from "./flaws";
-import { checkImageReferences, checkImageWidths } from "./check-images";
-import { getPageTitle } from "./page-title";
-import { syntaxHighlight } from "./syntax-highlight";
-import { formatNotecards } from "./format-notecards";
-import buildOptions from "./build-options";
-export { gather as gatherGitHistory } from "./git-history";
-export { buildSPAs } from "./spas";
-import LANGUAGES_RAW from "../libs/languages";
-import { safeDecodeURIComponent } from "../kumascript/src/api/util";
-import { wrapTables } from "./wrap-tables";
+} from "../kumascript/src/errors.js";
+
+import { Doc } from "../libs/types/document.js";
+import { Document, Image, execGit } from "../content/index.js";
+import { CONTENT_ROOT, REPOSITORY_URLS } from "../libs/env/index.js";
+import * as kumascript from "../kumascript/index.js";
+
+import { FLAW_LEVELS } from "../libs/constants/index.js";
+import { extractSections } from "./extract-sections.js";
+import { extractSidebar } from "./extract-sidebar.js";
+import { extractSummary } from "./extract-summary.js";
+export { default as SearchIndex } from "./search-index.js";
+import { addBreadcrumbData } from "./document-utils.js";
+import {
+ fixFixableFlaws,
+ injectFlaws,
+ injectSectionFlaws,
+} from "./flaws/index.js";
+import { checkImageReferences, checkImageWidths } from "./check-images.js";
+import { getPageTitle } from "./page-title.js";
+import { syntaxHighlight } from "./syntax-highlight.js";
+import { formatNotecards } from "./format-notecards.js";
+import buildOptions from "./build-options.js";
+export { gather as gatherGitHistory } from "./git-history.js";
+export { buildSPAs } from "./spas.js";
+import LANGUAGES_RAW from "../libs/languages/index.js";
+import { safeDecodeURIComponent } from "../kumascript/src/api/util.js";
+import { wrapTables } from "./wrap-tables.js";
const LANGUAGES = new Map(
Object.entries(LANGUAGES_RAW).map(([locale, data]) => {
diff --git a/build/page-title.ts b/build/page-title.ts
index 5d665082d4fa..e555be5d5a9f 100644
--- a/build/page-title.ts
+++ b/build/page-title.ts
@@ -1,4 +1,4 @@
-import { Document } from "../content";
+import { Document } from "../content/index.js";
/**
* Return the appropriate document title to go into the HTML
diff --git a/build/resolve-bcd.ts b/build/resolve-bcd.ts
index cc46dab04a7b..72357358446a 100644
--- a/build/resolve-bcd.ts
+++ b/build/resolve-bcd.ts
@@ -1,6 +1,7 @@
-// Note! This is copied verbatim from stumptown-content
+import bcdUntyped from "@mdn/browser-compat-data/forLegacyNode";
+import { CompatData } from "@mdn/browser-compat-data/types";
-import bcd from "@mdn/browser-compat-data";
+const bcd = bcdUntyped as CompatData;
export function packageBCD(query) {
const data = query.split(".").reduce((prev, curr) => {
diff --git a/build/search-index.ts b/build/search-index.ts
index f5197728f779..c1495714bb37 100644
--- a/build/search-index.ts
+++ b/build/search-index.ts
@@ -1,4 +1,4 @@
-import { getPopularities } from "../content";
+import { getPopularities } from "../content/index.js";
// getPopularities() is memoized so it's fast to call repeatedly
const getPopularity = (item) => getPopularities().get(item.url) || 0;
diff --git a/build/spas.ts b/build/spas.ts
index fb28bcc34ba7..dbb4c389dbe6 100644
--- a/build/spas.ts
+++ b/build/spas.ts
@@ -1,30 +1,31 @@
import fs from "node:fs";
import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+import cheerio from "cheerio";
import frontmatter from "front-matter";
import { fdir, PathsOutput } from "fdir";
+import gotPkg from "got";
-import { m2h } from "../markdown";
+import { m2h } from "../markdown/index.js";
-import { VALID_LOCALES, MDN_PLUS_TITLE } from "../libs/constants";
+import { VALID_LOCALES, MDN_PLUS_TITLE } from "../libs/constants/index.js";
import {
CONTENT_ROOT,
CONTENT_TRANSLATED_ROOT,
CONTRIBUTOR_SPOTLIGHT_ROOT,
BUILD_OUT_ROOT,
-} from "../libs/env";
-import { isValidLocale } from "../libs/locale-utils";
-import { DocFrontmatter } from "../libs/types/document";
+} from "../libs/env/index.js";
+import { isValidLocale } from "../libs/locale-utils/index.js";
+import { DocFrontmatter, NewsItem } from "../libs/types/document.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
-import { renderHTML } from "../ssr/dist/main";
-import got from "got";
-import { splitSections } from "./utils";
-import cheerio from "cheerio";
-import { findByURL } from "../content/document";
-import { buildDocument } from ".";
-import { NewsItem } from "../client/src/homepage/latest-news";
+import { renderHTML } from "../ssr/dist/main.js";
+import { splitSections } from "./utils.js";
+import { findByURL } from "../content/document.js";
+import { buildDocument } from "./index.js";
-const dirname = __dirname;
+const { default: got } = gotPkg;
const FEATURED_ARTICLES = [
"Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL",
@@ -224,7 +225,7 @@ export async function buildSPAs(options) {
}
await buildStaticPages(
- path.join(dirname, "../copy/plus/"),
+ fileURLToPath(new URL("../copy/plus/", import.meta.url)),
"plus/docs",
"MDN Plus"
);
diff --git a/build/syntax-highlight.ts b/build/syntax-highlight.ts
index 49b8c0f01e6a..a07770a4de54 100644
--- a/build/syntax-highlight.ts
+++ b/build/syntax-highlight.ts
@@ -1,5 +1,5 @@
import Prism from "prismjs";
-import loadLanguages from "prismjs/components/index";
+import loadLanguages from "prismjs/components/index.js";
const lazy = (creator) => {
let res;
diff --git a/build/tsconfig.json b/build/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/build/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/build/utils.ts b/build/utils.ts
index 576e1c33990b..1a031755c495 100644
--- a/build/utils.ts
+++ b/build/utils.ts
@@ -2,16 +2,19 @@ import fs from "node:fs";
import path from "node:path";
import * as cheerio from "cheerio";
-import got from "got";
+import gotPkg from "got";
import FileType from "file-type";
import imagemin from "imagemin";
-import imageminPngquant from "imagemin-pngquant";
+import imageminPngquantPkg from "imagemin-pngquant";
import imageminMozjpeg from "imagemin-mozjpeg";
import imageminGifsicle from "imagemin-gifsicle";
import imageminSvgo from "imagemin-svgo";
import sanitizeFilename from "sanitize-filename";
-import { VALID_MIME_TYPES } from "../libs/constants";
+import { VALID_MIME_TYPES } from "../libs/constants/index.js";
+
+const { default: got } = gotPkg;
+const { default: imageminPngquant } = imageminPngquantPkg;
export function humanFileSize(size) {
if (size < 1024) return `${size} B`;
diff --git a/client/config/env.js b/client/config/env.js
index 703cf6fbadf8..81e658551002 100644
--- a/client/config/env.js
+++ b/client/config/env.js
@@ -1,10 +1,9 @@
-const fs = require("fs");
-const path = require("path");
-const paths = require("./paths");
-
-// Make sure that including paths.js after env.js will read .env variables.
-delete require.cache[require.resolve("./paths")];
+import fs from "node:fs";
+import path from "node:path";
+import dotenv from "dotenv";
+import paths from "./paths.js";
+// Double-check to make sure NODE_ENV is defined
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
throw new Error(
@@ -17,7 +16,7 @@ const dotenvFile = ENV_FILE
? paths.dotenv.replace(".env", ENV_FILE)
: paths.dotenv;
-require("dotenv").config({
+dotenv.config({
path: dotenvFile,
});
@@ -82,4 +81,4 @@ function getClientEnvironment(publicUrl) {
return { raw, stringified };
}
-module.exports = getClientEnvironment;
+export default getClientEnvironment;
diff --git a/client/config/getHttpsConfig.js b/client/config/getHttpsConfig.js
index 5706e1b30fc0..fdfa66898605 100644
--- a/client/config/getHttpsConfig.js
+++ b/client/config/getHttpsConfig.js
@@ -1,8 +1,8 @@
-const fs = require("fs");
-const path = require("path");
-const crypto = require("crypto");
-const chalk = require("react-dev-utils/chalk");
-const paths = require("./paths");
+import fs from "node:fs";
+import path from "node:path";
+import crypto from "node:crypto";
+import chalk from "react-dev-utils/chalk.js";
+import paths from "./paths.js";
// Ensure the certificate and key provided are valid and if not
// throw an easy to debug error
@@ -61,4 +61,4 @@ function getHttpsConfig() {
return isHttps;
}
-module.exports = getHttpsConfig;
+export default getHttpsConfig;
diff --git a/client/config/jest/babelTransform.js b/client/config/jest/babelTransform.js
index 9709898f82b7..1597d57e3002 100644
--- a/client/config/jest/babelTransform.js
+++ b/client/config/jest/babelTransform.js
@@ -1,4 +1,7 @@
-const babelJest = require("babel-jest").default;
+import { createRequire } from "node:module";
+import babelJest from "babel-jest";
+
+const require = createRequire(import.meta.url);
const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === "true") {
@@ -13,7 +16,7 @@ const hasJsxRuntime = (() => {
}
})();
-module.exports = babelJest.createTransformer({
+export default babelJest.createTransformer({
presets: [
[
require.resolve("babel-preset-react-app"),
diff --git a/client/config/jest/cssTransform.js b/client/config/jest/cssTransform.js
index fc14ad7a9a18..03f7979f4511 100644
--- a/client/config/jest/cssTransform.js
+++ b/client/config/jest/cssTransform.js
@@ -1,7 +1,7 @@
// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html
-module.exports = {
+const transform = {
process() {
return { code: "module.exports = {};" };
},
@@ -10,3 +10,5 @@ module.exports = {
return "cssTransform";
},
};
+
+export default transform;
diff --git a/client/config/jest/fileTransform.js b/client/config/jest/fileTransform.js
index d678aa986b39..5bc7c6302acb 100644
--- a/client/config/jest/fileTransform.js
+++ b/client/config/jest/fileTransform.js
@@ -1,10 +1,10 @@
-const path = require("path");
-const camelcase = require("camelcase");
+import path from "node:path";
+import camelcase from "camelcase";
// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html
-module.exports = {
+const transform = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
@@ -38,3 +38,5 @@ module.exports = {
return { code: `module.exports = ${assetFilename};` };
},
};
+
+export default transform;
diff --git a/client/config/modules.js b/client/config/modules.js
index 44e8f2bbe499..10f693813941 100644
--- a/client/config/modules.js
+++ b/client/config/modules.js
@@ -1,8 +1,8 @@
-const fs = require("fs");
-const path = require("path");
-const paths = require("./paths");
-const chalk = require("react-dev-utils/chalk");
-const resolve = require("resolve");
+import fs from "node:fs";
+import path from "node:path";
+import resolve from "resolve";
+import chalk from "react-dev-utils/chalk.js";
+import paths from "./paths.js";
/**
* Get additional module paths based on the baseUrl of a compilerOptions object.
@@ -89,7 +89,7 @@ function getJestAliases(options = {}) {
}
}
-function getModules() {
+async function getModules() {
// Check if TypeScript is setup
const hasTsConfig = fs.existsSync(paths.appTsConfig);
const hasJsConfig = fs.existsSync(paths.appJsConfig);
@@ -106,14 +106,17 @@ function getModules() {
// TypeScript project and set up the config
// based on tsconfig.json
if (hasTsConfig) {
- const ts = require(resolve.sync("typescript", {
- basedir: paths.appNodeModules,
- }));
+ const { default: ts } = await import(
+ "file://" +
+ resolve.sync("typescript", {
+ basedir: paths.appNodeModules,
+ })
+ );
config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
// Otherwise we'll check if there is jsconfig.json
// for non TS projects.
} else if (hasJsConfig) {
- config = require(paths.appJsConfig);
+ config = await import("file://" + paths.appJsConfig).default;
}
config = config || {};
@@ -129,4 +132,4 @@ function getModules() {
};
}
-module.exports = getModules();
+export default getModules();
diff --git a/client/config/paths.js b/client/config/paths.js
index 81f9a522b13d..e240d3803ba2 100644
--- a/client/config/paths.js
+++ b/client/config/paths.js
@@ -1,11 +1,14 @@
-const path = require("path");
-const fs = require("fs");
-const getPublicUrlOrPath = require("react-dev-utils/getPublicUrlOrPath");
+import fs from "node:fs";
+import { fileURLToPath } from "node:url";
+import getPublicUrlOrPath from "react-dev-utils/getPublicUrlOrPath.js";
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
-const appDirectory = fs.realpathSync(process.cwd());
-const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
+const appDirectory = new URL("..", import.meta.url);
+const resolveApp = (relativePath) =>
+ fileURLToPath(new URL(relativePath, appDirectory));
+
+const appPackage = JSON.parse(fs.readFileSync(resolveApp("package.json")));
// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
@@ -15,7 +18,7 @@ const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
const publicUrlOrPath = getPublicUrlOrPath(
process.env.NODE_ENV === "development",
- require(resolveApp("package.json")).homepage,
+ appPackage.homepage,
process.env.PUBLIC_URL
);
@@ -49,7 +52,7 @@ const resolveModule = (resolveFn, filePath) => {
};
// config after eject: we're in ./config/
-module.exports = {
+const config = {
dotenv: resolveApp("../.env"),
appPath: resolveApp("."),
appBuild: resolveApp(buildPath),
@@ -69,6 +72,7 @@ module.exports = {
swSrc: resolveModule(resolveApp, "src/service-worker"),
publicUrlOrPath,
libsPath: resolveApp("../libs"),
+ moduleFileExtensions,
};
-module.exports.moduleFileExtensions = moduleFileExtensions;
+export default config;
diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js
index 46952f238979..f7ef4d6f729b 100644
--- a/client/config/webpack.config.js
+++ b/client/config/webpack.config.js
@@ -1,44 +1,48 @@
-const fs = require("fs");
-const path = require("path");
-const webpack = require("webpack");
-const resolve = require("resolve");
-const HtmlWebpackPlugin = require("html-webpack-plugin");
-const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
-const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
-const TerserPlugin = require("terser-webpack-plugin");
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
-const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
-const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
-const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
-const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
-const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
-const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent");
-const ESLintPlugin = require("eslint-webpack-plugin");
-const paths = require("./paths");
-const modules = require("./modules");
-const getClientEnvironment = require("./env");
-const ModuleNotFoundPlugin = require("react-dev-utils/ModuleNotFoundPlugin");
-const ForkTsCheckerWebpackPlugin =
- process.env.TSC_COMPILE_ON_ERROR === "true"
- ? require("react-dev-utils/ForkTsCheckerWarningWebpackPlugin")
- : require("react-dev-utils/ForkTsCheckerWebpackPlugin");
-const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+import resolve from "resolve";
+import webpack from "webpack";
+import HtmlWebpackPlugin from "html-webpack-plugin";
+import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
+import InlineChunkHtmlPlugin from "react-dev-utils/InlineChunkHtmlPlugin.js";
+import TerserPlugin from "terser-webpack-plugin";
+import MiniCssExtractPlugin from "mini-css-extract-plugin";
+import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
+import { WebpackManifestPlugin } from "webpack-manifest-plugin";
+import InterpolateHtmlPlugin from "react-dev-utils/InterpolateHtmlPlugin.js";
+import WorkboxWebpackPlugin from "workbox-webpack-plugin";
+import ModuleScopePlugin from "react-dev-utils/ModuleScopePlugin.js";
+import getCSSModuleLocalIdent from "react-dev-utils/getCSSModuleLocalIdent.js";
+import ESLintPlugin from "eslint-webpack-plugin";
+import ModuleNotFoundPlugin from "react-dev-utils/ModuleNotFoundPlugin.js";
+import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
-const createEnvironmentHash = require("./webpack/persistentCache/createEnvironmentHash");
+import paths from "./paths.js";
+import modules from "./modules.js";
+import getClientEnvironment from "./env.js";
+import createEnvironmentHash from "./webpack/persistentCache/createEnvironmentHash.js";
+
+const { default: ForkTsCheckerWebpackPlugin } = await import(
+ process.env.TSC_COMPILE_ON_ERROR === "true"
+ ? "react-dev-utils/ForkTsCheckerWarningWebpackPlugin.js"
+ : "react-dev-utils/ForkTsCheckerWebpackPlugin.js"
+);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
-const reactRefreshRuntimeEntry = require.resolve("react-refresh/runtime");
-const reactRefreshWebpackPluginRuntimeEntry = require.resolve(
+const reactRefreshRuntimeEntry = resolve.sync("react-refresh/runtime");
+const reactRefreshWebpackPluginRuntimeEntry = resolve.sync(
"@pmmmwh/react-refresh-webpack-plugin"
);
-const babelRuntimeEntry = require.resolve("babel-preset-react-app");
-const babelRuntimeEntryHelpers = require.resolve(
+const babelRuntimeEntry = resolve.sync("babel-preset-react-app");
+const babelRuntimeEntryHelpers = resolve.sync(
"@babel/runtime/helpers/esm/assertThisInitialized",
{ paths: [babelRuntimeEntry] }
);
-const babelRuntimeRegenerator = require.resolve("@babel/runtime/regenerator", {
+const babelRuntimeRegenerator = resolve.sync("@babel/runtime/regenerator", {
paths: [babelRuntimeEntry],
});
@@ -76,7 +80,7 @@ const hasJsxRuntime = (() => {
}
try {
- require.resolve("react/jsx-runtime");
+ resolve.sync("react/jsx-runtime");
return true;
} catch (e) {
return false;
@@ -85,7 +89,7 @@ const hasJsxRuntime = (() => {
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
-module.exports = function (webpackEnv) {
+function config(webpackEnv) {
const isEnvDevelopment = webpackEnv === "development";
const isEnvProduction = webpackEnv === "production";
@@ -105,7 +109,7 @@ module.exports = function (webpackEnv) {
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
- isEnvDevelopment && require.resolve("style-loader"),
+ isEnvDevelopment && resolve.sync("style-loader"),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder
@@ -115,14 +119,14 @@ module.exports = function (webpackEnv) {
: {},
},
{
- loader: require.resolve("css-loader"),
+ loader: resolve.sync("css-loader"),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
- loader: require.resolve("postcss-loader"),
+ loader: resolve.sync("postcss-loader"),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
@@ -167,14 +171,14 @@ module.exports = function (webpackEnv) {
if (preProcessor) {
loaders.push(
{
- loader: require.resolve("resolve-url-loader"),
+ loader: resolve.sync("resolve-url-loader"),
options: {
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
root: paths.appSrc,
},
},
{
- loader: require.resolve(preProcessor),
+ loader: resolve.sync(preProcessor),
options: {
sourceMap: true,
},
@@ -235,7 +239,7 @@ module.exports = function (webpackEnv) {
store: "pack",
buildDependencies: {
defaultWebpack: ["webpack/lib/"],
- config: [__filename],
+ config: [fileURLToPath(import.meta.url)],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter((f) =>
fs.existsSync(f)
),
@@ -344,7 +348,7 @@ module.exports = function (webpackEnv) {
enforce: "pre",
exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/,
- loader: require.resolve("source-map-loader"),
+ loader: resolve.sync("source-map-loader"),
},
{
// "oneOf" will traverse all following loaders until one will
@@ -379,7 +383,7 @@ module.exports = function (webpackEnv) {
test: /\.svg$/,
use: [
{
- loader: require.resolve("@svgr/webpack"),
+ loader: resolve.sync("@svgr/webpack"),
options: {
prettier: false,
svgo: false,
@@ -391,7 +395,7 @@ module.exports = function (webpackEnv) {
},
},
{
- loader: require.resolve("file-loader"),
+ loader: resolve.sync("file-loader"),
options: {
name: "static/media/[name].[hash].[ext]",
},
@@ -406,14 +410,14 @@ module.exports = function (webpackEnv) {
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
- loader: require.resolve("babel-loader"),
+ loader: resolve.sync("babel-loader"),
options: {
- customize: require.resolve(
+ customize: resolve.sync(
"babel-preset-react-app/webpack-overrides"
),
presets: [
[
- require.resolve("babel-preset-react-app"),
+ resolve.sync("babel-preset-react-app"),
{
runtime: hasJsxRuntime ? "automatic" : "classic",
},
@@ -423,7 +427,8 @@ module.exports = function (webpackEnv) {
plugins: [
isEnvDevelopment &&
shouldUseReactRefresh &&
- require.resolve("react-refresh/babel"),
+ resolve.sync("react-refresh/babel"),
+ resolve.sync("@babel/plugin-syntax-import-assertions"),
].filter(Boolean),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
@@ -439,14 +444,14 @@ module.exports = function (webpackEnv) {
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
- loader: require.resolve("babel-loader"),
+ loader: resolve.sync("babel-loader"),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
- require.resolve("babel-preset-react-app/dependencies"),
+ resolve.sync("babel-preset-react-app/dependencies"),
{ helpers: true },
],
],
@@ -726,8 +731,8 @@ module.exports = function (webpackEnv) {
new ESLintPlugin({
// Plugin options
extensions: ["js", "mjs", "jsx", "ts", "tsx"],
- formatter: require.resolve("react-dev-utils/eslintFormatter"),
- eslintPath: require.resolve("eslint"),
+ formatter: resolve.sync("react-dev-utils/eslintFormatter.js"),
+ eslintPath: resolve.sync("eslint"),
failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
context: paths.appSrc,
cache: true,
@@ -737,9 +742,11 @@ module.exports = function (webpackEnv) {
),
// ESLint class options
cwd: paths.appPath,
- resolvePluginsRelativeTo: __dirname,
+ resolvePluginsRelativeTo: fileURLToPath(
+ new URL(".", import.meta.url)
+ ),
baseConfig: {
- extends: [require.resolve("eslint-config-react-app/base")],
+ extends: [resolve.sync("eslint-config-react-app/base")],
rules: {
...(!hasJsxRuntime && {
"react/react-in-jsx-scope": "error",
@@ -752,4 +759,6 @@ module.exports = function (webpackEnv) {
// our own hints via the FileSizeReporter
performance: false,
};
-};
+}
+
+export default config;
diff --git a/client/config/webpack/persistentCache/createEnvironmentHash.js b/client/config/webpack/persistentCache/createEnvironmentHash.js
index 0c20d189f6c1..62d622389a1d 100644
--- a/client/config/webpack/persistentCache/createEnvironmentHash.js
+++ b/client/config/webpack/persistentCache/createEnvironmentHash.js
@@ -1,8 +1,10 @@
-const { createHash } = require("crypto");
+import { createHash } from "node:crypto";
-module.exports = (env) => {
+const createEnvironmentHash = (env) => {
const hash = createHash("md5");
hash.update(JSON.stringify(env));
return hash.digest("hex");
};
+
+export default createEnvironmentHash;
diff --git a/client/config/webpackDevServer.config.js b/client/config/webpackDevServer.config.js
index 3537d7314d86..dd7361dae8ec 100644
--- a/client/config/webpackDevServer.config.js
+++ b/client/config/webpackDevServer.config.js
@@ -1,17 +1,22 @@
-const fs = require("fs");
-const evalSourceMapMiddleware = require("react-dev-utils/evalSourceMapMiddleware");
-const noopServiceWorkerMiddleware = require("react-dev-utils/noopServiceWorkerMiddleware");
-const ignoredFiles = require("react-dev-utils/ignoredFiles");
-const redirectServedPath = require("react-dev-utils/redirectServedPathMiddleware");
-const paths = require("./paths");
-const getHttpsConfig = require("./getHttpsConfig");
+import fs from "node:fs";
+import evalSourceMapMiddleware from "react-dev-utils/evalSourceMapMiddleware.js";
+import noopServiceWorkerMiddleware from "react-dev-utils/noopServiceWorkerMiddleware.js";
+import ignoredFiles from "react-dev-utils/ignoredFiles.js";
+import redirectServedPath from "react-dev-utils/redirectServedPathMiddleware.js";
+
+import paths from "./paths.js";
+import getHttpsConfig from "./getHttpsConfig.js";
const host = process.env.HOST || "0.0.0.0";
const sockHost = process.env.WDS_SOCKET_HOST;
const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws'
const sockPort = process.env.WDS_SOCKET_PORT;
-module.exports = function (proxy, allowedHost) {
+const proxySetup = fs.existsSync(paths.proxySetup)
+ ? (await import("file://" + paths.proxySetup)).default
+ : null;
+
+function config(proxy, allowedHost) {
const disableFirewall =
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === "true";
return {
@@ -107,9 +112,9 @@ module.exports = function (proxy, allowedHost) {
evalSourceMapMiddleware(devServer)
);
- if (fs.existsSync(paths.proxySetup)) {
+ if (proxySetup) {
// This registers user provided middleware for proxy reasons
- require(paths.proxySetup)(devServer.app);
+ proxySetup(devServer.app);
}
middlewares.push(
@@ -127,4 +132,6 @@ module.exports = function (proxy, allowedHost) {
return middlewares;
},
};
-};
+}
+
+export default config;
diff --git a/client/package.json b/client/package.json
index 6471c9f20cfb..8b4daa5191e8 100644
--- a/client/package.json
+++ b/client/package.json
@@ -2,6 +2,7 @@
"name": "client",
"private": false,
"license": "MPL-2.0",
+ "type": "module",
"babel": {
"presets": [
"react-app"
diff --git a/client/pwa/package.json b/client/pwa/package.json
index c05558c857fd..4aa36277efbe 100644
--- a/client/pwa/package.json
+++ b/client/pwa/package.json
@@ -4,6 +4,7 @@
"description": "MDN Offline service worker",
"license": "MPL-2.0",
"author": "MDN Web Docs",
+ "type": "module",
"main": "service-worker.ts",
"scripts": {
"build": "webpack",
diff --git a/client/pwa/src/db.ts b/client/pwa/src/db.ts
index ee15e79fbc4a..eb8031511e32 100644
--- a/client/pwa/src/db.ts
+++ b/client/pwa/src/db.ts
@@ -85,9 +85,9 @@ export class MDNOfflineDB extends Dexie {
}
}
-const offlineDb = new MDNOfflineDB();
+export const offlineDb = new MDNOfflineDB();
-async function getContentStatus(): Promise {
+export async function getContentStatus(): Promise {
const current = await offlineDb.contentStatusHistory.toCollection().last();
return (
@@ -101,7 +101,7 @@ async function getContentStatus(): Promise {
);
}
-async function patchContentStatus(
+export async function patchContentStatus(
changes: Omit, "id" | "timestamp">
) {
const db = offlineDb;
@@ -129,5 +129,3 @@ async function patchContentStatus(
}
});
}
-
-export { offlineDb, getContentStatus, patchContentStatus };
diff --git a/client/pwa/src/fetch-interceptors.ts b/client/pwa/src/fetch-interceptors.ts
index 0bc1e0a3161c..fb915dcd1913 100644
--- a/client/pwa/src/fetch-interceptors.ts
+++ b/client/pwa/src/fetch-interceptors.ts
@@ -1,4 +1,4 @@
-import { MDNOfflineDB } from "./db";
+import { MDNOfflineDB } from "./db.js";
const PATH_WHOAMI = "/api/v1/whoami";
@@ -15,7 +15,7 @@ function jsonBlob(json) {
});
}
-class WhoamiInterceptor implements FetchInterceptor {
+export class WhoamiInterceptor implements FetchInterceptor {
db: MDNOfflineDB;
constructor(db: MDNOfflineDB) {
@@ -44,7 +44,7 @@ class WhoamiInterceptor implements FetchInterceptor {
}
}
-class DefaultApiInterceptor implements FetchInterceptor {
+export class DefaultApiInterceptor implements FetchInterceptor {
db: MDNOfflineDB;
constructor(db: MDNOfflineDB) {
@@ -76,5 +76,3 @@ class DefaultApiInterceptor implements FetchInterceptor {
}
}
}
-
-export { WhoamiInterceptor, DefaultApiInterceptor };
diff --git a/client/pwa/src/fetcher.ts b/client/pwa/src/fetcher.ts
index df9b9af035ad..71f3f8117136 100644
--- a/client/pwa/src/fetcher.ts
+++ b/client/pwa/src/fetcher.ts
@@ -1,9 +1,14 @@
/* eslint no-restricted-globals: ["off", "self"] */
-import { WhoamiInterceptor, DefaultApiInterceptor } from "./fetch-interceptors";
-import { offlineDb } from "./db";
-import { INTERACTIVE_EXAMPLES_URL } from "./service-worker";
-import { USER_CONTENT_URL } from "./service-worker";
-import { openCache } from "./caches";
+import {
+ WhoamiInterceptor,
+ DefaultApiInterceptor,
+} from "./fetch-interceptors.js";
+import { offlineDb } from "./db.js";
+import {
+ INTERACTIVE_EXAMPLES_URL,
+ USER_CONTENT_URL,
+} from "./service-worker.js";
+import { openCache } from "./caches.js";
let interceptors = [new WhoamiInterceptor(offlineDb)];
diff --git a/client/pwa/src/service-worker.ts b/client/pwa/src/service-worker.ts
index 0a8a12e890c0..41dfc42d586a 100644
--- a/client/pwa/src/service-worker.ts
+++ b/client/pwa/src/service-worker.ts
@@ -1,17 +1,17 @@
/* eslint no-restricted-globals: ["off", "location"] */
///
-import { cacheName, contentCache, openCache } from "./caches";
-import { respond } from "./fetcher";
-import { unpackAndCache } from "./unpack-cache";
+import { cacheName, contentCache, openCache } from "./caches.js";
+import { respond } from "./fetcher.js";
+import { unpackAndCache } from "./unpack-cache.js";
import {
ContentStatusPhase,
getContentStatus,
patchContentStatus,
RemoteContentStatus,
SwType,
-} from "./db";
-import { fetchWithExampleOverride } from "./fetcher";
+} from "./db.js";
+import { fetchWithExampleOverride } from "./fetcher.js";
export const INTERACTIVE_EXAMPLES_URL = new URL(
"https://interactive-examples.mdn.mozilla.net"
diff --git a/client/pwa/src/unpack-cache.ts b/client/pwa/src/unpack-cache.ts
index a80845c310f9..1232eef35df4 100644
--- a/client/pwa/src/unpack-cache.ts
+++ b/client/pwa/src/unpack-cache.ts
@@ -1,6 +1,6 @@
/* eslint no-restricted-globals: ["off", "self"] */
import * as zip from "@zip.js/zip.js";
-import { openContentCache } from "./caches";
+import { openContentCache } from "./caches.js";
zip.configure({
useWebWorkers: false,
diff --git a/client/pwa/tsconfig.json b/client/pwa/tsconfig.json
index 398b3c0c6da2..1f3d0ed8ddf0 100644
--- a/client/pwa/tsconfig.json
+++ b/client/pwa/tsconfig.json
@@ -1,15 +1,12 @@
{
+ "extends": "../tsconfig.json",
"compilerOptions": {
- "module": "ES2022",
- "target": "ES2021",
- "lib": ["es2021", "webworker"],
- "jsx": "react",
- "noImplicitAny": false,
- "sourceMap": true,
+ "lib": ["dom", "dom.iterable", "es2021", "webworker"],
+ "noEmit": false,
"preserveConstEnums": true,
- "outDir": "../public",
- "skipLibCheck": true,
- "moduleResolution": "node"
+ "strictNullChecks": false,
+ "target": "ES2021"
},
- "include": ["src/*.ts", "src/**/*.ts", "src/*.tsx", "src/**/*.tsx"]
+ "include": ["src"],
+ "exclude": []
}
diff --git a/client/pwa/webpack.config.js b/client/pwa/webpack.config.js
index 4b8889dafe8c..33c7f7fa4a4d 100644
--- a/client/pwa/webpack.config.js
+++ b/client/pwa/webpack.config.js
@@ -1,20 +1,17 @@
-const path = require("path");
-const commitHash = require("child_process")
- .execSync("git rev-parse --short HEAD")
- .toString()
- .trim();
-const webpack = require("webpack");
+import { fileURLToPath } from "node:url";
+import { execSync } from "node:child_process";
+import webpack from "webpack";
-const dirname = __dirname;
+const commitHash = execSync("git rev-parse --short HEAD").toString().trim();
-module.exports = {
+const config = {
entry: {
- bundle: path.join(dirname, "./src/service-worker.ts"),
+ bundle: "./src/service-worker.ts",
},
output: {
filename: "service-worker.js",
- path: path.join(dirname, "../public/"),
+ path: fileURLToPath(new URL("../public/", import.meta.url)),
},
mode: "development",
@@ -32,6 +29,9 @@ module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
+ extensionAlias: {
+ ".js": [".ts", ".js"],
+ },
plugins: [],
},
// plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
@@ -44,3 +44,5 @@ module.exports = {
],
},
};
+
+export default config;
diff --git a/client/pwa/webpack.production.config.js b/client/pwa/webpack.production.config.js
index 71ba13c73e7e..036b71fc6248 100644
--- a/client/pwa/webpack.production.config.js
+++ b/client/pwa/webpack.production.config.js
@@ -1,20 +1,17 @@
-const path = require("path");
-const commitHash = require("child_process")
- .execSync("git rev-parse --short HEAD")
- .toString()
- .trim();
-const webpack = require("webpack");
+import { fileURLToPath } from "node:url";
+import { execSync } from "node:child_process";
+import webpack from "webpack";
-const dirname = __dirname;
+const commitHash = execSync("git rev-parse --short HEAD").toString().trim();
-module.exports = {
+const config = {
entry: {
- bundle: path.join(dirname, "./src/service-worker.ts"),
+ bundle: "./src/service-worker.ts",
},
output: {
filename: "service-worker.js",
- path: path.join(dirname, "../public/"),
+ path: fileURLToPath(new URL("../public/", import.meta.url)),
},
mode: "production",
@@ -31,6 +28,9 @@ module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
+ extensionAlias: {
+ ".js": [".ts", ".js"],
+ },
plugins: [],
},
@@ -43,3 +43,5 @@ module.exports = {
],
},
};
+
+export default config;
diff --git a/client/scripts/build.js b/client/scripts/build.js
index 0dbadefe8092..0f4d6b6f8473 100644
--- a/client/scripts/build.js
+++ b/client/scripts/build.js
@@ -1,6 +1,20 @@
-// Do this as the first thing so that any code reading it knows the right env.
-process.env.BABEL_ENV = "production";
-process.env.NODE_ENV = "production";
+// Ensure environment variables are read.
+import "../config/env.js";
+
+import path from "node:path";
+import chalk from "react-dev-utils/chalk.js";
+import fs from "fs-extra";
+import bfj from "bfj";
+import webpack from "webpack";
+import { checkBrowsers } from "react-dev-utils/browsersHelper.js";
+import checkRequiredFiles from "react-dev-utils/checkRequiredFiles.js";
+import formatWebpackMessages from "react-dev-utils/formatWebpackMessages.js";
+import printHostingInstructions from "react-dev-utils/printHostingInstructions.js";
+import FileSizeReporter from "react-dev-utils/FileSizeReporter.js";
+import printBuildError from "react-dev-utils/printBuildError.js";
+
+import configFactory from "../config/webpack.config.js";
+import paths from "../config/paths.js";
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
@@ -9,21 +23,7 @@ process.on("unhandledRejection", (err) => {
throw err;
});
-// Ensure environment variables are read.
-require("../config/env");
-
-const path = require("path");
-const chalk = require("react-dev-utils/chalk");
-const fs = require("fs-extra");
-const bfj = require("bfj");
-const webpack = require("webpack");
-const configFactory = require("../config/webpack.config");
-const paths = require("../config/paths");
-const checkRequiredFiles = require("react-dev-utils/checkRequiredFiles");
-const formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
-const printHostingInstructions = require("react-dev-utils/printHostingInstructions");
-const FileSizeReporter = require("react-dev-utils/FileSizeReporter");
-const printBuildError = require("react-dev-utils/printBuildError");
+const appPackage = JSON.parse(fs.readFileSync(paths.appPackageJson));
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
@@ -49,7 +49,6 @@ const config = configFactory("production");
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
-const { checkBrowsers } = require("react-dev-utils/browsersHelper");
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
@@ -94,7 +93,6 @@ checkBrowsers(paths.appPath, isInteractive)
);
console.log();
- const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrlOrPath;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
diff --git a/client/scripts/start.js b/client/scripts/start.js
index 637fea7a9184..b322ca708ce6 100644
--- a/client/scripts/start.js
+++ b/client/scripts/start.js
@@ -1,6 +1,27 @@
-// Do this as the first thing so that any code reading it knows the right env.
-process.env.BABEL_ENV = "development";
-process.env.NODE_ENV = "development";
+// Ensure environment variables are read.
+import "../config/env.js";
+
+import fs from "node:fs";
+import chalk from "react-dev-utils/chalk.js";
+import webpack from "webpack";
+import WebpackDevServer from "webpack-dev-server";
+import clearConsole from "react-dev-utils/clearConsole.js";
+import checkRequiredFiles from "react-dev-utils/checkRequiredFiles.js";
+import { checkBrowsers } from "react-dev-utils/browsersHelper.js";
+import {
+ choosePort,
+ createCompiler,
+ prepareProxy,
+ prepareUrls,
+} from "react-dev-utils/WebpackDevServerUtils.js";
+import openBrowser from "react-dev-utils/openBrowser.js";
+import semver from "semver";
+import react from "react";
+
+import paths from "../config/paths.js";
+import configFactory from "../config/webpack.config.js";
+import createDevServerConfig from "../config/webpackDevServer.config.js";
+import getClientEnvironment from "../config/env.js";
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
@@ -9,28 +30,7 @@ process.on("unhandledRejection", (err) => {
throw err;
});
-// Ensure environment variables are read.
-require("../config/env");
-
-const fs = require("fs");
-const chalk = require("react-dev-utils/chalk");
-const webpack = require("webpack");
-const WebpackDevServer = require("webpack-dev-server");
-const clearConsole = require("react-dev-utils/clearConsole");
-const checkRequiredFiles = require("react-dev-utils/checkRequiredFiles");
-const {
- choosePort,
- createCompiler,
- prepareProxy,
- prepareUrls,
-} = require("react-dev-utils/WebpackDevServerUtils");
-const openBrowser = require("react-dev-utils/openBrowser");
-const semver = require("semver");
-const paths = require("../config/paths");
-const configFactory = require("../config/webpack.config");
-const createDevServerConfig = require("../config/webpackDevServer.config");
-const getClientEnvironment = require("../config/env");
-const react = require(require.resolve("react", { paths: [paths.appPath] }));
+const appPackageJson = JSON.parse(fs.readFileSync(paths.appPackageJson));
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const useYarn = fs.existsSync(paths.yarnLockFile);
@@ -64,7 +64,6 @@ if (process.env.HOST) {
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
-const { checkBrowsers } = require("react-dev-utils/browsersHelper");
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
@@ -79,7 +78,7 @@ checkBrowsers(paths.appPath, isInteractive)
const config = configFactory("development");
const protocol = process.env.HTTPS === "true" ? "https" : "http";
- const appName = require(paths.appPackageJson).name;
+ const appName = appPackageJson.name;
const useTypeScript = fs.existsSync(paths.appTsConfig);
const urls = prepareUrls(
@@ -98,7 +97,7 @@ checkBrowsers(paths.appPath, isInteractive)
webpack,
});
// Load proxy config
- const proxySetting = require(paths.appPackageJson).proxy;
+ const proxySetting = appPackageJson.proxy;
const proxyConfig = prepareProxy(
proxySetting,
paths.appPublic,
diff --git a/client/scripts/test.js b/client/scripts/test.js
index 8b03376f37a1..2ef4b4d29c5a 100644
--- a/client/scripts/test.js
+++ b/client/scripts/test.js
@@ -1,7 +1,8 @@
-// Do this as the first thing so that any code reading it knows the right env.
-process.env.BABEL_ENV = "test";
-process.env.NODE_ENV = "test";
-process.env.PUBLIC_URL = "";
+// Ensure environment variables are read.
+import "../config/env.js";
+
+import jest from "jest";
+import { execSync } from "node:child_process";
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
@@ -10,11 +11,6 @@ process.on("unhandledRejection", (err) => {
throw err;
});
-// Ensure environment variables are read.
-require("../config/env");
-
-const jest = require("jest");
-const execSync = require("child_process").execSync;
let argv = process.argv.slice(2);
function isInGitRepository() {
diff --git a/client/src/document/index.test.tsx b/client/src/document/index.test.tsx
index 09d3a06e6b4a..e78dc6c6164d 100644
--- a/client/src/document/index.test.tsx
+++ b/client/src/document/index.test.tsx
@@ -1,7 +1,8 @@
-import { Document } from "./index";
import { Route, Routes, MemoryRouter } from "react-router-dom";
import React from "react";
-const { render, waitFor } = require("@testing-library/react");
+import { render, waitFor } from "@testing-library/react";
+
+import { Document } from "./index";
declare var global: Window;
diff --git a/client/src/homepage/latest-news/index.tsx b/client/src/homepage/latest-news/index.tsx
index b5322d5859a0..1b17a45b8e12 100644
--- a/client/src/homepage/latest-news/index.tsx
+++ b/client/src/homepage/latest-news/index.tsx
@@ -3,22 +3,12 @@ import relativeTime from "dayjs/plugin/relativeTime";
import useSWR from "swr";
import { CRUD_MODE } from "../../env";
import { HydrationData } from "../../types/hydration";
+import { NewsItem } from "../../../../libs/types/document";
import "./index.scss";
dayjs.extend(relativeTime);
-export interface NewsItem {
- url: string;
- title: string;
- author?: string;
- source: {
- name: string;
- url: string;
- };
- published_at: string;
-}
-
export function LatestNews(props: HydrationData) {
const fallbackData = props.hyData ? props : undefined;
diff --git a/client/src/settings/db.ts b/client/src/settings/db.ts
index ee15e79fbc4a..eb8031511e32 100644
--- a/client/src/settings/db.ts
+++ b/client/src/settings/db.ts
@@ -85,9 +85,9 @@ export class MDNOfflineDB extends Dexie {
}
}
-const offlineDb = new MDNOfflineDB();
+export const offlineDb = new MDNOfflineDB();
-async function getContentStatus(): Promise {
+export async function getContentStatus(): Promise {
const current = await offlineDb.contentStatusHistory.toCollection().last();
return (
@@ -101,7 +101,7 @@ async function getContentStatus(): Promise {
);
}
-async function patchContentStatus(
+export async function patchContentStatus(
changes: Omit, "id" | "timestamp">
) {
const db = offlineDb;
@@ -129,5 +129,3 @@ async function patchContentStatus(
}
});
}
-
-export { offlineDb, getContentStatus, patchContentStatus };
diff --git a/client/src/setupProxy.js b/client/src/setupProxy.js
index 6b0e9a7a0b07..3d60e5ce1f57 100644
--- a/client/src/setupProxy.js
+++ b/client/src/setupProxy.js
@@ -1,9 +1,10 @@
-const { createProxyMiddleware } = require("http-proxy-middleware");
+import { createProxyMiddleware } from "http-proxy-middleware";
const SERVER_PORT = process.env.SERVER_PORT || 5042;
console.log(`Setting up a Proxy to localhost:${SERVER_PORT}`);
-module.exports = function (app) {
+
+function config(app) {
const proxy = createProxyMiddleware(["!**/*.hot-update.json"], {
target: `http://localhost:${SERVER_PORT}`,
changeOrigin: true,
@@ -17,4 +18,6 @@ module.exports = function (app) {
app.use("**/*.(png|webp|gif|jpe?g|svg)", proxy);
// All those root-level images like /favicon-48x48.png
app.use("/*.(png|webp|gif|jpe?g|svg)", proxy);
-};
+}
+
+export default config;
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 14c196eeb146..d98e742dc40f 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -1,14 +1,23 @@
{
- "extends": "../tsconfig.json",
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
- "module": "esnext",
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
"isolatedModules": true,
+ "jsx": "react-jsx",
+ "lib": ["dom", "dom.iterable", "es2020"],
+ "module": "ES2020",
+ "moduleResolution": "Node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
- "jsx": "react-jsx",
- "strictNullChecks": true
+ "noImplicitAny": false,
+ "preserveWatchOutput": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "target": "ES2020"
},
"include": ["src"]
}
diff --git a/content/document-paths.ts b/content/document-paths.ts
index a442af7f1208..a2ace0a9d7f5 100644
--- a/content/document-paths.ts
+++ b/content/document-paths.ts
@@ -2,8 +2,8 @@ import path from "node:path";
import { fdir, PathsOutput } from "fdir";
-import { HTML_FILENAME, MARKDOWN_FILENAME } from "../libs/constants";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
+import { HTML_FILENAME, MARKDOWN_FILENAME } from "../libs/constants/index.js";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
type Tree = { [key: string]: Tree | null };
diff --git a/content/document.test.ts b/content/document.test.ts
index 997403690106..67a99924a33b 100644
--- a/content/document.test.ts
+++ b/content/document.test.ts
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
-import * as Document from "./document";
+import * as Document from "./document.js";
describe("Document.findAll()", () => {
it("should always return files that exist", () => {
diff --git a/content/document.ts b/content/document.ts
index 9f8a42ff05b0..b8a48e58d0ee 100644
--- a/content/document.ts
+++ b/content/document.ts
@@ -6,18 +6,22 @@ import fm from "front-matter";
import yaml from "js-yaml";
import { fdir, PathsOutput } from "fdir";
-import { CONTENT_TRANSLATED_ROOT, CONTENT_ROOT, ROOTS } from "../libs/env";
+import {
+ CONTENT_TRANSLATED_ROOT,
+ CONTENT_ROOT,
+ ROOTS,
+} from "../libs/env/index.js";
import {
ACTIVE_LOCALES,
HTML_FILENAME,
MARKDOWN_FILENAME,
VALID_LOCALES,
-} from "../libs/constants";
-import { isValidLocale } from "../libs/locale-utils";
-import { getPopularities } from "./popularities";
-import { getWikiHistories } from "./wikihistories";
-import { getGitHistories } from "./githistories";
-import { childrenFoldersForPath } from "./document-paths";
+} from "../libs/constants/index.js";
+import { isValidLocale } from "../libs/locale-utils/index.js";
+import { getPopularities } from "./popularities.js";
+import { getWikiHistories } from "./wikihistories.js";
+import { getGitHistories } from "./githistories.js";
+import { childrenFoldersForPath } from "./document-paths.js";
import {
buildURL,
@@ -28,10 +32,11 @@ import {
urlToFolderPath,
toPrettyJSON,
MEMOIZE_INVALIDATE,
-} from "./utils";
-export { urlToFolderPath, MEMOIZE_INVALIDATE } from "./utils";
-import * as Redirect from "./redirect";
-import { DocFrontmatter } from "../libs/types";
+} from "./utils.js";
+import * as Redirect from "./redirect.js";
+import { DocFrontmatter } from "../libs/types/document.js";
+
+export { urlToFolderPath, MEMOIZE_INVALIDATE } from "./utils.js";
function buildPath(localeFolder: string, slug: string) {
return path.join(localeFolder, slugToFolder(slug));
diff --git a/content/image.ts b/content/image.ts
index 8551c47cc4a6..dad347cf9dac 100644
--- a/content/image.ts
+++ b/content/image.ts
@@ -5,9 +5,9 @@ import readChunk from "read-chunk";
import imageType from "image-type";
import isSvg from "is-svg";
-import { DEFAULT_LOCALE } from "../libs/constants";
-import { ROOTS } from "../libs/env";
-import { memoize, slugToFolder } from "./utils";
+import { DEFAULT_LOCALE } from "../libs/constants/index.js";
+import { ROOTS } from "../libs/env/index.js";
+import { memoize, slugToFolder } from "./utils.js";
function isImage(filePath) {
if (fs.statSync(filePath).isDirectory()) {
@@ -16,7 +16,6 @@ function isImage(filePath) {
if (filePath.toLowerCase().endsWith(".svg")) {
return isSvg(fs.readFileSync(filePath));
}
-
const buffer = readChunk.sync(filePath, 0, 12);
if (buffer.length === 0) {
return false;
diff --git a/content/index.ts b/content/index.ts
index 0852182fd1db..0c41d320022c 100644
--- a/content/index.ts
+++ b/content/index.ts
@@ -1,8 +1,16 @@
-export * as Document from "./document";
-export * as Translation from "./translation";
-export { getPopularities } from "./popularities";
-export * as Redirect from "./redirect";
-export * as Image from "./image";
-export { buildURL, memoize, slugToFolder, execGit, getRoot } from "./utils";
-export { resolveFundamental } from "../libs/fundamental-redirects";
-export { translationsOf } from "./translations";
+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 {
+ buildURL,
+ memoize,
+ slugToFolder,
+ execGit,
+ getRoot,
+ urlToFolderPath,
+ MEMOIZE_INVALIDATE,
+} from "./utils.js";
+export { resolveFundamental } from "../libs/fundamental-redirects/index.js";
+export { translationsOf } from "./translations.js";
diff --git a/content/popularities.ts b/content/popularities.ts
index 77c2298c7486..158dceece619 100644
--- a/content/popularities.ts
+++ b/content/popularities.ts
@@ -1,7 +1,4 @@
import fs from "node:fs";
-import path from "node:path";
-
-const dirname = __dirname;
// Module-level cache
const popularities = new Map();
@@ -9,9 +6,7 @@ const popularities = new Map();
export function getPopularities() {
if (!popularities.size) {
// This is the file that's *not* checked into git.
- const filePath = path.resolve(
- path.join(dirname, "..", "popularities.json")
- );
+ const filePath = new URL("../popularities.json", import.meta.url);
Object.entries(JSON.parse(fs.readFileSync(filePath, "utf-8"))).forEach(
([url, value]: [string, unknown]) => {
popularities.set(url, value as number);
diff --git a/content/redirect.test.ts b/content/redirect.test.ts
index e4bb4b2d9df4..8535b0af18c9 100644
--- a/content/redirect.test.ts
+++ b/content/redirect.test.ts
@@ -1,4 +1,4 @@
-import * as Redirect from "./redirect";
+import * as Redirect from "./redirect.js";
describe("short cuts", () => {
it("simple chain", () => {
diff --git a/content/redirect.ts b/content/redirect.ts
index d21fe4d7c24b..02fec571b58a 100644
--- a/content/redirect.ts
+++ b/content/redirect.ts
@@ -1,11 +1,11 @@
import fs from "node:fs";
import path from "node:path";
-import { resolveFundamental } from "../libs/fundamental-redirects";
-import { decodePath, slugToFolder } from "../libs/slug-utils";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { VALID_LOCALES } from "../libs/constants";
-import { getRoot } from "./utils";
+import { resolveFundamental } from "../libs/fundamental-redirects/index.js";
+import { decodePath, slugToFolder } from "../libs/slug-utils/index.js";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
+import { VALID_LOCALES } from "../libs/constants/index.js";
+import { getRoot } from "./utils.js";
type Pair = [string, string];
type Pairs = Pair[];
diff --git a/content/translation.ts b/content/translation.ts
index e97b6dd0c34d..69507cb226b6 100644
--- a/content/translation.ts
+++ b/content/translation.ts
@@ -1,4 +1,4 @@
-import Parser from "../kumascript/src/parser.js";
+import * as Parser from "../kumascript/src/parser.js";
function* fastKSParser(s: string) {
for (const match of s.matchAll(
diff --git a/content/translations.ts b/content/translations.ts
index 039e18b1faf9..c25f4be7f44f 100644
--- a/content/translations.ts
+++ b/content/translations.ts
@@ -1,6 +1,6 @@
-import * as Document from "./document";
-import { VALID_LOCALES } from "../libs/constants";
-import LANGUAGES_RAW from "../libs/languages";
+import * as Document from "./document.js";
+import { VALID_LOCALES } from "../libs/constants/index.js";
+import LANGUAGES_RAW from "../libs/languages/index.js";
const LANGUAGES = new Map(
Object.entries(LANGUAGES_RAW).map(([locale, data]) => {
diff --git a/content/tsconfig.json b/content/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/content/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/content/utils.ts b/content/utils.ts
index b24c47f5b512..6156f918d57a 100644
--- a/content/utils.ts
+++ b/content/utils.ts
@@ -1,9 +1,18 @@
import path from "node:path";
import childProcess from "node:child_process";
+
import LRU from "lru-cache";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { slugToFolder as _slugToFolder } from "../libs/slug-utils";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
+import { slugToFolder as _slugToFolder } from "../libs/slug-utils/index.js";
+
+let prettier = null;
+
+try {
+ prettier = (await import("prettier")).default;
+} catch (e) {
+ // If we failed to import Prettier, that's okay
+}
export const MEMOIZE_INVALIDATE = Symbol("force cache update");
@@ -106,17 +115,19 @@ export function execGit(args, opts: { cwd?: string } = {}, root = null) {
export function toPrettyJSON(value) {
const json = JSON.stringify(value, null, 2) + "\n";
- try {
- // eslint-disable-next-line n/no-unpublished-require
- return require("prettier").format(json, { parser: "json" });
- } catch (e) {
- return json;
+ if (prettier) {
+ try {
+ return prettier.format(json, { parser: "json" });
+ } catch (e) {
+ // If Prettier formatting failed, don't worry
+ }
}
+ return json;
}
export function urlToFolderPath(url) {
const [, locale, , ...slugParts] = url.split("/");
- return path.join(locale.toLowerCase(), slugToFolder(slugParts.join("/")));
+ return path.join(locale.toLowerCase(), _slugToFolder(slugParts.join("/")));
}
export function slugToFolder(slug) {
diff --git a/deployer/aws-lambda/mdn-stripe-price-ids/tests/handler.test.js b/deployer/aws-lambda/mdn-stripe-price-ids/tests/handler.test.js
index e444a8f2b02e..cf37abf35b98 100644
--- a/deployer/aws-lambda/mdn-stripe-price-ids/tests/handler.test.js
+++ b/deployer/aws-lambda/mdn-stripe-price-ids/tests/handler.test.js
@@ -1,4 +1,8 @@
+import { createRequire } from "node:module";
import { handler } from "../index.js";
+
+const require = createRequire(import.meta.url);
+
jest.mock("../plans-stage-lookup.json", () => {
return require("./__mocks__/plans-stage-lookup-test.json");
});
diff --git a/docs/conventions.md b/docs/conventions.md
index acea94734ffd..faa630eeb07e 100644
--- a/docs/conventions.md
+++ b/docs/conventions.md
@@ -19,7 +19,7 @@ which do not adhere to its formatting.
Things should be defined in order in which they are used. E.g.:
```javascript
-const dependency = require("dependency");
+import dependency from "dependency";
function doThing() {
// ...
diff --git a/filecheck/checker.ts b/filecheck/checker.ts
index ef7edfa0464c..ddc0d805b0da 100644
--- a/filecheck/checker.ts
+++ b/filecheck/checker.ts
@@ -10,17 +10,19 @@ import tempy from "tempy";
import * as cheerio from "cheerio";
import FileType from "file-type";
import imagemin from "imagemin";
-import imageminPngquant from "imagemin-pngquant";
+import imageminPngquantPkg from "imagemin-pngquant";
import imageminMozjpeg from "imagemin-mozjpeg";
import imageminGifsicle from "imagemin-gifsicle";
import imageminSvgo from "imagemin-svgo";
import isSvg from "is-svg";
-import { MAX_FILE_SIZE } from "../libs/env";
+import { MAX_FILE_SIZE } from "../libs/env/index.js";
import {
VALID_MIME_TYPES,
MAX_COMPRESSION_DIFFERENCE_PERCENTAGE,
-} from "../libs/constants";
+} from "../libs/constants/index.js";
+
+const { default: imageminPngquant } = imageminPngquantPkg;
function formatSize(bytes: number): string {
if (bytes > 1024 * 1024) {
diff --git a/filecheck/cli.ts b/filecheck/cli.ts
index 89cf787099fb..21e76255f61a 100644
--- a/filecheck/cli.ts
+++ b/filecheck/cli.ts
@@ -1,11 +1,13 @@
#!/usr/bin/env node
import path from "node:path";
-import { ActionParameters, program } from "@caporal/core";
+import caporal, { ActionParameters } from "@caporal/core";
-import { runChecker } from "./checker";
-import { MAX_COMPRESSION_DIFFERENCE_PERCENTAGE } from "../libs/constants";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
+import { runChecker } from "./checker.js";
+import { MAX_COMPRESSION_DIFFERENCE_PERCENTAGE } from "../libs/constants/index.js";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
+
+const { program } = caporal;
interface FilecheckArgsAndOptions extends ActionParameters {
args: {
diff --git a/filecheck/tsconfig.json b/filecheck/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/filecheck/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/jest.config.json b/jest.config.json
index bb41e8ee81f6..589b291d5804 100644
--- a/jest.config.json
+++ b/jest.config.json
@@ -1,11 +1,16 @@
{
- "preset": "ts-jest",
+ "extensionsToTreatAsEsm": [".ts", ".tsx"],
+ "moduleNameMapper": {
+ "^(\\.{1,2}/.*)\\.js$": "$1"
+ },
+ "preset": "ts-jest/presets/default-esm",
"testPathIgnorePatterns": ["headless*", "developing.spec.ts"],
"transform": {
"\\.tsx?$": [
"ts-jest",
{
- "babelConfig": true
+ "babelConfig": true,
+ "useESM": true
}
]
}
diff --git a/kumascript/index.ts b/kumascript/index.ts
index 2050f9822e28..b7cb1f446233 100644
--- a/kumascript/index.ts
+++ b/kumascript/index.ts
@@ -1,19 +1,19 @@
import LRU from "lru-cache";
+import * as cheerio from "cheerio";
-import { Document } from "../content";
-import { m2h } from "../markdown";
+import { Document } from "../content/index.js";
+import { m2h } from "../markdown/index.js";
-import info from "./src/info";
-import { render as renderMacros } from "./src/render";
-export { buildLiveSamplePages } from "./src/live-sample";
-import { HTMLTool } from "./src/api/util";
-import { DEFAULT_LOCALE } from "../libs/constants";
+import info from "./src/info.js";
+import { render as renderMacros } from "./src/render.js";
+export { buildLiveSamplePages } from "./src/live-sample.js";
+import { HTMLTool } from "./src/api/util.js";
+import { DEFAULT_LOCALE } from "../libs/constants/index.js";
import {
INTERACTIVE_EXAMPLES_BASE_URL,
LIVE_SAMPLES_BASE_URL,
-} from "../libs/env";
-import { SourceCodeError } from "./src/errors";
-import * as cheerio from "cheerio";
+} from "../libs/env/index.js";
+import { SourceCodeError } from "./src/errors.js";
const DEPENDENCY_LOOP_INTRO =
'The following documents form a circular dependency when rendering (via the "page" macros):';
diff --git a/kumascript/jest.config.js b/kumascript/jest.config.js
deleted file mode 100644
index 6908203f16dd..000000000000
--- a/kumascript/jest.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-module.exports = {
- testEnvironment: "node",
- coveragePathIgnorePatterns: ["./src/parser.js"],
-};
diff --git a/kumascript/macros/CSSAnimatedProperties.ejs b/kumascript/macros/CSSAnimatedProperties.ejs
index c5023786cf5a..cbbc587b8fa9 100644
--- a/kumascript/macros/CSSAnimatedProperties.ejs
+++ b/kumascript/macros/CSSAnimatedProperties.ejs
@@ -6,7 +6,8 @@
// Read all CSSData and extract animated properties
-var data = require('mdn-data/css');
+const data = require('mdn-data/css');
+
var animatedProps = [];
// Go through all properties, and test if they are animated. If so, add them to
diff --git a/kumascript/macros/CSSInfo.ejs b/kumascript/macros/CSSInfo.ejs
index 515c40d8cf8c..6a8c570279dd 100644
--- a/kumascript/macros/CSSInfo.ejs
+++ b/kumascript/macros/CSSInfo.ejs
@@ -6,12 +6,14 @@
$1 - @-rule - defaults to the @-rule within the URL
*/
+const data = require('mdn-data/css');
+const localStrings = require('mdn-data/l10n/css');
+
let slug = env.slug;
let name = $0 || (slug ? slug.split("/").pop().toLowerCase() :
"preview-wiki-content");
let atRule = $1;
-var data = require('mdn-data/css');
let formattedError = "$1$";
let locale = env.locale;
let localize = mdn.getLocalString;
@@ -21,7 +23,6 @@ let localWiki = wiki;
let currentPage = page;
let cssLocalStrings = web.getJSONData("L10n-CSS");
-let localStrings = require('mdn-data/l10n/css');
/*
Creates a link to another API, e.g. a CSS property or SVG element
diff --git a/kumascript/macros/CSS_Ref.ejs b/kumascript/macros/CSS_Ref.ejs
index a2bf755d7ab9..5f77e1f23c07 100644
--- a/kumascript/macros/CSS_Ref.ejs
+++ b/kumascript/macros/CSS_Ref.ejs
@@ -14,6 +14,7 @@
*/
const data = require('mdn-data/css');
+
let types = ["properties", "selectors", "types", "syntaxes", "at-rules",
"descriptors", "units"];
let groupingType = "alphabetically";
diff --git a/kumascript/src/api/mdn.ts b/kumascript/src/api/mdn.ts
index 2fecbc3a70d8..1055234ac126 100644
--- a/kumascript/src/api/mdn.ts
+++ b/kumascript/src/api/mdn.ts
@@ -1,6 +1,8 @@
-import got from "got";
-import { KumaThis } from "../environment";
-import * as util from "./util";
+import gotPkg from "got";
+import { KumaThis } from "../environment.js";
+import * as util from "./util.js";
+
+const { default: got } = gotPkg;
// Module level caching for repeat calls to fetchWebExtExamples().
let webExtExamples: any = null;
diff --git a/kumascript/src/api/page.ts b/kumascript/src/api/page.ts
index 7196d1055445..90b875afb6f4 100644
--- a/kumascript/src/api/page.ts
+++ b/kumascript/src/api/page.ts
@@ -1,5 +1,5 @@
-import { KumaThis } from "../environment";
-import { getBadgeTemplates } from "../lib/badges";
+import { KumaThis } from "../environment.js";
+import { getBadgeTemplates } from "../lib/badges.js";
const page = {
// Determines whether or not the page has the specified tag. Returns
diff --git a/kumascript/src/api/web.ts b/kumascript/src/api/web.ts
index 8b87c2c8dd0d..e79bf48c7efe 100644
--- a/kumascript/src/api/web.ts
+++ b/kumascript/src/api/web.ts
@@ -1,12 +1,12 @@
import fs from "node:fs";
import path from "node:path";
-import * as util from "./util";
+import * as util from "./util.js";
-const DUMMY_BASE_URL = "https://example.com";
+import { CONTENT_ROOT } from "../../../libs/env/index.js";
+import { KumaThis } from "../environment.js";
-import { CONTENT_ROOT } from "../../../libs/env";
-import { KumaThis } from "../environment";
+const DUMMY_BASE_URL = "https://example.com";
const _warned = new Map();
// The purpose of this function is to make sure `console.warn` is only called once
diff --git a/kumascript/src/api/wiki.ts b/kumascript/src/api/wiki.ts
index a2601f27e113..db8b3b15473d 100644
--- a/kumascript/src/api/wiki.ts
+++ b/kumascript/src/api/wiki.ts
@@ -1,5 +1,5 @@
-import { KumaThis } from "../environment";
-import * as util from "./util";
+import { KumaThis } from "../environment.js";
+import * as util from "./util.js";
const wiki = {
//
diff --git a/kumascript/src/environment.ts b/kumascript/src/environment.ts
index f77d66c8b68b..ad36e6aad89f 100644
--- a/kumascript/src/environment.ts
+++ b/kumascript/src/environment.ts
@@ -22,6 +22,9 @@
* from the rest of the file which is well tested.
*/
+import { createRequire } from "node:module";
+const require = createRequire(import.meta.url);
+
// The properties of this object will be globals in the macro
// execution environment.
const globalsPrototype = {
@@ -35,12 +38,12 @@ const globalsPrototype = {
require,
};
-import mdnPrototype from "./api/mdn";
-import wikiPrototype from "./api/wiki";
-import webPrototype from "./api/web";
-import pagePrototype from "./api/page";
-import info from "./info";
-import Templates from "./templates";
+import mdnPrototype from "./api/mdn.js";
+import wikiPrototype from "./api/wiki.js";
+import webPrototype from "./api/web.js";
+import pagePrototype from "./api/page.js";
+import info from "./info.js";
+import Templates from "./templates.js";
export interface KumaThis {
mdn: typeof mdnPrototype;
diff --git a/kumascript/src/errors.ts b/kumascript/src/errors.ts
index 1aaec8af802c..a4347e8c575f 100644
--- a/kumascript/src/errors.ts
+++ b/kumascript/src/errors.ts
@@ -2,7 +2,7 @@
* Error classes that can be thrown when trying to render the macros on a page.
*/
-import { RedirectInfo } from "../../libs/types/document";
+import { RedirectInfo } from "../../libs/types/document.js";
/**
* This is the common superclass of the other error classes here.
diff --git a/kumascript/src/info.ts b/kumascript/src/info.ts
index 13de3cb90789..14519ce424d9 100644
--- a/kumascript/src/info.ts
+++ b/kumascript/src/info.ts
@@ -1,9 +1,9 @@
import cheerio from "cheerio";
-import Parser from "./parser.js";
-import { Document, Redirect } from "../../content";
-import { isValidLocale } from "../../libs/locale-utils";
-import { m2hSync } from "../../markdown";
+import * as Parser from "./parser.js";
+import { Document, Redirect } from "../../content/index.js";
+import { isValidLocale } from "../../libs/locale-utils/index.js";
+import { m2hSync } from "../../markdown/index.js";
const DUMMY_BASE_URL = "https://example.com";
@@ -59,7 +59,7 @@ function repairURL(url) {
return url;
}
-const info = {
+export const info = {
getPathname(url) {
// This function returns just the pathname of the given "url", removing
// any trailing "/".
diff --git a/kumascript/src/lib/badges.ts b/kumascript/src/lib/badges.ts
index 440c2f944e33..c7f3f1b41e75 100644
--- a/kumascript/src/lib/badges.ts
+++ b/kumascript/src/lib/badges.ts
@@ -1,5 +1,5 @@
-import { KumaThis } from "../environment";
-import page from "../api/page";
+import { KumaThis } from "../environment.js";
+import page from "../api/page.js";
const badges = [
{
diff --git a/kumascript/src/live-sample.ts b/kumascript/src/live-sample.ts
index 7a2d56e6f64c..292bc95bee22 100644
--- a/kumascript/src/live-sample.ts
+++ b/kumascript/src/live-sample.ts
@@ -1,8 +1,8 @@
import cheerio from "cheerio";
import ejs from "ejs";
-import { MacroLiveSampleError } from "./errors";
-import { HTMLTool, KumascriptError, slugify } from "./api/util";
+import { MacroLiveSampleError } from "./errors.js";
+import { HTMLTool, KumascriptError, slugify } from "./api/util.js";
const LIVE_SAMPLE_HTML = `
diff --git a/kumascript/src/parser.js b/kumascript/src/parser.js
index 15e7e26819ee..bddf083bca80 100644
--- a/kumascript/src/parser.js
+++ b/kumascript/src/parser.js
@@ -2165,7 +2165,5 @@ function peg$parse(input, options) {
}
}
-module.exports = {
- SyntaxError: peg$SyntaxError,
- parse: peg$parse,
-};
+export const SyntaxError = peg$SyntaxError;
+export const parse = peg$parse;
diff --git a/kumascript/src/render.ts b/kumascript/src/render.ts
index 2ca4984357a5..642adf449ae6 100644
--- a/kumascript/src/render.ts
+++ b/kumascript/src/render.ts
@@ -38,9 +38,9 @@
* - the environment object that defines per-page values such as
* locale, title and slug.
*/
-import Parser from "./parser.js";
-import Templates from "./templates";
-import Environment from "./environment";
+import * as Parser from "./parser.js";
+import Templates from "./templates.js";
+import Environment from "./environment.js";
import {
MacroInvocationError,
MacroNotFoundError,
@@ -51,8 +51,8 @@ import {
MacroWrongXRefError,
MacroDeprecatedError,
MacroPagesError,
-} from "./errors";
-import { RedirectInfo } from "../../libs/types/document";
+} from "./errors.js";
+import { RedirectInfo } from "../../libs/types/document.js";
const defaultTemplates = new Templates();
diff --git a/kumascript/src/templates.ts b/kumascript/src/templates.ts
index 6d53d0810193..30169b343391 100644
--- a/kumascript/src/templates.ts
+++ b/kumascript/src/templates.ts
@@ -25,11 +25,12 @@
*/
import fs from "node:fs";
import path from "node:path";
+import { fileURLToPath } from "node:url";
import ejs from "ejs";
-const dirname = __dirname;
-
-const DEFAULT_MACROS_DIRECTORY = path.normalize(`${dirname}/../macros/`);
+const DEFAULT_MACROS_DIRECTORY = path.normalize(
+ fileURLToPath(new URL("../macros", import.meta.url))
+);
export default class Templates {
private macroDirectory: string;
@@ -103,7 +104,7 @@ export default class Templates {
try {
const rendered = await ejs.renderFile(path, args, {
async: true,
- cache: process.env.NODE_ENV === "production",
+ cache: args.cache || process.env.NODE_ENV === "production",
});
return rendered.trim();
} catch (error) {
diff --git a/kumascript/tests/environment.test.ts b/kumascript/tests/environment.test.ts
index 46213b97848d..4df4b1b9c0e9 100644
--- a/kumascript/tests/environment.test.ts
+++ b/kumascript/tests/environment.test.ts
@@ -1,4 +1,5 @@
-import Environment from "../src/environment";
+import { jest } from "@jest/globals";
+import Environment from "../src/environment.js";
// We test using `with` because that is what EJS uses. But Jest
// runs tests in strict mode, so we have to hide the with inside
diff --git a/kumascript/tests/fixtures/server/macros/autorequire-lib1.ejs b/kumascript/tests/fixtures/server/macros/autorequire-lib1.ejs
index cfeff7e7fa9f..5bc3d7c8b908 100644
--- a/kumascript/tests/fixtures/server/macros/autorequire-lib1.ejs
+++ b/kumascript/tests/fixtures/server/macros/autorequire-lib1.ejs
@@ -1,5 +1,5 @@
<%
-module.exports = buildAPI({
+export default buildAPI({
result: function (str) {
return str + '!';
}
diff --git a/kumascript/tests/fixtures/server/macros/library1-used.ejs b/kumascript/tests/fixtures/server/macros/library1-used.ejs
index 2c54e96d1b4b..4e1da1954414 100644
--- a/kumascript/tests/fixtures/server/macros/library1-used.ejs
+++ b/kumascript/tests/fixtures/server/macros/library1-used.ejs
@@ -1,2 +1,2 @@
<% var library1 = require_macro('library1'); %>
-The result <%= library1.result($0) %>
+The result <%= library1($0) %>
diff --git a/kumascript/tests/fixtures/server/macros/library1.ejs b/kumascript/tests/fixtures/server/macros/library1.ejs
index d3c380dd2859..08eb05662d86 100644
--- a/kumascript/tests/fixtures/server/macros/library1.ejs
+++ b/kumascript/tests/fixtures/server/macros/library1.ejs
@@ -1,5 +1,7 @@
<%
-module.exports.result = function (str) {
+function result (str) {
return "was " + str + "!";
};
+
+export default result;
%>
diff --git a/kumascript/tests/fixtures/server/macros/require-used.ejs b/kumascript/tests/fixtures/server/macros/require-used.ejs
index 94d62c1aeb78..f164f20cf109 100644
--- a/kumascript/tests/fixtures/server/macros/require-used.ejs
+++ b/kumascript/tests/fixtures/server/macros/require-used.ejs
@@ -1,6 +1,7 @@
<%
+import bcd from '@mdn/browser-compat-data' assert { type: "json" };
+
var queryString = $0;
-var bcd = require('@mdn/browser-compat-data');
function getData() {
return queryString.split('.').reduce(function(prev, curr) {
diff --git a/kumascript/tests/index.test.ts b/kumascript/tests/index.test.ts
index 80b22178ae2e..53cce7721f1a 100644
--- a/kumascript/tests/index.test.ts
+++ b/kumascript/tests/index.test.ts
@@ -1,76 +1,104 @@
-import { Document } from "../../content";
-import info from "../src/info";
-import { render } from "../index";
+import { jest } from "@jest/globals";
+
+import * as Content from "../../content/index.js";
import {
MacroNotFoundError,
MacroBrokenLinkError,
MacroRedirectedLinkError,
-} from "../src/errors";
+} from "../src/errors.js";
+
+const source = `
+{{cssxref("bigfoot")}}
+{{nonExistentMacro("yada")}}
+{{cssxref("dumber")}}
+{{cssxref("number")}}
+`.trim();
+
+const findByURL = jest.fn((url: string) => {
+ return {
+ "/en-us/docs/web/a": {
+ url: "/en-US/docs/Web/A",
+ metadata: {
+ title: "A",
+ locale: "en-US",
+ slug: "Web/A",
+ tags: ["Web"],
+ },
+ rawBody: source,
+ isMarkdown: false,
+ fileInfo: {
+ path: "testing/content/files/en-us/web/a",
+ frontMatterOffset: 8,
+ },
+ },
+ "/en-us/docs/web/b": {
+ url: "/en-US/docs/Web/B",
+ metadata: {
+ title: "B",
+ locale: "en-US",
+ slug: "Web/B",
+ tags: ["Web"],
+ },
+ rawBody: '{{cssxref("bigfoot")}}
',
+ isMarkdown: false,
+ fileInfo: {
+ path: "testing/content/files/en-us/web/b",
+ frontMatterOffset: 4,
+ },
+ },
+ "/en-us/docs/web/css/number": {
+ url: "/en-US/docs/Web/CSS/number",
+ metadata: {
+ title: "",
+ locale: "en-US",
+ slug: "Web/Number",
+ "page-type": "css-type",
+ },
+ rawBody: "This is the number test page.
",
+ isMarkdown: false,
+ fileInfo: {
+ path: "testing/content/files/en-us/web/css/number",
+ frontMatterOffset: 12,
+ },
+ },
+ }[url];
+});
+
+jest.unstable_mockModule("../../content/index.js", () => ({
+ ...Content,
+ Document: {
+ ...Content.Document,
+ findByURL,
+ },
+}));
+
+// Depends on mocking Document.findByURL
+const { default: info } = await import("../src/info.js");
+
+const cleanURL = jest.fn((url: string) => {
+ const result = url.toLowerCase();
+ if (result === "/en-us/docs/web/css/dumber") {
+ return "/en-us/docs/web/css/number";
+ }
+ return result;
+});
+
+jest.unstable_mockModule("../src/info.js", () => ({
+ default: {
+ ...info,
+ cleanURL,
+ getPageByURL: jest.fn((url: string) => {
+ const document = findByURL(cleanURL(url));
+ return document ? info.getPage(document) : {};
+ }),
+ },
+}));
+
+// Depends on module mocking
+const { render } = await import("../index.js");
describe("testing the main render() function", () => {
it("non-fatal errors in macros are returned by render()", async () => {
- info.cleanURL = jest.fn((url) => {
- const result = url.toLowerCase();
- if (result === "/en-us/docs/web/css/dumber") {
- return "/en-us/docs/web/css/number";
- }
- return result;
- });
- const source = `
- {{cssxref("bigfoot")}}
- {{nonExistentMacro("yada")}}
- {{cssxref("dumber")}}
- {{cssxref("number")}}
- `.trim();
- Document.findByURL = jest.fn((url) => {
- return {
- "/en-us/docs/web/a": {
- url: "/en-US/docs/Web/A",
- metadata: {
- title: "A",
- locale: "en-US",
- slug: "Web/A",
- tags: ["Web"],
- },
- rawBody: source,
- isMarkdown: false,
- fileInfo: {
- path: "testing/content/files/en-us/web/a",
- frontMatterOffset: 8,
- },
- },
- "/en-us/docs/web/b": {
- url: "/en-US/docs/Web/B",
- metadata: {
- title: "B",
- locale: "en-US",
- slug: "Web/B",
- tags: ["Web"],
- },
- rawBody: '{{cssxref("bigfoot")}}
',
- isMarkdown: false,
- fileInfo: {
- path: "testing/content/files/en-us/web/b",
- frontMatterOffset: 4,
- },
- },
- "/en-us/docs/web/css/number": {
- url: "/en-US/docs/Web/CSS/number",
- metadata: {
- title: "",
- locale: "en-US",
- slug: "Web/Number",
- "page-type": "css-type",
- },
- rawBody: "This is the number test page.
",
- isMarkdown: false,
- fileInfo: {
- path: "testing/content/files/en-us/web/css/number",
- frontMatterOffset: 12,
- },
- },
- }[url];
- });
const [$, errors] = await render("/en-us/docs/web/a");
const result = $.html();
// First, let's check the result.
@@ -104,7 +132,7 @@ describe("testing the main render() function", () => {
expect(errors[1]).toBeInstanceOf(MacroNotFoundError);
expect(errors[1]).toMatchObject({
line: 9,
- column: 7,
+ column: 1,
filepath: "testing/content/files/en-us/web/a",
macroName: "nonExistentMacro",
errorStack: expect.stringContaining("Unknown macro nonexistentmacro"),
@@ -113,7 +141,7 @@ describe("testing the main render() function", () => {
expect(errors[2]).toBeInstanceOf(MacroRedirectedLinkError);
expect(errors[2]).toMatchObject({
line: 10,
- column: 7,
+ column: 1,
filepath: "testing/content/files/en-us/web/a",
errorStack: expect.stringContaining(
"/en-US/docs/Web/CSS/dumber redirects to /en-US/docs/Web/CSS/number"
diff --git a/kumascript/tests/macros.test.ts b/kumascript/tests/macros.test.ts
index 842c12216f83..68db362113be 100644
--- a/kumascript/tests/macros.test.ts
+++ b/kumascript/tests/macros.test.ts
@@ -2,13 +2,16 @@
* Verify that all of the macros in ../macros/ compile without errors
*/
import fs from "node:fs";
+import { fileURLToPath } from "node:url";
+
import ejs from "ejs";
-import Templates from "../src/templates";
-const dirname = __dirname;
+import Templates from "../src/templates.js";
describe("macros/ directory", () => {
describe("compile all macros", () => {
- const templates = new Templates(`${dirname}/../macros`);
+ const templates = new Templates(
+ fileURLToPath(new URL("../macros", import.meta.url))
+ );
const templateMap = templates.getTemplateMap();
const macroNames = Array.from(templateMap.keys());
diff --git a/kumascript/tests/macros/Compat.test.ts b/kumascript/tests/macros/Compat.test.ts
index 162e50b9230e..b986149daf3a 100644
--- a/kumascript/tests/macros/Compat.test.ts
+++ b/kumascript/tests/macros/Compat.test.ts
@@ -1,18 +1,17 @@
-import { assert, itMacro, describeMacro, lintHTML } from "./utils";
+import { assert, itMacro, describeMacro, lintHTML } from "./utils.js";
import fs from "node:fs";
-import path from "node:path";
import { JSDOM } from "jsdom";
import extend from "extend";
-const dirname = __dirname;
-const fixture_dir = path.resolve(dirname, "fixtures/compat");
+
+const fixture_dir = new URL("./fixtures/compat/", import.meta.url);
let fixtureCompatData = {};
fs.readdirSync(fixture_dir).forEach(function (fn) {
fixtureCompatData = extend(
true,
fixtureCompatData,
- JSON.parse(fs.readFileSync(path.resolve(fixture_dir, fn), "utf-8"))
+ JSON.parse(fs.readFileSync(new URL(fn, fixture_dir), "utf-8"))
);
});
diff --git a/kumascript/tests/macros/DefaultAPISidebar.test.ts b/kumascript/tests/macros/DefaultAPISidebar.test.ts
index cd15e8878af1..2453f7890cce 100644
--- a/kumascript/tests/macros/DefaultAPISidebar.test.ts
+++ b/kumascript/tests/macros/DefaultAPISidebar.test.ts
@@ -1,33 +1,32 @@
+import fs from "node:fs";
import { JSDOM } from "jsdom";
+import { jest } from "@jest/globals";
-import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils";
-
-const dirname = __dirname;
+import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils.js";
/**
* Load all the fixtures.
*/
-import fs from "node:fs";
-import path from "node:path";
-const pagesFixturePath = path.resolve(
- dirname,
- "fixtures/defaultapisidebar/pages.json"
+
+const pagesFixturePath = new URL(
+ "./fixtures/defaultapisidebar/pages.json",
+ import.meta.url
);
const pagesJSON = JSON.parse(fs.readFileSync(pagesFixturePath, "utf-8"));
const subpagesJSON = [
pagesJSON["/en-US/docs/Web/API/TestInterface_API/MyGuidePage1"],
pagesJSON["/en-US/docs/Web/API/TestInterface_API/MyGuidePage2"],
];
-const commonl10nFixturePath = path.resolve(
- dirname,
- "fixtures/defaultapisidebar/commonl10n.json"
+const commonl10nFixturePath = new URL(
+ "./fixtures/defaultapisidebar/commonl10n.json",
+ import.meta.url
);
const commonl10nFixture = JSON.parse(
fs.readFileSync(commonl10nFixturePath, "utf-8")
);
-const groupDataFixturePath = path.resolve(
- dirname,
- "fixtures/defaultapisidebar/groupdata.json"
+const groupDataFixturePath = new URL(
+ "./fixtures/defaultapisidebar/groupdata.json",
+ import.meta.url
);
const groupDataFixture = JSON.parse(
fs.readFileSync(groupDataFixturePath, "utf-8")
@@ -310,7 +309,7 @@ describeMacro("DefaultAPISidebar", function () {
throw new Error(`Unimplemented mock fixture ${name}`);
});
// Mock calls to wiki.getPage()
- macro.ctx.wiki.getPage = jest.fn(async (url) => {
+ macro.ctx.wiki.getPage = jest.fn(async (url: string) => {
return pagesJSON[url];
});
});
diff --git a/kumascript/tests/macros/Deprecated.test.ts b/kumascript/tests/macros/Deprecated.test.ts
index a9de4912cda1..7310543ca530 100644
--- a/kumascript/tests/macros/Deprecated.test.ts
+++ b/kumascript/tests/macros/Deprecated.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
// TODO: Add tests for other {{Deprecated_*}} macros
describeMacro("Deprecated_Inline", function () {
diff --git a/kumascript/tests/macros/EmbedInteractiveExample.test.ts b/kumascript/tests/macros/EmbedInteractiveExample.test.ts
index 3bbdade930d4..4c67a6cab68d 100644
--- a/kumascript/tests/macros/EmbedInteractiveExample.test.ts
+++ b/kumascript/tests/macros/EmbedInteractiveExample.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
describeMacro("EmbedInteractiveExample", function () {
itMacro("Typical settings and argument", function (macro) {
diff --git a/kumascript/tests/macros/EmbedLiveSample.test.ts b/kumascript/tests/macros/EmbedLiveSample.test.ts
index c8e0de40dc7f..9ce4c6a72130 100644
--- a/kumascript/tests/macros/EmbedLiveSample.test.ts
+++ b/kumascript/tests/macros/EmbedLiveSample.test.ts
@@ -1,4 +1,5 @@
-import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils";
+import { jest } from "@jest/globals";
+import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils.js";
describeMacro("EmbedLiveSample", function () {
beforeEachMacro(function (macro) {
diff --git a/kumascript/tests/macros/HTTPSidebar.test.ts b/kumascript/tests/macros/HTTPSidebar.test.ts
index 5e236dd6999a..532baad2dbb8 100644
--- a/kumascript/tests/macros/HTTPSidebar.test.ts
+++ b/kumascript/tests/macros/HTTPSidebar.test.ts
@@ -1,43 +1,13 @@
import fs from "node:fs";
-import path from "node:path";
import { JSDOM } from "jsdom";
-import { Document } from "../../../content";
-import {
- assert,
- itMacro,
- beforeEachMacro,
- describeMacro,
- lintHTML,
-} from "./utils";
+import { jest } from "@jest/globals";
+import * as Content from "../../../content/index.js";
-const dirname = __dirname;
-
-// Load fixture data.
-const fixtureData = JSON.parse(
- fs.readFileSync(
- path.resolve(dirname, "fixtures", "documentData2.json"),
- "utf-8"
- )
-) as any;
-
-const locales = {
- "en-US": {
- ResourcesURI: "Resources and URIs",
- },
- es: {
- ResourcesURI: "Recursons y URIs",
- },
-};
-
-function checkSidebarDom(dom, locale) {
- const summaries = dom.querySelectorAll("summary");
- assert.equal(summaries[0].textContent, locales[locale].ResourcesURI);
-}
-
-describeMacro("HTTPSidebar", function () {
- beforeEachMacro(function (macro) {
- macro.ctx.env.url = "/en-US/docs/Web/HTTP/Overview";
- Document.findByURL = jest.fn((url) => {
+jest.unstable_mockModule("../../../content/index.js", () => ({
+ ...Content,
+ Document: {
+ ...Content.Document,
+ findByURL: jest.fn((url: string) => {
const data = fixtureData[url.toLowerCase()];
if (!data) {
return null;
@@ -52,8 +22,8 @@ describeMacro("HTTPSidebar", function () {
tags: data.tags,
},
};
- });
- Document.findChildren = jest.fn((url) => {
+ }),
+ findChildren: jest.fn((url: string) => {
const result: any[] = [];
const parent = `${url.toLowerCase()}/`;
for (const [key, data] of Object.entries(fixtureData) as any) {
@@ -72,7 +42,38 @@ describeMacro("HTTPSidebar", function () {
}
}
return result;
- });
+ }),
+ },
+}));
+
+const { assert, itMacro, beforeEachMacro, describeMacro, lintHTML } =
+ await import("./utils.js");
+
+// Load fixture data.
+const fixtureData = JSON.parse(
+ fs.readFileSync(
+ new URL("./fixtures/documentData2.json", import.meta.url),
+ "utf-8"
+ )
+) as any;
+
+const locales = {
+ "en-US": {
+ ResourcesURI: "Resources and URIs",
+ },
+ es: {
+ ResourcesURI: "Recursons y URIs",
+ },
+};
+
+function checkSidebarDom(dom, locale) {
+ const summaries = dom.querySelectorAll("summary");
+ assert.equal(summaries[0].textContent, locales[locale].ResourcesURI);
+}
+
+describeMacro("HTTPSidebar", function () {
+ beforeEachMacro(function (macro) {
+ macro.ctx.env.url = "/en-US/docs/Web/HTTP/Overview";
});
itMacro("Creates a sidebar object for en-US", function (macro) {
diff --git a/kumascript/tests/macros/ListGroups.test.ts b/kumascript/tests/macros/ListGroups.test.ts
index 8db03a3a1fa3..f314b7a8f195 100644
--- a/kumascript/tests/macros/ListGroups.test.ts
+++ b/kumascript/tests/macros/ListGroups.test.ts
@@ -1,17 +1,17 @@
-import { JSDOM } from "jsdom";
+import fs from "node:fs";
-import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils";
+import { jest } from "@jest/globals";
+import { JSDOM } from "jsdom";
-const dirname = __dirname;
+import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils.js";
/**
* Load all the fixtures.
*/
-import fs from "node:fs";
-import path from "node:path";
-const groupDataFixturePath = path.resolve(
- dirname,
- "fixtures/listgroups/groupdata.json"
+
+const groupDataFixturePath = new URL(
+ "./fixtures/listgroups/groupdata.json",
+ import.meta.url
);
const groupDataFixture = JSON.parse(
fs.readFileSync(groupDataFixturePath, "utf-8")
@@ -108,7 +108,7 @@ describeMacro("ListGroups", () => {
beforeEachMacro((macro) => {
macro.ctx.env.locale = "en-US";
// Mock calls to wiki.page
- macro.ctx.wiki.getPage = jest.fn((name) => {
+ macro.ctx.wiki.getPage = jest.fn((name: string) => {
return overviewPages[name];
});
// Mock calls to GroupData
diff --git a/kumascript/tests/macros/LiveSampleURL.test.ts b/kumascript/tests/macros/LiveSampleURL.test.ts
index 9f5d0ef011b2..218c695d7772 100644
--- a/kumascript/tests/macros/LiveSampleURL.test.ts
+++ b/kumascript/tests/macros/LiveSampleURL.test.ts
@@ -1,4 +1,5 @@
-import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils";
+import { jest } from "@jest/globals";
+import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils.js";
describeMacro("LiveSampleURL", function () {
beforeEachMacro(function (macro) {
@@ -33,7 +34,7 @@ describeMacro("LiveSampleURL", function () {
base_url: "https://mdn.mozillademos.org",
};
macro.ctx.info.hasPage = jest.fn(() => false);
- macro.ctx.info.getDescription = jest.fn((url) => url.toLowerCase());
+ macro.ctx.info.getDescription = jest.fn((url: string) => url.toLowerCase());
macro.ctx.env.url = "/en-US/docs/Learn/HTML";
await expect(
macro.call("No_JS", "/en-US/docs/does/not/exist")
diff --git a/kumascript/tests/macros/Specifications.test.ts b/kumascript/tests/macros/Specifications.test.ts
index 2704232c8190..770b79b99772 100644
--- a/kumascript/tests/macros/Specifications.test.ts
+++ b/kumascript/tests/macros/Specifications.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro, lintHTML } from "./utils";
+import { assert, itMacro, describeMacro, lintHTML } from "./utils.js";
import { JSDOM } from "jsdom";
diff --git a/kumascript/tests/macros/WebAssemblySidebar.test.ts b/kumascript/tests/macros/WebAssemblySidebar.test.ts
index 4fa395edb7f1..7a7affdb79f5 100644
--- a/kumascript/tests/macros/WebAssemblySidebar.test.ts
+++ b/kumascript/tests/macros/WebAssemblySidebar.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
const expected = `\
diff --git a/kumascript/tests/macros/addonsidebar.test.ts b/kumascript/tests/macros/addonsidebar.test.ts
index 6111dad51e3f..2102ed34029d 100644
--- a/kumascript/tests/macros/addonsidebar.test.ts
+++ b/kumascript/tests/macros/addonsidebar.test.ts
@@ -1,6 +1,7 @@
import { JSDOM } from "jsdom";
+import { jest } from "@jest/globals";
-import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils";
+import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils.js";
const SUMMARIES = {
"en-US": [
diff --git a/kumascript/tests/macros/apiref.test.ts b/kumascript/tests/macros/apiref.test.ts
index 90ce2f5dd45e..428cb02c2167 100644
--- a/kumascript/tests/macros/apiref.test.ts
+++ b/kumascript/tests/macros/apiref.test.ts
@@ -1,46 +1,46 @@
-import { JSDOM } from "jsdom";
+import fs from "node:fs";
-import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils";
+import { JSDOM } from "jsdom";
+import { jest } from "@jest/globals";
-const dirname = __dirname;
+import { beforeEachMacro, describeMacro, itMacro, lintHTML } from "./utils.js";
/**
* Load all the fixtures.
*/
-import fs from "node:fs";
-import path from "node:path";
-const subpagesFixturePath = path.resolve(
- dirname,
- "fixtures/apiref/subpages.json"
+
+const subpagesFixturePath = new URL(
+ "./fixtures/apiref/subpages.json",
+ import.meta.url
);
const subpagesFixture = JSON.parse(
fs.readFileSync(subpagesFixturePath, "utf-8")
);
-const commonl10nFixturePath = path.resolve(
- dirname,
- "fixtures/apiref/commonl10n.json"
+const commonl10nFixturePath = new URL(
+ "./fixtures/apiref/commonl10n.json",
+ import.meta.url
);
const commonl10nFixture = JSON.parse(
fs.readFileSync(commonl10nFixturePath, "utf-8")
);
-const groupDataFixturePath = path.resolve(
- dirname,
- "fixtures/apiref/groupdata.json"
+const groupDataFixturePath = new URL(
+ "./fixtures/apiref/groupdata.json",
+ import.meta.url
);
const groupDataFixture = JSON.parse(
fs.readFileSync(groupDataFixturePath, "utf-8")
);
-const interfaceDataNoEntriesFixturePath = path.resolve(
- dirname,
- "fixtures/apiref/interfacedata_no_entries.json"
+const interfaceDataNoEntriesFixturePath = new URL(
+ "./fixtures/apiref/interfacedata_no_entries.json",
+ import.meta.url
);
const interfaceDataNoEntriesFixture = fs.readFileSync(
interfaceDataNoEntriesFixturePath,
"utf-8"
);
-const interfaceDataFixturePath = path.resolve(
- dirname,
- "fixtures/apiref/interfacedata.json"
+const interfaceDataFixturePath = new URL(
+ "./fixtures/apiref/interfacedata.json",
+ import.meta.url
);
const interfaceDataFixture = JSON.parse(
fs.readFileSync(interfaceDataFixturePath, "utf-8")
diff --git a/kumascript/tests/macros/cssxref.test.ts b/kumascript/tests/macros/cssxref.test.ts
index 9eb3206fa8ea..3beda9da0f6d 100644
--- a/kumascript/tests/macros/cssxref.test.ts
+++ b/kumascript/tests/macros/cssxref.test.ts
@@ -1,4 +1,5 @@
-import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils";
+import { jest } from "@jest/globals";
+import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils.js";
// Basic const
const CSS_BASE_URL = "/en-US/docs/Web/CSS";
diff --git a/kumascript/tests/macros/firefoxsidebar.test.ts b/kumascript/tests/macros/firefoxsidebar.test.ts
index 7c311f7fcb49..3e891c758a7f 100644
--- a/kumascript/tests/macros/firefoxsidebar.test.ts
+++ b/kumascript/tests/macros/firefoxsidebar.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
import { JSDOM } from "jsdom";
const locales = {
diff --git a/kumascript/tests/macros/gamessidebar.test.ts b/kumascript/tests/macros/gamessidebar.test.ts
index 964391c29d2a..1c5089adb62f 100644
--- a/kumascript/tests/macros/gamessidebar.test.ts
+++ b/kumascript/tests/macros/gamessidebar.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
import { JSDOM } from "jsdom";
const locales = {
diff --git a/kumascript/tests/macros/httpheader.test.ts b/kumascript/tests/macros/httpheader.test.ts
index 1e692443d7e1..18e759f4d965 100644
--- a/kumascript/tests/macros/httpheader.test.ts
+++ b/kumascript/tests/macros/httpheader.test.ts
@@ -1,4 +1,5 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { jest } from "@jest/globals";
+import { assert, itMacro, describeMacro } from "./utils.js";
describeMacro("httpheader", function () {
itMacro("No arguments (en-US)", function (macro) {
diff --git a/kumascript/tests/macros/jsxref.test.ts b/kumascript/tests/macros/jsxref.test.ts
index 365add253dc6..079468ff4bdd 100644
--- a/kumascript/tests/macros/jsxref.test.ts
+++ b/kumascript/tests/macros/jsxref.test.ts
@@ -1,4 +1,6 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { jest } from "@jest/globals";
+
+import { assert, itMacro, describeMacro } from "./utils.js";
const js_ref_slug = "Web/JavaScript/Reference/";
const js_ref_url = `/en-US/docs/${js_ref_slug}`;
diff --git a/kumascript/tests/macros/mdnsidebar.test.ts b/kumascript/tests/macros/mdnsidebar.test.ts
index 0d92316503e1..063114ffeae9 100644
--- a/kumascript/tests/macros/mdnsidebar.test.ts
+++ b/kumascript/tests/macros/mdnsidebar.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
import { JSDOM } from "jsdom";
const locales = {
diff --git a/kumascript/tests/macros/page-api.test.ts b/kumascript/tests/macros/page-api.test.ts
index 53d40611e1e2..8a13456a2794 100644
--- a/kumascript/tests/macros/page-api.test.ts
+++ b/kumascript/tests/macros/page-api.test.ts
@@ -3,16 +3,62 @@
// part of ../../src/environment.js, but we're still testing them here.
import fs from "node:fs";
-import path from "node:path";
-import { Document } from "../../../content";
-import { assert, itMacro, describeMacro, beforeEachMacro } from "./utils";
+import { jest } from "@jest/globals";
-const dirname = __dirname;
+import * as Content from "../../../content/index.js";
+
+jest.unstable_mockModule("../../../content/index.js", () => ({
+ ...Content,
+ Document: {
+ ...Content.Document,
+ findByURL: jest.fn((url: string) => {
+ const data = fixtureData[url.toLowerCase()];
+ if (!data) {
+ return null;
+ }
+ return {
+ url: data.url,
+ metadata: {
+ title: data.title,
+ locale: data.locale,
+ summary: data.summary,
+ slug: data.slug,
+ tags: data.tags,
+ },
+ };
+ }),
+ findChildren: jest.fn((url: string) => {
+ const result: any[] = [];
+ const parent = `${url.toLowerCase()}/`;
+ for (const [key, data] of Object.entries(fixtureData) as any) {
+ if (!key.replace(parent, "").includes("/")) {
+ key.replace(`${url.toLowerCase()}/`, "");
+ result.push({
+ url: data.url,
+ metadata: {
+ title: data.title,
+ locale: data.locale,
+ summary: data.summary,
+ slug: data.slug,
+ tags: data.tags,
+ },
+ });
+ }
+ }
+ return result;
+ }),
+ },
+}));
+
+// Depends on module mocking
+const { assert, itMacro, describeMacro, beforeEachMacro } = await import(
+ "./utils.js"
+);
// Load fixture data.
const fixtureData = JSON.parse(
fs.readFileSync(
- path.resolve(dirname, "fixtures", "documentData1.json"),
+ new URL("./fixtures/documentData1.json", import.meta.url),
"utf-8"
)
);
@@ -74,45 +120,9 @@ function checkSubpagesResult(res) {
describeMacro("page API tests", function () {
beforeEachMacro(function (macro) {
- macro.ctx.info.cleanURL = jest.fn((url) =>
+ macro.ctx.info.cleanURL = jest.fn((url: string) =>
new URL(url, "https://example.com").pathname.toLowerCase()
);
- Document.findByURL = jest.fn((url) => {
- const data = fixtureData[url.toLowerCase()];
- if (!data) {
- return null;
- }
- return {
- url: data.url,
- metadata: {
- title: data.title,
- locale: data.locale,
- summary: data.summary,
- slug: data.slug,
- tags: data.tags,
- },
- };
- });
- Document.findChildren = jest.fn((url) => {
- const result = [];
- const parent = `${url.toLowerCase()}/`;
- for (const [key, data] of Object.entries(fixtureData) as any) {
- if (!key.replace(parent, "").includes("/")) {
- key.replace(`${url.toLowerCase()}/`, "");
- result.push({
- url: data.url,
- metadata: {
- title: data.title,
- locale: data.locale,
- summary: data.summary,
- slug: data.slug,
- tags: data.tags,
- },
- });
- }
- }
- return result;
- });
});
itMacro("dummy", function (macro) {
const pkg = macro.ctx.page;
diff --git a/kumascript/tests/macros/svgattr.test.ts b/kumascript/tests/macros/svgattr.test.ts
index 649c9dda884c..5d2618a1b61e 100644
--- a/kumascript/tests/macros/svgattr.test.ts
+++ b/kumascript/tests/macros/svgattr.test.ts
@@ -1,4 +1,4 @@
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
describeMacro("SVGAttr", () => {
for (const locale of ["en-US", "fr"]) {
diff --git a/kumascript/tests/macros/svginfo.test.ts b/kumascript/tests/macros/svginfo.test.ts
index b93095422304..84626630b41e 100644
--- a/kumascript/tests/macros/svginfo.test.ts
+++ b/kumascript/tests/macros/svginfo.test.ts
@@ -1,8 +1,9 @@
import fs from "node:fs";
import path from "node:path";
import cheerio from "cheerio";
+import { jest } from "@jest/globals";
-import { itMacro, describeMacro, beforeEachMacro } from "./utils";
+import { itMacro, describeMacro, beforeEachMacro } from "./utils.js";
const CONTENT_ROOT = process.env.CONTENT_ROOT;
if (!CONTENT_ROOT) {
diff --git a/kumascript/tests/macros/utils.test.ts b/kumascript/tests/macros/utils.test.ts
index f6224cbdd1f2..a94973f273bb 100644
--- a/kumascript/tests/macros/utils.test.ts
+++ b/kumascript/tests/macros/utils.test.ts
@@ -1,4 +1,4 @@
-import { lintHTML } from "./utils";
+import { lintHTML } from "./utils.js";
const ERROR_TEST_CASES = [
{
diff --git a/kumascript/tests/macros/utils.ts b/kumascript/tests/macros/utils.ts
index fdda687ce7a9..ff179a47fdf7 100644
--- a/kumascript/tests/macros/utils.ts
+++ b/kumascript/tests/macros/utils.ts
@@ -1,9 +1,9 @@
-import { HtmlValidate } from "html-validate";
+import { fileURLToPath } from "node:url";
-import Environment from "../../src/environment";
-import Templates from "../../src/templates";
+import { HtmlValidate } from "html-validate";
-const dirname = __dirname;
+import Environment from "../../src/environment.js";
+import Templates from "../../src/templates.js";
// When we were doing mocha testing, we used this.macro to hold this.
// But Jest doesn't use the this object, so we just store the object here.
@@ -55,7 +55,9 @@ assert.sameMembers = (a1, a2) => {
};
function createMacroTestObject(macroName) {
- const templates = new Templates(`${dirname}/../../macros/`);
+ const templates = new Templates(
+ fileURLToPath(new URL("../../macros/", import.meta.url))
+ );
const pageContext = {
locale: "en-US",
url: "",
diff --git a/kumascript/tests/macros/wiki-api.test.ts b/kumascript/tests/macros/wiki-api.test.ts
index 23e55020f76b..9cb8d1516469 100644
--- a/kumascript/tests/macros/wiki-api.test.ts
+++ b/kumascript/tests/macros/wiki-api.test.ts
@@ -2,7 +2,7 @@
// The functions defined by that macro have been moved to
// ../../src/environment.js, but the tests that are still relevant remain here.
-import { assert, itMacro, describeMacro } from "./utils";
+import { assert, itMacro, describeMacro } from "./utils.js";
describeMacro("dekiscript-wiki", function () {
itMacro("basic API", function (macro) {
diff --git a/kumascript/tests/parser.test.ts b/kumascript/tests/parser.test.ts
index 69d137640ea9..89e098810746 100644
--- a/kumascript/tests/parser.test.ts
+++ b/kumascript/tests/parser.test.ts
@@ -1,4 +1,4 @@
-import Parser from "../src/parser.js";
+import * as Parser from "../src/parser.js";
describe("Parser", function () {
it("input with no macros", () => {
diff --git a/kumascript/tests/render.test.ts b/kumascript/tests/render.test.ts
index 62486f1ff68f..932f53f1d2d5 100644
--- a/kumascript/tests/render.test.ts
+++ b/kumascript/tests/render.test.ts
@@ -1,20 +1,21 @@
import fs from "node:fs";
-import Templates from "../src/templates";
-import { render } from "../src/render";
+import { fileURLToPath } from "node:url";
+import { jest } from "@jest/globals";
+
+import Templates from "../src/templates.js";
+import { render } from "../src/render.js";
import {
MacroInvocationError,
MacroNotFoundError,
MacroCompilationError,
MacroExecutionError,
-} from "../src/errors";
-
-const dirname = __dirname;
+} from "../src/errors.js";
const PAGE_ENV = { slug: "" };
describe("render() function", () => {
function fixture(name) {
- return `${dirname}/fixtures/render/${name}`;
+ return fileURLToPath(new URL(`./fixtures/render/${name}`, import.meta.url));
}
function get(name) {
return fs.readFileSync(fixture(name), "utf-8");
diff --git a/kumascript/tests/templates.test.ts b/kumascript/tests/templates.test.ts
index 81b378c00c8b..009c8d4ee33e 100644
--- a/kumascript/tests/templates.test.ts
+++ b/kumascript/tests/templates.test.ts
@@ -1,8 +1,9 @@
-import EJS from "ejs";
-import Templates from "../src/templates";
+import { fileURLToPath } from "node:url";
import path from "node:path";
-const dirname = __dirname;
+import EJS from "ejs";
+import { jest } from "@jest/globals";
+import Templates from "../src/templates.js";
describe("Templates class", () => {
it("has the expected methods", () => {
@@ -12,7 +13,9 @@ describe("Templates class", () => {
});
function dir(name) {
- return path.resolve(dirname, "fixtures", "templates", name);
+ return fileURLToPath(
+ new URL(`./fixtures/templates/${name}`, import.meta.url)
+ );
}
it("throws on non-existent dir", () => {
@@ -74,21 +77,14 @@ describe("Templates class", () => {
expect(result).toEqual("3");
});
- ["development", "production"].forEach((mode) => {
- it(`loads files ${
- mode === "production" ? "only once" : "for each call"
- } in ${mode} mode`, async () => {
- process.env.NODE_ENV = mode;
+ [false, true].forEach((doCache) => {
+ it(`loads files ${doCache ? "only once" : "for each call"} in ${
+ doCache ? "production" : "development"
+ } mode`, async () => {
/**
- * Without `JSON.stringify(…)`, `\` in the file path would be treated
- * as part of an escape sequence (e.g.:
- * `C:\nodejs\...\kumascript\tests\fixtures\templates\test.ejs`
- * would be interpreted as:
- *
- * ```none
- * C:
- * odejs...kumascript ests␌ixtures emplates est.ejs
- * ```
+ * JSON.stringify is used here to handle Windows file paths. Without
+ * it, `\` in the file path would be treated as part of an escape
+ * sequence.
*/
const mockLoader = jest.fn(
(filename) => `<%= ${JSON.stringify(filename)} -%>`
@@ -98,21 +94,25 @@ describe("Templates class", () => {
const directory = dir("macros");
const macros = new Templates(directory);
- const result1 = await macros.render("test1");
+ const result1 = await macros.render("test1", {
+ cache: doCache,
+ });
expect(result1).toBe(path.resolve(directory, "test1.ejs"));
expect(mockLoader.mock.calls).toHaveLength(1);
- const result2 = await macros.render("test2");
+ const result2 = await macros.render("test2", {
+ cache: doCache,
+ });
expect(result2).toBe(path.resolve(directory, "Test2.ejs"));
expect(mockLoader.mock.calls).toHaveLength(2);
// Render the macros again, but don't expect any more loads
// when we're in production mode.
- await macros.render("test1");
- await macros.render("test2");
- await macros.render("test1");
- await macros.render("test2");
- expect(mockLoader.mock.calls).toHaveLength(mode === "production" ? 2 : 6);
+ await macros.render("test1", { cache: doCache });
+ await macros.render("test2", { cache: doCache });
+ await macros.render("test1", { cache: doCache });
+ await macros.render("test2", { cache: doCache });
+ expect(mockLoader.mock.calls).toHaveLength(doCache ? 2 : 6);
});
});
});
diff --git a/kumascript/tsconfig.json b/kumascript/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/kumascript/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/libs/constants/index.js b/libs/constants/index.js
index c46364b4a084..74c7384086d4 100644
--- a/libs/constants/index.js
+++ b/libs/constants/index.js
@@ -1,10 +1,10 @@
-const VALID_LOCALES = new Map(
+export const VALID_LOCALES = new Map(
["en-US", "es", "fr", "ja", "ko", "pt-BR", "ru", "zh-CN", "zh-TW"].map(
(x) => [x.toLowerCase(), x]
)
);
-const RETIRED_LOCALES = new Map(
+export const RETIRED_LOCALES = new Map(
[
"ar",
"bg",
@@ -32,9 +32,9 @@ const RETIRED_LOCALES = new Map(
].map((x) => [x.toLowerCase(), x])
);
-const DEFAULT_LOCALE = "en-US";
+export const DEFAULT_LOCALE = "en-US";
-const LOCALE_ALIASES = new Map([
+export const LOCALE_ALIASES = new Map([
// Case is not important on either the keys or the values.
["en", "en-us"],
["pt", "pt-br"],
@@ -46,8 +46,8 @@ const LOCALE_ALIASES = new Map([
// This must match what we do in `language-menu/index.tsx` where the cookie
// gets set in the client!
-const PREFERRED_LOCALE_COOKIE_NAME = "preferredlocale";
-const ACTIVE_LOCALES = new Set([
+export const PREFERRED_LOCALE_COOKIE_NAME = "preferredlocale";
+export const ACTIVE_LOCALES = new Set([
"en-us",
"es",
"fr",
@@ -59,7 +59,7 @@ const ACTIVE_LOCALES = new Set([
"zh-tw",
]);
-const CSP_SCRIPT_SRC_VALUES = [
+export const CSP_SCRIPT_SRC_VALUES = [
"'report-sample'",
"'self'",
@@ -83,7 +83,7 @@ const CSP_SCRIPT_SRC_VALUES = [
// - Current hash:
"'sha256-uogddBLIKmJa413dyT0iPejBg3VFcO+4x6B+vw3jng0='",
];
-const CSP_DIRECTIVES = {
+export const CSP_DIRECTIVES = {
"default-src": ["'self'"],
"script-src": CSP_SCRIPT_SRC_VALUES,
"script-src-elem": CSP_SCRIPT_SRC_VALUES,
@@ -146,18 +146,18 @@ const CSP_DIRECTIVES = {
"worker-src": ["'self'"],
};
-const cspToString = (csp) =>
+export const cspToString = (csp) =>
Object.entries(csp)
.map(([directive, values]) => `${directive} ${values.join(" ")};`)
.join(" ");
-const CSP_VALUE = cspToString(CSP_DIRECTIVES);
+export const CSP_VALUE = cspToString(CSP_DIRECTIVES);
// -----
// build
// -----
-const FLAW_LEVELS = Object.freeze({
+export const FLAW_LEVELS = Object.freeze({
ERROR: "error",
IGNORE: "ignore",
WARN: "warn",
@@ -171,7 +171,7 @@ const FLAW_LEVELS = Object.freeze({
//
// This list needs to be synced with the code. And the CLI arguments
// used with --flaw-checks needs to match this set.
-const VALID_FLAW_CHECKS = new Set([
+export const VALID_FLAW_CHECKS = new Set([
"macros",
"broken_links",
"bad_bcd_queries",
@@ -188,50 +188,23 @@ const VALID_FLAW_CHECKS = new Set([
// client
// ------
-const MDN_PLUS_TITLE = "MDN Plus";
+export const MDN_PLUS_TITLE = "MDN Plus";
// -------
// content
// -------
-const HTML_FILENAME = "index.html";
-const MARKDOWN_FILENAME = "index.md";
+export const HTML_FILENAME = "index.html";
+export const MARKDOWN_FILENAME = "index.md";
// ---------
// filecheck
// ---------
-const VALID_MIME_TYPES = new Set([
+export const VALID_MIME_TYPES = new Set([
"image/png",
"image/jpeg", // this is what you get for .jpeg *and* .jpg file extensions
"image/gif",
]);
-const MAX_COMPRESSION_DIFFERENCE_PERCENTAGE = 25; // percent
-
-module.exports = {
- ACTIVE_LOCALES,
- VALID_LOCALES,
- RETIRED_LOCALES,
- DEFAULT_LOCALE,
- LOCALE_ALIASES,
- PREFERRED_LOCALE_COOKIE_NAME,
-
- CSP_SCRIPT_SRC_VALUES,
- CSP_VALUE,
-
- // build
- FLAW_LEVELS,
- VALID_FLAW_CHECKS,
-
- // client
- MDN_PLUS_TITLE,
-
- // content
- HTML_FILENAME,
- MARKDOWN_FILENAME,
-
- // filecheck
- VALID_MIME_TYPES,
- MAX_COMPRESSION_DIFFERENCE_PERCENTAGE,
-};
+export const MAX_COMPRESSION_DIFFERENCE_PERCENTAGE = 25; // percent
diff --git a/libs/constants/package.json b/libs/constants/package.json
index 526b38a99480..2dbfbf33f64d 100644
--- a/libs/constants/package.json
+++ b/libs/constants/package.json
@@ -3,6 +3,8 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
+ "exports": "./index.js",
"main": "index.js",
"types": "index.d.ts"
}
diff --git a/libs/env/index.js b/libs/env/index.js
index d0b246b579e7..93feb114f5c9 100644
--- a/libs/env/index.js
+++ b/libs/env/index.js
@@ -1,12 +1,12 @@
-const fs = require("fs");
-const path = require("path");
+import fs from "node:fs";
+import path from "node:path";
+import { cwd } from "node:process";
-const dotenv = require("dotenv");
-const dirname = __dirname;
+import dotenv from "dotenv";
-const { VALID_FLAW_CHECKS } = require("../constants");
+import { VALID_FLAW_CHECKS } from "../constants/index.js";
-const ROOT = path.join(dirname, "..", "..");
+export const ROOT = path.join(cwd(), ".");
dotenv.config({
path: path.join(ROOT, process.env.ENV_FILE || ".env"),
@@ -16,27 +16,27 @@ dotenv.config({
// build
// -----
-const BUILD_OUT_ROOT =
+export const BUILD_OUT_ROOT =
process.env.BUILD_OUT_ROOT || path.join(ROOT, "client", "build");
// TODO (far future): Switch to "error" when number of flaws drops.
-const DEFAULT_FLAW_LEVELS = process.env.BUILD_FLAW_LEVELS || "*:warn";
+export const DEFAULT_FLAW_LEVELS = process.env.BUILD_FLAW_LEVELS || "*:warn";
-const FILES = process.env.BUILD_FILES || "";
-const FOLDERSEARCH = process.env.BUILD_FOLDERSEARCH || "";
-const GOOGLE_ANALYTICS_ACCOUNT =
+export const FILES = process.env.BUILD_FILES || "";
+export const FOLDERSEARCH = process.env.BUILD_FOLDERSEARCH || "";
+export const GOOGLE_ANALYTICS_ACCOUNT =
process.env.BUILD_GOOGLE_ANALYTICS_ACCOUNT || "";
-const GOOGLE_ANALYTICS_DEBUG = JSON.parse(
+export const GOOGLE_ANALYTICS_DEBUG = JSON.parse(
process.env.BUILD_GOOGLE_ANALYTICS_DEBUG || "false"
);
-const NO_PROGRESSBAR = Boolean(
+export const NO_PROGRESSBAR = Boolean(
JSON.parse(process.env.BUILD_NO_PROGRESSBAR || process.env.CI || "false")
);
-const FIX_FLAWS = JSON.parse(process.env.BUILD_FIX_FLAWS || "false");
-const FIX_FLAWS_DRY_RUN = JSON.parse(
+export const FIX_FLAWS = JSON.parse(process.env.BUILD_FIX_FLAWS || "false");
+export const FIX_FLAWS_DRY_RUN = JSON.parse(
process.env.BUILD_FIX_FLAWS_DRY_RUN || "false"
);
-const FIX_FLAWS_TYPES = new Set(
+export const FIX_FLAWS_TYPES = new Set(
(process.env.BUILD_FIX_FLAWS_TYPES &&
process.env.BUILD_FIX_FLAWS_TYPES.split(",")) || [...VALID_FLAW_CHECKS]
);
@@ -49,14 +49,14 @@ if ([...FIX_FLAWS_TYPES].some((flawType) => !VALID_FLAW_CHECKS.has(flawType))) {
);
}
-const FIX_FLAWS_VERBOSE = JSON.parse(
+export const FIX_FLAWS_VERBOSE = JSON.parse(
// It's on by default because it's such a sensible option to always have
// on.
process.env.BUILD_FIX_FLAWS_VERBOSE || "true"
);
// See explanation in docs/envvars.md
-const ALWAYS_ALLOW_ROBOTS = JSON.parse(
+export const ALWAYS_ALLOW_ROBOTS = JSON.parse(
process.env.BUILD_ALWAYS_ALLOW_ROBOTS || "false"
);
@@ -64,27 +64,27 @@ const ALWAYS_ALLOW_ROBOTS = JSON.parse(
// content
// -------
-const CONTENT_ROOT = correctContentPathFromEnv("CONTENT_ROOT");
+export const CONTENT_ROOT = correctContentPathFromEnv("CONTENT_ROOT");
-const CONTENT_TRANSLATED_ROOT = correctContentPathFromEnv(
+export const CONTENT_TRANSLATED_ROOT = correctContentPathFromEnv(
"CONTENT_TRANSLATED_ROOT"
);
-const CONTRIBUTOR_SPOTLIGHT_ROOT = correctContentPathFromEnv(
+export const CONTRIBUTOR_SPOTLIGHT_ROOT = correctContentPathFromEnv(
"CONTRIBUTOR_SPOTLIGHT_ROOT"
);
// This makes it possible to know, give a root folder, what is the name of
// the repository on GitHub.
// E.g. `'https://github.com/' + REPOSITORY_URLS[document.fileInfo.root]`
-const REPOSITORY_URLS = {
+export const REPOSITORY_URLS = {
[CONTENT_ROOT]: "mdn/content",
};
// Make a combined array of all truthy roots. This way, you don't
// need to constantly worry about CONTENT_TRANSLATED_ROOT potentially being
// null.
-const ROOTS = [CONTENT_ROOT];
+export const ROOTS = [CONTENT_ROOT];
if (CONTENT_TRANSLATED_ROOT) {
ROOTS.push(CONTENT_TRANSLATED_ROOT);
REPOSITORY_URLS[CONTENT_TRANSLATED_ROOT] = "mdn/translated-content";
@@ -115,7 +115,7 @@ function correctContentPathFromEnv(envVarName) {
// filecheck
// ---------
-const MAX_FILE_SIZE = JSON.parse(
+export const MAX_FILE_SIZE = JSON.parse(
process.env.FILECHECK_MAX_FILE_SIZE || 500 * 1024 // 500KiB
);
@@ -128,12 +128,12 @@ const SERVER_URL = `http://localhost:${SERVER_PORT}`;
// Allow the `process.env.BUILD_LIVE_SAMPLES_BASE_URL` to be falsy
// if it *is* set.
-const LIVE_SAMPLES_BASE_URL =
+export const LIVE_SAMPLES_BASE_URL =
process.env.BUILD_LIVE_SAMPLES_BASE_URL !== undefined
? process.env.BUILD_LIVE_SAMPLES_BASE_URL
: SERVER_URL;
-const INTERACTIVE_EXAMPLES_BASE_URL =
+export const INTERACTIVE_EXAMPLES_BASE_URL =
process.env.BUILD_INTERACTIVE_EXAMPLES_BASE_URL ||
"https://interactive-examples.mdn.mozilla.net";
@@ -141,45 +141,11 @@ const INTERACTIVE_EXAMPLES_BASE_URL =
// server
// ------
-const STATIC_ROOT =
+export const STATIC_ROOT =
process.env.SERVER_STATIC_ROOT || path.join(ROOT, "client", "build");
-const PROXY_HOSTNAME =
+export const PROXY_HOSTNAME =
process.env.REACT_APP_KUMA_HOST || "developer.mozilla.org";
-const CONTENT_HOSTNAME = process.env.SERVER_CONTENT_HOST;
-const OFFLINE_CONTENT = process.env.SERVER_OFFLINE_CONTENT === "true";
-
-const FAKE_V1_API = JSON.parse(process.env.SERVER_FAKE_V1_API || false);
-
-module.exports = {
- ROOT,
- // build
- BUILD_OUT_ROOT,
- DEFAULT_FLAW_LEVELS,
- FILES,
- FOLDERSEARCH,
- GOOGLE_ANALYTICS_ACCOUNT,
- GOOGLE_ANALYTICS_DEBUG,
- NO_PROGRESSBAR,
- FIX_FLAWS,
- FIX_FLAWS_DRY_RUN,
- FIX_FLAWS_TYPES,
- FIX_FLAWS_VERBOSE,
- ALWAYS_ALLOW_ROBOTS,
- // content
- CONTENT_ROOT,
- CONTENT_TRANSLATED_ROOT,
- CONTRIBUTOR_SPOTLIGHT_ROOT,
- REPOSITORY_URLS,
- ROOTS,
- // filecheck
- MAX_FILE_SIZE,
- // kumascript,
- LIVE_SAMPLES_BASE_URL,
- INTERACTIVE_EXAMPLES_BASE_URL,
- // server
- STATIC_ROOT,
- PROXY_HOSTNAME,
- CONTENT_HOSTNAME,
- OFFLINE_CONTENT,
- FAKE_V1_API,
-};
+export const CONTENT_HOSTNAME = process.env.SERVER_CONTENT_HOST;
+export const OFFLINE_CONTENT = process.env.SERVER_OFFLINE_CONTENT === "true";
+
+export const FAKE_V1_API = JSON.parse(process.env.SERVER_FAKE_V1_API || false);
diff --git a/libs/env/package.json b/libs/env/package.json
index 9a0a359f3ad6..49028c3465f2 100644
--- a/libs/env/package.json
+++ b/libs/env/package.json
@@ -3,6 +3,8 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
+ "exports": "./index.js",
"main": "index.js",
"types": "index.d.ts"
}
diff --git a/libs/fundamental-redirects/index.js b/libs/fundamental-redirects/index.js
index 3de55b4b92a3..a062306bae9b 100644
--- a/libs/fundamental-redirects/index.js
+++ b/libs/fundamental-redirects/index.js
@@ -1,9 +1,9 @@
-const {
+import {
DEFAULT_LOCALE,
VALID_LOCALES,
LOCALE_ALIASES,
RETIRED_LOCALES,
-} = require("../constants");
+} from "../constants/index.js";
const startRe = /^\^?\/?/;
const startTemplate = /^\//;
@@ -1262,7 +1262,7 @@ const REDIRECT_PATTERNS = [].concat(
const STARTING_SLASH = /^\//;
const ABSOLUTE_URL = /^https?:\/\/.*/;
-function resolveFundamental(path) {
+export function resolveFundamental(path) {
if (ABSOLUTE_URL.exec(path)) {
return {};
}
@@ -1275,7 +1275,3 @@ function resolveFundamental(path) {
}
return {};
}
-
-module.exports = {
- resolveFundamental,
-};
diff --git a/libs/fundamental-redirects/package.json b/libs/fundamental-redirects/package.json
index 7fcbb7c9fad7..845921aab952 100644
--- a/libs/fundamental-redirects/package.json
+++ b/libs/fundamental-redirects/package.json
@@ -3,6 +3,8 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
+ "exports": "./index.js",
"main": "index.js",
"types": "index.d.ts"
}
diff --git a/libs/languages/index.d.ts b/libs/languages/index.d.ts
index 8966599136b0..432066aa9981 100644
--- a/libs/languages/index.d.ts
+++ b/libs/languages/index.d.ts
@@ -1,3 +1,3 @@
declare const _exports: Record;
-export = _exports;
+export default _exports;
diff --git a/libs/languages/index.js b/libs/languages/index.js
index cac835d961fc..9b9fc2e8f31b 100644
--- a/libs/languages/index.js
+++ b/libs/languages/index.js
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
ach: {
English: "Acholi",
native: "Acholi",
diff --git a/libs/languages/package.json b/libs/languages/package.json
index 78a257e38fb0..37023822aa93 100644
--- a/libs/languages/package.json
+++ b/libs/languages/package.json
@@ -3,6 +3,8 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
+ "exports": "./index.js",
"main": "index.js",
"types": "index.d.ts"
}
diff --git a/libs/locale-utils/index.js b/libs/locale-utils/index.js
index 5067936544a8..52f6b8168059 100644
--- a/libs/locale-utils/index.js
+++ b/libs/locale-utils/index.js
@@ -1,11 +1,12 @@
-const { parse } = require("cookie");
-const acceptLanguageParser = require("accept-language-parser");
+import { parse } from "cookie";
+import acceptLanguageParser from "accept-language-parser";
-const {
+import {
DEFAULT_LOCALE,
VALID_LOCALES,
PREFERRED_LOCALE_COOKIE_NAME,
-} = require("../constants");
+} from "../constants/index.js";
+
const VALID_LOCALES_LIST = [...VALID_LOCALES.values()];
// From https://github.com/aws-samples/cloudfront-authorization-at-edge/blob/01c1bc843d478977005bde86f5834ce76c479eec/src/lambda-edge/shared/shared.ts#L216
@@ -29,7 +30,7 @@ function getCookie(headers, cookieKey) {
return extractCookiesFromHeaders(headers)[cookieKey];
}
-function getLocale(request, fallback = DEFAULT_LOCALE) {
+export function getLocale(request, fallback = DEFAULT_LOCALE) {
// First try by cookie.
const cookieLocale = getCookie(request.headers, PREFERRED_LOCALE_COOKIE_NAME);
if (cookieLocale) {
@@ -48,11 +49,6 @@ function getLocale(request, fallback = DEFAULT_LOCALE) {
return locale || fallback;
}
-function isValidLocale(locale) {
+export function isValidLocale(locale) {
return typeof locale === "string" && VALID_LOCALES.has(locale.toLowerCase());
}
-
-module.exports = {
- getLocale,
- isValidLocale,
-};
diff --git a/libs/locale-utils/package.json b/libs/locale-utils/package.json
index fafbe82503ea..2fa2fb0d4057 100644
--- a/libs/locale-utils/package.json
+++ b/libs/locale-utils/package.json
@@ -3,6 +3,8 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
+ "exports": "./index.js",
"main": "index.js",
"types": "index.d.ts"
}
diff --git a/libs/slug-utils/index.js b/libs/slug-utils/index.js
index 112d132ab3d0..ead7d9c19f08 100644
--- a/libs/slug-utils/index.js
+++ b/libs/slug-utils/index.js
@@ -1,6 +1,6 @@
-const sanitizeFilename = require("sanitize-filename");
+import sanitizeFilename from "sanitize-filename";
-function slugToFolder(slug, joiner = "/") {
+export function slugToFolder(slug, joiner = "/") {
return (
slug
// We have slugs with these special characters that would be
@@ -19,18 +19,12 @@ function slugToFolder(slug, joiner = "/") {
);
}
-function decodePath(path) {
+export function decodePath(path) {
const decoded = path.split("/").map(decodeURIComponent).join("/");
return decoded;
}
-function encodePath(path) {
+export function encodePath(path) {
const decoded = path.split("/").map(encodeURIComponent).join("/");
return decoded;
}
-
-module.exports = {
- slugToFolder,
- decodePath,
- encodePath,
-};
diff --git a/libs/slug-utils/package.json b/libs/slug-utils/package.json
index 44a125d7c6c1..c06a5066b00a 100644
--- a/libs/slug-utils/package.json
+++ b/libs/slug-utils/package.json
@@ -3,9 +3,11 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
+ "type": "module",
"main": "index.js",
"types": "index.d.ts",
"dependencies": {
"sanitize-filename": "^1.6.3"
- }
+ },
+ "export": "./index.js"
}
diff --git a/libs/types/document.ts b/libs/types/document.ts
index 502ed6e4c727..db2c159b76e8 100644
--- a/libs/types/document.ts
+++ b/libs/types/document.ts
@@ -1,5 +1,3 @@
-import * as BCD from "@mdn/browser-compat-data/types";
-
export interface Source {
folder: string;
github_url: string;
@@ -206,3 +204,14 @@ export interface BCDSection {
query: string;
};
}
+
+export interface NewsItem {
+ url: string;
+ title: string;
+ author?: string;
+ source: {
+ name: string;
+ url: string;
+ };
+ published_at: string;
+}
diff --git a/libs/types/package.json b/libs/types/package.json
index 0560a39e424b..da6a922d6395 100644
--- a/libs/types/package.json
+++ b/libs/types/package.json
@@ -3,5 +3,7 @@
"version": "0.0.1",
"private": true,
"license": "MPL-2.0",
- "main": "document.ts"
+ "type": "module",
+ "main": "document.ts",
+ "export": "./index.js"
}
diff --git a/libs/types/tsconfig.json b/libs/types/tsconfig.json
deleted file mode 100644
index 862ab5fd9f1c..000000000000
--- a/libs/types/tsconfig.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "extends": "../../tsconfig.json",
- "compilerOptions": {
- "sourceMap": true
- }
-}
diff --git a/lint-staged.config.js b/lint-staged.config.js
index 2a791d1697b9..d7e5d2118e33 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.js
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
"*.!{js,jsx,ts,tsx,css,scss}": "prettier --ignore-unknown --write",
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["stylelint --fix --allow-empty-input", "prettier --write"],
diff --git a/markdown/index.ts b/markdown/index.ts
index 6ed68305ff21..ccfbace1718c 100644
--- a/markdown/index.ts
+++ b/markdown/index.ts
@@ -1,2 +1,2 @@
-export * from "./utils";
-export * from "./m2h";
+export * from "./utils/index.js";
+export * from "./m2h/index.js";
diff --git a/markdown/m2h/cli.ts b/markdown/m2h/cli.ts
index b71175ce606e..433c0dd1cae3 100644
--- a/markdown/m2h/cli.ts
+++ b/markdown/m2h/cli.ts
@@ -1,11 +1,13 @@
import fm from "front-matter";
-import { program } from "@caporal/core";
+import caporal from "@caporal/core";
import chalk from "chalk";
-import { Document } from "../../content";
-import { saveFile } from "../../content/document";
-import { VALID_LOCALES } from "../../libs/constants";
+import { Document } from "../../content/index.js";
+import { saveFile } from "../../content/document.js";
+import { VALID_LOCALES } from "../../libs/constants/index.js";
-import { m2h } from "./";
+import { m2h } from "./index.js";
+
+const { program } = caporal;
interface CliOptions {
v?: boolean;
diff --git a/markdown/m2h/handlers/dl.ts b/markdown/m2h/handlers/dl.ts
index de3a7ade7452..bc4763d27ad9 100644
--- a/markdown/m2h/handlers/dl.ts
+++ b/markdown/m2h/handlers/dl.ts
@@ -1,4 +1,4 @@
-import { all, wrap } from "./mdast-util-to-hast-utils";
+import { all, wrap } from "./mdast-util-to-hast-utils.js";
export const DEFINITION_PREFIX = ": ";
diff --git a/markdown/m2h/handlers/index.ts b/markdown/m2h/handlers/index.ts
index 30ff9daccffd..7ab0a95005dc 100644
--- a/markdown/m2h/handlers/index.ts
+++ b/markdown/m2h/handlers/index.ts
@@ -1,25 +1,26 @@
import fs from "node:fs";
-import path from "node:path";
-import { DEFAULT_LOCALE } from "../../../libs/constants";
-import { code } from "./code";
-import { asDefinitionList, isDefinitionList } from "./dl";
-import { one, all, wrap } from "./mdast-util-to-hast-utils";
-const dirname = __dirname;
+import { DEFAULT_LOCALE } from "../../../libs/constants/index.js";
+import { code } from "./code.js";
+import { asDefinitionList, isDefinitionList } from "./dl.js";
+import { one, all, wrap } from "./mdast-util-to-hast-utils.js";
/* A utilitary function which parses a JSON gettext file
to return a Map with each localized string and its matching ID */
function getL10nCardMap(locale = DEFAULT_LOCALE) {
// Test if target localization file exists, if
// not, fallback on English
- let localeFilePath = path.join(dirname, `../../localizations/${locale}.json`);
+ let localeFilePath = new URL(
+ `../../localizations/${locale}.json`,
+ import.meta.url
+ );
if (!fs.existsSync(localeFilePath)) {
- localeFilePath = path.join(
- dirname,
- `../../localizations/${DEFAULT_LOCALE}.json`
+ localeFilePath = new URL(
+ `../../localizations/${DEFAULT_LOCALE}.json`,
+ import.meta.url
);
}
- const listMsgObj = JSON.parse(fs.readFileSync(localeFilePath, "utf-8"))[
+ const listMsgObj = JSON.parse(fs.readFileSync(localeFilePath, "utf8"))[
"translations"
][""];
const l10nCardMap = new Map();
diff --git a/markdown/m2h/index.ts b/markdown/m2h/index.ts
index efc0e46dd666..717ec86f42ee 100644
--- a/markdown/m2h/index.ts
+++ b/markdown/m2h/index.ts
@@ -6,8 +6,8 @@ import gfm from "remark-gfm";
import raw from "rehype-raw";
import format from "rehype-format";
-import { buildLocalizedHandlers } from "./handlers";
-import { decodeKS, encodeKS } from "../utils";
+import { buildLocalizedHandlers } from "./handlers/index.js";
+import { decodeKS, encodeKS } from "../utils/index.js";
interface ProcessorOptions {
locale?: string;
diff --git a/markdown/tsconfig.json b/markdown/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/markdown/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/package.json b/package.json
index 1f47fe588b91..9481280c1bde 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"repository": "https://github.com/mdn/yari",
"license": "MPL-2.0",
"author": "MDN Web Docs",
+ "type": "module",
"bin": {
"yari-build": "build/cli.js",
"yari-filecheck": "filecheck/cli.js",
@@ -14,37 +15,38 @@
"analyze": "source-map-explorer 'client/build/static/js/*.js'",
"analyze:css": "source-map-explorer 'client/build/static/css/*.css'",
"build": "cross-env NODE_ENV=production ts-node build/cli.ts",
- "build:client": "cd client && cross-env INLINE_RUNTIME_CHUNK=false node scripts/build.js",
+ "build:client": "cd client && cross-env NODE_ENV=production BABEL_ENV=production INLINE_RUNTIME_CHUNK=false node scripts/build.js",
"build:dist": "tsc -p tsconfig.dist.json",
"build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated",
"build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool google-analytics-code && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt",
"build:ssr": "cd ssr && webpack --mode=production",
"build:sw": "cd client/pwa && yarn && yarn build:prod",
"build:sw-dev": "cd client/pwa && yarn && yarn build",
- "check:tsc": "find . -mindepth 2 -name 'tsconfig.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -P 2 -0 bash -c 'cd `dirname $0` && echo \"🔄 $(pwd)\" && npx tsc --noEmit && echo \"☑️ $(pwd)\" || exit 255'",
+ "check:tsc": "find . -name 'tsconfig.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -P 2 -0 bash -c 'cd `dirname $0` && echo \"🔄 $(pwd)\" && npx tsc --noEmit && echo \"☑️ $(pwd)\" || exit 255'",
"dev": "yarn build:prepare && nf -j Procfile.dev start",
"eslint": "eslint .",
"filecheck": "ts-node filecheck/cli.ts",
+ "jest": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"m2h": "ts-node markdown/m2h/cli.ts",
"prepack": "yarn build:dist",
"prepare": "husky install",
"prettier-check": "prettier --check .",
"prettier-format": "prettier --write .",
"start": "(test -f client/build/index.html || yarn build:client) && (test -f ssr/dist/main.js || yarn build:ssr) && (test -d client/build/en-us/_spas || yarn tool spas) && nf -j Procfile.start start",
- "start:client": "cd client && cross-env BROWSER=none PORT=3000 node scripts/start.js",
- "start:dev-server": "node-dev server/index.ts",
+ "start:client": "cd client && cross-env NODE_ENV=development BABEL_ENV=development BROWSER=none PORT=3000 node scripts/start.js",
+ "start:dev-server": "node-dev --experimental-loader ts-node/esm server/index.ts",
"start:server": "ts-node server",
"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:client": "cd client && tsc --noEmit && node scripts/test.js --env=jsdom",
- "test:content": "jest content",
+ "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 content",
"test:developing": "cross-env CONTENT_ROOT=mdn/content/files TESTING_DEVELOPING=true playwright test developing",
"test:headless": "playwright test headless",
- "test:kumascript": "jest kumascript --env=node",
+ "test:kumascript": "yarn jest kumascript --env=node",
"test:prepare": "yarn build:prepare && yarn build && yarn start:static-server",
- "test:testing": "jest --rootDir testing",
+ "test:testing": "yarn jest --rootDir testing",
"tool": "ts-node tool/cli.ts",
"watch:ssr": "cd ssr && webpack --mode=production --watch"
},
@@ -131,7 +133,7 @@
"@types/async": "^3.2.16",
"@types/cli-progress": "^3.11.0",
"@types/hast": "^2.3.4",
- "@types/imagemin": "^8.0.0",
+ "@types/imagemin": "^7.0.1",
"@types/jest": "^29.4.0",
"@types/mdast": "^3.0.10",
"@types/node": "^16.18.12",
@@ -238,6 +240,6 @@
"workbox-webpack-plugin": "^6.5.4"
},
"engines": {
- "node": "^16.0.0 || >=18.0.0"
+ "node": "^16.17.0 || >=18.0.0"
}
}
diff --git a/playwright.config.js b/playwright.config.js
index a12f7d420513..a191f735af54 100644
--- a/playwright.config.js
+++ b/playwright.config.js
@@ -1,4 +1,4 @@
-const config = {
+export default {
use: {
channel: "chrome",
// See more interesting options at https://playwright.dev/docs/test-configuration/
@@ -6,5 +6,3 @@ const config = {
// video: "retain-on-failure",
},
};
-
-module.exports = config;
diff --git a/sd-config.js b/sd-config.js
index f8258f3518da..08c0a742bd74 100644
--- a/sd-config.js
+++ b/sd-config.js
@@ -1,4 +1,4 @@
-const StyleDictionary = require("style-dictionary"); // eslint-disable-line n/no-unpublished-require
+import StyleDictionary from "style-dictionary"; // eslint-disable-line n/no-unpublished-import
StyleDictionary.registerTransform({
name: "value/rewrite",
@@ -45,7 +45,7 @@ StyleDictionary.registerTransform({
},
});
-module.exports = {
+export default {
source: ["./client/src/ui/style-dictionary/**/*.json"],
platforms: {
scss: {
diff --git a/server/dev.ts b/server/dev.ts
index 687392763430..46b3223f181f 100644
--- a/server/dev.ts
+++ b/server/dev.ts
@@ -1,13 +1,11 @@
+import webpack from "webpack";
+import webpackDevMiddleware from "webpack-dev-middleware";
+import webpackHotMiddleware from "webpack-hot-middleware";
import type { WebpackConfiguration } from "webpack-dev-server";
export const devMiddlewares = [];
if (process.env.NODE_ENV === "development") {
- /* eslint-disable n/no-unpublished-require */
- const webpack = require("webpack");
- const webpackDevMiddleware = require("webpack-dev-middleware");
- const webpackHotMiddleware = require("webpack-hot-middleware");
-
const webpackConfig: WebpackConfiguration = {
entry: {
app: [
diff --git a/server/document.ts b/server/document.ts
index 24a471803e3f..d855081742b3 100644
--- a/server/document.ts
+++ b/server/document.ts
@@ -3,10 +3,10 @@ import path from "node:path";
import express, { Request } from "express";
-import { Document, slugToFolder } from "../content";
-import { buildDocument } from "../build";
-import { BUILD_OUT_ROOT } from "../libs/env";
-import { Doc } from "../libs/types/document";
+import { Document, slugToFolder } from "../content/index.js";
+import { buildDocument } from "../build/index.js";
+import { BUILD_OUT_ROOT } from "../libs/env/index.js";
+import { Doc } from "../libs/types/document.js";
const router = express();
diff --git a/server/flaws.ts b/server/flaws.ts
index ec035f90ab20..31ef17fadde5 100644
--- a/server/flaws.ts
+++ b/server/flaws.ts
@@ -1,16 +1,14 @@
-import { Doc } from "../libs/types/document";
-import { FlawFilters } from "./types";
-
import fs from "node:fs";
import path from "node:path";
import { fdir, PathsOutput } from "fdir";
-import { getPopularities } from "../content";
-import buildOptions from "../build/build-options";
-
-import { FLAW_LEVELS } from "../libs/constants";
-import { BUILD_OUT_ROOT } from "../libs/env";
+import { Doc } from "../libs/types/document.js";
+import { FlawFilters } from "./types.js";
+import { getPopularities } from "../content/index.js";
+import buildOptions from "../build/build-options.js";
+import { FLAW_LEVELS } from "../libs/constants/index.js";
+import { BUILD_OUT_ROOT } from "../libs/env/index.js";
// Module-level cache
const allPopularityValues = [];
diff --git a/server/index.ts b/server/index.ts
index 1cffffd4a67d..5d76a8428620 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -15,30 +15,31 @@ import {
buildDocument,
buildLiveSamplePageFromURL,
renderContributorsTxt,
-} from "../build";
-import { findDocumentTranslations } from "../content/translations";
-import { Document, Redirect, Image } from "../content";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { CSP_VALUE, DEFAULT_LOCALE } from "../libs/constants";
+} from "../build/index.js";
+import { findDocumentTranslations } from "../content/translations.js";
+import { Document, Redirect, Image } from "../content/index.js";
+import { CSP_VALUE, DEFAULT_LOCALE } from "../libs/constants/index.js";
import {
STATIC_ROOT,
PROXY_HOSTNAME,
FAKE_V1_API,
CONTENT_HOSTNAME,
OFFLINE_CONTENT,
-} from "../libs/env";
-
-import documentRouter from "./document";
-import fakeV1APIRouter from "./fake-v1-api";
-import { searchIndexRoute } from "./search-index";
-import flawsRoute from "./flaws";
-import { router as translationsRouter } from "./translations";
-import { staticMiddlewares, originRequestMiddleware } from "./middlewares";
-import { getRoot } from "../content/utils";
+ CONTENT_ROOT,
+ CONTENT_TRANSLATED_ROOT,
+} from "../libs/env/index.js";
+
+import documentRouter from "./document.js";
+import fakeV1APIRouter from "./fake-v1-api.js";
+import { searchIndexRoute } from "./search-index.js";
+import flawsRoute from "./flaws.js";
+import { router as translationsRouter } from "./translations.js";
+import { staticMiddlewares, originRequestMiddleware } from "./middlewares.js";
+import { getRoot } from "../content/utils.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
-import { renderHTML } from "../ssr/dist/main";
+import { renderHTML } from "../ssr/dist/main.js";
async function buildDocumentFromURL(url) {
const document = Document.findByURL(url);
diff --git a/server/middlewares.ts b/server/middlewares.ts
index 4cd286c64409..140acd30d0b7 100644
--- a/server/middlewares.ts
+++ b/server/middlewares.ts
@@ -1,10 +1,10 @@
import express from "express";
-import { CSP_VALUE } from "../libs/constants";
-import { STATIC_ROOT } from "../libs/env";
-import { resolveFundamental } from "../libs/fundamental-redirects";
-import { getLocale } from "../libs/locale-utils";
-import { devMiddlewares } from "./dev";
+import { CSP_VALUE } from "../libs/constants/index.js";
+import { STATIC_ROOT } from "../libs/env/index.js";
+import { resolveFundamental } from "../libs/fundamental-redirects/index.js";
+import { getLocale } from "../libs/locale-utils/index.js";
+import { devMiddlewares } from "./dev.js";
// Lowercase every request because every possible file we might have
// on disk is always in lowercase.
diff --git a/server/search-index.ts b/server/search-index.ts
index 9a8a681be92c..0e8b91fb1a6e 100644
--- a/server/search-index.ts
+++ b/server/search-index.ts
@@ -4,10 +4,10 @@ import path from "node:path";
import { fdir, PathsOutput } from "fdir";
import fm from "front-matter";
-import { VALID_LOCALES } from "../libs/constants";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { SearchIndex } from "../build";
-import { isValidLocale } from "../libs/locale-utils";
+import { VALID_LOCALES } from "../libs/constants/index.js";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
+import { SearchIndex } from "../build/index.js";
+import { isValidLocale } from "../libs/locale-utils/index.js";
interface DocAttributes {
locale: string;
diff --git a/server/static.ts b/server/static.ts
index 1ed9d50b5bc4..a3519ca254d6 100644
--- a/server/static.ts
+++ b/server/static.ts
@@ -4,9 +4,9 @@ import express from "express";
import compression from "compression";
import cookieParser from "cookie-parser";
-import { staticMiddlewares } from "./middlewares";
-import { resolveFundamental } from "../content";
-import { STATIC_ROOT } from "../libs/env";
+import { staticMiddlewares } from "./middlewares.js";
+import { resolveFundamental } from "../content/index.js";
+import { STATIC_ROOT } from "../libs/env/index.js";
const app = express();
app.use(express.json());
diff --git a/server/translations.ts b/server/translations.ts
index e0df07598e67..c6c8190985f7 100644
--- a/server/translations.ts
+++ b/server/translations.ts
@@ -1,21 +1,21 @@
import fs from "node:fs";
import path from "node:path";
-import { fdir } from "fdir";
-
import express from "express";
-export const router = express.Router();
+import { fdir } from "fdir";
-import { getPopularities, Document, Translation } from "../content";
+import { getPopularities, Document, Translation } from "../content/index.js";
import {
VALID_LOCALES,
ACTIVE_LOCALES,
DEFAULT_LOCALE,
-} from "../libs/constants";
-import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env";
-import { getLastCommitURL } from "../build";
-import LANGUAGES_RAW from "../libs/languages";
-import { isValidLocale } from "../libs/locale-utils";
+} from "../libs/constants/index.js";
+import { CONTENT_ROOT, CONTENT_TRANSLATED_ROOT } from "../libs/env/index.js";
+import { getLastCommitURL } from "../build/index.js";
+import LANGUAGES_RAW from "../libs/languages/index.js";
+import { isValidLocale } from "../libs/locale-utils/index.js";
+
+export const router = express.Router();
// Module-level cache
const allPopularityValues = [];
@@ -60,7 +60,7 @@ function packageTranslationDifferences(translationDifferences) {
}
const _foundDocumentsCache = new Map();
-function findDocuments({ locale }) {
+export function findDocuments({ locale }) {
const counts = {
// Number of documents found that aren't skipped
found: 0,
diff --git a/server/tsconfig.json b/server/tsconfig.json
deleted file mode 100644
index 379a994d81f6..000000000000
--- a/server/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["."]
-}
diff --git a/ssr/index.ts b/ssr/index.ts
index c15db913c065..ffff42b41e15 100644
--- a/ssr/index.ts
+++ b/ssr/index.ts
@@ -1,4 +1,4 @@
-import path from "node:path";
+import { fileURLToPath } from "node:url";
import dotenv from "dotenv";
import React from "react";
@@ -7,12 +7,12 @@ import { StaticRouter } from "react-router-dom/server";
import { App } from "../client/src/app";
import render from "./render";
-const dirname = __dirname;
-
// This is necessary because the ssr.js is in dist/ssr.js
// and we need to reach the .env this way.
dotenv.config({
- path: path.join(dirname, "..", process.env.ENV_FILE || ".env"),
+ path: fileURLToPath(
+ new URL("../" + (process.env.ENV_FILE || ".env"), import.meta.url)
+ ),
});
export function renderHTML(url, context) {
diff --git a/ssr/react-app.d.ts b/ssr/react-app.d.ts
new file mode 100644
index 000000000000..bf4ab9180050
--- /dev/null
+++ b/ssr/react-app.d.ts
@@ -0,0 +1,67 @@
+declare namespace NodeJS {
+ interface ProcessEnv {
+ readonly NODE_ENV: "development" | "production" | "test";
+ readonly PUBLIC_URL: string;
+ }
+}
+
+declare module "*.avif" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.bmp" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.gif" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.jpg" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.jpeg" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.png" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.webp" {
+ const src: string;
+ export default src;
+}
+
+declare module "*.svg" {
+ import * as React from "react";
+
+ export const ReactComponent: React.FunctionComponent<
+ React.SVGProps & { title?: string }
+ >;
+
+ const src: string;
+ export default src;
+}
+
+declare module "*.module.css" {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.scss" {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.sass" {
+ const classes: { readonly [key: string]: string };
+ export default classes;
+}
diff --git a/ssr/render.ts b/ssr/render.ts
index 95c4acf0c4fd..309751cb9dc8 100644
--- a/ssr/render.ts
+++ b/ssr/render.ts
@@ -1,11 +1,13 @@
import fs from "node:fs";
import path from "node:path";
+import { fileURLToPath } from "node:url";
+
import { renderToString } from "react-dom/server";
import { DEFAULT_LOCALE } from "../libs/constants";
import { ALWAYS_ALLOW_ROBOTS, BUILD_OUT_ROOT } from "../libs/env";
-const dirname = __dirname;
+const dirname = path.dirname(fileURLToPath(new URL(".", import.meta.url)));
// When there are multiple options for a given language, this gives the
// preferred locale for that language (language => preferred locale).
@@ -63,6 +65,7 @@ const lazy = (creator) => {
};
};
+// Path strings are preferred over URLs here to mitigate Webpack resolution
const clientBuildRoot = path.resolve(dirname, "../../client/build");
const readBuildHTML = lazy(() => {
diff --git a/ssr/tsconfig.json b/ssr/tsconfig.json
index e82a7a7c434b..bf2c0bd5252e 100644
--- a/ssr/tsconfig.json
+++ b/ssr/tsconfig.json
@@ -1,6 +1,5 @@
{
- "extends": "../tsconfig.json",
- "compilerOptions": {
- "sourceMap": true
- }
+ "extends": "../client/tsconfig.json",
+ "include": ["."],
+ "exclude": ["dist"]
}
diff --git a/ssr/webpack.config.js b/ssr/webpack.config.js
index fbbb078707b9..e66ff19f784b 100644
--- a/ssr/webpack.config.js
+++ b/ssr/webpack.config.js
@@ -1,24 +1,19 @@
-const path = require("node:path");
+import { fileURLToPath } from "node:url";
+import nodeExternals from "webpack-node-externals";
+import webpack from "webpack";
-const nodeExternals = require("webpack-node-externals");
-const webpack = require("webpack");
-
-const dirname = __dirname;
-
-module.exports = {
- context: path.resolve(dirname, "."),
+const config = {
+ context: fileURLToPath(new URL(".", import.meta.url)),
entry: "./index.ts",
output: {
- path: path.resolve(dirname, "dist"),
+ path: fileURLToPath(new URL("dist", import.meta.url)),
filename: "[name].js",
sourceMapFilename: "[name].js.map",
- libraryTarget: "commonjs2",
+ library: { type: "module" },
+ chunkFormat: "module",
},
target: "node",
- node: {
- __dirname: false,
- __filename: false,
- },
+ node: false,
// See all options here:
// https://webpack.js.org/configuration/stats/
stats: "errors-warnings",
@@ -75,4 +70,9 @@ module.exports = {
maxChunks: 1,
}),
],
+ experiments: {
+ outputModule: true,
+ },
};
+
+export default config;
diff --git a/testing/tests/csp.test.ts b/testing/tests/csp.test.ts
index 9aceb3910906..ac507e2989ee 100644
--- a/testing/tests/csp.test.ts
+++ b/testing/tests/csp.test.ts
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
-import { CSP_SCRIPT_SRC_VALUES } from "../../libs/constants";
+import { CSP_SCRIPT_SRC_VALUES } from "../../libs/constants/index.js";
describe("Content-Security-Policy", () => {
test('All inline