Soporte para múltiples marcos en Monorepo

Su tarea, si decide tomarla, es construir un componente Botón en cuatro marcos usando solo uno button.css ¡documento!

Esta idea es muy importante para mí. Estaba trabajando en una biblioteca de componentes llamada Interfaz de usuario agnóstica Su propósito es crear componentes de interfaz de usuario que no dependan de un marco de JavaScript específico. AgnosticUI funciona con React, Vue 3, Angular y Svelte. Esto es exactamente lo que haremos en este artículo de hoy: Cree un componente de botón que funcione con todos estos marcos.

El código fuente de este artículo es Disponible en GitHub existe the-little-button-that-could-series clon.

contenido

¿Por qué un repositorio?

Construiremos un micro monorepo basado en el espacio de trabajo de hilo. por qué Chris en realidad dio una buena descripción general de los beneficios en otro artículo. Pero aquí está mi propia lista sesgada de beneficios para nuestros esfuerzos de botón pequeño:

conector

Estamos tratando de construir un componente que use solo un botón button.css Archivos en varios marcos. Entonces, esencialmente, hay algunos específicos conector Entre diferentes implementaciones del marco y un archivo CSS de verdad. El ajuste monorepo proporciona una estructura conveniente para tocar nuestro single button.css componentes en varios proyectos marco.

proceso de trabajo

Supongamos que el botón debe ajustarse, como una implementación de anillo de enfoque o la cagamos. aria en la plantilla del componente. Idealmente, nos gustaría solucionar los problemas en un solo lugar, no arreglos individuales en repositorios separados.

prueba

Queríamos ejecutar convenientemente las cuatro implementaciones de botones simultáneamente para realizar pruebas. Con el desarrollo de proyectos de este tipo, es seguro asumir que habrá pruebas más apropiadas. Por ejemplo, en AgnosticUI actualmente uso Storybooks y, a menudo, ejecuto todos los marcos de Storybooks o ejecuto pruebas de instantáneas de todo el monorepo.

lo que me gusta Leonardo Losovitz Debo decir el método de monorepo. (Y sucede que corresponde a todo lo que hemos discutido hasta ahora).

Creo que monorepo es especialmente útil cuando todos los paquetes están codificados en el mismo lenguaje de programación, estrechamente relacionados y dependientes de las mismas herramientas.

configurar

Es hora de profundizar en el código: primero cree un directorio de nivel superior en la línea de comando para contener el proyecto, luego cd Adelante. (¿No se te ocurre un nombre? mkdir buttons && cd buttons funcionará bien )

Primero, inicialicemos el proyecto:

$ yarn init
yarn init v1.22.15
question name (articles): littlebutton
question version (1.0.0): 
question description: my little button project
question entry point (index.js): 
question repository url: 
question author (Rob Levin): 
question license (MIT): 
question private: 
success Saved package.json

Esto nos da una package.json El archivo es el siguiente:

{
  "name": "littlebutton",
  "version": "1.0.0",
  "description": "my little button project",
  "main": "index.js",
  "author": "Rob Levin",
  "license": "MIT"
}

Crear un espacio de trabajo básico

Podemos configurar el primero con:

mkdir -p ./littlebutton-css

Luego, debemos agregar las siguientes dos filas al nivel superior del monorepo package.json archivo para que podamos mantener el monorepo en privado. También declara nuestro espacio de trabajo:

// ...
"private": true,
"workspaces": ["littlebutton-react", "littlebutton-vue", "littlebutton-svelte", "littlebutton-angular", "littlebutton-css"]

ahora hasta littlebutton-css contenido. De nuevo querremos generar un package.json y yarn init. porque nombramos nuestro directorio littlebutton-css (con nosotros en nuestro workspaces existe package.json) podemos simplemente hacer clic Return Ingrese y acepte todas las indicaciones:

$ cd ./littlebutton-css && yarn init
yarn init v1.22.15
question name (littlebutton-css): 
question version (1.0.0): 
question description: 
question entry point (index.js): 
question repository url: 
question author (Rob Levin): 
question license (MIT): 
question private: 
success Saved package.json

En este punto, la estructura del directorio debería verse así:

├── littlebutton-css
│   └── package.json
└── package.json

En este punto, solo creamos el espacio de trabajo de CSS, ya que usaremos una herramienta similar para generar la implementación de nuestro marco. vite como resultado de un package.json y un directorio del proyecto para Ud. Debemos recordar que los nombres que elijamos para estos elementos generados deben corresponder a lo que tenemos package.json por nuestros anteriores workspaces Me voy a trabajar.

