diff --git a/CODEOWNERS b/CODEOWNERS index 0978d490b386e..fcee14d00d1ec 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -7,7 +7,7 @@ # Framework app/site/next.config.mjs @nodejs/web-infra -app/site/next.dynamic.mjs @nodejs/web-infra +app/site/next.dynamic.ts @nodejs/web-infra # Node.js Release Blog Posts app/site/pages/en/blog/release @nodejs/releasers diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index be844b55769b7..8ccd0e823841a 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -333,7 +333,7 @@ This is to ensure that the Website is always available and that we do not depend (For example, if we abandon Vercel, our Website should still completely work as standalone as possible) -#### What is `next.dynamic.mjs`? +#### What is `next.dynamic.ts`? Our whole Website uses a custom renderer for rendering the pages. As you might have seen, within the `apps/site/pages` directory we have [Next.js Dynamic Route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) named `[...path].tsx` that matches against all possible routes of the Website. @@ -342,7 +342,7 @@ This means that each `.md(x)` file within `apps/site/pages/` is not rendered by This custom render uses `getStaticPaths` and [Incremental Static Generation](https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration) to generate the full list of supported pages of the Website. For example, this allows us to generate Localized Pages for every page that is not translated, by telling Next.js to create a localised path. -`next.dynamic.mjs` is responsible for getting a full list of the source pages (`apps/site/pages/en`) and identifying which pages have been translated. +`next.dynamic.ts` is responsible for getting a full list of the source pages (`apps/site/pages/en`) and identifying which pages have been translated. Non-translated pages will have their Localized contexts and translated React message-bags (`next-intl`) but the content will be the same as the source page (English). Whereas localized pages will have localized context and content. diff --git a/apps/site/app/[locale]/[[...path]]/page.tsx b/apps/site/app/[locale]/[[...path]]/page.tsx index 14aff4e2d41b4..2ef833bec8d9c 100644 --- a/apps/site/app/[locale]/[[...path]]/page.tsx +++ b/apps/site/app/[locale]/[[...path]]/page.tsx @@ -7,14 +7,15 @@ import { setClientContext } from '@/client-context'; import { MDXRenderer } from '@/components/mdxRenderer'; import WithLayout from '@/components/withLayout'; import { ENABLE_STATIC_EXPORT, VERCEL_REVALIDATE } from '@/next.constants.mjs'; -import { PAGE_VIEWPORT, DYNAMIC_ROUTES } from '@/next.dynamic.constants.mjs'; -import { dynamicRouter } from '@/next.dynamic.mjs'; +import { dynamicRouter } from '@/next.dynamic'; +import { PAGE_VIEWPORT, DYNAMIC_ROUTES } from '@/next.dynamic.constants'; import { allLocaleCodes, availableLocaleCodes, defaultLocale, } from '@/next.locales.mjs'; import { MatterProvider } from '@/providers/matterProvider'; +import type { Layouts } from '@/types'; type DynamicStaticPaths = { path: Array; locale: string }; type DynamicParams = { params: DynamicStaticPaths }; @@ -37,7 +38,7 @@ export const generateMetadata = async ({ params }: DynamicParams) => { const mapRoutesForLocale = async (locale: string) => { const routesForLanguage = await dynamicRouter.getRoutesByLanguage(locale); - return routesForLanguage.map(pathname => + return routesForLanguage.map((pathname: string) => dynamicRouter.mapPathToRoute(locale, pathname) ); }; @@ -155,7 +156,7 @@ const getPage: FC = async ({ params }) => { // within a server-side context return ( - + diff --git a/apps/site/app/[locale]/next-data/page-data/route.ts b/apps/site/app/[locale]/next-data/page-data/route.ts index b779e64569224..7d3faa1748283 100644 --- a/apps/site/app/[locale]/next-data/page-data/route.ts +++ b/apps/site/app/[locale]/next-data/page-data/route.ts @@ -3,7 +3,7 @@ import { deflateSync } from 'node:zlib'; import matter from 'gray-matter'; import { VERCEL_REVALIDATE } from '@/next.constants.mjs'; -import { dynamicRouter } from '@/next.dynamic.mjs'; +import { dynamicRouter } from '@/next.dynamic'; import { defaultLocale } from '@/next.locales.mjs'; import { parseRichTextIntoPlainText } from '@/util/stringUtils'; diff --git a/apps/site/app/sitemap.ts b/apps/site/app/sitemap.ts index 5724851ac6460..ecb8d4e13084e 100644 --- a/apps/site/app/sitemap.ts +++ b/apps/site/app/sitemap.ts @@ -5,7 +5,7 @@ import { BASE_URL, EXTERNAL_LINKS_SITEMAP, } from '@/next.constants.mjs'; -import { dynamicRouter } from '@/next.dynamic.mjs'; +import { dynamicRouter } from '@/next.dynamic'; import { availableLocaleCodes, defaultLocale } from '@/next.locales.mjs'; // This is the combination of the Application Base URL and Base PATH diff --git a/apps/site/components/Common/ActiveLink/__tests__/index.test.mjs b/apps/site/components/Common/ActiveLink/__tests__/index.test.mjs index 31d25c633fe36..291e40e6eaf9d 100644 --- a/apps/site/components/Common/ActiveLink/__tests__/index.test.mjs +++ b/apps/site/components/Common/ActiveLink/__tests__/index.test.mjs @@ -5,8 +5,8 @@ import { VERSION_SUPPORT_SHORTCUT } from '@/next.constants.mjs'; import ActiveLink from '..'; // mock usePathname, but retain all the other imports -jest.mock('@/navigation.mjs', () => ({ - ...jest.requireActual('@/navigation.mjs'), +jest.mock('@/navigation', () => ({ + ...jest.requireActual('@/navigation'), usePathname: jest.fn(), })); @@ -48,7 +48,7 @@ describe('ActiveLink', () => { }); it('does not set active class when href base does not match', () => { - const { usePathname } = require('@/navigation.mjs'); + const { usePathname } = require('@/navigation'); usePathname.mockReturnValue('/not-link/sublink'); render( @@ -66,7 +66,7 @@ describe('ActiveLink', () => { }); it('sets active class when href is other than VERSION_SUPPORT_SHORTCUT', () => { - const { usePathname } = require('@/navigation.mjs'); + const { usePathname } = require('@/navigation'); usePathname.mockReturnValue('/link/sublink'); render( @@ -87,7 +87,7 @@ describe('ActiveLink', () => { }); it('does not set active class when href is VERSION_SUPPORT_SHORTCUT', () => { - const { usePathname } = require('@/navigation.mjs'); + const { usePathname } = require('@/navigation'); usePathname.mockReturnValue(VERSION_SUPPORT_SHORTCUT); render( diff --git a/apps/site/components/Common/ActiveLink/index.tsx b/apps/site/components/Common/ActiveLink/index.tsx index b1aaf220de125..8fb77116397ce 100644 --- a/apps/site/components/Common/ActiveLink/index.tsx +++ b/apps/site/components/Common/ActiveLink/index.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import type { ComponentProps, FC } from 'react'; import Link from '@/components/Link'; -import { usePathname } from '@/navigation.mjs'; +import { usePathname } from '@/navigation'; import { VERSION_SUPPORT_SHORTCUT } from '@/next.constants.mjs'; type ActiveLocalizedLinkProps = ComponentProps & { diff --git a/apps/site/components/Common/CodeTabs/index.tsx b/apps/site/components/Common/CodeTabs/index.tsx index 614db0017079b..31dda2053fdf3 100644 --- a/apps/site/components/Common/CodeTabs/index.tsx +++ b/apps/site/components/Common/CodeTabs/index.tsx @@ -2,7 +2,7 @@ import { ArrowUpRightIcon } from '@heroicons/react/24/solid'; import type { ComponentProps, FC, PropsWithChildren } from 'react'; import Tabs from '@/components/Common/Tabs'; -import { Link } from '@/navigation.mjs'; +import { Link } from '@/navigation'; import styles from './index.module.css'; diff --git a/apps/site/components/Common/FormattedTime.tsx b/apps/site/components/Common/FormattedTime.tsx index 68aff9ee78dd3..dfc75480bdbe4 100644 --- a/apps/site/components/Common/FormattedTime.tsx +++ b/apps/site/components/Common/FormattedTime.tsx @@ -2,7 +2,7 @@ import type { DateTimeFormatOptions } from 'next-intl'; import { useFormatter } from 'next-intl'; import type { FC } from 'react'; -import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants.mjs'; +import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants'; type FormattedTimeProps = { date: string | Date; diff --git a/apps/site/components/Common/Search/States/WithSearchBox.tsx b/apps/site/components/Common/Search/States/WithSearchBox.tsx index d3a2cff45087c..5ef280d104e4c 100644 --- a/apps/site/components/Common/Search/States/WithSearchBox.tsx +++ b/apps/site/components/Common/Search/States/WithSearchBox.tsx @@ -16,7 +16,7 @@ import { WithPoweredBy } from '@/components/Common/Search/States/WithPoweredBy'; import { WithSearchResult } from '@/components/Common/Search/States/WithSearchResult'; import Tabs from '@/components/Common/Tabs'; import { useClickOutside, useKeyboardCommands } from '@/hooks/react-client'; -import { useRouter } from '@/navigation.mjs'; +import { useRouter } from '@/navigation'; import { DEFAULT_ORAMA_QUERY_PARAMS } from '@/next.constants.mjs'; import { search as oramaSearch, getInitialFacets } from '@/next.orama.mjs'; import type { SearchDoc } from '@/types'; diff --git a/apps/site/components/Downloads/ChangelogModal/index.stories.tsx b/apps/site/components/Downloads/ChangelogModal/index.stories.tsx index a08e8bd299ee4..86dcd63c44772 100644 --- a/apps/site/components/Downloads/ChangelogModal/index.stories.tsx +++ b/apps/site/components/Downloads/ChangelogModal/index.stories.tsx @@ -3,7 +3,7 @@ import { VFile } from 'vfile'; import ChangelogModal from '@/components/Downloads/ChangelogModal'; import { MDXRenderer } from '@/components/mdxRenderer'; -import { compileMDX } from '@/next.mdx.compiler.mjs'; +import { compileMDX } from '@/next.mdx.compiler'; import { getGitHubAvatarUrl } from '@/util/gitHubUtils'; type Story = StoryObj; diff --git a/apps/site/components/Link.tsx b/apps/site/components/Link.tsx index 8253b32fad5b2..c277245654998 100644 --- a/apps/site/components/Link.tsx +++ b/apps/site/components/Link.tsx @@ -1,6 +1,6 @@ import type { FC, ComponentProps } from 'react'; -import { Link as LocalizedLink } from '@/navigation.mjs'; +import { Link as LocalizedLink } from '@/navigation'; type LinkProps = Omit, 'href'> & { href?: string; diff --git a/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx index cac02b5d71fc8..eb9a1b2878df2 100644 --- a/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx +++ b/apps/site/components/MDX/Calendar/UpcomingMeetings.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react'; import FormattedTime from '@/components/Common/FormattedTime'; import Event from '@/components/MDX/Calendar/Event'; import { getZoomLink, isZoned } from '@/components/MDX/Calendar/utils'; -import { CALENDAR_NODEJS_ID } from '@/next.calendar.constants.mjs'; -import { getCalendarEvents } from '@/next.calendar.mjs'; +import { getCalendarEvents } from '@/next.calendar'; +import { CALENDAR_NODEJS_ID } from '@/next.calendar.constants'; import type { CalendarEvent } from '@/types'; import styles from './calendar.module.css'; diff --git a/apps/site/components/MDX/CodeBox/index.stories.tsx b/apps/site/components/MDX/CodeBox/index.stories.tsx index a460d95f8da7b..ef0675dc9092b 100644 --- a/apps/site/components/MDX/CodeBox/index.stories.tsx +++ b/apps/site/components/MDX/CodeBox/index.stories.tsx @@ -2,7 +2,7 @@ import type { Meta as MetaObj, StoryObj } from '@storybook/react'; import { VFile } from 'vfile'; import { MDXRenderer } from '@/components/mdxRenderer'; -import { compileMDX } from '@/next.mdx.compiler.mjs'; +import { compileMDX } from '@/next.mdx.compiler'; type Props = { children: string }; diff --git a/apps/site/components/MDX/CodeTabs/index.stories.tsx b/apps/site/components/MDX/CodeTabs/index.stories.tsx index 45889e0b47888..60834acfae6a7 100644 --- a/apps/site/components/MDX/CodeTabs/index.stories.tsx +++ b/apps/site/components/MDX/CodeTabs/index.stories.tsx @@ -2,7 +2,7 @@ import type { Meta as MetaObj, StoryObj } from '@storybook/react'; import { VFile } from 'vfile'; import { MDXRenderer } from '@/components/mdxRenderer'; -import { compileMDX } from '@/next.mdx.compiler.mjs'; +import { compileMDX } from '@/next.mdx.compiler'; type Props = { children: string }; diff --git a/apps/site/components/mdxRenderer.tsx b/apps/site/components/mdxRenderer.tsx index abf9b4315e97e..d79f15943cc11 100644 --- a/apps/site/components/mdxRenderer.tsx +++ b/apps/site/components/mdxRenderer.tsx @@ -1,8 +1,8 @@ import type { MDXComponents, MDXContent } from 'mdx/types'; import type { FC } from 'react'; -import { htmlComponents, clientMdxComponents } from '@/next.mdx.use.client.mjs'; -import { mdxComponents } from '@/next.mdx.use.mjs'; +import { mdxComponents } from '@/next.mdx.use'; +import { htmlComponents, clientMdxComponents } from '@/next.mdx.use.client'; // Combine all MDX Components to be used const combinedComponents: MDXComponents = { diff --git a/apps/site/components/withChangelogModal.tsx b/apps/site/components/withChangelogModal.tsx index 59b2d1f5b5988..9af2571a46e38 100644 --- a/apps/site/components/withChangelogModal.tsx +++ b/apps/site/components/withChangelogModal.tsx @@ -6,8 +6,8 @@ import { VFile } from 'vfile'; import ChangelogModal from '@/components/Downloads/ChangelogModal'; import changelogData from '@/next-data/changelogData'; -import { compileMDX } from '@/next.mdx.compiler.mjs'; -import { clientMdxComponents, htmlComponents } from '@/next.mdx.use.client.mjs'; +import { compileMDX } from '@/next.mdx.compiler'; +import { clientMdxComponents, htmlComponents } from '@/next.mdx.use.client'; import type { NodeRelease } from '@/types'; import { getNodeJsChangelogAuthor, diff --git a/apps/site/components/withMetaBar.tsx b/apps/site/components/withMetaBar.tsx index 543d901d55f4b..32fc96349ff95 100644 --- a/apps/site/components/withMetaBar.tsx +++ b/apps/site/components/withMetaBar.tsx @@ -8,7 +8,7 @@ import GitHub from '@/components/Icons/Social/GitHub'; import Link from '@/components/Link'; import { useClientContext } from '@/hooks/react-client'; import useMediaQuery from '@/hooks/react-client/useMediaQuery'; -import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants.mjs'; +import { DEFAULT_DATE_FORMAT } from '@/next.calendar.constants'; import { getGitHubBlobUrl, getGitHubAvatarUrl } from '@/util/gitHubUtils'; const WithMetaBar: FC = () => { diff --git a/apps/site/components/withNavBar.tsx b/apps/site/components/withNavBar.tsx index 8353dac0330e6..2528eca9050bf 100644 --- a/apps/site/components/withNavBar.tsx +++ b/apps/site/components/withNavBar.tsx @@ -7,7 +7,7 @@ import type { FC } from 'react'; import NavBar from '@/components/Containers/NavBar'; import WithBanner from '@/components/withBanner'; import { useClientContext, useSiteNavigation } from '@/hooks'; -import { useRouter } from '@/navigation.mjs'; +import { useRouter } from '@/navigation'; import { availableLocales } from '@/next.locales.mjs'; const WithNavBar: FC = () => { diff --git a/apps/site/components/withRouterSelect.tsx b/apps/site/components/withRouterSelect.tsx index f0efadac95640..a93d2cf6563df 100644 --- a/apps/site/components/withRouterSelect.tsx +++ b/apps/site/components/withRouterSelect.tsx @@ -3,7 +3,7 @@ import type { ComponentProps, FC } from 'react'; import Select from '@/components/Common/Select'; -import { useRouter } from '@/navigation.mjs'; +import { useRouter } from '@/navigation'; type WithSidebarSelectProps = Pick< ComponentProps, diff --git a/apps/site/middleware.ts b/apps/site/middleware.ts index 95183c635d1c8..cdb5e243afc84 100644 --- a/apps/site/middleware.ts +++ b/apps/site/middleware.ts @@ -13,7 +13,7 @@ export default createMiddleware({ localePrefix: 'always', // We already have our own way of providing alternate links - // generated on `next.dynamic.mjs` + // generated on `next.dynamic.ts` alternateLinks: false, }); diff --git a/apps/site/navigation.mjs b/apps/site/navigation.ts similarity index 94% rename from apps/site/navigation.mjs rename to apps/site/navigation.ts index edff2d9a0c197..5413533bf261c 100644 --- a/apps/site/navigation.mjs +++ b/apps/site/navigation.ts @@ -1,5 +1,3 @@ -'use strict'; - import { createSharedPathnamesNavigation } from 'next-intl/navigation'; import { availableLocaleCodes } from './next.locales.mjs'; diff --git a/apps/site/next-data/generators/blogData.mjs b/apps/site/next-data/generators/blogData.mjs deleted file mode 100644 index c03e23c62a48a..0000000000000 --- a/apps/site/next-data/generators/blogData.mjs +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -import { createReadStream } from 'node:fs'; -import { basename, extname, join } from 'node:path'; -import readline from 'node:readline'; - -import graymatter from 'gray-matter'; - -import { getMarkdownFiles } from '../../next.helpers.mjs'; - -// gets the current blog path based on local module path -const blogPath = join(process.cwd(), 'pages/en/blog'); - -/** - * This contains the metadata of all available blog categories - */ -const blogCategories = new Set(['all']); - -/** - * This method parses the source (raw) Markdown content into Frontmatter - * and returns basic information for blog posts - * - * @param {string} filename the filename related to the blogpost - * @param {string} source the source markdown content of the blog post - */ -const getFrontMatter = (filename, source) => { - const { - title = 'Untitled', - author = 'The Node.js Project', - username, - date = new Date(), - category = 'uncategorized', - } = graymatter(source).data; - - // We also use publishing years as categories for the blog - const publishYear = new Date(date).getUTCFullYear(); - - // Provides a full list of categories for the Blog Post which consists of - // all = (all blog posts), publish year and the actual blog category - const categories = [category, `year-${publishYear}`, 'all']; - - // we add the year to the categories set - blogCategories.add(`year-${publishYear}`); - - // we add the category to the categories set - blogCategories.add(category); - - // this is the url used for the blog post it based on the category and filename - const slug = `/blog/${category}/${basename(filename, extname(filename))}`; - - return { title, author, username, date: new Date(date), categories, slug }; -}; - -/** - * This method is used to generate the Node.js Website Blog Data - * for self-consumption during RSC and Static Builds - * - * @return {Promise} - */ -const generateBlogData = async () => { - // We retrieve the full pathnames of all Blog Posts to read each file individually - const filenames = await getMarkdownFiles(process.cwd(), 'pages/en/blog', [ - '**/index.md', - ]); - - return new Promise(resolve => { - const posts = []; - const rawFrontmatter = []; - - filenames.forEach(filename => { - // We create a stream for reading a file instead of reading the files - const _stream = createReadStream(join(blogPath, filename)); - - // We create a readline interface to read the file line-by-line - const _readLine = readline.createInterface({ input: _stream }); - - // Creates an array of the metadata based on the filename - // This prevents concurrency issues since the for-loop is synchronous - // and these event listeners are not - rawFrontmatter[filename] = [0, '']; - - // We read line by line - _readLine.on('line', line => { - rawFrontmatter[filename][1] += `${line}\n`; - - // We observe the frontmatter separators - if (line === '---') { - rawFrontmatter[filename][0] += 1; - } - - // Once we have two separators we close the readLine and the stream - if (rawFrontmatter[filename][0] === 2) { - _readLine.close(); - _stream.close(); - } - }); - - // Then we parse gray-matter on the frontmatter - // This allows us to only read the frontmatter part of each file - // and optimise the read-process as we have thousands of markdown files - _readLine.on('close', () => { - posts.push(getFrontMatter(filename, rawFrontmatter[filename][1])); - - if (posts.length === filenames.length) { - resolve({ categories: [...blogCategories], posts }); - } - }); - }); - }); -}; - -export default generateBlogData; diff --git a/apps/site/next-data/generators/blogData.ts b/apps/site/next-data/generators/blogData.ts new file mode 100644 index 0000000000000..d785010670490 --- /dev/null +++ b/apps/site/next-data/generators/blogData.ts @@ -0,0 +1,71 @@ +import { createReadStream } from 'node:fs'; +import { basename, extname, join } from 'node:path'; +import readline from 'node:readline'; + +import graymatter from 'gray-matter'; + +import { getMarkdownFiles } from '@/next.helpers.mjs'; +import type { BlogData } from '@/types'; + +const blogPath = join(process.cwd(), 'pages/en/blog'); + +const blogCategories = new Set(['all']); + +const getFrontMatter = (filename: string, source: string) => { + const { + title = 'Untitled', + author = 'The Node.js Project', + username, + date = new Date(), + category = 'uncategorized', + } = graymatter(source).data; + + const publishYear = new Date(date).getUTCFullYear(); + + const categories = [category, `year-${publishYear}`, 'all']; + + blogCategories.add(`year-${publishYear}`); + blogCategories.add(category); + + const slug = `/blog/${category}/${basename(filename, extname(filename))}`; + + return { title, author, username, date: new Date(date), categories, slug }; +}; + +const generateBlogData = async (): Promise => { + const filenames = await getMarkdownFiles(process.cwd(), 'pages/en/blog', [ + '**/index.md', + ]); + + return new Promise(resolve => { + const posts = [] as BlogData['posts']; + const rawFrontmatter = new Map(); + + filenames.forEach((filename: string) => { + const _stream = createReadStream(join(blogPath, filename)); + const _readLine = readline.createInterface({ input: _stream }); + + rawFrontmatter.set(filename, [0, '']); + + _readLine.on('line', line => { + const [count, content] = rawFrontmatter.get(filename)!; + rawFrontmatter.set(filename, [count + (line === '---' ? 1 : 0), content + line + '\n']); + + if (rawFrontmatter.get(filename)![0] === 2) { + _readLine.close(); + _stream.close(); + } + }); + + _readLine.on('close', () => { + posts.push(getFrontMatter(filename, rawFrontmatter.get(filename)![1])); + + if (posts.length === filenames.length) { + resolve({ categories: [...blogCategories], posts }); + } + }); + }); + }); +}; + +export default generateBlogData; diff --git a/apps/site/next-data/generators/changelogData.mjs b/apps/site/next-data/generators/changelogData.ts similarity index 63% rename from apps/site/next-data/generators/changelogData.mjs rename to apps/site/next-data/generators/changelogData.ts index f0cb12f3dac75..5320f37db7e36 100644 --- a/apps/site/next-data/generators/changelogData.mjs +++ b/apps/site/next-data/generators/changelogData.ts @@ -1,18 +1,12 @@ -'use strict'; - import { fetchNodeJsChangelog } from '@/util/fetchNodeJsChangelog'; /** * This method is used to generate the Node.js Changelog Data * for self-consumption during RSC and Static Builds - * - * @returns {Promise} */ -const generateChangelogData = async version => { +const generateChangelogData = async (version: string): Promise => { // Get the raw changelog for the latest minor for a given major - const changelog = await fetchNodeJsChangelog(version); - - return changelog; + return fetchNodeJsChangelog(version); }; export default generateChangelogData; diff --git a/apps/site/next-data/generators/releaseData.mjs b/apps/site/next-data/generators/releaseData.mjs index 2beea90861c1e..bea9f366c14c4 100644 --- a/apps/site/next-data/generators/releaseData.mjs +++ b/apps/site/next-data/generators/releaseData.mjs @@ -1,5 +1,7 @@ 'use strict'; +// NOTE: this file canot be a ts file +// because `nodevu` din't have typedefs import nodevu from '@nodevu/core'; // Gets the appropriate release status for each major release diff --git a/apps/site/next-data/generators/websiteFeeds.mjs b/apps/site/next-data/generators/websiteFeeds.ts similarity index 64% rename from apps/site/next-data/generators/websiteFeeds.mjs rename to apps/site/next-data/generators/websiteFeeds.ts index af4ab72bc3afa..554953fc838fe 100644 --- a/apps/site/next-data/generators/websiteFeeds.mjs +++ b/apps/site/next-data/generators/websiteFeeds.ts @@ -1,9 +1,9 @@ -'use strict'; - import { Feed } from 'feed'; +import type { Feed as FeedType } from 'feed'; -import { BASE_URL, BASE_PATH } from '../../next.constants.mjs'; -import { siteConfig } from '../../next.json.mjs'; +import { BASE_URL, BASE_PATH } from '@/next.constants.mjs'; +import { siteConfig } from '@/next.json.mjs'; +import type { BlogPostsRSC } from '@/types'; // This is the Base URL for the Node.js Website // with English locale (which is where the website feeds run) @@ -12,16 +12,12 @@ const canonicalUrl = `${BASE_URL}${BASE_PATH}/en`; /** * This method generates RSS website feeds based on the current website configuration * and the current blog data that is available - * - * @param {import('../../types').BlogPostsRSC} blogData */ -const generateWebsiteFeeds = ({ posts }) => { +const generateWebsiteFeeds = ({ posts }: BlogPostsRSC) => { /** * This generates all the Website RSS Feeds that are used for the website - * - * @type {Array<[string, Feed]>} */ - const websiteFeeds = siteConfig.rssFeeds.map( + const websiteFeeds: Array<[string, FeedType]> = siteConfig.rssFeeds.map( ({ category, title, description, file }) => { const feed = new Feed({ id: file, @@ -29,6 +25,7 @@ const generateWebsiteFeeds = ({ posts }) => { language: 'en', link: `${canonicalUrl}/feed/${file}`, description: description, + copyright: '© OpenJS Foundation', }); const blogFeedEntries = posts @@ -36,18 +33,21 @@ const generateWebsiteFeeds = ({ posts }) => { .map(post => ({ id: post.slug, title: post.title, + // @TODO: use righ object type for date + // wait https://github.com/nodejs/nodejs.org/pull/7143 to be merged author: post.author, date: new Date(post.date), link: `${canonicalUrl}${post.slug}`, })); + // @ts-expect-error - The addItem need a valide Item type blogFeedEntries.forEach(entry => feed.addItem(entry)); return [file, feed]; } ); - return new Map(websiteFeeds); + return new Map(websiteFeeds); }; export default generateWebsiteFeeds; diff --git a/apps/site/next-data/providers/blogData.ts b/apps/site/next-data/providers/blogData.ts index 4f26637eac2a6..5b0ee8825cc10 100644 --- a/apps/site/next-data/providers/blogData.ts +++ b/apps/site/next-data/providers/blogData.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import generateBlogData from '@/next-data/generators/blogData.mjs'; +import generateBlogData from '@/next-data/generators/blogData'; import { BLOG_POSTS_PER_PAGE } from '@/next.constants.mjs'; import type { BlogPostsRSC } from '@/types'; diff --git a/apps/site/next-data/providers/changelogData.ts b/apps/site/next-data/providers/changelogData.ts index 8a07bab07fd2e..514095345dd06 100644 --- a/apps/site/next-data/providers/changelogData.ts +++ b/apps/site/next-data/providers/changelogData.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import generateChangelogData from '@/next-data/generators/changelogData.mjs'; +import generateChangelogData from '@/next-data/generators/changelogData'; export const provideChangelogData = cache((version: string) => { const changelog = generateChangelogData(version); diff --git a/apps/site/next-data/providers/websiteFeeds.ts b/apps/site/next-data/providers/websiteFeeds.ts index fff1eb6af6c10..9b7f41ba0bc01 100644 --- a/apps/site/next-data/providers/websiteFeeds.ts +++ b/apps/site/next-data/providers/websiteFeeds.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import generateWebsiteFeeds from '@/next-data/generators/websiteFeeds.mjs'; +import generateWebsiteFeeds from '@/next-data/generators/websiteFeeds'; import { provideBlogPosts } from '@/next-data/providers/blogData'; const websiteFeeds = generateWebsiteFeeds(provideBlogPosts('all')); diff --git a/apps/site/next.calendar.constants.mjs b/apps/site/next.calendar.constants.ts similarity index 90% rename from apps/site/next.calendar.constants.mjs rename to apps/site/next.calendar.constants.ts index c1e2712be9b82..8145bfe173ca3 100644 --- a/apps/site/next.calendar.constants.mjs +++ b/apps/site/next.calendar.constants.ts @@ -1,4 +1,4 @@ -'use strict'; +import type { DateTimeFormatOptions } from 'next-intl'; /** * This is used for Node.js Calendar and any other Google Calendar that we might want to load within the Website @@ -28,10 +28,8 @@ export const CALENDAR_NODEJS_ID = /** * Default Date format for Calendars and Time Components - * - * @type {import('next-intl').DateTimeFormatOptions} */ -export const DEFAULT_DATE_FORMAT = { +export const DEFAULT_DATE_FORMAT: DateTimeFormatOptions = { year: 'numeric', month: 'short', day: '2-digit', diff --git a/apps/site/next.calendar.mjs b/apps/site/next.calendar.ts similarity index 76% rename from apps/site/next.calendar.mjs rename to apps/site/next.calendar.ts index a30c48e0e0dcb..0dd282f01a542 100644 --- a/apps/site/next.calendar.mjs +++ b/apps/site/next.calendar.ts @@ -1,17 +1,11 @@ -'use strict'; +import type { CalendarEvent } from '@/types'; import { BASE_CALENDAR_URL, SHARED_CALENDAR_KEY, -} from './next.calendar.constants.mjs'; +} from './next.calendar.constants'; -/** - * - * @param {string} calendarId - * @param {number} maxResults - * @returns {Promise>} - */ -export const getCalendarEvents = async (calendarId = '', maxResults = 20) => { +export const getCalendarEvents = async (calendarId = '', maxResults = 20): Promise> => { const currentDate = new Date(); const nextWeekDate = new Date(); @@ -19,8 +13,8 @@ export const getCalendarEvents = async (calendarId = '', maxResults = 20) => { const calendarQueryParams = new URLSearchParams({ calendarId, - maxResults, - singleEvents: true, + maxResults: maxResults.toString(), + singleEvents: 'true', timeZone: 'Etc/Utc', key: SHARED_CALENDAR_KEY, timeMax: nextWeekDate.toISOString(), diff --git a/apps/site/next.dynamic.constants.mjs b/apps/site/next.dynamic.constants.ts similarity index 83% rename from apps/site/next.dynamic.constants.mjs rename to apps/site/next.dynamic.constants.ts index 3233d5a6940df..e5aba22295d50 100644 --- a/apps/site/next.dynamic.constants.mjs +++ b/apps/site/next.dynamic.constants.ts @@ -1,4 +1,4 @@ -'use strict'; +import type { Metadata, Viewport } from 'next'; import { provideBlogCategories, @@ -7,14 +7,18 @@ import { import { BASE_PATH, BASE_URL } from './next.constants.mjs'; import { siteConfig } from './next.json.mjs'; import { defaultLocale } from './next.locales.mjs'; +import type { Layouts } from './types'; + +type RouteSegment = { + locale: string; + pathname: string; +}; /** * This is a list of all static routes or pages from the Website that we do not * want to allow to be statically built on our Static Export Build. - * - * @type {Array<((route: import('./types').RouteSegment) => boolean)>} A list of Ignored Routes by Regular Expressions */ -export const IGNORED_ROUTES = [ +export const IGNORED_ROUTES: Array<(route: RouteSegment) => boolean> = [ // This is used to ignore all blog routes except for the English language ({ locale, pathname }) => locale !== defaultLocale.code && /^blog/.test(pathname), @@ -24,12 +28,10 @@ export const IGNORED_ROUTES = [ * This constant is used to create static routes on-the-fly that do not have a file-system * counterpart route. This is useful for providing routes with matching Layout Names * but that do not have Markdown content and a matching file for the route - * - * @type {Map} A Map of pathname and Layout Name */ -export const DYNAMIC_ROUTES = new Map([ +export const DYNAMIC_ROUTES = new Map([ // Provides Routes for all Blog Categories - ...provideBlogCategories().map(c => [`blog/${c}`, 'blog-category']), + ...provideBlogCategories().map(c => [`blog/${c}`, 'blog-category'] as [string, Layouts]), // Provides Routes for all Blog Categories w/ Pagination ...provideBlogCategories() // retrieves the amount of pages for each blog category @@ -38,15 +40,14 @@ export const DYNAMIC_ROUTES = new Map([ // each page for a category (i.e. blog/all/page/1) .map(([c, t]) => [...Array(t).keys()].map(p => `blog/${c}/page/${p + 1}`)) // creates a tuple of each pathname and layout for the route - .map(paths => paths.map(path => [path, 'blog-category'])) + .map(paths => paths.map(path => [path, 'blog-category'] as [string, Layouts])) // flattens the array since we have a .map inside another .map - .flat(), + .flat() ]); + /** * This is the default Next.js Page Metadata for all pages - * - * @type {import('next').Metadata} */ export const PAGE_METADATA = { metadataBase: new URL(`${BASE_URL}${BASE_PATH}`), @@ -55,7 +56,6 @@ export const PAGE_METADATA = { robots: { index: true, follow: true }, twitter: { card: siteConfig.twitter.card, - title: siteConfig.twitter.title, creator: siteConfig.twitter.username, images: { url: siteConfig.twitter.img, @@ -71,14 +71,12 @@ export const PAGE_METADATA = { }, icons: { icon: siteConfig.favicon }, openGraph: { images: siteConfig.twitter.img }, -}; +} as Metadata; /** * This is the default Next.js Viewport Metadata for all pages - * - * @return {import('next').Viewport} */ -export const PAGE_VIEWPORT = { +export const PAGE_VIEWPORT: Viewport = { themeColor: [ { color: siteConfig.lightAccentColor, diff --git a/apps/site/next.dynamic.mjs b/apps/site/next.dynamic.ts similarity index 70% rename from apps/site/next.dynamic.mjs rename to apps/site/next.dynamic.ts index c7879e2783a70..551c07d86eb22 100644 --- a/apps/site/next.dynamic.mjs +++ b/apps/site/next.dynamic.ts @@ -1,10 +1,9 @@ -'use strict'; - import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { join, normalize, sep } from 'node:path'; import matter from 'gray-matter'; +import type { Metadata } from 'next'; import { cache } from 'react'; import { VFile } from 'vfile'; @@ -13,17 +12,17 @@ import { IGNORED_ROUTES, DYNAMIC_ROUTES, PAGE_METADATA, -} from './next.dynamic.constants.mjs'; +} from './next.dynamic.constants'; import { getMarkdownFiles } from './next.helpers.mjs'; import { siteConfig } from './next.json.mjs'; import { availableLocaleCodes, defaultLocale } from './next.locales.mjs'; -import { compileMDX } from './next.mdx.compiler.mjs'; +import { compileMDX } from './next.mdx.compiler'; // This is the combination of the Application Base URL and Base PATH const baseUrlAndPath = `${BASE_URL}${BASE_PATH}`; // This is a small utility that allows us to quickly separate locale from the remaining pathname -const getPathname = (path = []) => path.join('/'); +const getPathname = (path: Array = []) => path.join('/'); // This maps a pathname into an actual route object that can be used // we use a platform-specific separator to split the pathname @@ -77,12 +76,11 @@ const getDynamicRouter = async () => { /** * This method returns a list of all routes that exist for a given locale - * - * @param {string} locale - * @returns {Promise>} */ - const getRoutesByLanguage = async (locale = defaultLocale.code) => { - const shouldIgnoreStaticRoute = pathname => + const getRoutesByLanguage = async ( + locale = defaultLocale.code + ): Promise> => { + const shouldIgnoreStaticRoute = (pathname: string) => IGNORED_ROUTES.every(e => !e({ pathname, locale })); return [...pathnameToFilename.keys()] @@ -94,12 +92,11 @@ const getDynamicRouter = async () => { * This method attempts to retrieve either a localized Markdown file * or the English version of the Markdown file if no localized version exists * and then returns the contents of the file and the name of the file (not the path) - * - * @param {string} locale - * @param {string} pathname - * @returns {Promise<{ source: string; filename: string }>} */ - const _getMarkdownFile = async (locale = '', pathname = '') => { + const _getMarkdownFile = async ( + locale = '', + pathname = '' + ): Promise<{ source: string; filename: string }> => { const normalizedPathname = normalize(pathname).replace('.', ''); // This verifies if the given pathname actually exists on our Map @@ -152,16 +149,13 @@ const getDynamicRouter = async () => { }; // Creates a Cached Version of the Markdown File Resolver - const getMarkdownFile = cache(async (locale, pathname) => { + const getMarkdownFile = cache(async (locale: string, pathname: string) => { return await _getMarkdownFile(locale, pathname); }); /** * This method runs the MDX compiler on the server-side and returns the * parsed JSX ready to be rendered on a page as a React Component - * - * @param {string} source - * @param {string} filename */ const _getMDXContent = async (source = '', filename = '') => { // We create a VFile (Virtual File) to be able to access some contextual @@ -177,20 +171,19 @@ const getDynamicRouter = async () => { }; // Creates a Cached Version of the MDX Compiler - const getMDXContent = cache(async (source, filename) => { + const getMDXContent = async (source: string, filename: string) => { return await _getMDXContent(source, filename); - }); + }; /** * This method generates the Next.js App Router Metadata * that can be used for each page to provide metadata - * - * @param {string} locale - * @param {string} path - * @returns {Promise} */ - const _getPageMetadata = async (locale = defaultLocale.code, path = '') => { - const pageMetadata = { ...PAGE_METADATA }; + const _getPageMetadata = async ( + locale = defaultLocale.code, + path = '' + ): Promise => { + const pageMetadata = PAGE_METADATA; const { source = '' } = await getMarkdownFile(locale, path); @@ -200,52 +193,67 @@ const getDynamicRouter = async () => { ? `${siteConfig.title} — ${data.title}` : siteConfig.title; - pageMetadata.twitter.title = pageMetadata.title; + if (pageMetadata.twitter) { + pageMetadata.twitter.title = pageMetadata.title; + } - const getUrlForPathname = (l, p) => + const getUrlForPathname = (l: string, p: string) => `${baseUrlAndPath}/${l}${p ? `/${p}` : ''}`; - pageMetadata.alternates.canonical = getUrlForPathname(locale, path); - - pageMetadata.alternates.languages['x-default'] = getUrlForPathname( - defaultLocale.code, - path - ); - - const blogMatch = path.match(/^blog\/(release|vulnerability)(\/|$)/); - if (blogMatch) { - const category = blogMatch[1]; - const currentFile = siteConfig.rssFeeds.find( - item => item.category === category - )?.file; - // Use getUrlForPathname to dynamically construct the XML path for blog/release and blog/vulnerability - pageMetadata.alternates.types['application/rss+xml'] = getUrlForPathname( - locale, - `feed/${currentFile}` - ); - } else { - // Use getUrlForPathname for the default blog XML feed path - pageMetadata.alternates.types['application/rss+xml'] = getUrlForPathname( - locale, - 'feed/blog.xml' - ); + if (pageMetadata.alternates) { + pageMetadata.alternates.canonical = getUrlForPathname(locale, path); + + if (pageMetadata.alternates.languages) { + pageMetadata.alternates.languages['x-default'] = getUrlForPathname( + defaultLocale.code, + path + ); + } + + const blogMatch = path.match(/^blog\/(release|vulnerability)(\/|$)/); + if (blogMatch) { + const category = blogMatch[1]; + const currentFile = siteConfig.rssFeeds.find( + item => item.category === category + )?.file; + // Use getUrlForPathname to dynamically construct the XML path for blog/release and blog/vulnerability + if (pageMetadata.alternates.types) { + pageMetadata.alternates.types['application/rss+xml'] = + getUrlForPathname(locale, `feed/${currentFile}`); + } + } else { + // Use getUrlForPathname for the default blog XML feed path + if (pageMetadata.alternates.types) { + pageMetadata.alternates.types['application/rss+xml'] = + getUrlForPathname(locale, 'feed/blog.xml'); + } + } + + availableLocaleCodes.forEach(currentLocale => { + // Ensure currentLocale is a valid key for languages + if ( + pageMetadata.alternates?.languages && + currentLocale in pageMetadata.alternates.languages + ) { + pageMetadata.alternates.languages[ + currentLocale as keyof typeof pageMetadata.alternates.languages + ] = getUrlForPathname(currentLocale, path); + } + }); } - availableLocaleCodes.forEach(currentLocale => { - pageMetadata.alternates.languages[currentLocale] = getUrlForPathname( - currentLocale, - path + if (pageMetadata.openGraph) { + pageMetadata.openGraph.images = availableLocaleCodes.map( + currentLocale => + `${currentLocale}/next-data/og?title=${pageMetadata.title}&type=${data.category ?? 'announcement'}` ); - pageMetadata.openGraph.images = [ - `${currentLocale}/next-data/og?title=${pageMetadata.title}&type=${data.category ?? 'announcement'}`, - ]; - }); + } return pageMetadata; }; // Creates a Cached Version of the Page Metadata Context - const getPageMetadata = cache(async (locale, path) => { + const getPageMetadata = cache(async (locale: string, path: string) => { return await _getPageMetadata(locale, path); }); diff --git a/apps/site/next.mdx.compiler.mjs b/apps/site/next.mdx.compiler.ts similarity index 64% rename from apps/site/next.mdx.compiler.mjs rename to apps/site/next.mdx.compiler.ts index 6fcdf963cdd39..e350529b94a5b 100644 --- a/apps/site/next.mdx.compiler.mjs +++ b/apps/site/next.mdx.compiler.ts @@ -1,11 +1,13 @@ -'use strict'; - import { evaluate } from '@mdx-js/mdx'; +import type { Heading } from '@vcarl/remark-headings'; +import type { MDXContent } from 'mdx/types'; import { Fragment, jsx, jsxs } from 'react/jsx-runtime'; +import type { ReadTimeResults } from 'reading-time'; +import type { VFile } from 'vfile'; import { matter } from 'vfile-matter'; -import { NEXT_REHYPE_PLUGINS, NEXT_REMARK_PLUGINS } from './next.mdx.mjs'; -import { createGitHubSlugger } from './util/gitHubUtils'; +import { NEXT_REHYPE_PLUGINS, NEXT_REMARK_PLUGINS } from '@/next.mdx'; +import { createGitHubSlugger } from '@/util/gitHubUtils'; // Defines the React Runtime Components const reactRuntime = { Fragment, jsx, jsxs }; @@ -13,17 +15,16 @@ const reactRuntime = { Fragment, jsx, jsxs }; /** * This is our custom simple MDX Compiler that is used to compile Markdown and MDX * this returns a serializable VFile as a string that then gets passed to our MDX Provider - * - * @param {import('vfile').VFile} source - * @param {'md' | 'mdx'} fileExtension - * @returns {Promise<{ - * MDXContent: import('mdx/types').MDXContent; - * headings: Array; - * frontmatter: Record; - * readingTime: import('reading-time').ReadTimeResults; - * }>} */ -export async function compileMDX(source, fileExtension) { +export async function compileMDX( + source: VFile, + fileExtension: 'md' | 'mdx' +): Promise<{ + MDXContent: MDXContent; + headings: Array; + frontmatter: Record; + readingTime: ReadTimeResults; +}> { // Parses the Frontmatter to the VFile and removes from the original source // cleaning the frontmatter to the source that is going to be parsed by the MDX Compiler matter(source, { strip: true }); @@ -40,7 +41,15 @@ export async function compileMDX(source, fileExtension) { // Retrieve some parsed data from the VFile metadata // such as frontmatter and Markdown headings - const { headings, matter: frontmatter, readingTime } = source.data; + const { + headings, + matter: frontmatter, + readingTime, + } = source.data as { + headings: Array; + matter: Record; + readingTime: ReadTimeResults; + }; headings.forEach(heading => { // we re-sluggify the links to match the GitHub slugger diff --git a/apps/site/next.mdx.mjs b/apps/site/next.mdx.ts similarity index 76% rename from apps/site/next.mdx.mjs rename to apps/site/next.mdx.ts index be5d28a9e9fac..ac05c1009549f 100644 --- a/apps/site/next.mdx.mjs +++ b/apps/site/next.mdx.ts @@ -1,19 +1,16 @@ -'use strict'; - import remarkHeadings from '@vcarl/remark-headings'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import rehypeSlug from 'rehype-slug'; import remarkGfm from 'remark-gfm'; import readingTime from 'remark-reading-time'; +import type { Pluggable } from 'unified'; import rehypeShikiji from './next.mdx.shiki.mjs'; /** * Provides all our Rehype Plugins that are used within MDX - * - * @type {Array} */ -export const NEXT_REHYPE_PLUGINS = [ +export const NEXT_REHYPE_PLUGINS: Array = [ // Generates `id` attributes for headings (H1, ...) rehypeSlug, // Automatically add anchor links to headings (H1, ...) @@ -25,7 +22,9 @@ export const NEXT_REHYPE_PLUGINS = [ /** * Provides all our Remark Plugins that are used within MDX - * - * @type {Array} */ -export const NEXT_REMARK_PLUGINS = [remarkGfm, remarkHeadings, readingTime]; +export const NEXT_REMARK_PLUGINS: Array = [ + remarkGfm, + remarkHeadings, + readingTime, +]; diff --git a/apps/site/next.mdx.use.client.mjs b/apps/site/next.mdx.use.client.ts similarity index 82% rename from apps/site/next.mdx.use.client.mjs rename to apps/site/next.mdx.use.client.ts index bff703e1c72d4..4576852441c0a 100644 --- a/apps/site/next.mdx.use.client.mjs +++ b/apps/site/next.mdx.use.client.ts @@ -1,4 +1,4 @@ -'use strict'; +import type { MDXComponents } from 'mdx/types'; import Blockquote from './components/Common/Blockquote'; import Button from './components/Common/Button'; @@ -10,8 +10,6 @@ import MDXImage from './components/MDX/Image'; /** * A full list of React Components that we want to pass through to MDX - * - * @satisfies {import('mdx/types').MDXComponents} */ export const clientMdxComponents = { // Renders MDX CodeTabs @@ -22,14 +20,12 @@ export const clientMdxComponents = { LinkWithArrow: LinkWithArrow, // Regular links (without arrow) Link: Link, -}; +} satisfies MDXComponents; /** * A full list of wired HTML elements into custom React Components - * - * @type {import('mdx/types').MDXComponents} */ -export const htmlComponents = { +export const htmlComponents: MDXComponents = { // Renders a Link Component for `a` tags a: Link, // Renders a Blockquote Component for `blockquote` tags @@ -37,5 +33,6 @@ export const htmlComponents = { // Renders a CodeBox Component for `pre` tags pre: MDXCodeBox, // Renders an Image Component for `img` tags + // @ts-expect-error - MDXImage is a wrapper of Next Image so it's didn't have same props as HTMLImageElement img: MDXImage, }; diff --git a/apps/site/next.mdx.use.mjs b/apps/site/next.mdx.use.ts similarity index 98% rename from apps/site/next.mdx.use.mjs rename to apps/site/next.mdx.use.ts index 54191ff55b952..6548d6773ecfa 100644 --- a/apps/site/next.mdx.use.mjs +++ b/apps/site/next.mdx.use.ts @@ -1,4 +1,4 @@ -'use strict'; +import type { MDXComponents } from 'mdx/types'; import DownloadButton from './components/Downloads/DownloadButton'; import DownloadLink from './components/Downloads/DownloadLink'; @@ -24,8 +24,6 @@ import WithNodeRelease from './components/withNodeRelease'; /** * A full list of React Components that we want to pass through to MDX - * - * @satisfies {import('mdx/types').MDXComponents} */ export const mdxComponents = { DownloadReleasesTable: DownloadReleasesTable, @@ -73,4 +71,4 @@ export const mdxComponents = { // Renders a Changelog Modal Link Button ChangelogLink: ChangelogLink, }, -}; +} as MDXComponents; diff --git a/apps/site/shiki.config.mjs b/apps/site/shiki.config.ts similarity index 88% rename from apps/site/shiki.config.mjs rename to apps/site/shiki.config.ts index d28ee4d3a8ecf..dcb94ac6af948 100644 --- a/apps/site/shiki.config.mjs +++ b/apps/site/shiki.config.ts @@ -1,5 +1,4 @@ -'use strict'; - +import type { LanguageRegistration } from 'shiki'; import diffLanguage from 'shiki/langs/diff.mjs'; import dockerLanguage from 'shiki/langs/docker.mjs'; import javaScriptLanguage from 'shiki/langs/javascript.mjs'; @@ -12,15 +11,13 @@ import shikiNordTheme from 'shiki/themes/nord.mjs'; /** * All languages needed within the Node.js website for syntax highlighting. - * - * @type {Array} */ -export const LANGUAGES = [ +export const LANGUAGES: Array = [ { ...javaScriptLanguage[0], // We path the JavaScript language to include the CommonJS and ES Module aliases // that are commonly used (non-standard aliases) within our API docs and Blog posts - aliases: javaScriptLanguage[0].aliases.concat('cjs', 'mjs'), + aliases: javaScriptLanguage[0].aliases?.concat('cjs', 'mjs'), }, ...jsonLanguage, ...typeScriptLanguage, diff --git a/apps/site/util/getHighlighter.ts b/apps/site/util/getHighlighter.ts index 8b9b5b43e438f..f07894503eb03 100644 --- a/apps/site/util/getHighlighter.ts +++ b/apps/site/util/getHighlighter.ts @@ -2,7 +2,7 @@ import { getSingletonHighlighterCore } from '@shikijs/core'; import type { HighlighterCore } from '@shikijs/core'; import { createJavaScriptRegexEngine } from '@shikijs/engine-javascript'; -import { LANGUAGES, DEFAULT_THEME } from '@/shiki.config.mjs'; +import { LANGUAGES, DEFAULT_THEME } from '@/shiki.config'; // This creates a memoized minimal Shikiji Syntax Highlighter export const shikiPromise = getSingletonHighlighterCore({ diff --git a/apps/site/util/getLanguageDisplayName.ts b/apps/site/util/getLanguageDisplayName.ts index 5b1f167ab7092..4569f900ea723 100644 --- a/apps/site/util/getLanguageDisplayName.ts +++ b/apps/site/util/getLanguageDisplayName.ts @@ -1,4 +1,4 @@ -import { LANGUAGES } from '@/shiki.config.mjs'; +import { LANGUAGES } from '@/shiki.config'; export const getLanguageDisplayName = (language: string): string => { const languageByIdOrAlias = LANGUAGES.find(