Léon  Peltier

Léon Peltier

1656569820

Comment Créer Un Popover Dans Svelte

J'ai implémenté les composants de l'interface utilisateur de mon dernier projet Papyrs sans aucune bibliothèque de système de conception tiers - c'est-à-dire que j'ai créé tous les composants à partir de zéro. Je l'ai fait pour obtenir un contrôle total et une flexibilité sur les diverses briques de ma mise en page opiniâtre.

Dans cet article de blog, je partage comment vous pouvez développer un composant popover dans Svelte .

 

Squelette

Un popover est un conteneur flottant qui est rendu sur le contenu à côté d'une ancre - généralement un bouton - qui lance son affichage. Pour améliorer la mise au point visuelle de la superposition, une toile de fond est généralement utilisée pour obscurcir partiellement la vue derrière elle.

Nous pouvons commencer l'implémentation en répliquant le squelette ci-dessus dans un composant nomméPopover.svelte​​ qui contient abutton​ et div​.

<button>Open</button>

<div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
>
    <div>Backdrop</div>
    <div>Content</div>
</div>

Pour améliorer l'accessibilité, nous pouvons définir le dialogrôle et fournir des ariainformations (voir les documents MDN pour plus de détails).

 

Animation

Nous créons un booleanétat —visible​​ — pour afficher ou fermer le popover. Lorsque lebutton​ est cliqué, l'état est défini surtrue​ et la superposition est rendue. Au contraire, lorsque l'arrière-plan est cliqué, il se transforme falseet se ferme.

De plus, nous ajoutons un écouteur de clic sur le popover qui ne fait rien d'autre que d'arrêter la propagation de l'événement. Ceci est utile pour éviter de fermer la superposition lorsque l'utilisateur interagit avec son contenu.

Nous pouvons également faire apparaître et disparaître la superposition avec élégance grâce à la directive de transition - également connue sous le nom de "magie noire de Svelte" 😁.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
    >
      Backdrop
    </div>
    <div>Content</div>
  </div>
{/if}

 

​Position sur le contenu

Le popover doit être rendu sur tout le contenu, que la page ait été défilée ou non. Par conséquent, nous pouvons utiliser une fixedposition comme point de départ. Son contenu et sa toile de fond sont tous deux définis par un absolutepositionnement. La toile de fond doit également couvrir l'écran mais est un enfant de la superposition - donc "l'absolu" - et le contenu doit être positionné à côté de l'ancre.

Le reste du code CSS que nous ajoutons à notre solution sont des paramètres de style minimaux pour la largeur, la hauteur ou les couleurs.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

Position à côté de l'ancre

Pour définir la superposition à côté du bouton, nous devons obtenir une référence sur cet élément pour trouver sa position dans la fenêtre. À cette fin, nous pouvons bindl'ancre.

Lorsque la référence est prête ou lorsque la fenêtre est redimensionnée (la position peut changer si l'utilisateur redimensionne le navigateur), nous utilisons la méthode getBoundingClientRect() pour interroger les informations sur la position.

Nous traduisons finalement ces informations JavaScript en variables CSS pour rendre le contenu du popover à la position exacte que nous souhaitons définir.

<script lang="ts">
  // ...
  
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <!-- ... -->
  </div>
{/if}

<style>
  /** ... */

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    /** ... */
  }
</style>

L'extrait de code ci-dessus a été coupé pour ne montrer que ce qui est lié à ce chapitre. Au total, le code du composant est le suivant :

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

Et c'est tout! Nous avons implémenté un popover personnalisé minimum qui peut être utilisé dans toutes les applications Svelte sans aucune dépendance.

Conclusion

Nous n'avons pas embelli la solution, donc le résultat reste visuellement rugueux sur les bords mais, le popover fonctionne comme prévu.

Pour itérer à partir de là, implémenter plus d'options ou le rendre brillant, vous pouvez par exemple consulter le code open source de Papyrs sur GitHub 🤗.

​Vers l'infini et au-delà
David

 Source : https://betterprogramming.pub/create-a-popover-in-svelte-fe7dd2eeebb1

#svelte 

What is GEEK

Buddha Community

Comment Créer Un Popover Dans Svelte
Léon  Peltier

Léon Peltier

1656569820

Comment Créer Un Popover Dans Svelte

J'ai implémenté les composants de l'interface utilisateur de mon dernier projet Papyrs sans aucune bibliothèque de système de conception tiers - c'est-à-dire que j'ai créé tous les composants à partir de zéro. Je l'ai fait pour obtenir un contrôle total et une flexibilité sur les diverses briques de ma mise en page opiniâtre.

Dans cet article de blog, je partage comment vous pouvez développer un composant popover dans Svelte .

 

Squelette

Un popover est un conteneur flottant qui est rendu sur le contenu à côté d'une ancre - généralement un bouton - qui lance son affichage. Pour améliorer la mise au point visuelle de la superposition, une toile de fond est généralement utilisée pour obscurcir partiellement la vue derrière elle.

Nous pouvons commencer l'implémentation en répliquant le squelette ci-dessus dans un composant nomméPopover.svelte​​ qui contient abutton​ et div​.

<button>Open</button>

<div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
>
    <div>Backdrop</div>
    <div>Content</div>
</div>

Pour améliorer l'accessibilité, nous pouvons définir le dialogrôle et fournir des ariainformations (voir les documents MDN pour plus de détails).

 