HTML básico y CSS

Vamos a quedarnos ./littlebutton-css Workspace y cree nuestro componente de botón simple usando archivos HTML y CSS normales.

touch index.html ./css/button.css

Ahora el directorio de nuestro proyecto debería verse así:

littlebutton-css
├── css
│   └── button.css
├── index.html
└── package.json

Continuemos y conectemos algunos puntos con alguna plantilla HTML ./index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>The Little Button That Could</title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://css-tricks.com/make-a-component-multiple-frameworks-in-a-monorepo/css/button.css">
</head>
<body>
  <main>
    <button class="btn">Go</button>
  </main>
</body>
</html>

Y para darnos una pequeña prueba visual, podemos agregar un poco de color ./css/button.css:

.btn {
  color: hotpink;
}

ábrelo ahora index.html página en el navegador Si ve un botón compartido feo hotpink Texto… ¡éxito!

Espacios de trabajo específicos de fotogramas

Entonces, lo que acabamos de terminar es la línea principal de nuestro componente de botón. Lo que haremos ahora es abstraerlo un poco para poder extenderlo a otros marcos, y así sucesivamente. Por ejemplo, ¿qué pasa si queremos usar botones en nuestro proyecto React? Necesitaremos tener espacios de trabajo para cada espacio de trabajo en nuestro monorepo. Comenzaremos con React y luego con Vue 3, Angular y Svelte.

reacción

Generaremos nuestro proyecto React con la ayuda de Rápido, constructor muy ligero y extremadamente rápido. Tenga en cuenta que si intenta usar create-react-app, es probable que encuentre conflictos más adelante react-scripts y configuraciones conflictivas de paquetes web o Babel de otros marcos como Angular.

Para activar nuestro espacio de trabajo React, volvamos a la terminal y cd Archivar en un directorio del más alto nivel, de ahí usaremos vite Inicializar un nuevo proyecto – llamémoslo littlebutton-react «Por su puesto que lo hare.» nosotros elegimos react Como marco y opción en el aviso:

$ yarn create vite
yarn create v1.22.15
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Installed "[email protected]" with binaries:
      - create-vite
      - cva
✔ Project name: … littlebutton-react
✔ Select a framework: › react
✔ Select a variant: › react

Scaffolding project in /Users/roblevin/workspace/opensource/guest-posts/articles/littlebutton-react...

Done. Now run:

  cd littlebutton-react
  yarn
  yarn dev

✨  Done in 17.90s.

Luego inicializamos la aplicación React con el siguiente comando:

cd littlebutton-react
yarn
yarn dev

Con React instalado y probado, reemplácelo src/App.jsx Usa el siguiente código para colocar nuestro botón:

import "./App.css";

const Button = () => {
  return <button>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button />
    </div>
  );
}

export default App;

Ahora escribiremos un pequeño script de nodo para replicar el nuestro. littlebutton-css/css/button.css Directamente en nuestra aplicación React. Este paso es probablemente el más divertido para mí, porque es tanto mágico como feo. Esto es sorprendente porque significa que nuestro componente de botón React realmente deriva sus estilos del mismo CSS escrito en el proyecto principal. Esto es feo porque llegamos desde un espacio de trabajo y tomamos archivos de otro. ¯ _ (ツ) _ / (

Agregue el siguiente script de nodo pequeño a littlebutton-react/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
fs.writeFileSync("./src/button.css", css, "utf8");

pongamos un node comandos package.json un escenario que sucedió antes dev guion en littlebutton-react/package.jsonAñadiremos un syncStyles y actualizar dev Llamar syncStyles adelante vite:

"syncStyles": "node copystyles.js",
"dev": "yarn syncStyles && vite",

Ahora que estamos lanzando nuestra aplicación React yarn dev, comenzaremos copiando el archivo CSS. En esencia, estamos «obligados» a no desviarnos de los paquetes CSS. button.css en nuestro botón Reaccionar.

Pero también queremos usar Módulo CSS Para evitar conflictos de nombres y fugas globales de CSS, debemos dar otro paso para conectarlo (desde el mismo littlebutton-react contenido):

touch src/button.module.css

Luego agregue lo siguiente al nuevo src/button.module.css documento:

.btn {
  composes: btn from './button.css';
}

encontré composes (también conocido como trabajo) se ha convertido en una de las mejores características de los módulos CSS. En resumen, copiamos nuestra versión HTML/CSS button.css Venta al por mayor y luego maquillaje de uno de los nuestros. .btn reglas de estilo

Con esto podemos volver a lo nuestro. src/App.jsx e importar el módulo CSS styles En nuestro componente React:

import "./App.css";
import styles from "./button.module.css";

const Button = () => {
  return <button className={styles.btn}>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button />
    </div>
  );
}

