1
+ import { Fragment , useMemo } from 'react' ;
1
2
import { Page } from 'components/Layout/Page' ;
2
3
import { MDXComponents } from 'components/MDX/MDXComponents' ;
3
- import ErrorDecoder from 'components/ErrorDecoder' ;
4
4
import sidebarLearn from 'sidebarLearn.json' ;
5
5
import type { RouteItem } from 'components/Layout/getRouteMeta' ;
6
6
import { GetStaticPaths , GetStaticProps , InferGetStaticPropsType } from 'next' ;
7
-
8
- const { MaxWidth, p : P , Intro} = MDXComponents ;
7
+ import { ErrorDecoderContext } from '../../components/MDX/ErrorDecoderContext' ;
9
8
10
9
interface ErrorDecoderProps {
11
10
errorCode : string ;
12
11
errorMessages : string ;
12
+ content : string ;
13
13
}
14
14
15
15
export default function ErrorDecoderPage ( {
16
16
errorMessages,
17
+ content,
17
18
} : InferGetStaticPropsType < typeof getStaticProps > ) {
19
+ const parsedContent = useMemo < React . ReactNode > (
20
+ ( ) => JSON . parse ( content , reviveNodeOnClient ) ,
21
+ [ content ]
22
+ ) ;
23
+
18
24
return (
19
- < Page
20
- toc = { [ ] }
21
- meta = { { title : 'Error Decoder' } }
22
- routeTree = { sidebarLearn as RouteItem }
23
- section = "unknown" >
24
- < MaxWidth >
25
- < Intro >
25
+ < ErrorDecoderContext . Provider value = { errorMessages } >
26
+ < Page
27
+ toc = { [ ] }
28
+ meta = { { title : 'Error Decoder' } }
29
+ routeTree = { sidebarLearn as RouteItem }
30
+ section = "unknown" >
31
+ { parsedContent }
32
+ { /* <MaxWidth>
26
33
<P>
27
- In the minified production build of React, we avoid sending down
28
- full error messages in order to reduce the number of bytes sent over
29
- the wire.
34
+ We highly recommend using the development build locally when debugging
35
+ your app since it tracks additional debug info and provides helpful
36
+ warnings about potential problems in your apps, but if you encounter
37
+ an exception while using the production build, this page will
38
+ reassemble the original error message.
30
39
</P>
31
- </ Intro >
32
- < P >
33
- We highly recommend using the development build locally when debugging
34
- your app since it tracks additional debug info and provides helpful
35
- warnings about potential problems in your apps, but if you encounter
36
- an exception while using the production build, this page will
37
- reassemble the original error message.
38
- </ P >
39
- < ErrorDecoder errorMessages = { errorMessages } />
40
- </ MaxWidth >
41
- </ Page >
40
+ <ErrorDecoder />
41
+ </MaxWidth> */ }
42
+ </ Page >
43
+ </ ErrorDecoderContext . Provider >
42
44
) ;
43
45
}
44
46
47
+ // Deserialize a client React tree from JSON.
48
+ function reviveNodeOnClient ( key : unknown , val : any ) {
49
+ if ( Array . isArray ( val ) && val [ 0 ] == '$r' ) {
50
+ // Assume it's a React element.
51
+ let type = val [ 1 ] ;
52
+ let key = val [ 2 ] ;
53
+ let props = val [ 3 ] ;
54
+ if ( type === 'wrapper' ) {
55
+ type = Fragment ;
56
+ props = { children : props . children } ;
57
+ }
58
+ if ( type in MDXComponents ) {
59
+ type = MDXComponents [ type as keyof typeof MDXComponents ] ;
60
+ }
61
+ if ( ! type ) {
62
+ console . error ( 'Unknown type: ' + type ) ;
63
+ type = Fragment ;
64
+ }
65
+ return {
66
+ $$typeof : Symbol . for ( 'react.element' ) ,
67
+ type : type ,
68
+ key : key ,
69
+ ref : null ,
70
+ props : props ,
71
+ _owner : null ,
72
+ } ;
73
+ } else {
74
+ return val ;
75
+ }
76
+ }
77
+
45
78
/**
46
79
* Next.js Page Router doesn't have a way to cache specific data fetching request.
47
80
* But since Next.js uses limited number of workers, keep "cachedErrorCodes" as a
@@ -68,15 +101,179 @@ export const getStaticProps: GetStaticProps<ErrorDecoderProps> = async ({
68
101
} ;
69
102
}
70
103
71
- return {
104
+ /**
105
+ * Read Markdown files from disk and render into MDX, then into JSON
106
+ *
107
+ * This is copied from [[...markdownPath]].js
108
+ *
109
+ * TODO: build a shared function
110
+ */
111
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112
+ // ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~
113
+ const DISK_CACHE_BREAKER = 0 ;
114
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
115
+ const fs = require ( 'fs' ) ;
116
+ const {
117
+ prepareMDX,
118
+ PREPARE_MDX_CACHE_BREAKER ,
119
+ } = require ( '../../utils/prepareMDX' ) ;
120
+ const rootDir = process . cwd ( ) + '/src/content/errors' ;
121
+ const mdxComponentNames = Object . keys ( MDXComponents ) ;
122
+
123
+ // Read MDX from the file.
124
+ let path = params ?. error_code || 'default' ;
125
+ let mdx ;
126
+ try {
127
+ mdx = fs . readFileSync ( rootDir + path + '.md' , 'utf8' ) ;
128
+ } catch {
129
+ // if error_code.md is not found, fallback to default.md
130
+ mdx = fs . readFileSync ( rootDir + '/default.md' , 'utf8' ) ;
131
+ }
132
+
133
+ // See if we have a cached output first.
134
+ const { FileStore, stableHash} = require ( 'metro-cache' ) ;
135
+ const store = new FileStore ( {
136
+ root : process . cwd ( ) + '/node_modules/.cache/react-docs-mdx/' ,
137
+ } ) ;
138
+ const hash = Buffer . from (
139
+ stableHash ( {
140
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141
+ // ~~~~ IMPORTANT: Everything that the code below may rely on.
142
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
143
+ mdx,
144
+ mdxComponentNames,
145
+ DISK_CACHE_BREAKER ,
146
+ PREPARE_MDX_CACHE_BREAKER ,
147
+ lockfile : fs . readFileSync ( process . cwd ( ) + '/yarn.lock' , 'utf8' ) ,
148
+ } )
149
+ ) ;
150
+ const cached = await store . get ( hash ) ;
151
+ if ( cached ) {
152
+ console . log (
153
+ 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/'
154
+ ) ;
155
+ return cached ;
156
+ }
157
+ if ( process . env . NODE_ENV === 'production' ) {
158
+ console . log (
159
+ 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/'
160
+ ) ;
161
+ }
162
+
163
+ // If we don't add these fake imports, the MDX compiler
164
+ // will insert a bunch of opaque components we can't introspect.
165
+ // This will break the prepareMDX() call below.
166
+ let mdxWithFakeImports =
167
+ mdx +
168
+ '\n\n' +
169
+ mdxComponentNames
170
+ . map ( ( key ) => 'import ' + key + ' from "' + key + '";\n' )
171
+ . join ( '\n' ) ;
172
+
173
+ console . log ( { mdxComponentNames} ) ;
174
+
175
+ // Turn the MDX we just read into some JS we can execute.
176
+ const { remarkPlugins} = require ( '../../../plugins/markdownToHtml' ) ;
177
+ const { compile : compileMdx } = await import ( '@mdx-js/mdx' ) ;
178
+ const visit = ( await import ( 'unist-util-visit' ) ) . default ;
179
+ const jsxCode = await compileMdx ( mdxWithFakeImports , {
180
+ remarkPlugins : [
181
+ ...remarkPlugins ,
182
+ ( await import ( 'remark-gfm' ) ) . default ,
183
+ ( await import ( 'remark-frontmatter' ) ) . default ,
184
+ ] ,
185
+ rehypePlugins : [
186
+ // Support stuff like ```js App.js {1-5} active by passing it through.
187
+ function rehypeMetaAsAttributes ( ) {
188
+ return ( tree ) => {
189
+ visit ( tree , 'element' , ( node ) => {
190
+ if (
191
+ 'tagName' in node &&
192
+ typeof node . tagName === 'string' &&
193
+ node . tagName === 'code' &&
194
+ node . data &&
195
+ node . data . meta
196
+ ) {
197
+ // @ts -expect-error -- properties is a valid property
198
+ node . properties . meta = node . data . meta ;
199
+ }
200
+ } ) ;
201
+ } ;
202
+ } ,
203
+ ] ,
204
+ } ) ;
205
+ const { transform} = require ( '@babel/core' ) ;
206
+ const jsCode = await transform ( jsxCode , {
207
+ plugins : [ '@babel/plugin-transform-modules-commonjs' ] ,
208
+ presets : [ '@babel/preset-react' ] ,
209
+ } ) . code ;
210
+
211
+ // Prepare environment for MDX.
212
+ let fakeExports = { } ;
213
+ const fakeRequire = ( name : string ) => {
214
+ if ( name === 'react/jsx-runtime' ) {
215
+ return require ( 'react/jsx-runtime' ) ;
216
+ } else {
217
+ // For each fake MDX import, give back the string component name.
218
+ // It will get serialized later.
219
+ return name ;
220
+ }
221
+ } ;
222
+ const evalJSCode = new Function ( 'require' , 'exports' , jsCode ) ;
223
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224
+ // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!!
225
+ // In this case it's okay because anyone who can edit our MDX can also edit this file.
226
+ evalJSCode ( fakeRequire , fakeExports ) ;
227
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
228
+ // @ts -expect-error -- default exports is existed after eval
229
+ const reactTree = fakeExports . default ( { } ) ;
230
+
231
+ // Pre-process MDX output and serialize it.
232
+ let { toc, children} = prepareMDX ( reactTree . props . children ) ;
233
+ if ( path === 'index' ) {
234
+ toc = [ ] ;
235
+ }
236
+
237
+ // Parse Frontmatter headers from MDX.
238
+ const fm = require ( 'gray-matter' ) ;
239
+ const meta = fm ( mdx ) . data ;
240
+
241
+ const output = {
72
242
props : {
243
+ content : JSON . stringify ( children , stringifyNodeOnServer ) ,
73
244
errorCode : code ,
74
245
errorMessages : errorCodes [ code ] ,
246
+ toc : JSON . stringify ( toc , stringifyNodeOnServer ) ,
247
+ meta,
75
248
} ,
76
249
} ;
250
+
251
+ // Serialize a server React tree node to JSON.
252
+ function stringifyNodeOnServer ( key : unknown , val : any ) {
253
+ if ( val != null && val . $$typeof === Symbol . for ( 'react.element' ) ) {
254
+ // Remove fake MDX props.
255
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
256
+ const { mdxType, originalType, parentName, ...cleanProps } = val . props ;
257
+ return [
258
+ '$r' ,
259
+ typeof val . type === 'string' ? val . type : mdxType ,
260
+ val . key ,
261
+ cleanProps ,
262
+ ] ;
263
+ } else {
264
+ return val ;
265
+ }
266
+ }
267
+
268
+ // Cache it on the disk.
269
+ await store . set ( hash , output ) ;
270
+ return output ;
77
271
} ;
78
272
79
273
export const getStaticPaths : GetStaticPaths = async ( ) => {
274
+ /**
275
+ * Fetch error codes from GitHub
276
+ */
80
277
const errorCodes = ( cachedErrorCodes ||= await (
81
278
await fetch (
82
279
'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
0 commit comments