Animation

Nous créons un booleanétat —visible​​ — pour afficher ou fermer le popover. Lorsque lebutton​ est cliqué, l'état est défini surtrue​ et la superposition est rendue. Au contraire, lorsque l'arrière-plan est cliqué, il se transforme falseet se ferme.

De plus, nous ajoutons un écouteur de clic sur le popover qui ne fait rien d'autre que d'arrêter la propagation de l'événement. Ceci est utile pour éviter de fermer la superposition lorsque l'utilisateur interagit avec son contenu.

Nous pouvons également faire apparaître et disparaître la superposition avec élégance grâce à la directive de transition - également connue sous le nom de "magie noire de Svelte" 😁.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
    >
      Backdrop
    </div>
    <div>Content</div>
  </div>
{/if}

 

​Position sur le contenu

Le popover doit être rendu sur tout le contenu, que la page ait été défilée ou non. Par conséquent, nous pouvons utiliser une fixedposition comme point de départ. Son contenu et sa toile de fond sont tous deux définis par un absolutepositionnement. La toile de fond doit également couvrir l'écran mais est un enfant de la superposition - donc "l'absolu" - et le contenu doit être positionné à côté de l'ancre.

Le reste du code CSS que nous ajoutons à notre solution sont des paramètres de style minimaux pour la largeur, la hauteur ou les couleurs.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

Position à côté de l'ancre

Pour définir la superposition à côté du bouton, nous devons obtenir une référence sur cet élément pour trouver sa position dans la fenêtre. À cette fin, nous pouvons bindl'ancre.

Lorsque la référence est prête ou lorsque la fenêtre est redimensionnée (la position peut changer si l'utilisateur redimensionne le navigateur), nous utilisons la méthode getBoundingClientRect() pour interroger les informations sur la position.

Nous traduisons finalement ces informations JavaScript en variables CSS pour rendre le contenu du popover à la position exacte que nous souhaitons définir.

<script lang="ts">
  // ...
  
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <!-- ... -->
  </div>
{/if}

<style>
  /** ... */

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    /** ... */
  }
</style>

L'extrait de code ci-dessus a été coupé pour ne montrer que ce qui est lié à ce chapitre. Au total, le code du composant est le suivant :

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

Et c'est tout! Nous avons implémenté un popover personnalisé minimum qui peut être utilisé dans toutes les applications Svelte sans aucune dépendance.

Conclusion

Nous n'avons pas embelli la solution, donc le résultat reste visuellement rugueux sur les bords mais, le popover fonctionne comme prévu.

Pour itérer à partir de là, implémenter plus d'options ou le rendre brillant, vous pouvez par exemple consulter le code open source de Papyrs sur GitHub 🤗.

​Vers l'infini et au-delà
David

 Source : https://betterprogramming.pub/create-a-popover-in-svelte-fe7dd2eeebb1

#svelte 

Poppy Cooke

Poppy Cooke

1600073509

Svelte Tutorial - Create a Svelte Project

In this Svelte tutorial we’ll be taking a look at creating a Svelte project using the Svelte template. We’ll go over the file structure, install a VS Code extension and look at starting the built-in development server.

For your reference, check this out:
https://svelte.dev/tutorial/basics

#svelte #web-development #svelte

Saul  Alaniz

Saul Alaniz

1656565800

Cómo Crear Un Popover En Svelte

Implementé los componentes de la interfaz de usuario de mi último proyecto Papyrs sin ninguna biblioteca de sistema de diseño de terceros, es decir, creé todos los componentes desde cero. Lo hice para obtener control total y flexibilidad sobre los ladrillos misceláneos de mi diseño obstinado.

En esta publicación de blog, comparto cómo puede desarrollar un componente emergente en Svelte .

 

Esqueleto

Un popover es un contenedor flotante que se representa sobre el contenido junto a un ancla, comúnmente un botón, que inicia su visualización. Para mejorar el enfoque visual de la superposición, generalmente se usa un fondo para ofuscar parcialmente la vista detrás de él.

Podemos comenzar la implementación replicando el esqueleto anterior en un componente llamado Popover.svelteque contiene a buttony div.

<button>Open</button>

<div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
>
    <div>Backdrop</div>
    <div>Content</div>
</div>

Para mejorar la accesibilidad, podemos establecer el dialogrol y proporcionar cierta ariainformación (consulte los documentos de MDN para obtener más detalles).

 

Animación

Creamos un booleanestado — visible— para mostrar o cerrar el popover. Cuando buttonse hace clic en, el estado se establece en truey la superposición se procesa. Por el contrario, cuando se hace clic en el fondo, se gira falsey se cierra.

​Además, agregamos un detector de clics en la ventana emergente que no hace nada más que detener la propagación del evento. Esto es útil para evitar cerrar la superposición cuando el usuario interactúa con su contenido.

También podemos hacer que la superposición aparezca y desaparezca con gracia gracias a la directiva de transición , también conocida como "magia negra de Svelte" 😁.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
    >
      Backdrop
    </div>
    <div>Content</div>
  </div>
{/if}

 

Posición sobre el contenido

La ventana emergente debe mostrarse sobre todo el contenido, independientemente de si la página se ha desplazado o no. Por lo tanto, podemos usar una fixedposición como punto de partida. Tanto su contenido como su telón de fondo se establecen en un absoluteposicionamiento. El fondo también debe cubrir la pantalla, pero es un elemento secundario de la superposición, por lo tanto, el "absoluto", y el contenido debe colocarse junto al ancla.