export default App;

¡Vamos! Hagamos una pausa e intentemos ejecutar nuestra aplicación React nuevamente:

yarn dev

Si todo va bien, debería ver el mismo botón común, pero con hotpink texto. Antes de pasar al siguiente cuadro, regresemos a nuestro directorio monorepo de nivel superior y actualicemos su package.json:

{
  "name": "littlebutton",
  "version": "1.0.0",
  "description": "toy project",
  "main": "index.js",
  "author": "Rob Levin",
  "license": "MIT",
  "private": true,
  "workspaces": ["littlebutton-react", "littlebutton-vue", "littlebutton-svelte", "littlebutton-angular"],
  "scripts": {
    "start:react": "yarn workspace littlebutton-react dev"
  }
}

atropellar yarn Instale amplificadores monorepo desde el comando de directorio de nivel superior.

El único cambio que hicimos en ese package.json es nuevo scripts La parte que inicia una aplicación React con un script start:react ahora podemos correr yarn start:react Desde nuestro directorio de nivel superior, lanzará el proyecto que acabamos de construir ./littlebutton-react No hay necesidad cd‘ing – ¡muy conveniente!

Luego hablaremos de Vue y Svelte. Resulta que podemos aplicarles un enfoque muy similar, ya que ambos usan componentes de un solo archivo (SFC). En principio, podemos mezclar HTML, CSS y JavaScript en un solo archivo. Ya sea que le guste o no el enfoque SFC, es lo suficientemente bueno para crear presentaciones o componentes de interfaz de usuario sin formato.

vista

De acuerdo a Documentación de scaffolding de invitación Ejecutaremos los siguientes comandos desde el directorio de nivel superior de monorepo para inicializar la aplicación Vue:

yarn create vite littlebutton-vue --template vue

Esto genera un andamio con algunas instrucciones proporcionadas para ejecutar la aplicación de inicio de Vue:

cd littlebutton-vue
yarn
yarn dev

Esto debería abrir una página de inicio del navegador con un título como «Hello Vue 3 + Vite». Desde aquí podemos actualizar src/App.vue llega:

<template>
  <div id="app">
    <Button class="btn">Go</Button>
  </div>
</template>

<script>
import Button from './components/Button.vue'

export default {
  name: 'App',
  components: {
    Button
  }
}
</script>

