Skip to content

Commit 988752e

Browse files
authored
Merge pull request #1005 from dnum-mi/develop
Develop
2 parents f606c76 + b08d00c commit 988752e

34 files changed

+460
-90
lines changed

.github/workflows/run-tests.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ jobs:
3434
run: pnpm check-exports-ci
3535
- name: Test
3636
run: pnpm test
37-
- name: Install Playwright dependencies
37+
- name: Install Playwright with dependencies
3838
run: pnpx playwright install --with-deps
3939
- name: Install Playwright
4040
run: pnpx playwright install
41+
4142
- name: Build Storybook
4243
run: pnpm build-storybook --quiet
4344
- name: Serve Storybook and run tests

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,10 @@ $RECYCLE.BIN/
320320
*.lnk
321321

322322
# End of https://www.toptal.com/developers/gitignore/api/node,visualstudiocode,intellij,windows,macos,linux
323+
323324
stats.html
324325
types/
325-
326+
meta-dts/
326327

327328
# Vitepress
328329
.vitepress/dist

docs/guide/icones.md

+157-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Ci-dessous un exemple :
2020
<<< ../docs-demo/IconesOfficielles.vue [Code de la démo]
2121
:::
2222

23-
##  Utiliser les icônes dans les composants de VueDsfr
23+
## Utiliser les icônes dans les composants de VueDsfr
2424

2525
Plusieurs composants (`DsfrButton`, `DsfrBadge`, `DsfrCallout`...) ont la prop `icon` qui permet d’ajouter une icône.
2626

@@ -55,16 +55,14 @@ exemple :
5555
</template>
5656
```
5757

58-
Cependant, si le préfix est lui-même sans tiret `-`, alors l’écriture tout en kebab-case est acceptée :
58+
Cependant, si le préfixe est lui-même sans tiret `-`, alors l’écriture tout en kebab-case est acceptée :
5959

6060
```vue
6161
<template>
6262
Nom d’icône accepté : <VIcon name="ri-close-line" />
6363
</template>
6464
```
6565

66-
Ainsi, si vous utilisiez jusqu’ici que des collections remix icon (`ri`) et bootstrap icons (`bi`) vous ne devriez rien avoir à changer.
67-
6866
:::
6967

7068
::: info Les collections disponibles
@@ -94,11 +92,165 @@ import { VIcon } from '@gouvminint/vue-dsfr'
9492
9593
```
9694

