マークダウンは確かに素晴らしいフォーマットです。誰もが迅速に学ぶことができるプレーンテキストに十分近く、それは解析するのに十分な十分に構造化されており、最終的にあなたが望む形式に変換されます。
ただし、マークダウンの解析、処理、強化、および変換にはコードが必要です。クライアントにこのすべてのコードを展開すると、価格があります。それ自体はそれほど大きくはありませんが、それでもマークダウンを処理するためだけに使用されているのは数十KBのコードであり、他の目的はありません。
この記事では、Unified/Learm Ecosystemを使用して、Next.jsアプリケーションでクライアントからマークダウンを締め出す方法について説明します(使用する名前はわかりませんが、これは混乱しすぎます)。
アイデアは、ビルドプロセス中にこれを行うには、next.jsのgetStaticProps
関数のMarkDownのみを使用することです(Vercelのインクリメンタルビルドを使用する場合、次のサーバーレス機能で行われます)が、クライアント側では決して使用されません。 getServerSideProps
も大丈夫だと思いますが、 getStaticProps
一般的なユースケースになる可能性が高いと思います。
これにより、マークダウンコンテンツの解析と処理によって生成されたAST(抽象的構文ツリー、つまりコンテンツを記述する大きなネストされたオブジェクト)によって生成され、クライアントはASTをReactコンポーネントにレンダリングする責任があります。
getStaticProps
でHTMLとしてMarkDownを直接レンダリングして、 dangerouslySetInnerHtml
setinnerhtmlでレンダリングするように戻すことさえできますが、私たちはそのような人ではありません。安全性は重要です。また、マークダウンを純粋なHTMLとしてレンダリングするのではなく、自分のコンポーネントを使用して必要な方法でレンダリングする柔軟性があります。真剣に、友達、そうしないでください。 ?
const getstaticProps = async()=> { // CMSなど、どこかからマークダウンコンテンツを取得します。この記事に関する限り、これは重要ではありません。ファイルから読み取ることもできます。 const markdown = await getmarkdowncontentfromsomewhere() const ast = parsemarkdown(markdown) return {props:{ast}} } const page = props => { //これには通常、レイアウトなどが含まれますが、簡単にするためにここでは省略されています。 戻る<markdownrenderer ast="{props.ast}"></markdownrenderer> } デフォルトページをエクスポートします
Unified/Learm Ecosystemを使用します。私たちは統一された発言権をインストールする必要があります、それだけです。マークダウン自体を解析することは比較的簡単です:
「unified」から{unified}をインポートします 「aremerparse」から発言をインポートする const parsemarkdown = content => unified()。use(aremparse).parse(content) デフォルトのパーセマークダウンをエクスポートします
さて、私が理解するのに長い時間がかかったのは、なぜ驚異的なプラグインやシーケルスラグのような私の余分なプラグインがこのように機能しないのかということです。これは、Unified .parse(..)
メソッドがプラグインを使用してASTを処理しないためです。名前が示すように、それはマークダウン文字列のコンテンツをツリーに解析するだけです。
Unifiedをプラグインに適用したい場合は、「実行中」フェーズと呼ばれるものを実行するためにUnifiedが必要です。通常、これは.process(..)
メソッドの代わりに.parse(..)
メソッドを使用して行われます。残念ながら、 .process(..)
はマークダウンを解析してプラグインを適用するだけでなく、ASTを別の形式に描きます(たとえば、derme-htmlを介してHTMLを使用するか、remark-reactを介してJSXを使用します)。そして、ASTを維持したいので、それは私たちが望むものではなく、プラグインによって処理された後です。
<code>| ........................ process ........................... | | .......... parse ... | ... run ... | ... stringify ..........| -------- ----------输入->- | 解析器| ->- 语法树->- | 编译器| ->- 输出-------- | ---------- X | -------------- | 变换器| --------------</code>
したがって、私たちがする必要があるのは、解析フェーズと実行フェーズを実行するだけではありません。 Unifiedは、これら3つの段階のうち2つを実行する方法を提供しませんが、各段階に個別の方法を提供するため、手動で実行できます。
「unified」から{unified}をインポートします 「aremerparse」から発言をインポートする 「発言権」からの発言プリズムをインポートする const parsemarkdown = content => { const engine = unified()。use(aremparse).use(aremprism) const ast = engine.parse(content) // Unified's * Process *には、解析、実行、および弦の3つの異なる段階が含まれています。 「.process(..)」を呼び出すことができないように、astを維持したいので、描画段階を通過したくありません。ただし、プラグイン(したがってプリズム)が実行フェーズ中に実行されるため、 `.Parse(..)`を呼び出すだけでは不十分です。したがって、実行フェーズを手動で呼び出す必要があります(簡単に、同期して)。 //参照:https://github.com/unifiedjs/unified#description return engine.runsync(ast) }
見て!マークダウンを構文ツリーに解析しました。次に、そのツリーでプラグインを実行します(簡単にするために同期して行われますが、 .run(..)
を使用して非同期に行うことができます)。ただし、ツリーをHTMLやJSXなどの他の構文に変換しませんでした。私たちはレンダリングでそれを自分で行うことができます。
クールな木が準備が整ったので、意図したとおりにレンダリングすることができます。ツリーをast
プロパティとして使用し、ReactコンポーネントでレンダリングするMarkdownRenderer
コンポーネントを作成しましょう。
const getComponent = node => { switch(node.type){ ケース「ルート」: return({children})=> {children} > ケース「段落」: return({children})=><p> {子供たち}</p> ケース「強調」: return({children})=> <em>{children}</em> ケース「見出し」: return({children、dept = 2})=> { const heading = `h $ {depth}` 戻る<heading>{子供たち}</heading> } ケース「テキスト」: return({value})=> {value} > / *ここですべてのタイプを処理します... */ デフォルト: console.log(「未処理ノードタイプ」、ノード) return({children})=> {children} > } } const node =({node})=> { const Component = getComponent(ノード) const {children} = node 子供を返しますか? <component> {children.map((child、index)=>( <node key="{index}" node="{child}"></node> ))} </component> ):( <component></component> )) } const markdownRenderer =({ast})=><node node="{ast}"></node> エクスポートデフォルトReact.memo(MarkDownRenderer)
レンダラーのロジックのほとんどは、 Node
コンポーネントにあります。 ASTノードのtype
キー(これは各タイプのノードを扱うgetComponent
メソッドです)に基づいてレンダリングするものを見つけて、レンダリングします。ノードに子供がいる場合、それ以外の場合は再帰的に入力します。
使用している備考プラグインに応じて、ページをレンダリングしようとするときに次の問題に遭遇する可能性があります。
エラー:シリアル化.content [0] .content.children [3] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .data.hname(from getstaticprops in '/')。原因:未定義をJSONに連続化することはできません。 nullを使用するか、この値を省略してください。
これは、ASTに未定義の値を持つキーが含まれているために起こります。これは、JSONに安全にシリアル化できるものではありません。次に、解決策を提供します。値を完全に省略するか、多かれ少なかれ必要な場合はnullに置き換えることができます。
ただし、各パスを手動で修正することはないため、そのASTを再帰的に横断してクリーンアップする必要があります。これは、発言権(コードブロック構文の強調表示を可能にするプラグイン)を使用するときに起こることがわかりました。プラグインは[data]
オブジェクトをノードに追加します。
私たちにできることは、ASTを返す前にこれらのノードをクリーンアップするために繰り返します。
const cleannode = node => { if(node.value === undefined)delete node.value if(node.tagname === undefined)delete node.tagname if(node.data){ node.data.hnameを削除します node.data.hchildrenを削除します node.data.hpropertiesを削除します } if(node.children)node.children.foreach(cleannode) ノードを返します } const parsemarkdown = content => { const engine = unified()。use(aremparse).use(aremprism) const ast = engine.parse(content) const processedast = engine.runsync(ast) cleannode(processedast) Processedastを返します }
最後にできることは、各ノードに存在するposition
オブジェクトを削除することです。これは、マークダウン文字列の元の位置を保持します。大きなオブジェクトではありません(キーは2つしかありません)が、ツリーが大きくなるとすぐに蓄積します。
const cleannode = node => { node.positionを削除します // ...その他のクリーニングロジック}
それでおしまい!マークダウン処理をなんとか制限/サーバーサイドコードに制限することができたため、ブラウザに不必要なマークダウンランタイムを送信することはありませんでした。データツリーをクライアントに渡し、それを繰り返して、必要な任意のReactコンポーネントに変換できます。
これが役立つことを願っています。 :)
以上がnext.jsの責任あるマークダウンの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。