reemplazaremos cualquiera src/components/* y src/components/Button.vue:

<template>
  <button :class="classes"><slot /></button>
</template>

<script>
export default {
  name: 'Button',
  computed: {
    classes() {
      return {
        [this.$style.btn]: true,
      }
    }
  }
}
</script>

<style module>
.btn {
  color: slateblue;
}
</style>

Vamos a desglosarlo:

  • :class="classes" utiliza los enlaces de Vue para llamar al cálculo classes método.
  • Esta classes los métodos a su vez son explotados Módulos CSS en Vue y this.$style.btn utilizará la sintaxis de los estilos contenidos en un <style module> Etiqueta.

Actualmente estamos codificando duro color: slateblue Solo para probar si las cosas dentro del componente funcionan correctamente. Intente ejecutar la aplicación nuevamente. yarn devSi ve un botón con el color de prueba que declaramos, ¡entonces funciona!

Ahora escribiremos un script de nodo para replicar el nuestro. littlebutton-css/css/button.css en nuestro Button.vue Los archivos son similares a lo que hicimos para la implementación de React, como mencionamos, este componente es SFC, por lo que tendremos que usar un simple expresión regular.

Agregue el siguiente script pequeño de Node.js a littlebutton-vue/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
const vue = fs.readFileSync("./src/components/Button.vue", "utf8");
// Take everything between the starting and closing style tag and replace
const styleRegex = /<style module>([sS]*?)</style>/;
let withSynchronizedStyles = vue.replace(styleRegex, `<style module>n${css}n</style>`);
fs.writeFileSync("./src/components/Button.vue", withSynchronizedStyles, "utf8");

Este script es un poco complicado, pero se usa replace Copiar texto entre abrir y cerrar style Las etiquetas de expresiones regulares no son malas.

Ahora agreguemos los siguientes dos scripts a scripts en las condiciones littlebutton-vue/package.json documento:

"syncStyles": "node copystyles.js",
"dev": "yarn syncStyles && vite",

corre ahora yarn syncStyles Mira ./src/components/Button.vue de nuevo. Debería ver nuestro módulo de estilo reemplazado por:

<style module>
.btn {
  color: hotpink;
}
</style>

Inicie la aplicación Vue nuevamente yarn dev Y asegúrese de obtener el resultado esperado: sí, un botón de texto de colores vivos. Si es así, ¡estamos emocionados de pasar al siguiente espacio de trabajo en el marco!

Delgado

De acuerdo a documento delgado, tenemos que empezar nuestro littlebutton-svelte Espacio de trabajo con el siguiente contenido, comenzando desde el directorio de nivel superior de monorepo:

npx degit sveltejs/template littlebutton-svelte
cd littlebutton-svelte
yarn && yarn dev

Confirme que puede hacer clic en la página de inicio «Hello World». http://localhost:5000Entonces actualiza littlebutton-svelte/src/App.svelte:

<script>
  import Button from './Button.svelte';
</script>
<main>
  <Button>Go</Button>
</main>

Además, c. littlebutton-svelte/src/main.js, queremos eliminar name accesorios, por lo que se ve así:

import App from './App.svelte';

const app = new App({
  target: document.body
});

export default app;

Finalmente agregar littlebutton-svelte/src/Button.svelte tiene lo siguiente:

<button class="btn">
  <slot></slot>
</button>

<script>
</script>

<style>
  .btn {
    color: saddlebrown;
  }
</style>

Una última cosa: Svelte parece estar nombrando el nuestro. solicitud: "name": "svelte-app" dentro package.jsoncambiarlo a "name": "littlebutton-svelte" Entonces es lo mismo que workspaces llamado a nuestro más alto nivel package.json documento.

Podemos repetir nuestra línea de partida de nuevo littlebutton-css/css/button.css en nuestro Button.svelteComo mencionamos, este componente es SFC, por lo que tendremos que usarlo expresión regularAgregue el siguiente script de nodo a littlebutton-svelte/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
const svelte = fs.readFileSync("./src/Button.svelte", "utf8");
const styleRegex = /<style>([sS]*?)</style>/;
let withSynchronizedStyles = svelte.replace(styleRegex, `<style>n${css}n</style>`);
fs.writeFileSync("./src/Button.svelte", withSynchronizedStyles, "utf8");

Esto es muy similar al script de copia que usamos en Vue, ¿verdad? Agregaremos un script similar a nuestro package.json texto:

"dev": "yarn syncStyles && rollup -c -w",
"syncStyles": "node copystyles.js",

corre ahora yarn syncStyles && yarn devSi todo está bien, necesitamos ver el botón de nuevo. hotpink texto.

Si esto vuelve a suceder, todo lo que tengo que decir es Bienvenido a mi mundoEl proceso que les muestro aquí es esencialmente el mismo que usé para construir mi propio Interfaz de usuario agnóstica ¡proyecto!

bocina

Probablemente ya conoces el ejercicio, desde el directorio de nivel superior de monorepo instala Angular y Crear una aplicación AngularSi estuviéramos creando una biblioteca de interfaz de usuario completa, podríamos usarla ng generate library incluso nxPero para hacer las cosas lo más simples posible, configuraremos una aplicación Angular estándar como esta:

npm install -g @angular/cli ### unless you already have installed
ng new littlebutton-angular ### choose no for routing and CSS
? Would you like to add Angular routing? (y/N) N
❯ CSS 
  SCSS   [ https://sass-lang.com/documentation/syntax#scss ] 
  Sass   [ https://sass-lang.com/documentation/syntax#the-indented-syntax ] 
  Less   [ http://lesscss.org ]

cd littlebutton-angular && ng serve --open

Después de confirmar la configuración de Angular, actualicemos algunos archivos. cd littlebutton-angular, Borrar src/app/app.component.spec.ts archivo y agregarle un componente de botón src/components/button.component.tscomo esto:

import { Component } from '@angular/core';

@Component({
  selector: 'little-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
})
export class ButtonComponent {}

Agregue lo siguiente a src/components/button.component.html:

<button class="btn">Go</button>

y ponlo src/components/button.component.css Archivo de prueba:

.btn {
  color: fuchsia;
}

existe src/app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { ButtonComponent } from '../components/button.component';

@NgModule({
  declarations: [AppComponent, ButtonComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Luego reemplaza src/app/app.component.ts y:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {}

Entonces cambia src/app/app.component.html y:

<main>
  <little-button>Go</little-button>
</main>

Corramos yarn start y confirma nuestro botón fuchsia El texto se muestra como se esperaba.

Nuevamente, queremos copiar CSS del espacio de trabajo principal. Podemos hacerlo agregándolo a littlebutton-angular/copystyles.js:

const fs = require("fs");
let css = fs.readFileSync("../littlebutton-css/css/button.css", "utf8");
fs.writeFileSync("./src/components/button.component.css", css, "utf8");

Angular es bueno porque usa ViewEncapsulation es el predeterminado emulate imitar Según la documentación,

[…] El comportamiento de Shadow DOM se logra preprocesando (y renombrando) el código CSS para limitar efectivamente el CSS a la apariencia del componente.

Esto básicamente significa que podemos copiar literalmente button.css y úsalo como está.

la última actualización package.json Al agregar estas dos líneas al archivo scripts parte:

"start": "yarn syncStyles && ng serve",
"syncStyles": "node copystyles.js",

Podemos huir con él ahora. yarn start Vuelva a comprobar el color del texto de nuestro botón (esto es fuchsia) Ahora es hotpink.

¿Qué acabamos de hacer?

Tomemos un descanso de la codificación y miremos el panorama general y lo que acabamos de hacer. Básicamente, configuramos un sistema para realizar cualquier cambio en nuestro paquete CSS. button.css Se replicará en todas las implementaciones del marco por nuestras razones. copystyles.js Guión de nodo. Además, hemos incluido convenciones idiomáticas para cada marco:

  • SFC Funciona con Vue y Svelte
  • CSS Modules para React (y Vue en SFC) <style module> ajustar)
  • ViewEncapsulation por una esquina

Por supuesto, dejo en claro que estas no son las únicas formas de imponer CSS en cada uno de los marcos anteriores (por ejemplo, CSS-in-JS es una opción popular), pero ciertamente también son prácticas aceptadas para nuestros más antiguos. El objetivo es tener una única fuente de datos CSS para administrar la implementación de todos los marcos.

Por ejemplo, si se usa nuestro botón y nuestro equipo de diseño decide que queremos comenzar 4px ellos llegan 3px border-radius, podemos actualizar el archivo y todas las conversiones individuales permanecerán sincronizadas.

Si tiene un equipo de desarrollo políglota al que le gusta trabajar en varios marcos, o un equipo extranjero (3 veces más productivo en Angular que en Angular) encargado de crear una aplicación de back-end, pero sus productos principales están integrados, entonces esto será muy convincente para responder. O tal vez esté creando una consola de administración temporal y desee probar Vue o Svelte. te dan la imagen.

trabajos de acabado

Bien, entonces tenemos arquitectura monorepo en un lugar realmente agradable. Sin embargo, hay algunas cosas que podemos hacer para que sea más útil en términos de experiencia del desarrollador.

mejores scripts de inicio

Volvamos a nuestro directorio monorepo de nivel superior y actualícelo package.json scripts sección con lo siguiente para que podamos prescindir cd‘En g:

// ...
"scripts": {
  "start:react": "yarn workspace littlebutton-react dev",
  "start:vue": "yarn workspace littlebutton-vue dev ",
  "start:svelte": "yarn workspace littlebutton-svelte dev",
  "start:angular": "yarn workspace littlebutton-angular start"
},

mejor estilo básico

También podríamos darle al botón un mejor conjunto de estilos básicos para que comience en una posición agradable y neutral. littlebutton-css/css/button.css documento.

Ver el clip completo

.btn {
  --button-dark: #333;
  --button-line-height: 1.25rem;
  --button-font-size: 1rem;
  --button-light: #e9e9e9;
  --button-transition-duration: 200ms;
  --button-font-stack:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    Ubuntu,
    "Helvetica Neue",
    sans-serif;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  user-select: none;
  appearance: none;
  cursor: pointer;
  box-sizing: border-box;
  transition-property: all;
  transition-duration: var(--button-transition-duration);
  color: var(--button-dark);
  background-color: var(--button-light);
  border-color: var(--button-light);
  border-style: solid;
  border-width: 1px;
  font-family: var(--button-font-stack);
  font-weight: 400;
  font-size: var(--button-font-size);
  line-height: var(--button-line-height);
  padding-block-start: 0.5rem;
  padding-block-end: 0.5rem;
  padding-inline-start: 0.75rem;
  padding-inline-end: 0.75rem;
  text-decoration: none;
  text-align: center;
}

/* Respect users reduced motion preferences */
@media (prefers-reduced-motion) {
  .btn {
    transition-duration: 0.001ms !important;
  }
}

