A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 41KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Ébauche de workflow Gulp : tâches courantes, unCSS, includes HTML et critical-CSS (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://www.alsacreations.com/tuto/lire/1685-ebauche-de-workflow-gulp-taches-uncss-includes-critical-css.html">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Ébauche de workflow Gulp : tâches courantes, unCSS, includes HTML et critical-CSS (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://www.alsacreations.com/tuto/lire/1685-ebauche-de-workflow-gulp-taches-uncss-includes-critical-css.html">Source originale du contenu</a></h3>
  445. <p>
  446. <em>Préambule : cet article part du principe que vous n’êtes pas totalement étranger aux notions et outils tels que LESS, NodeJS, Gulp ni à la ligne de commande, il ne s'agit d'un tutoriel de découverte de ces outils mais d'usage en environnement professionnel.</em></p>
  447. <h3 id="introduction">
  448. Introduction</h3>
  449. <p>
  450. Au sein de l’agence web <a href="http://www.alsacreations.fr">Alsacreations.fr</a>, nous avons instauré un processus de travail (un “workflow”) composé de langage <a href="http://lesscss.org/">LESS</a>, compilé avec des tâches <a href="http://gulpjs.com/">Gulp</a> et saupoudré de conventions internes et de <a href="http://knacss.com/">KNACSS</a>.</p>
  451. <p>
  452. Le site <a href="http://goetter.fr">goetter.fr</a> est mon site personnel, mon bac à sable et mon espace de test pour moults expériences web.<br/>
  453. La version actuelle du site est très artisanale et manuelle. J’ai voulu tester la mise en place d’un workflow automatisé afin de profiter de toutes les tâches courantes (compilation LESS, minification, préfixes CSS automatiques, concaténation des fichiers JS, optimisation des images) tout en testant deux fonctionnalités intéressantes : les includes de fichiers en HTML ainsi que l’outil de perf web prôné par Google : “critical CSS”.</p>
  454. <p>
  455. Cela m’a pris une journée de reprendre tout le workflow de mon site perso. Je vous la partage ici…</p>
  456. <p class="center">
  457. <img alt="goetter.fr" src="/xmedia/doc/full/goetterfr.png"/></p>
  458. <h3 id="lenvironnement-gulp">
  459. L’environnement Gulp</h3>
  460. <p>
  461. Dans la version précédente de mon site personnel, mon dossier de travail était le même que celui de production : je faisais simplement attention à ne pas mettre en prod les fichiers inutiles (CSS non minifié, images non optimisées) et j'employais le logiciel Prepros.io pour compiler mes fichiers.</p>
  462. <p>
  463. Avec le choix de Gulp, mon processus de travail est aujourd'hui différent dans la mesure où je scinde deux environnements distincts :</p>
  464. <ul>
  465. <li>
  466. tous mes fichiers en développement sont dans <code>_src/</code></li>
  467. <li>
  468. les fichiers compilés, à envoyer en prod sont tous dans <code>_dist/</code></li>
  469. </ul>
  470. <p>
  471. Gulp est un outil d'automatisation de tâches (un "task manager") basé sur <a href="https://nodejs.org/">nodeJS</a>. Une fois NodeJS en place, Gulp s'installe en ligne de commande :</p>
  472. <pre class="code">
  473. <code>npm install gulp -g</code></pre>
  474. <p>
  475. Pour fonctionner, Gulp nécessite deux fichiers de travail :</p>
  476. <ul>
  477. <li>
  478. <code>package.json</code> (contient tous les plugins nécessaires aux tâches à éxécuter)</li>
  479. <li>
  480. <code>gulpfile.js</code> (exécute les tâches)</li>
  481. </ul>
  482. <p class="remarque">
  483. Pour rappel, je ne prévois pas de rentrer dans les détails de ce que sont gulp, <code>gulpfile.js</code> et <code>package.json</code> car cet article n'est pas une introduction à ces outils. Si ces termes vous sont totalement étrangers, je vous invite à vous renseigner sur les très nombreux écrits sur ce sujet (on me souffle dans l'oreillette qu'il va prochainement y avoir un article à ce sujet sur Alsacréations).</p>
  484. <p>
  485. J’utilise à présent Gulp pour réaliser toutes mes tâches courantes (pas bien complexes mais souvent répétitives), et voici quels sont les plugins prévus au sein de mon fichier <code>package.json</code> :</p>
  486. <ul>
  487. <li>
  488. “gulp”</li>
  489. <li>
  490. “gulp-less” (compilation LESS vers CSS)</li>
  491. <li>
  492. “gulp-autoprefixer” (ajout des préfixes nécessaires)</li>
  493. <li>
  494. “gulp-minify-css” (minification CSS)</li>
  495. <li>
  496. “gulp-rename” (renommage en “.min.css”)</li>
  497. <li>
  498. “gulp-uglify” (minification JS)</li>
  499. <li>
  500. “gulp-imagemin” (optimisation des images)</li>
  501. <li>
  502. “gulp-html-extend” (includes HTML)</li>
  503. <li>
  504. “gulp-uncss” (suppression des CSS non utilisés)</li>
  505. <li>
  506. “critical” (CSS inline)</li>
  507. </ul>
  508. <h3 id="les-tâches-courantes">
  509. Les tâches courantes</h3>
  510. <p>
  511. Toutes les tâches suivantes sont inclues dans mon fichier <code>gulpfile.js</code> et exécutées sur demande ou automatiquement (<code>watch</code>) en ligne de commande.</p>
  512. <h4 id="tâches-css">
  513. tâches CSS</h4>
  514. <ul>
  515. <li>
  516. LESS</li>
  517. <li>
  518. autoprefixer</li>
  519. <li>
  520. minification</li>
  521. </ul>
  522. <p>
  523. La tâche <strong>“css”</strong> du fichier <code>gulpfile.js</code> :</p>
  524. <pre class="code">
  525. <code class="javascript"><span class="hljs-comment">// Tâche "css" = LESS + autoprefixer + minify</span>
  526. gulp.task(<span class="hljs-string">'css'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> {</span>
  527. <span class="hljs-keyword">return</span> gulp.src(source + <span class="hljs-string">'/assets/css/styles.less'</span>)
  528. .pipe(less())
  529. .pipe(autoprefixer())
  530. .pipe(rename({
  531. suffix: <span class="hljs-string">'.min'</span>
  532. }))
  533. .pipe(minify())
  534. .pipe(gulp.dest(prod + <span class="hljs-string">'/assets/css/'</span>));
  535. });</code></pre>
  536. <p>
  537. <strong>Explications :</strong> mon fichier de travail (<code>styles.less</code>) est tout d'abord compilé en CSS classique, puis préfixé, puis renommé en <code>.min.css</code> avant d'être minifié et placé dans mon dossier de production.</p>
  538. <h4 id="tâches-javascript">
  539. tâches JavaScript</h4>
  540. <ul>
  541. <li>
  542. concaténation</li>
  543. <li>
  544. minification (uglify)</li>
  545. </ul>
  546. <p>
  547. La tâche <strong>“js”</strong> du fichier <code>gulpfile.js</code> :</p>
  548. <pre class="code">
  549. <code class="javascript"><span class="hljs-comment">// Tâche "js" = uglify + concat</span>
  550. gulp.task(<span class="hljs-string">'js'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> {</span>
  551. <span class="hljs-keyword">return</span> gulp.src(source + <span class="hljs-string">'/assets/js/*.js'</span>)
  552. .pipe(uglify())
  553. .pipe(concat(<span class="hljs-string">'global.min.js'</span>))
  554. .pipe(gulp.dest(prod + <span class="hljs-string">'/assets/js/'</span>));
  555. });</code></pre>
  556. <p>
  557. <strong>Explications :</strong><span> tous les fichiers JavaScript du dossier  </span><code>/js/</code><span> sont minifiés pour concaténés (regroupés) en un seul fichier <code>global.min.js</code></span><span> et placé dans mon dossier de production.</span></p>
  558. <h4 id="tâches-doptimisation-dimages">
  559. tâches d’optimisation d’images</h4>
  560. <ul>
  561. <li>
  562. optimisation PNG, JPG, SVG</li>
  563. </ul>
  564. <p>
  565. La tâche <strong>“img”</strong> du fichier <code>gulpfile.js</code> :</p>
  566. <pre class="code">
  567. <code class="javascript"><span class="hljs-comment">// Tâche "img" = Images optimisées</span>
  568. gulp.task(<span class="hljs-string">'img'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span>
  569. <span class="hljs-keyword">return</span> gulp.src(source + <span class="hljs-string">'/assets/img/*.{png,jpg,jpeg,gif,svg}'</span>)
  570. .pipe(imagemin())
  571. .pipe(gulp.dest(prod + <span class="hljs-string">'/assets/img'</span>));
  572. });</code></pre>
  573. <h3>
  574. unCSS pour alléger les fichiers</h3>
  575. <p>
  576. <strong>unCSS</strong> est un outil (disponible en versions <a href="https://www.npmjs.com/package/grunt-uncss">grunt</a> et <a href="https://www.npmjs.com/package/gulp-uncss">gulp</a>) permettant de supprimer les styles CSS non utilisés au sein de votre projet.</p>
  577. <p>
  578. Il s'agit d'une véritable bénédiction lorsque vous travaillez à l'aide de frameworks CSS tels que Bootstrap ou KNACSS car il va réduire drastiquement le poids de vos fichiers CSS :</p>
  579. <blockquote class="twitter-tweet" lang="fr">
  580. <p dir="ltr" lang="en">
  581. If you're building a <a href="https://twitter.com/hashtag/twitterbootstrap?src=hash">#twitterbootstrap</a> page with <a href="https://twitter.com/hashtag/grunt?src=hash">#grunt</a> or <a href="https://twitter.com/hashtag/gulp?src=hash">#gulp</a>, consider using <a href="https://twitter.com/hashtag/uncss?src=hash">#uncss</a>. &gt; CSS file reduced from 150kB to 11kB! <a href="https://twitter.com/hashtag/Javascript?src=hash">#Javascript</a></p>
  582. — Sascha Sambale (@mastixmc) <a href="https://twitter.com/mastixmc/status/575676418266390529">11 Mars 2015</a></blockquote>
  583. <p>
  584. unCSS fonctionne bien évidemment sur un ensemble de fichiers et fait très bien son boulot... à condition que vous n'ayez oublié aucun fichier dans la boucle !</p>
  585. <p>
  586. Sur le site de <strong>goetter.fr</strong>, la feuille de style CSS minifiée est passée <strong>de 16ko à 10ko</strong> après l'action de unCSS, soit une réduction de 40%. Imaginez le résultat sur des gros fichiers de centaines de ko. Le pire est que le site fonctionne toujours, rien n'est cassé :)</p>
  587. <p>
  588. unCSS a été intégrée dans ma tâche "css" ainsi :</p>
  589. <pre class="code">
  590. <code class="javascript">// Tâche "css" = LESS + autoprefixer <strong>+ unCSS</strong> + minify
  591. gulp.task('css', function() {
  592. return gulp.src(source + '/assets/css/*.less')
  593. .pipe(less())
  594. .pipe(autoprefixer())
  595. <strong>.pipe(uncss({</strong>
  596. <strong>html: [source + '/{,_includes/}/{,conf/}/{,livres/}*.html']</strong>
  597. <strong>}))</strong>
  598. .pipe(rename({
  599. suffix: '.min'
  600. }))
  601. .pipe(minify())
  602. .pipe(gulp.dest(prod + '/assets/css/'));
  603. });</code></pre>
  604. <p>
  605. <span><strong>Explications :</strong> tous les fichiers HTML à la racine du site ainsi que ceux situés dans les dossiers </span><code>/conf</code><span>,</span><span> </span><code>/livres </code><span>et </span><code>/_include</code><span> seront testés par un navigateur fantôme. Tous les styles qui ne sont pas utilises au sein de ces fichiers sont supprimés.</span></p>
  606. <h3 id="des-includes-en-html">
  607. Des includes en HTML</h3>
  608. <p>
  609. Passons à présent à des tâches moins classiques, voire carrément spécifiques à mes besoins et envies.</p>
  610. <p>
  611. En tant qu'intégrateur HTML/CSS, j’ai toujours été quelque peu frustré de devoir passer par un langage serveur pour faire des inclusions de fichiers partiels (header, footer, nav, etc.).</p>
  612. <p>
  613. Bien-sûr, il est possible de passer par JavaScript, AJAX, webcomponents / polymer ou des langages tels Jade, HAML ou Handlebars pour atteindre cet objectif, mais en toute honnêteté je ne suis pas fan de rajouter une couche de complexité à mon workflow et je ne supporte pas les syntaxes “à la Jade”.</p>
  614. <p>
  615. Je veux écrire du bête HTML <strong>et</strong> avoir des include de fichiers. Je veux le beurre et l’argent du beurre. Et là, sur mon site perso, je peux me le permettre.</p>
  616. <p>
  617. Par chance, puisque mon workflow est déjà basé sur NodeJS / Gulp, il existe des plugins pour parvenir à mes fins.</p>
  618. <p>
  619. L’un de ces plugins est <strong><a href="https://www.npmjs.com/package/gulp-html-extend">“Gulp HTML extend”</a></strong>.</p>
  620. <p>
  621. À l’aide de simples commentaires HTML, il offre (au-moins) deux fonctionnalités qui m’intéressent tout particulièrement :</p>
  622. <ul>
  623. <li>
  624. les inclusions de fichiers partiels</li>
  625. <li>
  626. l’utilisation de variables</li>
  627. </ul>
  628. <p>
  629. Le principe est simplissime : les instructions se présentent sous forme de commentaires HTML (pour préserver la syntaxe et la validité), par exemple <code>&lt;!-- @@include fichier.txt --&gt;</code> pour inclure un fichier dans le document, ou bien <code>&lt;!-- @@var variable --&gt;</code> pour inclure une variable.</p>
  630. <p>
  631. Voici un exemple de page <code>index.html</code> avant compilation :</p>
  632. <pre class="code">
  633. <code class="html"><span class="hljs-tag">&lt;<span class="hljs-title">head</span>&gt;</span>
  634. <span class="hljs-comment">&lt;!-- @@include _includes/head.html {"title": "Mon site web personnel", "path": "../"} --&gt;</span>
  635. <span class="hljs-tag">&lt;/<span class="hljs-title">head</span>&gt;</span></code></pre>
  636. <p>
  637. Le fichier inclus <code>head.html</code> :</p>
  638. <pre class="code">
  639. <code class="html"><span class="hljs-tag">&lt;<span class="hljs-title">meta</span> <span class="hljs-attribute">charset</span>=<span class="hljs-value">"UTF-8"</span>&gt;</span>
  640. <span class="hljs-tag">&lt;<span class="hljs-title">title</span>&gt;</span><span class="hljs-comment">&lt;!-- @@var title --&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">title</span>&gt;</span>
  641. ...
  642. <span class="hljs-tag">&lt;<span class="hljs-title">link</span> <span class="hljs-attribute">rel</span>=<span class="hljs-value">"stylesheet"</span> <span class="hljs-attribute">href</span>=<span class="hljs-value">"&lt;!-- @@var path --&gt;assets/css/styles.min.css"</span> <span class="hljs-attribute">media</span>=<span class="hljs-value">"all"</span>&gt;</span></code></pre>
  643. <p>
  644. Et le résultat compilé <code>index.html</code> en prod :</p>
  645. <pre class="code">
  646. <code class="html"><span class="hljs-tag">&lt;<span class="hljs-title">meta</span> <span class="hljs-attribute">charset</span>=<span class="hljs-value">"UTF-8"</span>&gt;</span>
  647. <span class="hljs-tag">&lt;<span class="hljs-title">title</span>&gt;</span>Mon site web personnel<span class="hljs-tag">&lt;/<span class="hljs-title">title</span>&gt;</span>
  648. ...
  649. <span class="hljs-tag">&lt;<span class="hljs-title">link</span> <span class="hljs-attribute">rel</span>=<span class="hljs-value">"stylesheet"</span> <span class="hljs-attribute">href</span>=<span class="hljs-value">"../assets/css/styles.min.css"</span> <span class="hljs-attribute">media</span>=<span class="hljs-value">"all"</span>&gt;</span>
  650. <span class="hljs-tag">&lt;/<span class="hljs-title">head</span>&gt;</span></code></pre>
  651. <p>
  652. Voici la tâche <strong>“html”</strong> du fichier <code>gulpfile.js</code> correspondant à mes besoins :</p>
  653. <pre class="code">
  654. <code class="html"><span class="hljs-comment">// Tâche "html" = includes HTML</span>
  655. gulp.task(<span class="hljs-string">'html'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> {</span>
  656. <span class="hljs-keyword">return</span> gulp.src(source + <span class="hljs-string">'/{,conf/}/{,livres/}*.html'</span>)
  657. <span class="hljs-comment">// Generates HTML includes</span>
  658. .pipe(extender({
  659. annotations: <span class="hljs-literal">false</span>,
  660. verbose: <span class="hljs-literal">false</span>
  661. })) <span class="hljs-comment">// default options</span>
  662. .pipe(gulp.dest(prod))
  663. });</code></pre>
  664. <p>
  665. <strong>Explications :</strong> tous les fichiers HTML à la racine du site ainsi que ceux situés dans les dossiers <code>/conf</code> et <code>/livres</code> seront compilés en tenant compte des inclusions et des variables fournies.</p>
  666. <p>
  667. La documentation et les options de ce plugin : <a href="https://www.npmjs.com/package/gulp-html-extend">https://www.npmjs.com/package/gulp-html-extend</a></p>
  668. <h3 id="critical-css">
  669. Critical CSS</h3>
  670. <p>
  671. Autre plugin que j’ai pu mettre en place sur mon site perso : “Critical CSS”.</p>
  672. <p>
  673. Vous avez déjà certainement été confronté à l’un des conseils récurrents de <a href="https://developers.google.com/speed/pagespeed/insights/">Google PageSpeed Insights</a> (l’outil de perf web très pratique pour tester son site mobile et desktop) qui est <q><em>Éliminer les codes JavaScript et CSS qui bloquent l’affichage du contenu au-dessus de la ligne de flottaison</em></q>.</p>
  674. <p>
  675. L’explication est celle-ci : JavaScript et CSS retardent l’affichage de la page tant qu’ils ne sont pas chargés.<br/>
  676. Google considère que la partie visible d’une page web, c’est à dire tout se qui se trouve au-dessus de la <em>ligne de flottaison</em> (ou “above the fold”), devrait être affiché instantanément, soit :</p>
  677. <ul>
  678. <li>
  679. en différant ou en rendant les ressources asynchrones (<code>async</code>, <code>defer</code>),</li>
  680. <li>
  681. soit en éliminant toutes les requêtes de <a href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-blocking-css?hl=fr">styles CSS “critiques”</a> (= ceux participant à l’affichage du design au dessus de la ligne de flottaison). Pour cela, la méthode est d’insérer ces CSS “critiques” directement dans la page HTML (on “inline” les CSS dans l’élément <code>&lt;style&gt;</code> comme au bon vieux temps des newsletter).</li>
  682. </ul>
  683. <p class="center">
  684. <img alt="ligne de flottaison" src="/xmedia/doc/full/ligne_de_flottaison.png"/></p>
  685. <p>
  686. Sans surprise, la méthode d‘“inliner” les CSS dans l’élément <code>&lt;style&gt;</code> présente un sacré paquet d’inconvénients (un peu les mêmes que l’on retrouve avec les <code>data-URI</code>) :</p>
  687. <ul>
  688. <li>
  689. les fichiers HTML sont beaucoup plus lourds</li>
  690. <li>
  691. ils sont également moins lisibles et plus difficiles à maintenir</li>
  692. <li>
  693. c’est moche</li>
  694. </ul>
  695. <p>
  696. La contrepartie est que les avantages... ne sont pas négligeables non plus :</p>
  697. <ul>
  698. <li>
  699. les résultats sont très probants en terme de performance d’affichage (des gains de parfois plusieurs secondes)</li>
  700. <li>
  701. de très gros sites comme <a href="http://www.theguardian.com/">The Guardian</a> ont réussi à charger leur page d’accueil en moins de 1 seconde grâce à l’optimisation de leurs CSS “critiques”.</li>
  702. <li>
  703. il est possible d’automatiser ce processus</li>
  704. </ul>
  705. <p>
  706. Quelques ressources intéressantes sur ce sujet qui ne l’est pas moins :</p>
  707. <p>
  708. Si vous ne deviez retenir qu’une seule ressource, je vous invite vivement à vous imprégner de la conférence de Patrick Hamann (The Guardian), “CSS and the critical path” dont voici <a href="https://speakerdeck.com/patrickhamann/css-and-the-critical-path">les slides</a> et <a href="https://www.youtube.com/watch?v=_0Fk85to6hA">la vidéo associée</a>.</p>
  709. <h4 id="critical-css-en-pratique">
  710. Critical CSS en pratique</h4>
  711. <p>
  712. Il existe deux outils connus et suffisamment maintenus et testés pour réaliser la tâche de “inliner des CSS” : <a href="https://github.com/addyosmani/critical"><strong>Critical</strong></a> d’Addy Osmani et <a href="https://github.com/filamentgroup/criticalCSS">CriticalCSS</a> de Filament Group. Les deux proposent une version Grunt et Gulp.</p>
  713. <p>
  714. Pour ma part et sur mon site perso <a href="http://goetter.fr">goetter.fr</a>, j’ai utilisé le premier de ces deux outils, <strong>Critical</strong>, en version Gulp bien sûr.</p>
  715. <p>
  716. Voici ma tâche <strong>“critical”</strong> du fichier <code>gulpfile.js</code> :</p>
  717. <pre class="code">
  718. <code class="javascript"><span class="hljs-comment">// Tâche "critical" = critical inline CSS</span>
  719. gulp.task(<span class="hljs-string">'critical'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> {</span>
  720. <span class="hljs-keyword">return</span> gulp.src(prod + <span class="hljs-string">'/*.html'</span>)
  721. .pipe(critical({
  722. base: prod,
  723. inline: <span class="hljs-literal">true</span>,
  724. width: <span class="hljs-number">320</span>,
  725. height: <span class="hljs-number">480</span>,
  726. minify: <span class="hljs-literal">true</span>
  727. }))
  728. .pipe(gulp.dest(prod));
  729. });</code></pre>
  730. <p>
  731. Le principe général est celui-ci :</p>
  732. <ul>
  733. <li>
  734. un navigateur fantôme parcourt la(es) page(s) concernée(s) dans les dimensions précisées (ici 320x480)</li>
  735. <li>
  736. il en déduit les CSS “critiques”, participant à l’affichage au dessus de la ligne de flottaison</li>
  737. <li>
  738. il les extrait et les insère directement (minifiés) au sein d’une balise <code>&lt;style&gt;</code> dans la page</li>
  739. <li>
  740. le “reste” de la feuille de style est chargée de manière asynchrone via JavaScript et <a href="https://github.com/filamentgroup/loadCSS">LoadCSS</a></li>
  741. <li>
  742. pour les navigateurs sans JS, la feuille de style est chargée au sein d’un <code>&lt;noscript&gt;</code></li>
  743. <li>
  744. et tout ça tout seul comme un grand !</li>
  745. </ul>
  746. <p>
  747. Dans mon cas, j’ai décidé que seront concernés tous les fichiers à la racine du site (et uniquement ceux-là).</p>
  748. <h4 id="critical-efficace">
  749. Critical : efficace ?</h4>
  750. <p>
  751. Maintenant que l’on s’est bien amusé, passons quelques tests pour découvrir si ça en valait vraiment la peine…</p>
  752. <h5 id="daprès-google-pagespeed-insights">
  753. D’après Google PageSpeed Insights</h5>
  754. <p>
  755. <a href="https://developers.google.com/speed/pagespeed/insights/">Google PageSpeed Insights</a> donne une “note globale de performance” sur 100 à votre page en version mobile et desktop.</p>
  756. <ul>
  757. <li>
  758. <strong>Avant Critical :</strong>
  759. <ul>
  760. <li>
  761. note mobile : 91/100</li>
  762. <li>
  763. note desktop : 97/100</li>
  764. </ul>
  765. </li>
  766. <li>
  767. <strong>Après Critical :</strong>
  768. <ul>
  769. <li>
  770. note mobile : <strong>99/100</strong>*</li>
  771. <li>
  772. note desktop : 99/100* <em>(*le 1% restant est dû à… la présence du script Google Analytics)</em></li>
  773. </ul>
  774. </li>
  775. </ul>
  776. <p>
  777. Illustration avant critical :</p>
  778. <p class="center">
  779. <img alt="avant critical" src="/xmedia/doc/full/before-critical.png"/></p>
  780. <p class="center">
  781. Illustration après critical :</p>
  782. <p class="center">
  783. <img alt="après critical" src="/xmedia/doc/full/after-critical.png"/></p>
  784. <h5 id="daprès-dareboost">
  785. D’après Dareboost</h5>
  786. <p>
  787. <a href="https://www.dareboost.com">Dareboost</a> testé sur mobile (Nexus 5, en 3G et en France) attribue une note totale de 92% avant et après critical.</p>
  788. <p>
  789. <strong>Avant Critical :</strong></p>
  790. <ul>
  791. <li>
  792. chargement complet en 2.30s (visuellement complet en 1.37s)</li>
  793. <li>
  794. <a href="http://www.sitepoint.com/speed-index-measuring-page-load-time-different-way/">speed index</a> de 607</li>
  795. </ul>
  796. <p>
  797. <strong>Après Critical :</strong></p>
  798. <ul>
  799. <li>
  800. chargement complet en 2.01s <strong>(visuellement complet en 0.80s)</strong></li>
  801. <li>
  802. speed index de 436</li>
  803. </ul>
  804. <p>
  805. Illustration : comparaison avant et après :</p>
  806. <p class="center">
  807. <img alt="avant et après critical" src="/xmedia/doc/full/dareboost-before-after-critical.png"/></p>
  808. <h3 id="la-tâche-de-mise-en-production">
  809. La tâche de mise en production</h3>
  810. <p>
  811. Voici la tâche complète de mise en production de l’ensemble des contenus de <code>_src/</code> vers <code>_dist/</code> que j’emploie :</p>
  812. <pre class="code">
  813. <code class="javascript">// Tâche "prod" = toutes les tâches ensemble
  814. gulp.task('prod', gulpsync.sync(['css', 'js', 'html', 'critical','img']));</code></pre>
  815. <p>
  816. En un clic, voici les actions menées automatiquement :</p>
  817. <ul>
  818. <li>
  819. styles LESS compilés en CSS</li>
  820. <li>
  821. préfixes CSS3 ajoutés au besoin</li>
  822. <li>
  823. concaténation et minification des fichiers CSS</li>
  824. <li>
  825. concaténation et minification des fichiers JavaScript</li>
  826. <li>
  827. optimisation des images PNG, JPG et SVG</li>
  828. <li>
  829. compilation des fichiers HTML pour permettre les inclusions de fichiers partiels et des variables</li>
  830. <li>
  831. “inline” des styles CSS “critiques” directement dans la page</li>
  832. </ul>
  833. <p>
  834. Et pour finir, voici la tâche "watch" qui va surveiller en continu toutes les modifications opérées sur les fichiers LESS et HTML et qui va automatiquement lancer les tâches correspondantes :</p>
  835. <pre class="code">
  836. <code class="javascript">// Tâche "watch" = je surveille LESS et HTML
  837. gulp.task('watch', function () {
  838. gulp.watch(source + '/assets/css/*.less', ['css']);
  839. gulp.watch(source + '/{,conf/}/{,livres/}*.html', ['html']);
  840. });</code></pre>
  841. <p>
  842. Si l'envie vous prend et si vous avez envie de tester tout ça chez vous, vous pouvez récupérer les fichiers <code>package.json</code> et <code>gulpfile.js</code> sur mon espace Gist.</p>
  843. <p>
  844. <a class="demo" href="https://gist.github.com/raphaelgoetter/9713f2d953b645b342a5">Télécharger les fichiers</a></p>
  845. <h3>
  846. Que conclure de tout ça ?</h3>
  847. <p>
  848. Disposer d'un environnement de travail automatisé (au moins en partie) est devenu une évidence de nos jours où nous ne pouvons plus nous contenter d'écrire du HTML et du CSS. L'industrialisation de notre métier exige d'aller vite, de tester sur de nombreux devices, d'éviter les régressions, de maintenir son code et de le rendre le plus performant possible.</p>
  849. <p>
  850. Des outils tels que Grunt, Gulp ou Brunch offrent un "workflow" automatique pour la plupart des tâches courantes.</p>
  851. <p>
  852. Pour ce qui est du reste, par exemple inclure des fichiers en HTML ou rendre inline ses CSS "critiques"... <em>il y a aussi une application pour ça ! </em>:)</p>
  853. </article>
  854. </section>
  855. <nav id="jumpto">
  856. <p>
  857. <a href="/david/blog/">Accueil du blog</a> |
  858. <a href="http://www.alsacreations.com/tuto/lire/1685-ebauche-de-workflow-gulp-taches-uncss-includes-critical-css.html">Source originale</a> |
  859. <a href="/david/stream/2019/">Accueil du flux</a>
  860. </p>
  861. </nav>
  862. <footer>
  863. <div>
  864. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  865. <p>
  866. Bonjour/Hi!
  867. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  868. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  869. </p>
  870. <p>
  871. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  872. </p>
  873. <p>
  874. Voici quelques articles choisis :
  875. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  876. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  877. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  878. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  879. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  880. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  881. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  882. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  883. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  884. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  885. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  886. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  887. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  888. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  889. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  890. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  891. </p>
  892. <p>
  893. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  894. </p>
  895. <p>
  896. Je ne traque pas ta navigation mais mon
  897. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  898. conserve des logs d’accès.
  899. </p>
  900. </div>
  901. </footer>
  902. <script type="text/javascript">
  903. ;(_ => {
  904. const jumper = document.getElementById('jumper')
  905. jumper.addEventListener('click', e => {
  906. e.preventDefault()
  907. const anchor = e.target.getAttribute('href')
  908. const targetEl = document.getElementById(anchor.substring(1))
  909. targetEl.scrollIntoView({behavior: 'smooth'})
  910. })
  911. })()
  912. </script>