95+
## Éviter les appels réseaux (optionnel - pour les applications internes)
96+
97+
Si vous développez des applications destinées à des agents internes avec potentiellement des accès internet réduits, il
98+
est possible que les appels vers l’API iconify soient bloqués. Vous voudrez donc éviter ces appels réseaux.
99+
100+
Dans ce but, depuis la version [7.3.0](https://github.com/dnum-mi/vue-dsfr/releases/tag/v7.3.0), la dépendance `@iconify/vue`
101+
n’est plus incluse dans la bibliothèque, et doit être installée dans votre application.
102+
103+
Avec cette modification, il est possible d’ajouter des collections d’icônes qui ne feront pas d’appels réseaux.
104+
105+
### TL;DR;
106+
107+
- créer un fichier `scripts/icons.js` dans votre projet avec un contenu de cette forme :
108+
```js
109+
// @ts-check
110+
import { icons as mdiCollection } from '@iconify-json/mdi'
111+
import { icons as riCollection } from '@iconify-json/ri'
112+
113+
/**
114+
* Liste de nom d’icônes **sans** le préfixe de la collection Remix Icons qui sont utilisées dans l’application
115+
* @type {string[]}
116+
*/
117+
const riIconNames = [
118+
'flag-line',
119+
'home-4-line',
120+
'question-mark',
121+
]
122+
123+
/**
124+
* Liste de nom d’icônes **sans** le préfixe de la collection Material Design Icons qui sont utilisées dans l’application
125+
* @type {string[]}
126+
*/
127+
const mdiIconNames = [
128+
'account-heart',
129+
'account-key',
130+
]
131+
132+
/**
133+
* Liste de tuples [collectionDIcônes, tableauDeNomsDIcônesUtiliséesDansLApplication]
134+
* @type {[import('@iconify/vue').IconifyJSON, string[]][]}
135+
*/
136+
export const collectionsToFilter = [
137+
[riCollection, riIconNames],
138+
[mdiCollection, mdiIconNames],
139+
]
140+
```
141+
N.B. : l’exemple ci-dessus montre comment utiliser les icônes `'ri-flag-line'`, `'ri-home-4-line'`, `'ri-question-mark'` de la collection
142+
remix icons (`ri`) et les icônes `'mdi-account-heart'` et `'mdi-account-key'` de la collection Material Design Icons (`mdi`).
143+
- ajouter un script `"icons"` dans le `package.json` de votre application avec cette commande:
144+
`"vue-dsfr-icons -s scripts/icons.js -t src/icon-collections.ts"`
145+
- lancer le script `icons` (e.g. : `npm run icons` si vous utilisez `npm` comme gestionnaire de paquet) à chaque fois
146+
que vous modifiez le fichier `scripts/icons.js`. Pour le fichier `scripts/icons.js`, cela générera le fichier `src/icon-collections.ts` suivant :
147+
```ts
148+
import type { IconifyJSON } from '@iconify/vue'
149+
150+
const collections: IconifyJSON[] = [{ prefix: 'ri', icons: { 'flag-line': { body: '<path fill="currentColor" d="M12.382 3a1 1 0 0 1 .894.553L14 5h6a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-6.382a1 1 0 0 1-.894-.553L12 16H5v6H3V3zm-.618 2H5v9h8.236l1 2H19V7h-6.236z"/>' }, 'home-4-line': { body: '<path fill="currentColor" d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1m-6-2h5V9.157l-6-5.454l-6 5.454V19h5v-6h2z"/>' }, 'question-mark': { body: '<path fill="currentColor" d="M12 19a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3m0-17a6 6 0 0 1 6 6c0 2.165-.753 3.29-2.674 4.923C13.399 14.56 13 15.297 13 17h-2c0-2.474.787-3.695 3.031-5.601C15.548 10.11 16 9.434 16 8a4 4 0 0 0-8 0v1H6V8a6 6 0 0 1 6-6"/>' } }, width: 24, height: 24 }, { prefix: 'mdi', icons: { 'account-heart': { body: '<path fill="currentColor" d="M15 14c-2.7 0-8 1.3-8 4v2h16v-2c0-2.7-5.3-4-8-4m0-2a4 4 0 0 0 4-4a4 4 0 0 0-4-4a4 4 0 0 0-4 4a4 4 0 0 0 4 4M5 15l-.6-.5C2.4 12.6 1 11.4 1 9.9c0-1.2 1-2.2 2.2-2.2c.7 0 1.4.3 1.8.8c.4-.5 1.1-.8 1.8-.8C8 7.7 9 8.6 9 9.9c0 1.5-1.4 2.7-3.4 4.6z"/>' }, 'account-key': { body: '<path fill="currentColor" d="M11 10v2H9v2H7v-2H5.8c-.4 1.2-1.5 2-2.8 2c-1.7 0-3-1.3-3-3s1.3-3 3-3c1.3 0 2.4.8 2.8 2zm-8 0c-.6 0-1 .4-1 1s.4 1 1 1s1-.4 1-1s-.4-1-1-1m13 4c2.7 0 8 1.3 8 4v2H8v-2c0-2.7 5.3-4 8-4m0-2c-2.2 0-4-1.8-4-4s1.8-4 4-4s4 1.8 4 4s-1.8 4-4 4"/>' } }, width: 24, height: 24 }]
151+
export default collections
152+
```
153+
154+
ou formatté autrement :
155+
156+
```ts
157+
import type { IconifyJSON } from '@iconify/vue'
158+
159+
const collections: IconifyJSON[] = [{
160+
prefix: 'ri',
161+
icons: {
162+
'flag-line': { body: '<path fill="currentColor" d="M12.382 3a1 1 0 0 1 .894.553L14 5h6a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-6.382a1 1 0 0 1-.894-.553L12 16H5v6H3V3zm-.618 2H5v9h8.236l1 2H19V7h-6.236z"/>' },
163+
'home-4-line': { body: '<path fill="currentColor" d="M19 21H5a1 1 0 0 1-1-1v-9H1l10.327-9.388a1 1 0 0 1 1.346 0L23 11h-3v9a1 1 0 0 1-1 1m-6-2h5V9.157l-6-5.454l-6 5.454V19h5v-6h2z"/>' },
164+
'question-mark': { body: '<path fill="currentColor" d="M12 19a1.5 1.5 0 1 1 0 3a1.5 1.5 0 0 1 0-3m0-17a6 6 0 0 1 6 6c0 2.165-.753 3.29-2.674 4.923C13.399 14.56 13 15.297 13 17h-2c0-2.474.787-3.695 3.031-5.601C15.548 10.11 16 9.434 16 8a4 4 0 0 0-8 0v1H6V8a6 6 0 0 1 6-6"/>' },
165+
},
166+
width: 24,
167+
height: 24,
168+
}, {
169+
prefix: 'mdi',
170+
icons: {
171+
'account-heart': { body: '<path fill="currentColor" d="M15 14c-2.7 0-8 1.3-8 4v2h16v-2c0-2.7-5.3-4-8-4m0-2a4 4 0 0 0 4-4a4 4 0 0 0-4-4a4 4 0 0 0-4 4a4 4 0 0 0 4 4M5 15l-.6-.5C2.4 12.6 1 11.4 1 9.9c0-1.2 1-2.2 2.2-2.2c.7 0 1.4.3 1.8.8c.4-.5 1.1-.8 1.8-.8C8 7.7 9 8.6 9 9.9c0 1.5-1.4 2.7-3.4 4.6z"/>' },
172+
'account-key': { body: '<path fill="currentColor" d="M11 10v2H9v2H7v-2H5.8c-.4 1.2-1.5 2-2.8 2c-1.7 0-3-1.3-3-3s1.3-3 3-3c1.3 0 2.4.8 2.8 2zm-8 0c-.6 0-1 .4-1 1s.4 1 1 1s1-.4 1-1s-.4-1-1-1m13 4c2.7 0 8 1.3 8 4v2H8v-2c0-2.7 5.3-4 8-4m0-2c-2.2 0-4-1.8-4-4s1.8-4 4-4s4 1.8 4 4s-1.8 4-4 4"/>' },
173+
},
174+
width: 24,
175+
height: 24,
176+
}]
177+
export default collections
178+
```
179+
- ajouter les collections dans votre point d’entrée (généralement `src/main.ts`) :
180+
```ts
181+
// (...)
182+
import collections from './icon-collections.js'
183+
// (...)
184+
185+
for (const collection of collections) {
186+
addCollection(collection)
187+
}
188+
189+
// (...)
190+
```
191+
192+
### Plus d’explication pour éviter les appels réseaux
193+
194+
En interne, depuis la version [`6.0.0`](https://github.com/dnum-mi/vue-dsfr/releases/tag/v6.0.0) VueDsfr utilise [iconify](https://iconify.design/docs/icon-components/vue).
195+
Pour comprendre la section précédente, il faut comprendre comment fonctionne iconify.
196+
197+
#### Iconify
198+
199+
::: info
200+
201+
Veuillez consulter la [documentation officielle de @iconify/vue](https://iconify.design/docs/icon-components/vue) pour plus de détails.
202+
203+
:::
204+
205+
Le principe de iconify est de ne pas inclure les icônes dans le bundle et de faire un appel réseau en arrière-plan (XHR ou fetch) pour récupérer les SVG des icônes utilisées à la demande, c’est-à-dire dès qu’un composant qui utilise des icônes iconify est rendu dans le DOM.
206+
207+
Or ces appels API nécessitent pour l’utilisateur de l’application d’avoir accès à internet, car par défaut l’API utilisée pour récupérer les icônes est celle d’iconify, sur internet.
208+
209+
Il est possible d’[héberger soi-même un serveur API](https://iconify.design/docs/api/#hosting-api) et de dire à iconify d’utiliser cette API. C’est néanmoins compliqué.
210+
211+
Il est possible aussi d’utiliser la fonction [`addCollection(collection: IconifyJSON)` exposée par `@iconify/vue`](https://iconify.design/docs/icon-components/vue/add-collection.html#iconify-for-vue-function-addcollection) pour inclure des icônes et faire en sorte qu’[aucun appel réseau ne soit effectué](https://iconify.design/docs/icon-components/vue/add-collection.html#api-provider).
212+
213+
Le plus simple, c’est donc d’utiliser un sous-ensemble d’une collection existante (par exemple `@iconify-json/ri` pour Remix Icons) en créant une nouvelle collection et d’ajouter cette collection. C’est ce que fait la fonction `filterIcons (collection: import('@iconify/vue').IconifyJSON, iconNames: string[])` exposée par `@gouvminint/vue-dsfr/meta`. Vous ne voudrez sans doute pas l’utiliser directement.
214+
215+
Cette fonction `filterIcons()` est utilisée par la fonction `createCustomCollectionFile (sourcePath: string, targetPath: string)` aussi exposée par `@gouvminint/vue-dsfr/meta`, dont voici la partie importante :
216+
217+
```ts
218+
// (...)
219+
220+
const collectionsToFilter = await import(sourcePath).then(({ collectionsToFilter }) => collectionsToFilter)
221+
222+
const collections = collectionsToFilter.map(tuple => filterIcons(...tuple))
223+
224+
const code = `import type { IconifyJSON } from '@iconify/vue'
225+
const collections: IconifyJSON[] = ${JSON.stringify(collections)}
226+
export default collections`
227+
228+
// (...)
229+
```
230+
231+
::: tip
232+
233+
- `sourcePath` est le chemin vers le fichier qui contient le code listé plus dans le fichier `scripts/icons.js`
234+
- `targetPath` est le chemin vers le fichier qui contiendra le code généré et appelé plus haut `src/icon-collections.js`
235+
236+
Libre à vous d’adapter les chemins et les noms de fichiers, veillez simplement à modifier en fonction le script `"icons"` de `package.json` et l’import dans votre fichier d’entrée (souvent `src/main.ts`).
237+
238+
:::
239+
240+
::: tip
241+
242+
Nous vous invitons à regarder les fichiers suivants :
243+
244+
- [`meta/custom-icon-collections-creator.js`](https://github.com/dnum-mi/vue-dsfr/blob/v7.2.0/meta/custom-icon-collections-creator.js)
245+
- [`meta/custom-icon-collections-creator-bin.js`](https://github.com/dnum-mi/vue-dsfr/blob/v7.2.0/meta/custom-icon-collections-creator-bin.js) (celui qui est aliasé en binaire `vue-dsfr-icons` et qui utilise `custom-icon-collections-creator.js`)
246+
247+
:::
248+
97249
## Pour Nuxt 3
98250

99251
Veillez simplement à utiliser la prop `ssr` à `true`.
100252

101-
Plus de détails dans la [documentation officielle de @iconify/vue](https://iconify.design/docs/icon-components/vue/#ssr-attribute).
253+
Plus de détails dans la [documentation officielle de @iconify/vue pour le SSR](https://iconify.design/docs/icon-components/vue/#ssr-attribute).
102254

103255
<script lang="ts" setup>
104256
import IconesOfficielles from '../docs-demo/IconesOfficielles.vue'

meta/autoimport-preset.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Preset Autoimport pour le plugin unplugin-auto-import pour les composables de VueDsfr
3+
*/
4+
export const vueDsfrAutoimportPreset = Object.freeze({
5+
from: '@gouvminint/vue-dsfr',
6+
imports: Object.freeze([
7+
'useScheme',
8+
'useTabs',
9+
]),
10+
})

meta/component-resolver.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Component resolver pour le plgin unplugin-vue-components pour les composants VueDsfr
3+
*
4+
* @function
5+
* @param {string} componentName - Nom du composant à chercher
6+
*
7+
* @returns {{ name: string, from: string } | undefined} Objet de retour pour le plugin unplugin-vue-components
8+
*/
9+
export const vueDsfrComponentResolver = (componentName) => {
10+
if (componentName.startsWith('Dsfr') || componentName === 'VIcon') {
11+
return {
12+
name: componentName,
13+
from: '@gouvminint/vue-dsfr',
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env node
2+
3+
import path from 'node:path'
4+
import process from 'node:process'
5+
6+
import { Command } from 'commander'
7+
import chalk from 'chalk'
8+
9+
import { createCustomCollectionFile } from './custom-icon-collections-creator.js'
10+
11+
const program = new Command()
12+
13+
program
14+
.option('-s, --source <filepath>', 'Chemin vers le fichier de tuples [IconifyJSON, string[]]')
15+
.option('-t, --target <filepath>', 'Chemin vers le fichier destination (src/icons.ts par défaut)')
16+
.parse(process.argv)
17+
18+
const options = program.opts()
19+
20+
if (options.source && options.target) {
21+
createCustomCollectionFile(path.resolve(process.cwd(), options.source), path.resolve(process.cwd(), options.target))
22+
console.log(chalk.green('Les icônes ont été générées')) // eslint-disable-line no-console
23+
}
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// @ts-check
2+
/* eslint-disable no-console */
3+
import childProcess from 'node:child_process'
4+
import fs from 'node:fs/promises'
5+
import path from 'node:path'
6+
import process from 'node:process'
7+
import util from 'node:util'
8+
9+
const execPromise = util.promisify(childProcess.exec)
10+
11+
/**
12+
* Filtre les icônes d'une collection en fonction d'une liste de noms.
13+
* @function
14+
*
15+
* @param {string} sourcePath - Fichier source
16+
* @param {string} targetPath - Fichier destination
17+
*
18+
*/
19+
export async function createCustomCollectionFile (sourcePath, targetPath) {
20+
/**
21+
* @type {[import('@iconify/vue').IconifyJSON, string[]][]}
22+
*/
23+
const collectionsToFilter = await import(sourcePath).then(({ collectionsToFilter }) => collectionsToFilter)
24+
25+
const collections = collectionsToFilter.map(tuple => filterIcons(...tuple))
26+
27+
const code = `import type { IconifyJSON } from '@iconify/vue'
28+
const collections: IconifyJSON[] = ${JSON.stringify(collections)}
29+
export default collections`
30+
31+
await fs.writeFile(targetPath, code)
32+
33+
await runShellCommand(`npx eslint ${path.resolve(process.cwd(), targetPath)} --fix`)
34+
}
35+
36+
/**
37+
* Fonctions utilitaires
38+
*/
39+
40+
/**
41+
* Filtre les icônes d'une collection en fonction d'une liste de noms.
42+
* @function
43+
*
44+
* @param {import('@iconify/vue').IconifyJSON} collection - La collection d'icônes.
45+
* @param {string[]} iconNames - La liste des noms d'icônes à conserver.
46+
*
47+
* @returns {import('@iconify/vue').IconifyJSON} - Une nouvelle collection filtrée.
48+
*/
49+
export function filterIcons (collection, iconNames) {
50+
const icons = Object.fromEntries(Object.entries(collection.icons).filter(([key]) => {
51+
return iconNames.includes(key)
52+
}))
53+
const { lastModified, aliases, provider, ...useful } = collection
54+
return {
55+
...useful, // prefix, width, height
56+
icons,
57+
}
58+
}
59+
60+
/**
61+
* Lance une commande shell.
62+
* @function
63+
*
64+
* @param {string} command - La commande shell à lancer
65+
*
66+
* @returns {Promise<undefined>} - Une nouvelle collection filtrée.
67+
*/
68+
export async function runShellCommand (command) {
69+
try {
70+
const { stdout, stderr } = await execPromise(command)
71+
if (stderr) {
72+
console.error('Erreur :', stderr)
73+
}
74+
console.log(stdout)
75+
} catch (error) {
76+
console.error('Erreur d’exécution :', error)
77+
}
78+
}

meta/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './autoimport-preset.js'
2+
export * from './component-resolver.js'
3+
export * from './custom-icon-collections-creator.js'

0 commit comments

Comments
 (0)