¡Vamos a probarlo! Inicie cada una de las cuatro implementaciones de cuadros con los scripts de inicio nuevos y mejorados y confirme que los cambios de estilo surtieron efecto.

Botón de estilo neutro (gris) en marco monorepo

Una actualización de un archivo CSS se expande a cuatro cuadros, ¡genial, eh! ?

establecer el modo básico

agregaremos un mode Apoya cada uno de nuestros botones y herramientas primary el próximo modelo. El botón principal puede ser de cualquier color, pero usaremos verde para el fondo y texto blanco. Del mismo modo en la hoja de estilo principal:

.btn {
  --button-primary: #14775d;
  --button-primary-color: #fff;
  /* ... */
}

entonces, justo antes @media (prefers-reduced-motion) solicitud, agregue lo siguiente btn-primary a la misma hoja de estilo principal:

.btn-primary {
  background-color: var(--button-primary);
  border-color: var(--button-primary);
  color: var(--button-primary-color);
}

¡vamos! Cierta comodidad para los desarrolladores y una mejor base ¡estilo!

Actualice cada componente para obtener mode Propiedad

Ahora hemos añadido otros nuevos. primary modo presentado .btn-primary class, queremos sincronizar los estilos implementados por los cuatro marcos. Entonces, agreguemos más package.json Guión a nuestro más alto nivel scripts:

