Markdown sememangnya format yang hebat. Ia cukup dekat dengan teks biasa bahawa sesiapa sahaja boleh belajar dengan cepat, dan ia cukup berstruktur untuk dihuraikan dan akhirnya ditukar kepada format apa sahaja yang anda inginkan.
Walau bagaimanapun: parsing, pemprosesan, peningkatan, dan penukaran markdown memerlukan kod. Menggunakan semua kod ini pada pelanggan datang pada harga. Ia tidak besar dalam dirinya sendiri, tetapi ia masih beberapa dozen kb kod yang hanya digunakan untuk mengendalikan markdown dan tidak ada tujuan lain.
Dalam artikel ini, saya akan menerangkan bagaimana untuk mengekalkan markdown daripada pelanggan dalam aplikasi Next.js, menggunakan ekosistem bersatu/ucapan (saya tidak tahu nama mana yang hendak digunakan, yang terlalu mengelirukan).
Idea ini hanya menggunakan markdown dalam fungsi getStaticProps
di Next.js untuk melakukan ini semasa proses membina (jika anda menggunakan binaan tambahan Vercel, ia dilakukan dalam fungsi tanpa pelayan seterusnya), tetapi ia tidak digunakan di sisi klien. Saya rasa getServerSideProps
juga ok, tetapi saya fikir getStaticProps
lebih cenderung menjadi kes penggunaan biasa.
Ini mengembalikan AST yang dihasilkan oleh parsing dan pemprosesan kandungan markdown ( Abstrak Sintaks Tree , iaitu, objek bersarang besar yang menggambarkan kandungan kami), dan pelanggan hanya bertanggungjawab untuk menjadikan AST menjadi komponen React.
Saya rasa kita juga boleh menjadikan Markdown sebagai HTML secara langsung di getStaticProps
dan mengembalikannya untuk memberi dangerouslySetInnerHtml
, tetapi kita bukan orang seperti itu. Keselamatan adalah penting. Juga, fleksibiliti untuk menjadikan Markdown seperti yang kita mahu dengan komponen kita sendiri dan bukannya menjadikannya sebagai HTML tulen. Serius, kawan, jangan buat begitu. ?
Export const getStaticProps = async () => { // Dapatkan kandungan markdown dari suatu tempat, seperti CMS atau sesuatu. Setakat artikel ini, ini tidak penting. Ia juga boleh dibaca dari fail. const markdown = menunggu getmarkdownContentFromSomeWhere () const ast = parsemarkdown (markdown) kembali {props: {ast}} } const page = props => { // Ini biasanya termasuk susun atur anda dan sebagainya, tetapi ia ditinggalkan di sini untuk kesederhanaan. Kembali<markdownrenderer ast="{props.ast}"></markdownrenderer> } halaman lalai eksport
Kami akan menggunakan ekosistem bersatu/ucapan. Kita perlu memasang bersatu dan bersuara-passe, itu sahaja. Ia agak mudah untuk menghuraikan markdown sendiri:
import {bersatu} dari 'bersatu' Import Posting dari 'Catatan-Passe' const parseMarkdown = content => bersatu (). Gunakan (fressparse) .parse (kandungan) Eksport ParsemarkDown lalai
Sekarang, apa yang saya buat masa yang lama untuk memahami adalah mengapa plugin tambahan saya seperti Catatan-Prisma atau Slug tidak berfungsi seperti ini. Ini kerana kaedah bersatu .parse(..)
tidak mengendalikan AST menggunakan plugin. Seperti namanya, ia hanya memasangkan kandungan rentetan markdown ke dalam pokok.
Sekiranya kami mahu bersatu untuk memohon plugin kami, kami perlu bersatu untuk melalui apa yang mereka panggil fasa "berjalan". Biasanya, ini dilakukan dengan menggunakan kaedah .process(..)
dan bukannya kaedah .parse(..)
. Malangnya, .process(..)
bukan sahaja parses markdown dan memohon plugin, ia juga menyentuh AST ke dalam format lain (contohnya, menggunakan HTML melalui Catatan-HTML, atau menggunakan JSX melalui RE-REACT). Dan itu bukan apa yang kita mahu kerana kita mahu menyimpan AST, tetapi selepas ia diproses oleh plugin.
<code>| ........................ process ........................... | | .......... parse ... | ... run ... | ... stringify ..........| -------- ----------输入->- | 解析器| ->- 语法树->- | 编译器| ->- 输出-------- | ---------- X | -------------- | 变换器| --------------</code>
Oleh itu, semua yang perlu kita lakukan ialah menjalankan parsing dan menjalankan fasa, tetapi bukan fasa penyerapan. Bersatu tidak menyediakan kaedah untuk melaksanakan dua daripada tiga peringkat ini, tetapi ia menyediakan kaedah yang berasingan untuk setiap peringkat supaya kita dapat melakukannya secara manual:
import {bersatu} dari 'bersatu' Import Posting dari 'Catatan-Passe' Import Ramalan dari 'Catatan-Prisma' const parsemarkdown = content => { enjin const = bersatu (). Penggunaan (fressParse). Gunakan (freakprism) const ast = enjin.parse (kandungan) // Proses * Unified * mengandungi tiga peringkat yang berbeza: parsing, lari, dan stringification. Kami tidak mahu melalui fasa penyerapan kerana kami mahu menyimpan AST supaya kami tidak dapat memanggil `.process (..)`. Walau bagaimanapun, memanggil `.parse (..)` tidak mencukupi, kerana plugin (dan oleh itu prisma) dilaksanakan semasa fasa larian. Oleh itu, kita perlu memanggil fasa lari secara manual (untuk kesederhanaan, serentak). // Lihat: https://github.com/unifiedjs/unified#description Kembali Engine.Runsync (AST) }
Lihat! Kami menghuraikan markdown ke dalam pokok sintaks. Kami kemudian menjalankan plugin kami di atas pokok itu (ia dilakukan serentak untuk kesederhanaan, tetapi anda boleh melakukannya secara tidak segerak menggunakan .run(..)
). Walau bagaimanapun, kami tidak menukar pokok kami ke sintaks lain seperti HTML atau JSX. Kita boleh melakukannya sendiri dalam rendering.
Sekarang kita mempunyai pokok sejuk kita siap, kita boleh menjadikannya seperti yang kita inginkan. Mari buat komponen MarkdownRenderer
yang mengambil pokok itu sebagai harta ast
dan menjadikannya dengan komponen React.
const getComponent = node => { suis (node.type) { kes 'akar': kembali ({Children}) => {Children} > kes 'perenggan': kembali ({anak}) =><p> {anak}</p> kes 'penekanan': kembali ({anak}) => <em>{anak}</em> kes 'tajuk': kembali ({kanak -kanak, kedalaman = 2}) => { const heading = `h $ {kedalaman}` Kembali<heading> {anak}</heading> } kes 'teks': kembali ({value}) => {value} > / * Mengendalikan semua jenis di sini ... */ Lalai: Console.log ('jenis nod yang tidak diproses', nod) kembali ({Children}) => {Children} > } } const node = ({node}) => { Const Component = getComponent (node) const {children} = node Kembali kanak -kanak? <component> {children.map ((anak, indeks) => ( <node key="{index}" node="{child}"></node> ))} </component> ): ( <component></component> ) } const markdownRenderer = ({ast}) =><node node="{ast}"></node> Eksport React.Memo (MarkDownRenderer)
Kebanyakan logik penerima kami terletak di komponen Node
. Ia mengetahui apa yang akan diberikan berdasarkan kunci type
nod AST (ini adalah kaedah getComponent
kami berkaitan dengan setiap jenis nod) dan kemudian menjadikannya. Jika nod mempunyai anak -anak, ia secara rekursif memasuki nod kanak -kanak;
Bergantung pada plugin Catatan yang kami gunakan, kami mungkin menghadapi isu -isu berikut ketika cuba memberikan halaman:
Ralat: Kesalahan berlaku semasa bersiri. Content [0] .Content.Children [3] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .data.hname (dari getstaticprops dalam '/'). Punca: Undefined tidak boleh diserahkan kepada JSON. Sila gunakan NULL atau isi nilai ini.
Ini berlaku kerana AST kami mengandungi kunci dengan nilai -nilai yang tidak ditentukan, yang bukan sesuatu yang dapat diserahkan dengan selamat kepada JSON. Seterusnya memberi kita penyelesaian: kita boleh menghilangkan nilai sama sekali, atau menggantikannya dengan null jika kita memerlukannya lebih kurang.
Walau bagaimanapun, kami tidak akan membetulkan setiap laluan secara manual, jadi kami perlu melintasi rekursif dan membersihkannya. Saya dapati ini berlaku apabila menggunakan Catatan-Prisma (plugin yang membolehkan penonjolan sintaks blok kod). Plugin tidak menambah objek [data]
ke nod.
Apa yang boleh kita lakukan adalah melelehkannya untuk membersihkan nod ini sebelum kembali AST:
const cleannode = node => { jika (node.value === Undefined) Padam Node.Value jika (node.tagname === undefined) padam nod.tagname jika (node.data) { Padam node.data.hname Padam node.data.hchildren Padam node.data.hproperties } jika (node.children) node.children.Foreach (CleanNode) Node kembali } const parsemarkdown = content => { enjin const = bersatu (). Penggunaan (fressParse). Gunakan (freakprism) const ast = enjin.parse (kandungan) Const Processedast = Engine.RunSync (AST) CleanNode (diproses) pulangan diproses }
Perkara terakhir yang boleh kita lakukan ialah memadam objek position
yang wujud pada setiap nod, yang memegang kedudukan asal dalam rentetan markdown. Ia bukan objek besar (ia hanya mempunyai dua kunci), tetapi ia berkumpul dengan cepat apabila pokok itu semakin besar.
const cleannode = node => { padam node.position // ... logik pembersihan lain}
Itu sahaja! Kami berjaya mengehadkan pemprosesan markdown untuk membina/kod sampingan pelayan, jadi kami tidak menghantar runtime markdown yang tidak perlu kepada penyemak imbas, yang tidak perlu meningkatkan kos. Kami lulus pokok data kepada pelanggan, dan kami boleh melangkah ke atasnya dan mengubahnya menjadi komponen reaksi yang kami mahukan.
Harap ini membantu. ""
Atas ialah kandungan terperinci Markdown bertanggungjawab di Next.js. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!