​El resto del código CSS que agregamos a nuestra solución son configuraciones de estilo mínimas para ancho, alto o colores.

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
</script>

<button on:click={() => (visible = true)}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

Posición al lado del ancla

Para establecer la superposición junto al botón, debemos obtener una referencia en ese elemento para encontrar su posición dentro de la ventana gráfica. Para tal fin podemos bindel ancla.

Cuando la referencia está lista o cuando se cambia el tamaño de la ventana (la posición puede cambiar si el usuario cambia el tamaño del navegador), usamos el método getBoundingClientRect() para consultar la información sobre la posición.

En última instancia, traducimos esta información de JavaScript a variables CSS para representar el contenido de la ventana emergente en la posición exacta que nos gustaría establecer.

<script lang="ts">
  // ...
  
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <!-- ... -->
  </div>
{/if}

<style>
  /** ... */

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    /** ... */
  }
</style>

El fragmento de código anterior se recortó para mostrar solo lo relacionado con este capítulo. En conjunto, el código del componente es el siguiente:

<script lang="ts">
  import { fade, scale } from 'svelte/transition';
  import { quintOut } from 'svelte/easing';

  let visible = false;
  let anchor: HTMLButtonElement | undefined = undefined;

  let bottom: number;
  let left: number;

  const initPosition = () =>
    ({ bottom, left } = anchor?.getBoundingClientRect() ?? { bottom: 0, left: 0 });

  $: anchor, initPosition();
</script>

<svelte:window on:resize={initPosition} />

<button on:click={() => (visible = true)} bind:this={anchor}>Open</button>