"sync:react": "yarn workspace littlebutton-react syncStyles",
"sync:vue": "yarn workspace littlebutton-vue syncStyles",
"sync:svelte": "yarn workspace littlebutton-svelte syncStyles",
"sync:angular": "yarn workspace littlebutton-angular syncStyles"

¡Recuerda seguir las reglas de comas de JSON! Depende de dónde coloques estas líneas en tu scripts: {...}, debe asegurarse de que no falten ni comas finales.

Continúe y ejecute el siguiente comando para sincronizar completamente los estilos:

yarn sync:angular && yarn sync:react && yarn sync:vue && yarn sync:svelte

Ejecutarlo no cambiará nada, ya que aún no hemos implementado la clase base, pero si observa el componente CSS del botón de marco, al menos debería ver que CSS está copiado.

reacción

Si aún no lo ha hecho, verifique nuevamente para ver si el CSS actualizado se ha copiado en littlebutton-react/src/button.cssSi no, puedes ejecutar yarn syncStylesTenga en cuenta que si olvida ejecutar yarn syncStyles nuestro dev De todos modos, la próxima vez que inicie la aplicación, el script lo hará por nosotros:

"dev": "yarn syncStyles && vite",

Para nuestra implementación de React también necesitamos agregar un composición Las clases de módulos CSS están en littlebutton-react/src/button.module.css Esto es nuevo .btn-primary:

.btnPrimary {
  composes: btn-primary from './button.css';
}

también actualizaremos littlebutton-react/src/App.jsx:

import "./App.css";
import styles from "./button.module.css";

const Button = ({ mode }) => {
  const primaryClass = mode ? styles[`btn${mode.charAt(0).toUpperCase()}${mode.slice(1)}`] : '';
  const classes = primaryClass ? `${styles.btn} ${primaryClass}` : styles.btn;
  return <button className={classes}>Go</button>;
};

function App() {
  return (
    <div className="App">
      <Button mode="primary" />
    </div>
  );
}

export default App;

Inicie la aplicación Reaccionar yarn start:react del directorio de nivel superior. Si todo salió bien, ahora debería ver su botón verde de inicio.

Botón verde oscuro con texto blanco en el centro de la pantalla.

Como ilustración, mantengo el componente Button dentro App.jsx En breve. Si esto le preocupa, no dude en organizar el componente Botón en su propio archivo.

vista

Verifique nuevamente si el estilo del botón está copiado, si no, ejecute yarn syncStyles.

