Markdown est en effet un grand format. Il est assez proche du texte brut que n'importe qui peut apprendre rapidement, et il est assez structuré pour être analysé et finalement converti au format que vous voulez.
Cependant: l'analyse, le traitement, l'amélioration et la conversion de la marque nécessitent du code. Le déploiement de tout ce code sur le client a un prix. Ce n'est pas énorme en soi, mais c'est encore quelques dizaines de KB de code qui n'est utilisé que pour gérer Markdown et aucun autre objectif.
Dans cet article, je vais expliquer comment garder Markdown hors du client dans une application Next.js, en utilisant l'écosystème unifié / remarque (je ne sais vraiment pas quel nom utiliser, ce qui est trop déroutant).
L'idée est d'utiliser uniquement Markdown dans la fonction getStaticProps
dans Next.js pour ce faire pendant le processus de construction (si vous utilisez la version incrémentielle de Vercel, cela se fait dans la prochaine fonction sans serveur), mais elle n'est en aucun cas utilisée du côté client. Je suppose que getServerSideProps
est également OK aussi, mais je pense que getStaticProps
est plus susceptible d'être un cas d'utilisation courant.
Cela renvoie un AST généré par l'analyse et le traitement du contenu de marque ( arborescence de syntaxe abstraite , c'est-à-dire un grand objet imbriqué qui décrit notre contenu), et le client n'est responsable que du rendu l'AST en un composant React.
Je suppose que nous pouvons même rendre Markdown en tant que HTML directement dans getStaticProps
et le rendre dangereux avec dangerouslySetInnerHtml
, mais nous ne sommes pas ce genre de personnes. La sécurité est importante. En outre, la flexibilité pour rendre Markdown comme nous le souhaitons avec nos propres composants au lieu de le rendre pur HTML. Sérieusement, amis, ne fais pas ça. ?
export const getStaticProps = async () => { // Obtenez du contenu de Markdown de quelque part, comme CMS ou quelque chose comme ça. En ce qui concerne cet article, ce n'est pas important. Il peut également être lu à partir du fichier. const Markdown = attendre getMarkdownContentFromsomewhere () const ast = parsemarkdown (Markdown) return {accessoires: {ast}} } const page = props => { // Cela inclut généralement votre mise en page et ainsi de suite, mais c'est omis ici pour simplifier. Retour<markdownrenderer ast="{props.ast}"></markdownrenderer> } Exporter la page par défaut
Nous utiliserons l'écosystème unifié / Remarque. Nous devons installer unifié et remarquable, c'est tout. Il est relativement simple de analyser la marque elle-même:
import {Unified} de 'unifié' Importer un remarquable à partir de «remarque-parse» const parsemarkdown = contenu => unifié (). Utilisation (Rocucherse) .Parse (Content) Exporter PARSEMARKDOWN par défaut
Maintenant, ce qui m'a pris beaucoup de temps à comprendre, c'est pourquoi mes plugins supplémentaires comme les remarques ou les remarques ne fonctionnent pas comme ça. En effet, la méthode .parse(..)
unifiée ne gère pas AST à l'aide du plugin. Comme son nom l'indique, il analyse uniquement le contenu de la chaîne de marque dans une arborescence.
Si nous voulons que Unified applique nos plugins, nous avons besoin d'Unified pour passer par ce qu'ils appellent la phase "en cours d'exécution". En règle générale, cela se fait en utilisant .process(..)
au lieu de .parse(..)
. Malheureusement, .process(..)
Analyse non seulement Markdown et applique des plugins, mais il met également l'AST dans un autre format (par exemple, en utilisant HTML via Remark-HTML, ou en utilisant JSX via Remark-React). Et ce n'est pas ce que nous voulons parce que nous voulons garder l'AST, mais après avoir été traité par le plugin.
<code>| ........................ process ........................... | | .......... parse ... | ... run ... | ... stringify ..........| -------- ----------输入->- | 解析器| ->- 语法树->- | 编译器| ->- 输出-------- | ---------- X | -------------- | 变换器| --------------</code>
Par conséquent, tout ce que nous devons faire est d'exécuter les phases d'analyse et d'exécution, mais pas la phase de chaîne. Unified ne fournit pas de méthode pour exécuter deux de ces trois étapes, mais il fournit une méthode distincte pour chaque étape afin que nous puissions le faire manuellement:
import {Unified} de 'unifié' Importer un remarquable à partir de «remarque-parse» Importer un remarquable à la «remarque de la remarque» const parsemarkdown = contenu => { Const Engine = Unified (). Utilisation (RemarkParse) .User (RemarkPrism) const ast = moteur.parse (contenu) // Le processus * d'Unified * contient trois étapes différentes: analyse, fonctionnement et chaîne. Nous ne voulons pas passer par la phase de chaîne parce que nous voulons garder AST afin que nous ne puissions pas appeler `.Process (..) '. Cependant, appeler «.parse (..)» ne suffit pas, car le plugin (et donc prisme) est exécuté pendant la phase d'exécution. Nous devons donc appeler la phase d'exécution manuellement (pour simplicité, de manière synchrone). // Voir: https://github.com/Unifiedjs/Unified#description RETOUR MOTEUR.RUNSYNC (AST) }
Regarder! Nous avons analysé Markdown dans un arbre de syntaxe. Nous exécutons ensuite notre plugin sur cet arbre (il est fait de manière synchrone pour la simplicité, mais vous pouvez le faire de manière asynchrone en utilisant .run(..)
). Cependant, nous n'avons pas converti notre arbre en d'autres syntaxes tels que HTML ou JSX. Nous pouvons le faire nous-mêmes dans le rendu.
Maintenant que nous avons notre arbre cool prêt, nous pouvons le rendre comme nous l'avons l'intention. Créons un composant MarkdownRenderer
qui prend l'arbre comme ast
et le rend avec le composant React.
const getComponent = node => { switch (node.type) { case «racine»: return ({enfants}) => {enfants} > cas «paragraphe»: return ({enfants}) =><p> {enfants}</p> Cas «accent»: return ({enfants}) => <em>{enfants}</em> Cas «En-tête»: return ({enfants, profondeur = 2}) => { constant const = `h $ {de profondeur}` Retour<heading> {enfants}</heading> } case «texte»: return ({value}) => {valeur} > / * Gérer tous les types ici ... * / défaut: console.log («type de nœud non traité», nœud) return ({enfants}) => {enfants} > } } const node = ({node}) => { const composant = getComponent (nœud) const {enfants} = nœud retour des enfants? <component> {enfants.map ((enfant, index) => ( <node key="{index}" node="{child}"></node> ))} </component> ): ( <component></component> ) } const markdownRenderer = ({ast}) =><node node="{ast}"></node> Exporter par défaut React.Memo (MarkdownRenderer)
La majeure partie de la logique de notre rendu est située dans Node
. Il découvre quoi rendre en fonction de type
du nœud AST (il s'agit de notre méthode getComponent
avec chaque type de nœud), puis le rend. Si le nœud a des enfants, il entre récursivement dans le nœud enfant;
Selon le plugin de remarques que nous utilisons, nous pouvons rencontrer les problèmes suivants lorsque nous essayons de rendre la page:
Erreur: une erreur s'est produite lors de la sérialisation .Content [0] .Content.children [3] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .data.hchildren [0] .Data.hname (de GetstaticProps in '/'). Cause: Undefined ne peut pas être sérialisé en JSON. Veuillez utiliser NULL ou omettre cette valeur.
Cela se produit parce que notre AST contient des clés avec des valeurs non définies, ce qui n'est pas quelque chose qui peut être sérialisé en toute sécurité en JSON. Next nous donne une solution: nous pouvons omettre complètement la valeur, ou le remplacer par null si nous en avons besoin plus ou moins.
Cependant, nous ne fixerons pas chaque chemin manuellement, nous devons donc traverser récursivement cette AST et le nettoyer. J'ai trouvé que cela se produit lors de l'utilisation de Remark-Prism (un plugin qui permet la modération de la syntaxe du bloc de code). Le plugin ajoute un objet [data]
au nœud.
Ce que nous pouvons faire, c'est la diffuser pour nettoyer ces nœuds avant de retourner AST:
const CleanNode = node => { if (node.value === Undefined) supprimer node.value if (node.tagname === Undefined) supprimer node.tagname if (node.data) { Supprimer Node.Data.hname Supprimer Node.data.hchildren Supprimer Node.data.hproperties } if (node.children) node.children.foreach (cleannode) Node de retour } const parsemarkdown = contenu => { Const Engine = Unified (). Utilisation (RemarkParse) .User (RemarkPrism) const ast = moteur.parse (contenu) const procedast = moteur.runsync (AST) CleanNode (processEdast) Retour ProcessEtast }
La dernière chose que nous pouvons faire est de supprimer l'objet position
qui existe sur chaque nœud, qui contient la position d'origine dans la chaîne Markdown. Ce n'est pas un grand objet (il n'a que deux clés), mais il s'accumule rapidement lorsque l'arbre grossit.
const CleanNode = node => { supprimer le nœud.position // ... autre logique de nettoyage}
C'est ça! Nous avons réussi à limiter le traitement de Markdown pour construire / du code côté serveur, nous n'envoyons donc pas des temps de démarrage inutiles au navigateur, ce qui augmente inutilement les coûts. Nous transmettons l'arborescence de données au client, et nous pouvons les itérer et les convertir en n'importe quel composant React que nous voulons.
J'espère que cela aide. :)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!