Skip to content

Commit

Permalink
loader refactor, type-safe __nextra_resolvePageMap, avoid code inte…
Browse files Browse the repository at this point in the history
…rpolation in loader.ts (#1268)

* changeset

* fix typecheck

* f

* apply shu review changes
  • Loading branch information
Dimitri POSTOLOV committed Jan 18, 2023
1 parent da3d10a commit 163065c
Show file tree
Hide file tree
Showing 21 changed files with 241 additions and 136 deletions.
7 changes: 7 additions & 0 deletions .changeset/pink-guests-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'nextra': patch
'nextra-theme-blog': patch
'nextra-theme-docs': patch
---

loader refactor, type-safe `__nextra_resolvePageMap`, avoid code interpolation in loader.ts
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ packages/nextra-theme-*/style.css
.vercel
.idea/
.eslintcache
.env
2 changes: 1 addition & 1 deletion docs/pages/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: 'Nextra – Next.js Static Site Generator'
title: Nextra – Next.js Static Site Generator
---

import Link from 'next/link'
Expand Down
2 changes: 1 addition & 1 deletion examples/swr-site/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function getStaticProps() {

export default withNextra({
i18n: {
locales: ["en-US", "es-ES", "ja", "ko", "ru", "zh-CN"],
locales: ["en-US", "es-ES", "ru"],
defaultLocale: "en-US",
},
// basePath: "/some-base-path",
Expand Down
11 changes: 8 additions & 3 deletions examples/swr-site/pages/remote/[slug].mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ import { buildDynamicMDX, buildDynamicMeta } from 'nextra/remote'
import { RemoteContent } from 'nextra/data'

export const getStaticProps = async ({ params }) => {
const token = process.env.GITHUB_TEST_REMOTE_MDX
const res = await fetch(
'https://api.github.com/gists/c204a2da82cd3ed8e676f35c493ab59f/comments/' + params.slug,
{
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
...(process.env.GITHUB_TEST_REMOTE_MDX ? { "Authorization": "Bearer " + process.env.GITHUB_TEST_REMOTE_MDX } : {})
...(token && { Authorization: `Bearer ${token}` })
},
}
);
const comment = await res.json();
if (!comment.body) {
throw new Error(`Error while fetch data from GitHub.\n${JSON.stringify(comment, null, 4)}`)
}
return {
props: {
...(await buildDynamicMDX(comment.body)),
Expand All @@ -23,13 +27,14 @@ export const getStaticProps = async ({ params }) => {
}

export const getStaticPaths = async () => {
const token = process.env.GITHUB_TEST_REMOTE_MDX
const res = await fetch(
"https://api.github.com/gists/c204a2da82cd3ed8e676f35c493ab59f/comments",
{
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
...(process.env.GITHUB_TEST_REMOTE_MDX ? { "Authorization": "Bearer " + process.env.GITHUB_TEST_REMOTE_MDX } : {})
...(token && { Authorization: `Bearer ${token}` })
},
}
);
Expand All @@ -42,4 +47,4 @@ export const getStaticPaths = async () => {
}
}

<RemoteContent/>
<RemoteContent />
6 changes: 5 additions & 1 deletion examples/swr-site/pages/test.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ mdxOptions: { format: "md" }

# Hello!

This is a MD file instead of MDX, which means you can use syntax like this:
This is an MD file instead of MDX, which means you can use syntax like this:

```md
<!-- this is a comment -->
```

<!-- this is a comment -->

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build:tailwind build --filter=./packages/\\*",
"types:check": "turbo run types:check",
"lint": "eslint --cache --ignore-path .gitignore --max-warnings 0 .",
Expand Down
6 changes: 6 additions & 0 deletions packages/nextra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
"layout": [
"./dist/layout.d.ts"
],
"setup-page": [
"./dist/setup-page.d.ts"
],
"remote": [
"./dist/remote.d.ts"
],
Expand Down Expand Up @@ -92,6 +95,7 @@
"graceful-fs": "^4.2.10",
"gray-matter": "^4.0.3",
"katex": "^0.16.4",
"lodash.get": "^4.4.2",
"next-mdx-remote": "^4.2.0",
"p-limit": "^3.1.0",
"rehype-katex": "^6.0.2",
Expand All @@ -112,10 +116,12 @@
"devDependencies": {
"@types/github-slugger": "^1.3.0",
"@types/graceful-fs": "^4.1.5",
"@types/lodash.get": "^4.4.7",
"@types/mdast": "^3.0.10",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.18.0",
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
4 changes: 0 additions & 4 deletions packages/nextra/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,3 @@ declare module 'title' {
}
)
}

declare namespace globalThis {
var __nextra_temp_do_not_use: () => void
}
7 changes: 7 additions & 0 deletions packages/nextra/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PageMapItem } from './types'

declare global {
function __nextra_temp_do_not_use(): void

function __nextra_resolvePageMap(): Promise<PageMapItem[]>
}
118 changes: 11 additions & 107 deletions packages/nextra/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,6 @@ const initGitRepo = (async () => {
return {}
})()

/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {number} [seed] optionally pass the hash of the previous chunk
* @returns {string}
*/
function hashFnv32a(str: string, seed = 0x811c9dc5): string {
let hval = seed

for (let i = 0; i < str.length; i++) {
hval ^= str.charCodeAt(i)
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24)
}

// Convert to 8 digit hex string
return ('0000000' + (hval >>> 0).toString(16)).substring(-8)
}

async function loader(
context: LoaderContext<LoaderOptions>,
source: string
Expand Down Expand Up @@ -242,7 +221,7 @@ export default MDXContent`
const layout = isLocalTheme ? path.resolve(theme) : theme

const themeConfigImport = themeConfig
? `import __nextra_themeConfig__ from '${slash(path.resolve(themeConfig))}'`
? `import __nextra_themeConfig from '${slash(path.resolve(themeConfig))}'`
: ''

const pageOpts: PageOpts = {
Expand All @@ -269,102 +248,27 @@ export default MDXContent`
// Remove the only `index` route
.replace(/^index$/, '')

const convertToRelativePath = (filePath: string): string => {
const relative = path.relative(path.dirname(mdxPath), filePath)
const finalPath = relative.startsWith('.') ? relative : `./${relative}`
return slash(finalPath)
}

const dynamicMetaResolver = dynamicMetaItems.length
? `if (typeof window === 'undefined') {
globalThis.__nextra_resolvePageMap__ = async () => {
const clonedPageMap = JSON.parse(JSON.stringify(__nextra_pageOpts__.pageMap))
const executePromises = []
const importPromises = [
${dynamicMetaItems
.map(
({
metaFilePath,
metaObjectKeyPath,
metaParentKeyPath
}) => `import('${convertToRelativePath(metaFilePath)}').then(m => {
const getMeta = Promise.resolve(m.default()).then(metaData => {
const meta = clonedPageMap${metaObjectKeyPath}
meta.data = metaData
const parentRoute = clonedPageMap${metaParentKeyPath.replace(
/\.children$/,
''
)}.route || ''
for (const key of Object.keys(metaData)) {
clonedPageMap${metaParentKeyPath}.push({
kind: 'MdxPage',
locale: meta.locale,
name: key.split('/').pop(),
route: parentRoute + '/' + key
})
}
})
executePromises.push(getMeta)
})`
)
.join(',\n ')}
]
await Promise.all(importPromises)
await Promise.all(executePromises)
return clonedPageMap
}
}`
: ''

const stringifiedPageNextRoute = JSON.stringify(pageNextRoute)
const stringifiedPageOpts = JSON.stringify(pageOpts)
const pageOptsChecksum = IS_PRODUCTION ? '' : hashFnv32a(stringifiedPageOpts)

const finalResult = transform
? await transform(result, { route: pageNextRoute })
: result

return `import __nextra_layout__ from '${layout}'
return `import { setupNextraPage } from 'nextra/setup-page'
import __nextra_layout from '${layout}'
${themeConfigImport}
${katexCssImport}
${cssImport}
${dynamicMetaResolver}
const __nextra_pageOpts__ = ${stringifiedPageOpts}
// Make sure the same component is always returned so Next.js will render the
// stable layout. We then put the actual content into a global store and use
// the route to identify it.
const NEXTRA_INTERNAL = Symbol.for('__nextra_internal__')
const __nextra_internal__ = (globalThis[NEXTRA_INTERNAL] ||= Object.create(null))
__nextra_internal__.pageMap = __nextra_pageOpts__.pageMap
__nextra_internal__.route = __nextra_pageOpts__.route
__nextra_internal__.context ||= Object.create(null)
__nextra_internal__.refreshListeners ||= Object.create(null)
__nextra_internal__.Layout = __nextra_layout__
${finalResult}
__nextra_internal__.context[${stringifiedPageNextRoute}] = {
setupNextraPage({
pageNextRoute: ${JSON.stringify(pageNextRoute)},
pageOpts: ${JSON.stringify(pageOpts)},
nextraLayout: __nextra_layout,
themeConfig: ${themeConfigImport ? '__nextra_themeConfig' : 'null'},
Content: MDXContent,
pageOpts: __nextra_pageOpts__,
themeConfig: ${themeConfigImport ? '__nextra_themeConfig__' : 'null'}
}
if (${!IS_PRODUCTION} && module.hot) {
const checksum = "${pageOptsChecksum}"
module.hot.data ||= Object.create(null)
if (module.hot.data.prevPageOptsChecksum !== checksum) {
__nextra_internal__.refreshListeners[${stringifiedPageNextRoute}]?.forEach(listener => listener())
}
module.hot.dispose(data => {
data.prevPageOptsChecksum = checksum
})
}
hot: module.hot,
dynamicMetaItems: ${JSON.stringify(dynamicMetaItems)}
})
export { default } from 'nextra/layout'`
}
Expand Down
2 changes: 1 addition & 1 deletion packages/nextra/src/mdx-plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { parseMeta, attachMeta } from './rehype'
export { remarkHeadings } from './remark'
export { remarkHeadings } from './remark-headings'
export { remarkStaticImage } from './static-image'
export { structurize } from './structurize'
2 changes: 1 addition & 1 deletion packages/nextra/src/mdx-plugins/rehype.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Slugger from 'github-slugger'
import { getFlattenedValue } from './remark'
import { getFlattenedValue } from './remark-headings'
import { CODE_BLOCK_FILENAME_REGEX } from '../constants'

function visit(node, tagNames, handler) {
Expand Down
File renamed without changes.
8 changes: 1 addition & 7 deletions packages/nextra/src/page-map.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FileMap, MdxPath, PageMapItem } from './types'
import { FileMap, MdxPath, PageMapItem, DynamicMetaDescriptor } from './types'
import { parseFileName } from './utils'
import filterRouteLocale from './filter-route-locale'

Expand All @@ -9,12 +9,6 @@ type PageMapProps = {
defaultLocale: string
}

type DynamicMetaDescriptor = {
metaFilePath: string
metaObjectKeyPath: string
metaParentKeyPath: string
}

function getDynamicMeta(
path: string,
items: PageMapItem[]
Expand Down
2 changes: 1 addition & 1 deletion packages/nextra/src/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const buildDynamicMDX = async (content: string) => {
}

export const buildDynamicMeta = async () => {
const resolvePageMap = (globalThis as any).__nextra_resolvePageMap__
const resolvePageMap = (globalThis as any).__nextra_resolvePageMap
if (resolvePageMap) {
return {
__nextra_pageMap: await resolvePageMap()
Expand Down
Loading

1 comment on commit 163065c

@vercel
Copy link

@vercel vercel bot commented on 163065c Jan 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.