A continuación, sí <script> parte littlebutton-vue/src/components/Button.vue:

<script>
export default {
  name: 'Button',
  props: {
    mode: {
      type: String,
      required: false,
      default: '',
      validator: (value) => {
        const isValid = ['primary'].includes(value);
        if (!isValid) {
          console.warn(`Allowed types for Button are primary`);
        }
        return isValid;
      },
    }
  },
  computed: {
    classes() {
      return {
        [this.$style.btn]: true,
        [this.$style['btn-primary']]: this.mode === 'primary',
      }
    }
  }
}
</script>

Ahora podemos actualizar la etiqueta. littlebutton-vue/src/App.vue usa uno nuevo mode pilar:

<Button mode="primary">Go</Button>

ahora usted puede yarn start:vue Marque el mismo botón verde en el directorio de nivel superior.

Delgado

Nos deja cd Ingresar littlebutton-svelte y validar los estilos en littlebutton-svelte/src/Button.svelte Nuevo .btn-primary la clase se copia y yarn syncStyles si necesitas. de nuevo, dev Si lo olvida accidentalmente, el script lo hará por nosotros la próxima vez que lo carguemos.

Luego actualice la plantilla Svelte para cambiar mode sobre primaryexiste src/App.svelte:

<script>
  import Button from './Button.svelte';
</script>
<main>
  <Button mode="primary">Go</Button>
</main>

También necesitamos actualizar nuestro top src/Button.svelte El componente mismo acepta mode Mantener e implementar clases de módulos CSS:

<button class="{classes}">
  <slot></slot>
</button>
<script>
  export let mode = "";
  const classes = [
    "btn",
    mode ? `btn-${mode}` : "",
  ].filter(cls => cls.length).join(" ");
</script>

Tenga en cuenta, <styles> La parte del componente Svelte no debe tocarse en este paso.

y ahora puedes yarn dev desde littlebutton-svelte (o yarn start:svelte desde un directorio superior) ¡confirme el éxito del botón verde!

bocina

Lo mismo, marco diferente: asegúrese de que los estilos se copien y ejecuten yarn syncStyles si necesario.

agreguemos mode apoya littlebutton-angular/src/app/app.component.html documento:

<main>
  <little-button mode="primary">Go</little-button>
</main>

Ahora tenemos que configurar el enlace a classes rendimientos compute La clase correcta se basa en si mode Se pasa al componente Agregue esto a littlebutton-angular/src/components/button.component.html (y tenga en cuenta que el enlace se realiza con corchetes):

<button [class]="classes">Go</button>

Entonces en realidad tenemos que crear classes Conéctese a nuestro componente littlebutton-angular/src/components/button.component.ts:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'little-button',
  templateUrl: './button.component.html',
  styleUrls: ['./button.component.css'],
})
export class ButtonComponent {
  @Input() mode: 'primary' | undefined = undefined;

  public get classes(): string {
    const modeClass = this.mode ? `btn-${this.mode}` : '';
    return [
      'btn',
      modeClass,
    ].filter(cl => cl.length).join(' ');
  }
}

Usamos Input se acepta la instruccion mode props, luego creamos un classes El accesor, si se transmite, agrega la clase al esquema.

¡Enciéndelo y busca el botón verde!

finalización del código

Si has llegado hasta aquí, ¡felicidades, has terminado con el código! Si algo sale mal, te recomiendo que lo reenvíes. Código fuente de GitHub existe the-little-button-that-could-series clon. Debido a que los paquetes tienden a cambiar repentinamente, es posible que desee adjuntar la versión del paquete a la versión en esta rama si encuentra problemas de dependencia.

Tómese un momento para dar un paso atrás y comparar las cuatro implementaciones basadas en marcos de los componentes de botones que acabamos de crear. Todavía son lo suficientemente pequeños como para detectar rápidamente algunas diferencias interesantes. accesorios vamos como vamos unir a los accesorios y cómo usar CSS choque de nombres Bloqueado en otros tonos A medida que sigo agregando componentes Interfaz de usuario agnóstica (Admite los cuatro marcos idénticos) y siempre pienso cuál proporciona la mejor experiencia para los desarrolladores. ¿Qué piensas?

Trabajar desde casa

Si eres del tipo al que le gusta resolver problemas por su cuenta o le gusta profundizar más, aquí hay algunas ideas.

estado del botón

El estilo del botón actual no tiene en cuenta los diferentes estados, como :hoverCreo que esta es una buena primera práctica.

