# đ«đ· LitElement, sans Polymer CLI et avec de vrais fichiers CSS
Je continue mon apprentissage avec LitElement, mais jusqu'ici j'ai plus passé de temps sur 2 sujets liés aux paramétrages de mon application de départ:
- Comment intégrer facilement une webapp LitElement dans un projet Express ou Vert-x (ou tout autre framework)?
- Comment utiliser un framework css existant (comme BootStrap par exemple) avec LitElement, sachant que les frameworks css du moment n'ont pas été pensés pour jouer "simplement" avec le shadow dom?
Mais voyons déjà comment régler le premier sujet.
# Comment oublier la Polymer CLI
La Polymer CLI impose une structure de projet trĂšs spĂ©cifique, qui rend difficile l'intĂ©gration d'une WebApp LitElement dans un projet web de type Express ou Vert-x, ou alors il faut faire quelques gymnastiques "scriptiques" qui n'Ă©tonnent plus les "JavaScripteurs", mais qui me piquent les yeux et me donne le mal de mer. Sans compter le fait, que d'ĂȘtre obligĂ© de builder toute une application JavaScript juste pour vĂ©rifier que j'ai bien corrigĂ© une typo, je trouve ça un peu "sur dimensionnĂ©"...
# Mon problĂšme
désolé, c'est un peu long
Le plus souvent la structure de mes applications Express est la suivante:
.
âââ index.js # mon application Express
âââ public
â âââ index.html # ma webapp JavaScript
â âââ js
â âââ css
â âââ components
Si vous utilisez LitElement de façon "classique" (avec la Polymer CLI), vous aurez une structure qui va plutÎt ressembler à ceci:
.
âââ index.html # webapp LitElement
âââ css
âââ src
â âââ main-application.js
â âââ my-title.js
Et pour pouvoir l'intégrer dans un autre projet, il faut "builder", cela va générer votre web app dans build/es6-bundled
et ensuite vous pourrez mettre le contenu dans le répertoire public
de votre projet Express.
.
âââ build
â âââ es6-bundled
â âââ css
â âââ index.html
âââ index.html # webapp LitElement
âââ css
âââ src
â âââ main-application.js
â âââ my-title.js
En mode "dĂ©veloppement" pour avoir les modifications en "temps rĂ©el", la Polymer CLI fournit son propre serveur http đ... mais si votre webapp "appelle" des services de votre application web Express qui est elle mĂȘme un serveur http, ça se complique đĄ: vous devrez tenir compte du fait qu'en mode dĂ©veloppement vos appels ressembleront Ă :
fetch("http://my-express-application.test/api/say-hello")
cela va probablement vous obliger Ă rĂ©gler 2,3 choses liĂ©es Ă CORS đ
et en mode production Ă ceci:
fetch("/api/say-hello")
ben oui, on est dans le mĂȘme projet
"Tout ça pour dire" que c'est tout mĂȘme super c%%%%t en termes d'expĂ©rience utilisateur/dĂ©veloppeur pour afficher une page html...
# La solution
J'ai des notes partout (papier, fichier texte, ...) parce que je n'ai pas de mĂ©moire, et je suis retombĂ© sur l'une d'entre elles qui date du dernier DevFestLille (opens new window) ou Horacio Gonzalez (opens new window) (mon maĂźtre web components) m'avait expliquĂ© que Pika (opens new window) pouvait rĂ©gler mon problĂšme. Pika pour simplifier, c'est un outil qui prend toutes les dĂ©pendances des modules node pour les transformer en un fichier JavaScript que vous pouvez facilement inclure "Ă l'ancienne" dans votre page html, et vous n'aurez plus besoin de "builder" pendant le dĂ©veloppement đ
Cette excellente vidéo vous explique comment faire ceci en détail: https://www.youtube.com/watch?v=bCsS-M4a1rg&feature=youtu.be (opens new window), mais je vous explique tout de suite (de maniÚre courte) comment le faire pour répondre à "ma problématique"
# Initialisation de ma webapp pour faciliter ma vie de développeur
# Structure du projet
Je crée une structure de projet comme celle-ci:
.
âââ index.js # mon application Express
âââ public
â âââ index.html # ma webapp LitElement
â âââ js
â âââ css
â âââ components
â âââ MainApplication.js
â âââ AppTitle.js
â âââ AppSubtitle.js
âââ package.json
Le fichier package.json
va contenir tout ce dont j'ai besoin pour installer les outils nécessaires:
{
"name": "lit-simple",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@pika/web": "^0.5.3",
"express": "^4.17.1",
"lit-element": "^2.2.1"
}
}
Le fichier index.js
est mon application Express (donc mon serveur d'application, ou mon serveur http)
const express = require('express')
const app = express()
const port = process.env.PORT || 9090
app.use(express.static('public'))
app.use(express.json());
app.listen(port, () => console.log(
`đ listening on port ${port}!`
))
# Installation de départ
Dans votre répertoire projet, tapez les commandes suivantes:
npm install
npx @pika/web --dest /public/web_modules
c'est npx @pika/web --dest /public/web_modules
qui va faciliter votre vie de développeur. Cette commande va créer un répertoire web_modules
et générer dans ce répertoire le fichier lit-elements.js
directement utilisable dans votre page index.html
:
.
âââ index.js # mon application Express
âââ public
â âââ index.html # ma webapp LitElement
â âââ js
â âââ css
â âââ components
â â âââ MainApplication.js
â â âââ AppTitle.js
â â âââ AppSubtitle.js
â âââ web_modules
â â âââ import-map.json
â â âââ lit-element.js # tout est lĂ đ
âââ package.json
Nous pouvons maintenant Ă©crire nos composants
# Les composants LitElement
AppTitle.js
import { LitElement, html } from '../web_modules/lit-element.js'
export class AppTitle extends LitElement {
render() {
return html`<h1>đ live long and prosper đ</h1>`
}
}
customElements.define('app-title', AppTitle)
AppSubtitle.js
import { LitElement, html } from '../web_modules/lit-element.js'
export class AppSubtitle extends LitElement {
render() {
return html`<h2>made with 𧥠and đ”</h2>`
}
}
customElements.define('app-subtitle', AppSubtitle)
MainApplication.js
import { LitElement, html } from '../web_modules/lit-element.js'
import {} from './AppTitle.js'
import {} from './AppSubtitle.js'
export class MainApplication extends LitElement {
static get styles() { return [window.simpleCssResult] }
render() {
return html`
<div class="container">
<div>
<app-title></app-title>
<app-subtitle></app-subtitle>
</div>
</div>
`
}
}
customElements.define('main-application', MainApplication)
# La PAGE index.html
index.html
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Hello World!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main-application></main-application>
<script type="module" src="./components/MainApplication.js"></script>
</body>
</html>
# On lance le tout
Lancez un npm start
ou un node index.js
et allez sur http://localhost:9090/ (opens new window):
Vous pouvez modifier vos composants et faire un "refresh" de la page, les modifications seront prises en charge sans avoir aucun build Ă faire.
Maintenant, il faut rendre cette page un peu plus "jolie"
# Maintenant, la partie sur le CSS đ„¶
Alors, styler les webcomponents n'est pas forcĂ©ment la chose la plus facile Ă faire. Le projet LitElement recommande d'utiliser le systĂšme des "Constructable Stylesheets (opens new window)". Voir l'explication par ici: "Define styles in a static styles property (opens new window)", oĂč grosso modo il est expliquĂ© que les "styles statiques" sont plus performants:
We recommend using static styles for optimal performance. LitElement uses Constructable Stylesheets in browsers that support it, with a fallback for browsers that donât. Constructable Stylesheets allow the browser to parse styles exactly once and reuse the resulting Stylesheet object for maximum efficiency.
Pour faire court, cela signifie que vous allez "mettre votre css dans du js". Ok, c'est trÚs court, alors voici un exemple appliqué à LitElement:
Vous définissez vos styles dans un fichiers JavaScript (ou plusieurs), comme cela:
main-styles.js
import { css } from 'lit-element'
export const style = css`
.container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.title {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 100px;
color: #35495e;
letter-spacing: 1px;
}
`
donc littéralement, vous avez collé votre css dans une string et dans du JavaScript et
css
va vous transformer ça en un objetCSSResult
qui est une représentation "JavaScript" de votre feuille de style. (cf. https://lit-element.polymer-project.org/api/classes/lib_css_tag.cssresult.html (opens new window))
Et vous pourrez l'utiliser comme ceci dans vos composants LitElement:
import { LitElement, html } from 'lit-element'
import {style} from './main-styles.js' // #1
export class MyTitle extends LitElement {
static get styles() { return [style] } // #2
render(){
return html`<h1 class="title">đ Hello đ</h1>` // #3
}
}
customElements.define('my-title', MyTitle)
- vous importez votre style
- vous l'associez aux styles de votre composant
- vous l'utilisez
C'est facile, est avec import {style} from './main-styles.js'
vous pouvez facilement partager vos styles entre vos composants. Mais ...
# Nouveau problĂšme: comment j'utilise un framework css existant?
Alors vous pourriez dĂ©clarer les feuilles de styles Ă chaque fois pour chaque composant, dirextement dans leur code html (ce que j'ai fait Ă mes dĂ©buts avec LitElement), cela fonctionne, mais c'est loin d'ĂȘtre optimisĂ© (vous chargez les feuilles de styles Ă chaque fois đ±).
Mais les frameworks css existants ne sont pas au format Constructable Stylesheets đą et le faire vous mĂȘme Ă la main, comment dire ...
Vous avez différentes solutions:
- Vous pouvez utiliser la solution de Horacio Gonzalez (opens new window) qui fournit l'outillage pour cela par exemple pour Bootstrap avec granite-bootstrap (opens new window) ou pour d'autres frameworks css granite-lit-bulma (opens new window), granite-lit-spectre (opens new window)
- Ou vous pouvez ma solution (de faignasse), trĂšs courte et facile Ă mettre en oeuvre (moins optimisĂ©e au chargement, mais elle fonctionne bien tout de mĂȘme)
# Ma solution pour transformer directement les fichiers css en CSSResult
Tout d'abord, ajoutez un fichier simple.css
Ă votre projet:
.
âââ index.js # mon application Express
âââ public
â âââ index.html # ma webapp LitElement
â âââ js
â âââ css
â â âââ simple.css # ma âš feuille de styles
â âââ components
â â âââ MainApplication.js
â â âââ AppTitle.js
â â âââ AppSubtitle.js
â âââ web_modules
â â âââ import-map.json
â â âââ lit-element.js # tout est lĂ đ
âââ package.json
avec le contenu suivant:
body {
background-color: beige;
}
.container {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.title {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
display: block;
font-weight: 300;
font-size: 100px;
color: #35495e;
letter-spacing: 1px;
}
.subtitle {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
font-weight: 300;
font-size: 42px;
color: #526488;
word-spacing: 5px;
padding-bottom: 15px;
}
Et on va faire faire le travail par le navigateur via la page index.html
que nous allons modifier de la façon suivante:
ma méthode est largement inspirée de celle d'Horacio, mais je le fait dynamiquement
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Hello World!</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main-application></main-application>
<script type="module">
import { css, unsafeCSS } from './web_modules/lit-element.js' // #1
let styleSheetPromise = (cssPath, variableName) => fetch(cssPath) // #2
.then(response => response.text()) // #3
.then(cssRaw => {
let cssResult = css`${unsafeCSS(cssRaw)}` // #4
window[variableName] = cssResult // #5
})
.catch(error => console.log(`đĄ [${variableName}]`, error))
Promise.all([ // #6
styleSheetPromise("/css/simple.css", "simpleCssResult") // #7
])
.then(styleSheets => {
import("./components/MainApplication.js") // #8
})
</script>
</body>
</html>
- j'importe les éléments dont j'ai besoin pour retraiter ma feuille de stylme
- je crée une promise via une lambda à laquelle je passe le chemin du fichier css et un nom de variable qui me servira à "stocker" le résultat du traitement
- je "charge" le texte du fichier css
- je transforme le texte en un objet de type
CSSResult
- je stocke cet objet dans une variable globale
window.simpleCssResult
que je pourrais utiliser dans mes composants - j'appelle ma promise avec
Promise.all
, comme cela j'ai la possibilitĂ© de charger plusieurs feuilles de styles - je dĂ©finie la promise et la "passeĂ© Ă
Promise.all
- une fois la(les) feuille(s) de styles chargée(s) je charge mon composant principal
MainApplication
Il ne me reste plus qu'Ă modifier mes composants de la maniĂšre suivante pour prendre en compte les styles:
MainApplication.js
import { LitElement, html } from '../web_modules/lit-element.js'
import {} from './AppTitle.js'
import {} from './AppSubtitle.js'
export class MainApplication extends LitElement {
static get styles() { return [window.simpleCssResult] } // #1
render() { // #2
return html`
<div class="container">
<div>
<app-title></app-title>
<app-subtitle></app-subtitle>
</div>
</div>
`
}
}
customElements.define('main-application', MainApplication)
- je définis la propriété statique
styles
pour qu'elle retourne un tableau contenant ma variable globale:[window.simpleCssResult]
- je peux ensuite utiliser mes styles simplement
Et je modifie donc les autres composants de la mĂȘme façon:
AppTitle.js
import { LitElement, html } from '../web_modules/lit-element.js'
export class AppTitle extends LitElement {
static get styles() { return [window.simpleCssResult] }
render() {
return html`<h1 class="title">đ live long and prosper đ</h1>`
}
}
customElements.define('app-title', AppTitle)
AppSubtitle.js
import { LitElement, html } from '../web_modules/lit-element.js'
export class AppSubtitle extends LitElement {
static get styles() { return [window.simpleCssResult] }
render() {
return html`<h2 class="subtitle">made with 𧥠and đ”</h2>`
}
}
customElements.define('app-subtitle', AppSubtitle)
Et nous obtiendrons (aprĂšs un refresh) cette magnifique page:
C'est plus joli, mais ... Nous avons encore un problĂšme đ„”
# Nouveau problĂšme: il manque quelque chose...
Souvenez vous de cette partie du fichier css:
body {
background-color: beige;
}
Normalement la couleur de fond de ma page devrait ĂȘtre beige
, et elle est blanche! A aucun moment en fait le style de body
est appliqué. Je purrais faire un include du fichier css dans ma page index.html
, mais cela reviendrait Ă la charger 2 fois, ce qui n'est pas tip top.
# Modifions notre code de chargement
Pour pouvoir utiliser notre objet CSSResult
avec notre page index.html
, nous allons utiliser la propriété adoptedStyleSheets
qui permet d'appliquer Ă un document
un objet CSSStyleSheet
et il se trouve que notre objet CSSResult
a une propriété styleSheet
de type CSSStyleSheet
qui contient tous les éléménts de notre feuille de style.
Vous pouvez aller lire le § "Using Constructed StyleSheets" de ce document https://developers.google.com/web/updates/2019/02/constructable-stylesheets (opens new window)
Mais revenons Ă nos modifications, qui tiennent en l'ajout de 2 lignes uniquement:
<script type="module">
import { css, unsafeCSS } from './web_modules/lit-element.js'
let styleSheetPromise = (cssPath, variableName) => fetch(cssPath)
.then(response => response.text())
.then(cssRaw => {
let cssResult = css`${unsafeCSS(cssRaw)}`
window[variableName] = cssResult
return cssResult.styleSheet // #1
})
.catch(error => console.log(`đĄ [${variableName}]`, error))
Promise.all([
styleSheetPromise("/css/simple.css", "simpleCssResult")
])
.then(styleSheets => { // #2
document.adoptedStyleSheets = styleSheets // #3
import("./components/MainApplication.js")
})
</script>
- Ma promise va retourner une valeur, qui est la propriété
styleSheet
decssResult
styleSheets
est unArray
qui contient tous les objets de typestyleSheet
retournés par les promises- j'applique le tableau
styleSheets
de style au document courant
Et si vous sauvegardez et raffraichissez, vous avez enfin un fond beige
pour votre page:
Et ce systÚme fonctionne trÚs bien avec Bootstrap (je vous donnerais un lien vers une démo à la fin de l'article).
# Un dernier problĂšme đ€... Facile Ă rĂ©gler đ
J'avais oublié de vous dire, adoptedStyleSheets
ne fonctionne que dans Chrome (Ă partir de la version 73).
Heureusement, il existe un polyfill pour cela: https://github.com/calebdwilliams/construct-style-sheets#readme (opens new window) et il fonctionne trĂšs bien.
Il suffit d'insérer <script src="./js/adoptedStyleSheets.js"></script>
juste avant votre script de chargement, et l'affaire est dans le sac (cela fonctionnera mĂȘme avec Safari).
# Quelques ressources
Avant de nous quitter
# DĂ©mos
- le code source des exemples utilisés pour cet article: https://gitlab.com/k33g/lit-simple (opens new window)
et le mirroring GitHub https://github.com/k33g/lit-simple (opens new window)
- la mĂȘme choste mais avec Bootstrap: https://gitlab.com/k33g/lit-bootstrap (opens new window)
et le mirroring GitHub https://github.com/k33g/lit-bootstrap (opens new window)
Ă venir: des starterkits avec probablement Bulma, Spectre, Semantic UI, ...
# Quelques pages Ă lire
- Encapsulating Style and Structure with Shadow DOM (opens new window)
- Styling a Web Component (opens new window)
đ bonne fin de week-end et bon courage pour lundi đ
Last Articles
- đ«đ· Type Result en Kotlin | 2020-10-31 | Kotlin
- đ«đ· Type Result en Kotlin | 2020-10-31 | Kotlin
- đŹđ§ Every GitLab Page deserves a real CI/CD | 2020-07-23 | GitLab CI
- đ«đ· Lit-Element, commencer doucement | 2020-07-20 | WebComponent
- đŹđ§ Build quickly and host easily your Docker images with GitLab and GitLab CI | 2020-06-02 | GitLab CI
- đŹđ§ Deploy quickly on Clever Cloud with GitLab CI | 2020-05-31 | GitLab CI
- đ«đ· Borg Collective, mes jouets pour apprendre Knative | 2020-05-30 | Knative
- đŹđ§ Borg Collective, Toys to learn Knative | 2020-05-30 | Knative
- đ«đ· M5Stack, une petit device IOT bien sympathique, programmable en Python | 2020-05-09 | IOT
- đ«đ· Knative, l'outil qui rend Kubernetes sympathique | 2020-05-02 | kubernetes