{#if visible}
  <div
    role="dialog"
    aria-labelledby="Title"
    aria-describedby="Description"
    aria-orientation="vertical"
    transition:fade
    class="popover"
    on:click|stopPropagation
    style="--popover-top: {`${bottom}px`}; --popover-left: {`${left}px`}"
  >
    <div
      on:click|stopPropagation={() => (visible = false)}
      transition:scale={{ delay: 25, duration: 150, easing: quintOut }}
      class="backdrop"
    />
    <div class="wrapper">Content</div>
  </div>
{/if}

<style>
  .popover {
    position: fixed;
    inset: 0;

    z-index: 997;
  }

  .backdrop {
    position: absolute;
    inset: 0;

    background: rgba(0, 0, 0, 0.3);
  }

  .wrapper {
    position: absolute;

    top: calc(var(--popover-top) + 10px);
    left: var(--popover-left);

    min-width: 200px;
    max-width: 200px;

    min-height: 100px;

    width: fit-content;
    height: auto;

    overflow: hidden;

    display: flex;
    flex-direction: column;
    align-items: flex-start;

    background: white;
    color: black;
  }
</style>

 

¡Y eso es! Hemos implementado una ventana emergente personalizada mínima que se puede usar en cualquier aplicación Svelte sin ninguna dependencia.

Conclusión

​No embellecimos la solución, por lo tanto, el resultado sigue siendo visualmente tosco en los bordes, pero el popover funciona como se esperaba.

Para iterar desde allí, implementar más opciones o hacerlo brillante, podría, por ejemplo, echar un vistazo al código fuente abierto de Papyrs en GitHub 🤗.

Hasta el infinito y más allá
David

 Fuente: https://betterprogramming.pub/create-a-popover-in-svelte-fe7dd2eeebb1

#svelte 

Diego  Elizondo

Diego Elizondo

1652926080

Cómo Construir Un Analizador En Rust Por Diversión Y Ganancias

Un viernes por la mañana, holgazaneando, estás pensando en los nuevos programas de Netflix para ver. Su jefe viene y le pide que escriba un analizador para un archivo de unidad de Systemd .

Lo necesita para el lunes.

Cada vez que pensabas que el fin de semana estaba disponible

Estás nervioso.

La última vez que se le pidió que escribiera un analizador, se metió en la madriguera del conejo de la web, copiando y pegando fórmulas Regex hasta que funcionó™.

Básicamente lo que todos los desarrolladores habían hecho (sangre en la mano)

Tomas un sorbo de tu té de boba para calmarte. Buscas Systemd en Google y te gusta... no, no es tan simple como pensabas.

Ese sentimiento cuando estás tan desesperado porque te quitan el fin de semana

Tu buen truco de copiar y pegar expresiones regulares sale volando por la ventana. Las posibilidades de pasar un fin de semana sin interrupciones para atracones de programas se acercan rápidamente null.

#PEG al rescate

No pierdas la esperanza todavía. Permítame presentarle Parse Expression Grammer (PEG) , una manera fácil de familiarizarse con los analizadores y ahorrar su valioso fin de semana.

PEG es una forma legible de escribir reglas de sintaxis y es bastante similar a las expresiones regulares, que es diferente de una contraparte de gramática libre de contexto como Backus-Naur Form (BNF) en la que las expresiones deben reducirse a símbolos más pequeños. Lo siento, Noam Chomsky , quizás otros días de trabajo.

Usaré una biblioteca de análisis de Rust PEG llamada Pest , que es bastante impresionante. Si aún no lo has hecho, esperaré mientras instalas Rust .

En serio, ¿quién no se oxida en estos días?

#Empezando

Comencemos con una gramática simple para analizar una declaración de declaración de variable similar a JavaScript. Comenzaremos pensando en un conjunto de reglas para entradas válidas.

#Una declaración de declaración:

  • comienza con una palabra clave var, seguida de uno o más identificadores.
  • es insensible al espacio en blanco
  • termina con un punto y coma ( ;)

#Un grupo de identificadores:

  • está precedido por una varpalabra clave
  • está delimitado por comas
  • es insensible al espacio en blanco

#Un identificador:

  • puede contener cualquier número de dígitos, caracteres y guiones bajos en mayúsculas y minúsculas
  • no puede comenzar con un dígito
  • no puede contener ningún espacio

Un identificador es un término , lo que significa que es una pieza irrompible del token. También lo son las varpalabras clave y los puntos y comas.

Regresa un poco al lado BNF, usando Extended Backus-Naur Grammar (EBNF) , podrías definir formalmente la gramática anterior de esta manera:

<alpha>  := 'a' | 'b' | 'c' | 'd' | 'e' | /* ... */ 'z'
         |  'A' | 'B' | 'C' | 'D' | 'E' | /* ... */ 'Z'

<digit>  := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

<Decl>   := 'var' <Idents> '\n'? ';'
<Idents> := <Ident> ('\n'? ',' <Ident>)*
<Ident>  := <alpha>+ (<alpha> | <digit> | '_')*

El nombre de la regla en mayúsculas representa un símbolo que no termina (es decir, se puede dividir en términos más pequeños). El nombre en minúscula representa un término.

En aras de la brevedad, omitimos los espacios en blanco implícitos de las reglas. Básicamente, pueden existir uno o más espacios en blanco entre cada símbolo que ve.

¡Vamos a profundizar en ello! Las reglas <alpha>y <digit>se explican por sí mismas, por lo que dejaremos que lo adivines.

<Decl>es la regla más compleja para una instrucción de declaración, que presenta una varpalabra clave, un <Idents>símbolo, una nueva línea opcional y termina con un punto y coma. Básicamente, esta regla dice: "Cualquier entrada de cadena que comience con var, seguida de uno o más espacios, luego una subregla <Idents>, seguida de uno o más espacios y finalmente termine con un solo punto y coma es válida y felizmente la masticaré. "

<Idents>puede ser un solo <Ident>símbolo, seguido de cero o más pares de comas y <Ident>.

Finalmente, un <Ident>debe comenzar con uno o más caracteres, seguido de cero o más caracteres, dígitos o guiones bajos.

#Ok, código por favor

¡Espera, joven Anakin! Cabeza caliente, eres. He aquí que, con la gramática definida, podremos analizar estas declaraciones:

var foo, bar, baz;

var   foo_1, baRamYu,baz99;

var foo_x
  , baroo
  , bazoo
  ;

No, no me olvidé de formatear mi código. ¡Es JS válido, y también es para nuestro analizador! Aquí hay algo que nuestras reglas no soportarán:

var 99Problems

¿Alguien quiere adivinar qué está mal aquí? ✋ Deja tu respuesta en el comentario. (psss, si tu respuesta es correcta, te sigo + 3 me gusta en tu publicación 👍)

#Entra el óxido y las plagas

Ok, espero que ya tengas Rust instalado (En tu terminal, intenta escribir which cargoy ver si aparece). Comience creando un nuevo proyecto binario de Rust con

$ cargo new --bin maybe-js; cd maybe-js

Dentro de la carpeta del proyecto, abra el Cargo.tomlarchivo y agregue lo siguiente en dependencies, y ejecútelo cargo updatepara instalarlo.

[dependencies]
pest = "2.0"
pest_derive = "2.0"

Una vez hecho esto, cdingrese srcy cree un archivo llamado grammar.pesty pegue lo siguiente en él:

alpha = { 'a'..'z' | 'A'..'Z' }
digit = { '0'..'9' }
underscore = { "_" }
newline = _{ "\n" | "\r" }
WHITESPACE = _{ " " }

declaration = { "var" ~ !newline ~ idents ~ newline? ~ ";" }
idents = { ident ~ (newline? ~ "," ~ ident)* }
ident = @{ !digit ~ (alpha | digit | underscore)+ }

Ahora bien, si he tenido su atención durante los últimos minutos, no debería ser difícil adivinar lo que está sucediendo aquí. (Oh, ¿no lo he hecho? De todos modos... aquí vamos)

Los cinco primeros son todos términos. Son conjuntos de valores válidos. el | se llama Choice Operator , que es como "o-si no".

first | or_else

Al hacer coincidir una expresión de elección, firstse intenta. Si first coincide con éxito, la expresión completa se realiza correctamente de inmediato. Sin embargo, si firstfalla, or_elsese intenta a continuación.

La newlineregla tiene un peculiar _antes del corchete, que en Pest habla significa "silencioso": simplemente no lo queremos como parte de nuestros tokens analizados, pero de todos modos es parte de una sintaxis válida.

La WHITESPACEregla tiene un lugar especial en Pest. Si lo definió, Pest insertará automáticamente espacios en blanco opcionales implícitos (de acuerdo con la WHITESPACEregla que defina) entre cada símbolo. Nuevamente, _dice que queremos silenciarlo, ya que no queremos toneladas de espacios en blanco como parte de nuestro árbol de sintaxis.

La declarationregla es muy similar a la contraparte de EBNF que aprendimos antes. Las tildes ("~") simplemente significan "y luego". La regla comienza con una palabra clave "var", seguida de cualquier cosa que no sea un salto de línea (el "!" hace lo que hubiera adivinado intuitivamente: negar una regla), seguida de una subregla idents, un salto de línea opcional y finaliza con un punto y coma.

La identsregla es nuevamente similar al ejemplo EBNF. Es un solo ident, seguido de cero o más idents separados por comas.

La identregla es un poco especial. La "@", conocida como Atomic , delante del corchete está ahí para decir: "No quiero que se apliquen espacios en blanco implícitos a esta regla". Seguro que no queremos incluir ningún espacio en nuestro nombre de variable. Además, marcar una regla como atómica de esta manera trata la regla como un término, silenciando las reglas internas de coincidencia. Cualquier regla interna se descarta.

string_lit = { "\"" ~ inner ~ "\"" }
inner = { ASCII_ALPHANUMERIC* }

Tenga en cuenta que ASCII_ALPHANUMERICes una regla incorporada conveniente en Pest para cualquier carácter y dígito ASCII.

Si analizamos una cadena "hola" con esta regla, esto generará primero un string_litnodo, que a su vez tiene un innernodo que contiene la cadena "hola", sin comillas.

Árbol de regla no atómica

Agregar un sigilo "@" delante del string_litcorchete:

string_lit = @{ "\"" ~ inner ~ "\"" }
inner = { ASCII_ALPHANUMERIC* }

string_litTerminaremos con un nodo plano que contiene ""hola"".

Árbol de la regla atómica

Un sigilo similar "$" conocido como Compound Atomic , protege los espacios en blanco implícitos dentro de la regla. La diferencia es que permite que las reglas de coincidencia internas se analicen normalmente.

La !digitparte evita que la regla avance si comienza con un número. Si no es así, una o más combinaciones de caracteres, números y guiones bajos están bien.

#Pero espera, ¿dónde está el código?

¡Dang, mi inteligente explorador! Pareces arrinconarme en cada movimiento. Sí, eso no era código en absoluto, sino una definición de gramática de Pest. Ahora tenemos que escribir un código de Rust para analizar un texto. Inicie src/main.rsy agregue lo siguiente:

/// You need to do this to use macro
extern crate pest;                                                                                                                                                                                   
#[macro_use]                                                                                                                                                                                         
extern crate pest_derive;                                                                                                                                                                            

/// 1. Import modules                                                                                                                                                                                            
use std::fs;                                                                                                                                                                                         
use pest::Parser;                                                                                                                                                                                    
use pest::iterators::Pair;                                                                                                                                                                           

/// 2. Define a "marker" struct and add a path to our grammar file.                                                                                                                                                                                                 
#[derive(Parser)]                                                                                                                                                                                    
#[grammar = "grammar.pest"]                                                                                                                                                                            
struct IdentParser; 

/// 3. Print the detail of a current Pair and optional divider
fn print_pair(pair: &Pair<Rule>, hard_divider: bool) {
    println!("Rule: {:?}", pair.as_rule());
    println!("Span: {:?}", pair.as_span());
    println!("Text: {:?}", pair.as_str());
    if hard_divider {
        println!("{:=>60}", "");
    } else {
        println!("{:->60}", "");
    }
}

fn main() {
    /// 4. Parse a sample string input
    let pair = IdentParser::parse(Rule::declaration, "var foo1, bar_99, fooBar;")
        .expect("unsuccessful parse")
        .next().unwrap();

    print_pair(&pair, true);
    
    /// 5. Iterate over the "inner" Pairs
    for inner_pair in pair.into_inner() {
 
        print_pair(&inner_pair, true);

        match inner_pair.as_rule() {
            /// 6. If we match an idents rule...
            Rule::idents =>  {
                /// 7. Iterate over another inner Pairs
                for inner_inner_pair in inner_pair.into_inner() {
                    match inner_inner_pair.as_rule() {
                        /// 8. The term ident is the last level
                        Rule::ident => {
                            print_pair(&inner_inner_pair, false);
                        }
                        _ => unreachable!(),
                    }
                }
            }
            _ => unreachable!(),
        }
    }
}

Está bien si no entiendes la mayoría de las cosas aquí. Ejecutémoslo cargo runen el directorio de su proyecto y miremos la salida impresa.

Rule: declaration
Span: Span { str: "var foo1, bar_99, fooBar;", start: 0, end: 28 }
Text: "var foo1, bar_99, fooBarBaz;"
============================================================
Rule: idents
Span: Span { str: "foo1, bar_99, fooBar", start: 4, end: 27 }
Text: "foo1, bar_99, fooBarBaz"
============================================================
Rule: ident
Span: Span { str: "foo1", start: 4, end: 8 }
Text: "foo1"
------------------------------------------------------------
Rule: ident
Span: Span { str: "bar_99", start: 10, end: 16 }
Text: "bar_99"
------------------------------------------------------------
Rule: ident
Span: Span { str: "fooBar", start: 18, end: 27 }
Text: "fooBarBaz"
------------------------------------------------------------

El concepto más importante aquí es un Pair. Representa un par de tokens coincidentes o, de manera equivalente, el texto distribuido que una regla con nombre coincidió correctamente.

A menudo usamos Pairs para:

Determinar qué regla produjo elPair

Usar el Paircomo materia prima&str

Inspeccionar las sub-reglas internas nombradas que produjeron elPair

let pair = Parser::parse(Rule::enclosed, "(..6472..) and more text")
    .unwrap().next().unwrap();

assert_eq!(pair.as_rule(), Rule::enclosed);
assert_eq!(pair.as_str(), "(..6472..)");

let inner_rules = pair.into_inner();
println!("{}", inner_rules); // --> [number(3, 7)]

A Pairpuede tener cero, una o más reglas internas. Para máxima flexibilidad, Pair::into_inner()devuelve Pairs, que es un tipo de iterador sobre cada par.

💡 Pair::into_inner()es un idioma muy común cuando se trabaja con Pest. Asegúrate de entender qué Paires a.

#Vamos a analizar Systemd

Si has llegado hasta aquí, te mereces este gatito.

Ahora es el momento de poner el trabajo. Aquí hay un ejemplo de un archivo de unidad Systemd:

[Unit]
Description=Nginx
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/home/ec2-user/.local/bin
Environment=LD_LIBRARY_PATH=/usr/local/lib
Environment=PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
ExecStart=/usr/local/sbin/nginx-runner.sh
Restart=on-failure
RestartSec=0
KillMode=process

[Install]
WantedBy=multi-user.target

El archivo está agrupado en secciones, cada una con un nombre entre corchetes. Cada sección contiene cero o más pares de nombre y valor de propiedad, separados por un signo igual "=".

Tratemos de desarrollar un conjunto de reglas. Cree un nuevo proyecto de óxido con cargo new --bin systemd-parser, luego cree un archivo con el nombre src/grammar.pestcon las siguientes reglas:


/// Implicit white spaces are ok.
WHITESPACE = _{ " " }

/// Set of characters permited 
char = { ASCII_ALPHANUMERIC | "." | "_" | "/" | "-"  }

/// name is one or more chars. Note that white spaces are allowed.
name = { char+ }

// value can be zero or more char, plus = and : for path variables.
value = { (char | "=" | ":" )* }

/// section is a name, enclosed by square brackets.
section = { "[" ~ name ~ "]" }

/// property pair is a name and value, separated by an equal sign.
property = { name ~ "=" ~ value }

/// A Systemd unit file structure
file = {
    SOI ~
    ((section | property)? ~ NEWLINE)* ~
    EOI
}

En el main.rsarchivo, comience con lo siguiente

extern crate pest;
#[macro_use]
extern crate pest_derive;

use std::fs;
use std::env::current_dir;
use std::collections::HashMap;
use pest::Parser;

#[derive(Parser)]
#[grammar = "grammar.pest"]
struct SystemdParser;

/// Implement a simple AST representation
#[derive(Debug, Clone)]
pub enum SystemdValue {
    List(Vec<String>),
    Str(String),
}

// ...

Como primer paso, después de las importaciones y la configuración iniciales, definimos una SystemdValueenumeración como una representación simple del tipo de datos en un archivo Systemd. SystemdValue::Str(String)capturará un solo valor de propiedad y SystemdValue::List(Vec<String>)capturará varios valores de propiedad con un nombre de clave de propiedad duplicado. Por ejemplo, en el nginx.servicearchivo anterior hay varias Environmentpropiedades.

Aquí está la mainfunción:


fn main() {
    // Read and parse the unit file.
    let unparsed_file = fs::read_to_string("nginx.service")
        .expect("cannot read file");
    let file = SystemdParser::parse(Rule::file, &unparsed_file).expect("fail to parse")
        .next()
        .unwrap();

    // Create a fresh HashMap to store the data.
    let mut properties: HashMap<String, HashMap<String, SystemdValue>> = HashMap::new();

    // These two mutable variables will be used to store
    // section name and property key name.
    let mut current_section_name = String::new();
    let mut current_key_name = String::new();

    // Iterate over the file line-by-line.
    for line in file.into_inner() {
		match line.as_rule() {
			Rule::section => {
                // Update the current_section_name
                let mut inner_rules = line.into_inner();
                current_section_name = inner_rules.next().unwrap().as_str().to_string();
			}
			Rule::property => {
                let mut inner_rules = line.into_inner();
                // Get a sub map of properties with the current_section_name key, or create new.
                let section = properties.entry(current_section_name.clone()).or_default();

                // Get the current property name and value.
                let name = inner_rules.next().unwrap().as_str().to_string();
                let value = inner_rules.next().unwrap().as_str().to_string();

                // If the property name already exists...
                if name == current_key_name {
                    // Get the section of the map with the key name, or insert a new SytemdValue::List.
                    let entry = section.entry(current_key_name.clone()).or_insert(SystemdValue::List(vec![]));
                    // Push the value onto the inner vector of SystemdValue::List.
                    if let SystemdValue::List(ent) = entry {
                        ent.push(value);
                    }
                } else {
                    // Create a new SystemdValue::List and save it under name key.
                    let entry = section.entry(name.clone()).or_insert(SystemdValue::List(vec![]));
                    // Push the current value onto the vector, then set the
                    // current_key_name to the current name.
                    if let SystemdValue::List(ent) = entry {
                        ent.push(value);
                    }
                    current_key_name = name;
                }
			}
			Rule::EOI => (),
			_ => unreachable!(),
		}
    }
}

Todo esto está bien, pero no usamos SystemdValue::Strninguna parte del código. Para mantener limpio el código, decidimos tratar cada propiedad como HashMap<String, SystemdValue::List(Vec<String>), donde la clave del mapa es la clave de propiedad y el vector String almacena la lista de valores de propiedad. Si no hay valor, el vector está vacío. Si hay un valor, este vector contiene ese único valor, y así sucesivamente.

Para que la API sea un poco más fácil de usar, escribiremos una pequeña función auxiliar para procesar todos los correos electrónicos de un solo valor Systemd::List(Vec<String>)y convertirlos en archivos Systemd::Str(String).

// Iterate over the nested maps, and convert empty and 
// single-element `SystemdValue::List<Vec<String>>` to 
// `SystemdValue::Str(String)`.
fn pre_process_map(map: &mut HashMap<String, HashMap<String, SystemdValue>>) {
    for (_, value) in map.into_iter() {
		for (_, v) in value.into_iter() {
			if let SystemdValue::List(vs) = v {
				if vs.len() == 0 {
					let v_ = SystemdValue::Str(String::new());
					*v = v_.clone();
				} else if vs.len() == 1 {
					let v_ = SystemdValue::Str((vs[0]).clone());
					*v = v_.clone();
				}
			}
		}
    }
}

¡Ahora estamos listos para imprimirlo para que el mundo lo vea!

fn main() {

    // Our main code 

    pre_process_map(properties);

    println!("{:#?}", properties);
}

¡Auge! ¡Felicidades! Acaba de escribir un analizador de archivos Systemd, además de uno mini JS. 🤯 Ahora tendrás algo de tiempo libre para divertirte el viernes por la noche. Con otra tarde, es posible que incluso descubras cómo serializar el archivo de tu unidad Systemd en JSON para impresionar a tu jefe el lunes.

Puede consultar el código del analizador implementado como biblioteca en [este repositorio] (https://github.com/jochasinga/systemd-parser)

Fuente: https://hackernoon.com/how-to-build-a-parser-in-rust-for-fun-and-profit

#rust 

Poppy Cooke

Poppy Cooke

1660752264

Comment créer un raccourcisseur d'URL avec SvelteKit

Ce tutoriel montre comment créer un raccourcisseur d'URL avec SvelteKit. Utilisez un point de terminaison SvelteKit pour rediriger les requêtes qui lui sont faites. Cela redirigera l'URL source vers l'URL cible ou de destination.
 

Raccourcisseurs d'URL, utilisez-les lorsque vous souhaitez partager un lien facile à retenir. Vous pouvez utiliser un service comme Bitly ou TinyURL ou l'un des autres déjà disponibles, ou vous pouvez en faire quelque chose que vous voudriez utiliser et avoir un peu plus de connexion en créant le vôtre !

Dans le passé, j'ai créé un raccourcisseur d'URL personnel avec un _redirectsfichier Netlify et avec un fichier Vercel vercel.json. Dans cet article, je vais créer un raccourcisseur d'URL avec SvelteKit.

Je vais utiliser un point de terminaison SvelteKit pour rediriger les requêtes qui lui sont faites. Cela redirigera l' sourceURL vers la cible ou l' destinationURL.

Un exemple pourrait être l'URL donnée à ce projet, disons https://svort.li. tout ce qui suit le TLD ( .li) sera sourcedonc https://svort.li/meredirigé vers l destination' URL de cette source https://scottspence.com.

Dans les deux projets précédents que j'ai réalisés, il n'y avait rien dans la voie d'un framework frontal car il s'agissait simplement de fichiers de configuration pour effectuer les redirections sur le serveur.

C'est toujours à peu près la même chose car il prendra une demande entrante sur le serveur (dans le point de terminaison SvelteKit) et la redirigera.

Configurer le projet

Je vais échafauder un SvelteKit squelette en utilisant la commande suivante :

npm init svelte@next svort-urls

Je vais suivre les invites, je serai oui à toutes les invites là-bas. Qui sont.

? Which Svelte app template? › - Use arrow-keys. Return to submit.
    SvelteKit demo app
❯   Skeleton project

✔ Which Svelte app template? › Skeleton project
✔ Use TypeScript? … Yes
✔ Add ESLint for code linting? … Yes
✔ Add Prettier for code formatting? … Yes
✔ Add Playwright for browser testing? … Yes

Créer le point de terminaison

Dans le routesdossier, je vais créer un nouveau [slug].tsfichier.

touch src/routes/'[slug]'.ts

Le [slug].tsfichier est un point de terminaison, un point de terminaison HTTP dans SvelteKit, vous pouvez utiliser des méthodes HTTP dans les points de terminaison. Donc, si je veux GETdes données dans un itinéraire, je peux y accéder via ces fichiers SvelteKit spéciaux.

Dans ce cas, j'utilise une GETméthode pour que le sourcepeut être redirigé vers le destination.

export const get = async () => {
  return {
    headers: { Location: '/' },
    status: 301,
  }
}

Cela acceptera tout ce qui se trouve après le chemin racine ( /) et le redirigera pour le moment vers la page d' accueil /.

Donc, va localhost:3000/merediriger vers localhost:3000/.

C'est à peu près tout !

Pour la liste des liens, j'utiliserai un fichier de configuration local, mais vous pouvez utiliser quelque chose comme un CMS ou une base de données pour contrôler cela.

Ajouter des URL source et de destination

Je vais ajouter les URL source et destination à un fichier de configuration. Dans SvelteKit, la place pour cela serait dans un libdossier.

# create the lib folder
mkdir src/lib
# create a file for the urls
touch src/lib/urls-list.ts

Dans le urls-list.tsfichier, j'ajouterai les URL source et de destination que je veux, j'en ajouterai un exemple ici.

export const urls = [
  {
    source: '/me',
    destination: 'https://scottspence.com',
  },
  {
    source: '/twitter',
    destination: 'https://twitter.com/spences10',
  },
  {
    source: '/git',
    destination: 'https://github.com/spences10',
  },
]

Rediriger vers l'URL de destination

Avec ma liste de liens courts en place, je peux les utiliser dans le point de [slug].tsterminaison. alors localhost:3000/meje vais maintenant vouloir rediriger vers https://scottspence.com.

J'aurai besoin d'un moyen de savoir quelle est l'URL source dans le point de terminaison afin de pouvoir la déstructurer hors du contexte transmis au point de terminaison.

Jetons un coup d'œil à ce que nous obtenons dans l' objet context(ou ctx).

export const get = async ctx => {
  console.log(ctx)
  return {
    headers: { Location: '/' },
    status: 301,
  }
}

Alors maintenant, si je navigue vers localhost:3000/something, je verrai la sortie suivante dans le terminal :

{
  request: Request {
    size: 0,
    follow: 20,
    compress: true,
    counter: 0,
    agent: undefined,
    highWaterMark: 16384,
    insecureHTTPParser: false,
    [Symbol(Body internals)]: {
      body: null,
      stream: null,
      boundary: null,
      disturbed: false,
      error: null
    },
    [Symbol(Request internals)]: {
      method: 'GET',
      redirect: 'follow',
      headers: [Object],
      parsedURL: [URL],
      signal: null,
      referrer: undefined,
      referrerPolicy: ''
    }
  },
  url: URL {
    href: 'http://localhost:3000/something',
    origin: 'http://localhost:3000',
    protocol: 'http:',
    username: '',
    password: '',
    host: 'localhost:3000',
    hostname: 'localhost',
    port: '3000',
    pathname: '/something',
    search: '',
    searchParams: URLSearchParams {},
    hash: ''
  },
  params: { slug: 'something' },
  locals: {},
  platform: undefined
}

Donc, ce qui m'intéresse ici, c'est l' urlobjet, plus précisément le fichier url.pathname. Cela va m'aider à identifier où je veux que la demande soit redirigée.

url: URL {
  href: 'http://localhost:3000/something',
  origin: 'http://localhost:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost:3000',
  hostname: 'localhost',
  port: '3000',
  pathname: '/something',
  search: '',
  searchParams: URLSearchParams {},
  hash: ''
}

Je pourrais aussi utiliser params.slugobject pour cela aussi.

params: { slug: 'something' },

Dans cet exemple, je vais utiliser l' urlobjet. Je vais donc déstructurer l' urlobjet hors de l'objet de contexte et importer le urls-listfichier.

import { urls } from '$lib/urls-list'

export const get = async ({ url }) => {
  return {
    headers: { Location: '/' },
    status: 301,
  }
}

Ensuite, je peux obtenir une redirection hors du urlstableau. Je vais déclarer ceci comme une [redirect]variable.

Je vais donc voir ce que j'obtiens si je me déconnecte du contenu de [redirect]maintenant, je vais vouloir filtrer tout ce qui correspond au fichier url.pathnamefrom urls-list, donc pour l'instant je vais console.logsortir les résultats :

import { urls } from '$lib/urls-list'

export const get = async ({ url }) => {
  const [redirect] = urls.filter(item => {
    console.log(item)
  })

  return {
    headers: { Location: '/' },
    status: 301,
  }
}

Maintenant, si je navigue vers localhost:3000/something, je verrai ce qui suit dans le terminal :

{ source: '/me', destination: 'https://scottspence.com' }
{ source: '/twitter', destination: 'https://twitter.com/spences10' }
{ source: '/git', destination: 'https://github.com/spences10' }

Sucré! Alors maintenant, je peux utiliser une logique pour déterminer si le url.pathnamecorrespond à ce qui est dans le urlstableau.

Ainsi, avec le itemque j'utilise dans le filtre, je peux comparer avec le url.pathname. S'il y a une correspondance valide, je peux obtenir le destinationà partir du urlstableau.

import { urls } from '$lib/urls-list'

export const get = async ({ url }) => {
  const [redirect] = urls.filter(item => item.source === url.pathname)

  if (redirect) {
    return {
      headers: { Location: redirect.destination },
      status: 301,
    }
  } else if (!redirect && url.pathname.length > 1) {
    return {
      headers: { Location: '/' },
      status: 301,
    }
  } else return {}
}

Je peux utiliser un ifpour vérifier une correspondance valide. S'il y a une correspondance valide, définissez le headers.Locationà destinationpartir du urlstableau.

Si cela ne correspond pas, je redirigerai vers la page d'accueil ( /) et j'aurai une dernière prise pour renvoyer un objet vide.

Conclusion

C'est ça! J'ai créé une redirection simple dans SvelteKit qui prendra une URL entrante et redirigera vers une URL de destination.

Je peux maintenant utiliser la page d'accueil comme page de destination pour mes URL courtes afin que toute personne venant sur le site puisse consulter l'un des liens disponibles.

Source de l'article original sur https://scottspence.com

#sveltekit #svelte #javascript