/* You should really implement the following states
   but I will leave it as an exercise for you to 
   decide how to and what values to use.
*/
.btn:focus {
  /* If you elect to remove the outline, replace it
     with another proper affordance and research how
     to use transparent outlines to support windows
     high contrast
  */
}
.btn:hover { }
.btn:visited { }
.btn:active { }
.btn:disabled { }

Opciones

La mayoría de las bibliotecas de botones admiten muchas variaciones de botones, como el tamaño, la forma y el color. Prueba a crear más primary El modelo que ya tenemos, tal vez uno secondary La diversidad es una warning o success? Quizás filled y outline• Puede ver AgnosticUI de manera similar página de botones ocurrencia.

Propiedades personalizadas de CSS

Si no ha comenzado a usar propiedades CSS personalizadas, lo recomiendo encarecidamente. Puede consultar AgnosticUI primero común estiloConfío mucho en las propiedades personalizadas allí. Aquí hay algunos artículos excelentes sobre qué son los atributos personalizados y cómo aprovecharlos:

Escribe

No… yo no escribo, pero <button> elemental type Atributos. No cubrimos esto en nuestro componente, pero es posible extender el componente a otros usos con tipos válidos, como button, submit, y resetEsto es fácil de implementar y mejorará mucho la API del botón.

más ideas

Dios, hay tantas cosas que puedes hacer: agregar un enlace, convertirlo a TypeScript, auditar la accesibilidad, etc.

La implementación actual de Svelte tiene algunos supuestos muy libres, porque si es válido primary el modo no se transmite; esto crea una clase CSS basura:

mode ? `btn-${mode}` : "",

Puedes decir: «Bueno, .btn-garbage Porque la clase no es precisamente dañina. «Pero puede ser una buena idea tener un estilo de defensa cuando y donde sea posible.

peligros potenciales

Antes de continuar con este enfoque, debe tener en cuenta algunas cosas:

  • El posicionamiento de CSS basado en una estructura de marcado no funciona con la técnica basada en módulos CSS que se usa aquí.
  • Angular dificulta las técnicas de posicionamiento durante la construcción :host elemento Representa una vista de cada componente. Esto significa que tiene estos elementos adicionales entre sus plantillas o estructuras de etiquetas. Tienes que arreglar esto.
  • Copiar estilos en paquetes de espacio de trabajo es un poco antimodelo para algunas personas. Lo justifiqué porque creo que los beneficios superan los costos; además, no me siento tan mal con este enfoque cuando pienso en cómo los monorepositorios usan enlaces simbólicos y (menos resistente a fallas) levantamiento.
  • Debe suscribirse a la técnica de separación utilizada aquí, por lo que no hay CSS-in-JS.

Creo que todos los métodos de desarrollo de software tienen sus pros y sus contras y, en última instancia, debe decidir si compartir un archivo CSS es apropiado para usted o su proyecto en particular. Por supuesto, hay otras formas de hacerlo (como usar littlebuttons-css dependiendo del paquete npm) si es necesario.

en conclusión

Espero haber despertado su interés y ahora realmente desea crear una biblioteca con componentes independientes de la interfaz de usuario y/o un sistema de diseño. Tal vez tenga mejores ideas sobre cómo lograr esto. ¡Estaré encantado de escuchar sus pensamientos en los comentarios!

Estoy seguro de que has visto al venerable TodoMVC proyecto y cuántas implementaciones de marco se han creado para él. Además, ¿no sería bueno tener una biblioteca de componentes de interfaz de usuario disponibles para muchos marcos? Abra la interfaz de usuario Se han hecho grandes avances en la estandarización adecuada de la configuración predeterminada para los componentes de nuestra propia interfaz de usuario, pero creo que siempre tendremos que involucrarnos en algún momento. Por supuesto, pasar un año construyendo un sistema de diseño personalizado rápidamente cae en desgracia. y las empresas están cuestionando seriamente el reySe necesita algún tipo de andamiaje para hacer este trabajo.

Visión Interfaz de usuario agnóstica Existe una forma relativamente agnóstica de construir rápidamente sistemas de diseño que no estén limitados por un marco frontal específico. El proyecto es muy temprano y accesible, si se ve obligado a involucrarse, ¡estaré encantado de ayudarlo! Además, después de completar este tutorial, ¡ya está bastante familiarizado con el funcionamiento del proyecto!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

rtp live