|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- <!doctype html><!-- This is a valid HTML5 document. -->
- <!-- Screen readers, SEO, extensions and so on. -->
- <html lang=fr>
- <!-- Has to be within the first 1024 bytes, hence before the <title>
- See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
- <meta charset=utf-8>
- <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
- <!-- The viewport meta is quite crowded and we are responsible for that.
- See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
- <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
- <!-- Required to make a valid HTML5 document. -->
- <title>The End of Global CSS (archive) — David Larlet</title>
- <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
- <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
- <link rel="manifest" href="/manifest.json">
- <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
- <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
- <meta name="apple-mobile-web-app-title" content="David Larlet">
- <meta name="application-name" content="David Larlet">
- <meta name="msapplication-TileColor" content="#da532c">
- <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
- <meta name="theme-color" content="#f0f0ea">
- <!-- That good ol' feed, subscribe :p. -->
- <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
-
- <meta name="robots" content="noindex, nofollow">
- <meta content="origin-when-cross-origin" name="referrer">
- <!-- Canonical URL for SEO purposes -->
- <link rel="canonical" href="https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284">
-
- <style>
- /* http://meyerweb.com/eric/tools/css/reset/ */
- html, body, div, span,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, address, big, cite, code,
- del, dfn, em, img, ins,
- small, strike, strong, tt, var,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td,
- article, aside, canvas, details, embed,
- figure, figcaption, footer, header, hgroup,
- menu, nav, output, ruby, section, summary,
- time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
- }
- /* HTML5 display-role reset for older browsers */
- article, aside, details, figcaption, figure,
- footer, header, hgroup, menu, nav, section { display: block; }
- body { line-height: 1; }
- blockquote, q { quotes: none; }
- blockquote:before, blockquote:after,
- q:before, q:after {
- content: '';
- content: none;
- }
- table {
- border-collapse: collapse;
- border-spacing: 0;
- }
-
- /* http://practicaltypography.com/equity.html */
- /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
- /* https://www.filamentgroup.com/lab/js-web-fonts.html */
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
- font-weight: 300;
- font-style: italic;
- font-display: swap;
- }
- @font-face {
- font-family: 'EquityTextB';
- src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
- url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
- font-weight: 700;
- font-style: normal;
- font-display: swap;
- }
-
- @font-face {
- font-family: 'ConcourseT3';
- src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
- url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
- font-weight: 300;
- font-style: normal;
- font-display: swap;
- }
-
-
- /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
- body {
- /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
- font-family: "EquityTextB", Palatino, serif;
- background-color: #f0f0ea;
- color: #07486c;
- font-kerning: normal;
- -moz-osx-font-smoothing: grayscale;
- -webkit-font-smoothing: subpixel-antialiased;
- text-rendering: optimizeLegibility;
- font-variant-ligatures: common-ligatures contextual;
- font-feature-settings: "kern", "liga", "clig", "calt";
- }
- pre, code, kbd, samp, var, tt {
- font-family: 'TriplicateT4c', monospace;
- }
- em {
- font-style: italic;
- color: #323a45;
- }
- strong {
- font-weight: bold;
- color: black;
- }
- nav {
- background-color: #323a45;
- color: #f0f0ea;
- display: flex;
- justify-content: space-around;
- padding: 1rem .5rem;
- }
- nav:last-child {
- border-bottom: 1vh solid #2d7474;
- }
- nav a {
- color: #f0f0ea;
- }
- nav abbr {
- border-bottom: 1px dotted white;
- }
-
- h1 {
- border-top: 1vh solid #2d7474;
- border-bottom: .2vh dotted #2d7474;
- background-color: #e3e1e1;
- color: #323a45;
- text-align: center;
- padding: 5rem 0 4rem 0;
- width: 100%;
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- }
- h1.single {
- padding-bottom: 10rem;
- }
- h1 span {
- position: absolute;
- top: 1vh;
- left: 20%;
- line-height: 0;
- }
- h1 span a {
- line-height: 1.7;
- padding: 1rem 1.2rem .6rem 1.2rem;
- border-radius: 0 0 6% 6%;
- background: #2d7474;
- font-size: 1.3rem;
- color: white;
- text-decoration: none;
- }
- h2 {
- margin: 4rem 0 1rem;
- border-top: .2vh solid #2d7474;
- padding-top: 1vh;
- }
- h3 {
- text-align: center;
- margin: 3rem 0 .75em;
- }
- hr {
- height: .4rem;
- width: .4rem;
- border-radius: .4rem;
- background: #07486c;
- margin: 2.5rem auto;
- }
- time {
- display: bloc;
- margin-left: 0 !important;
- }
- ul, ol {
- margin: 2rem;
- }
- ul {
- list-style-type: square;
- }
- a {
- text-decoration-skip-ink: auto;
- text-decoration-thickness: 0.05em;
- text-underline-offset: 0.09em;
- }
- article {
- max-width: 50rem;
- display: flex;
- flex-direction: column;
- margin: 2rem auto;
- }
- article.single {
- border-top: .2vh dotted #2d7474;
- margin: -6rem auto 1rem auto;
- background: #f0f0ea;
- padding: 2rem;
- }
- article p:last-child {
- margin-bottom: 1rem;
- }
- p {
- padding: 0 .5rem;
- margin-left: 3rem;
- }
- p + p,
- figure + p {
- margin-top: 2rem;
- }
-
- blockquote {
- background-color: #e3e1e1;
- border-left: .5vw solid #2d7474;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 1rem;
- margin: 1.5rem;
- }
- blockquote cite {
- font-style: italic;
- }
- blockquote p {
- margin-left: 0;
- }
-
- figure {
- border-top: .2vh solid #2d7474;
- background-color: #e3e1e1;
- text-align: center;
- padding: 1.5rem 0;
- margin: 1rem 0 0;
- font-size: 1.5rem;
- width: 100%;
- }
- figure img {
- max-width: 250px;
- max-height: 250px;
- border: .5vw solid #323a45;
- padding: 1px;
- }
- figcaption {
- padding: 1rem;
- line-height: 1.4;
- }
- aside {
- display: flex;
- flex-direction: column;
- background-color: #e3e1e1;
- padding: 1rem 0;
- border-bottom: .2vh solid #07486c;
- }
- aside p {
- max-width: 50rem;
- margin: 0 auto;
- }
-
- /* https://fvsch.com/code/css-locks/ */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: 1rem;
- line-height: calc( 1.5em + 0.2 * 1rem );
- }
- h1 {
- font-size: 1.9rem;
- line-height: calc( 1.2em + 0.2 * 1rem );
- }
- h2 {
- font-size: 1.6rem;
- line-height: calc( 1.3em + 0.2 * 1rem );
- }
- h3 {
- font-size: 1.35rem;
- line-height: calc( 1.4em + 0.2 * 1rem );
- }
- @media (min-width: 20em) {
- /* The (100vw - 20rem) / (50 - 20) part
- resolves to 0-1rem, depending on the
- viewport width (between 20em and 50em). */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
- margin-left: 0;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
- line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
- }
- }
- @media (min-width: 50em) {
- /* The right part of the addition *must* be a
- rem value. In this example we *could* change
- the whole declaration to font-size:2.5rem,
- but if our baseline value was not expressed
- in rem we would have to use calc. */
- p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
- font-size: calc( 1rem + .6 * 1rem );
- line-height: 1.5em;
- }
- p, li, pre, details {
- margin-left: 3rem;
- }
- h1 {
- font-size: calc( 1.9rem + 1.5 * 1rem );
- line-height: 1.2em;
- }
- h2 {
- font-size: calc( 1.5rem + 1.5 * 1rem );
- line-height: 1.3em;
- }
- h3 {
- font-size: calc( 1.35rem + 1.5 * 1rem );
- line-height: 1.4em;
- }
- figure img {
- max-width: 500px;
- max-height: 500px;
- }
- }
-
- figure.unsquared {
- margin-bottom: 1.5rem;
- }
- figure.unsquared img {
- height: inherit;
- }
-
-
-
- @media print {
- body { font-size: 100%; }
- a:after { content: " (" attr(href) ")"; }
- a, a:link, a:visited, a:after {
- text-decoration: underline;
- text-shadow: none !important;
- background-image: none !important;
- background: white;
- color: black;
- }
- abbr[title] { border-bottom: 0; }
- abbr[title]:after { content: " (" attr(title) ")"; }
- img { page-break-inside: avoid; }
- @page { margin: 2cm .5cm; }
- h1, h2, h3 { page-break-after: avoid; }
- p3 { orphans: 3; widows: 3; }
- img {
- max-width: 250px !important;
- max-height: 250px !important;
- }
- nav, aside { display: none; }
- }
-
- ul.with_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_columns {
- column-count: 2;
- }
- }
- @media (min-width: 50em) {
- ul.with_columns {
- column-count: 3;
- }
- }
- ul.with_two_columns {
- column-count: 1;
- }
- @media (min-width: 20em) {
- ul.with_two_columns {
- column-count: 1;
- }
- }
- @media (min-width: 50em) {
- ul.with_two_columns {
- column-count: 2;
- }
- }
-
- .gallery {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-around;
- }
- .gallery figure img {
- margin-left: 1rem;
- margin-right: 1rem;
- }
- .gallery figure figcaption {
- font-family: 'ConcourseT3'
- }
-
- footer {
- font-family: 'ConcourseT3';
- display: flex;
- flex-direction: column;
- border-top: 3px solid white;
- padding: 4rem 0;
- background-color: #07486c;
- color: white;
- }
- footer > * {
- max-width: 50rem;
- margin: 0 auto;
- }
- footer a {
- color: #f1c40f;
- }
- footer .avatar {
- width: 200px;
- height: 200px;
- border-radius: 50%;
- float: left;
- -webkit-shape-outside: circle();
- shape-outside: circle();
- margin-right: 2rem;
- padding: 2px 5px 5px 2px;
- background: white;
- border-left: 1px solid #f1c40f;
- border-top: 1px solid #f1c40f;
- border-right: 5px solid #f1c40f;
- border-bottom: 5px solid #f1c40f;
- }
- </style>
-
- <h1>
- <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
- The End of Global CSS (archive)
- <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
- </h1>
- <section>
- <article>
- <h3><a href="https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284">Source originale du contenu</a></h3>
- <p name="d899" id="d899" class="graf--p graf--first">On <a href="https://github.com/webpack/css-loader/commit/d2c9c25721a711b0fe041c597b43646e82d9f145" data-href="https://github.com/webpack/css-loader/commit/d2c9c25721a711b0fe041c597b43646e82d9f145" class="markup--anchor markup--p-anchor" rel="nofollow">April 22, 2015</a>, <a href="https://github.com/sokra" data-href="https://github.com/sokra" class="markup--anchor markup--p-anchor" rel="nofollow">Tobias Koppers</a> — the ever tireless author of Webpack — committed the first iteration of a new feature to css-loader, at the time called <em class="markup--em markup--p-em">placeholders</em>, now known as <a href="https://github.com/webpack/css-loader#local-scope" data-href="https://github.com/webpack/css-loader#local-scope" class="markup--anchor markup--p-anchor" rel="nofollow">local scope</a>.</p>
-
- <p name="2794" id="2794" class="graf--p">This feature allows us to export class names from our CSS into the consuming JavaScript code.</p>
-
- <p name="3479" id="3479" class="graf--p">In short, instead of writing this:</p>
-
- <pre name="8c0d" id="8c0d" class="graf--pre"><strong class="markup--strong markup--pre-strong">require</strong>('./MyComponent.css');</pre>
-
- <p name="2bd7" id="2bd7" class="graf--p">We write this:</p>
-
- <pre name="db53" id="db53" class="graf--pre"><strong class="markup--strong markup--pre-strong">import</strong> styles <strong class="markup--strong markup--pre-strong">from</strong> './MyComponent.css';</pre>
-
- <p name="130c" id="130c" class="graf--p">So, in this example, what does <em class="markup--em markup--p-em">styles </em>evaluate to?</p>
-
- <p name="d8a4" id="d8a4" class="graf--p">To see what is exported from our CSS, let’s take a look at an example of what our style sheet might look like.</p>
-
- <pre name="a4f1" id="a4f1" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>foo) {<br/> <strong class="markup--strong markup--pre-strong">color</strong>: red;<br/>}</pre>
-
- <pre name="01b4" id="01b4" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>bar) {<br/> <strong class="markup--strong markup--pre-strong">color</strong>: blue;<br/>}</pre>
-
- <p name="067a" id="067a" class="graf--p">In this case, we’ve used css-loader’s custom <em class="markup--em markup--p-em">:local(.identifier)</em> syntax to export two identifiers — <em class="markup--em markup--p-em">foo</em> and <em class="markup--em markup--p-em">bar</em>.</p>
-
- <p name="7aae" id="7aae" class="graf--p">These identifiers map to class strings that we can use in our JavaScript file. For example, when using <a href="http://facebook.github.io/react/" data-href="http://facebook.github.io/react/" class="markup--anchor markup--p-anchor" rel="nofollow">React</a>:</p>
-
- <pre name="4e34" id="4e34" class="graf--pre"><strong class="markup--strong markup--pre-strong">import</strong> styles <strong class="markup--strong markup--pre-strong">from</strong> './MyComponent.css';</pre>
-
- <pre name="fe30" id="fe30" class="graf--pre"><strong class="markup--strong markup--pre-strong">import</strong> React, { Component } <strong class="markup--strong markup--pre-strong">from</strong> 'react';</pre>
-
- <pre name="7c51" id="7c51" class="graf--pre"><strong class="markup--strong markup--pre-strong">export default class</strong> MyComponent <strong class="markup--strong markup--pre-strong">extends</strong> Component {</pre>
-
- <pre name="cced" id="cced" class="graf--pre"> <strong class="markup--strong markup--pre-strong">render</strong>() {<br/> return (<br/> <div><br/> <div className={<strong class="markup--strong markup--pre-strong">styles.foo</strong>}><strong class="markup--strong markup--pre-strong">Foo</strong></div><br/> <div className={<strong class="markup--strong markup--pre-strong">styles.bar</strong>}><strong class="markup--strong markup--pre-strong">Bar</strong></div><br/> </div><br/> );<br/> }</pre>
-
- <pre name="14c7" id="14c7" class="graf--pre">}</pre>
-
- <p name="6f59" id="6f59" class="graf--p">Importantly, these identifiers map to class strings that are guaranteed to be unique in a global context.</p>
-
- <p name="05fe" id="05fe" class="graf--p graf--last">We no longer need to add lengthy prefixes to all of our selectors to simulate scoping. More components could define their own <em class="markup--em markup--p-em">foo</em> and <em class="markup--em markup--p-em">bar</em> identifiers which — unlike the traditional global selector model—wouldn’t produce any naming collisions.</p>
-
- <p name="e256" id="e256" class="graf--p graf--first">It’s critical to recognise the massive shift that’s occurring here.</p>
-
- <p name="8051" id="8051" class="graf--p">We can now make changes to our CSS with confidence that we’re not accidentally affecting elements elsewhere in the page. We’ve introduced a sane scoping model to our CSS.</p>
-
- <p name="0169" id="0169" class="graf--p">The benefits of global CSS — style re-use between components via utility classes, etc. — are still achievable with this model. The key difference is that, just like when we work in other technologies, we need to explicitly import the classes that we depend on. Our code can’t make many, if any, assumptions about the global environment.</p>
-
- <blockquote name="db73" id="db73" class="graf--pullquote pullquote graf--last">Writing maintainable CSS is now encouraged, not by careful adherence to a naming convention, but by style encapsulation during development.</blockquote>
-
- <p></div></div></section><section name="5fe1" class=" section--content"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="032b" id="032b" class="graf--p graf--first">As a result of this scoping model, we’ve handed control of the <em class="markup--em markup--p-em">actual</em> class names over to Webpack. Luckily, this is something that we can configure.</p><p name="b528" id="b528" class="graf--p">By default, css-loader transforms our identifiers into hashes.</p><p name="f6c6" id="f6c6" class="graf--p">For example, this:</p><pre name="98b2" id="98b2" class="graf--pre">:local(.foo) { … }</pre><p name="75ad" id="75ad" class="graf--p">Is compiled into this:</p><pre name="06d9" id="06d9" class="graf--pre">._1rJwx92-gmbvaLiDdzgXiJ { … }</pre><p name="679e" id="679e" class="graf--p">In development, this isn’t terribly helpful for debugging purposes. To make the classes more useful, we can configure the class format in our Webpack config as a parameter to css-loader:</p><pre name="6240" id="6240" class="graf--pre">loaders: [<br> ...<br> {<br> test: /.css$/,<br> loader: 'css?<strong class="markup--strong markup--pre-strong">localIdentName=[name]<strong>[local]<strong><em>[hash:base64:5]</strong>'<br> }<br>]</pre><p name="d33e" id="d33e" class="graf--p">In this case, our <em class="markup--em markup--p-em">foo </em>class identifier from earlier would compile into this:</p><pre name="adae" id="adae" class="graf--pre">.<strong class="markup--strong markup--pre-strong">MyComponent</em>_foo</strong>_1rJwx</strong> { … }</pre><p name="0931" id="0931" class="graf--p">We can now clearly see the name of the identifier, as well as the component that it came from.</p><p name="8d8d" id="8d8d" class="graf--p">Using the <em class="markup--em markup--p-em">node_env</em> environment variable, we can configure different class patterns for development and production.</p><pre name="bda0" id="bda0" class="graf--pre">loader: 'css?<strong class="markup--strong markup--pre-strong">localIdentName=' + </strong>(<br> process.env.NODE_ENV === 'development' ?<strong class="markup--strong markup--pre-strong"><br> '[name]</strong>[local]___[hash:base64:5]</strong>' :<br> '<strong class="markup--strong markup--pre-strong">[hash:base64:5]'<br></strong>)</pre><blockquote name="b66f" id="b66f" class="graf--pullquote pullquote graf--last">Now that Webpack has control of our class names, we can trivially<br>add support for minified classes in production.</blockquote></div></div></section><section name="830e" class=" section--content"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="a492" id="a492" class="graf--p graf--first">As soon as we discovered this feature, we didn’t hesitate to localise the styles in our most recent project. We were already scoping our CSS to each component with BEM — if only by convention — so it was a natural fit.</p><p name="e180" id="e180" class="graf--p">Interestingly, a pattern quickly emerged. Most of our CSS files contained nothing but local identifiers:</p><pre name="e8ea" id="e8ea" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>backdrop) { … }</pre><pre name="a47b" id="a47b" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>root_isCollapsed .backdrop) { … }</pre><pre name="56b6" id="56b6" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>field) { … }</pre><pre name="3375" id="3375" class="graf--pre"><strong class="markup--strong markup--pre-strong">:local(.</strong>field):focus { … }</pre><pre name="ebb3" id="ebb3" class="graf--pre">etc…</pre><p name="ce4f" id="ce4f" class="graf--p">Global selectors were only required in a few places in the application. This instinctively led towards a very important question.</p><blockquote name="562d" id="562d" class="graf--pullquote pullquote">What if — instead of requiring a special syntax — our selectors were local by default, and global selectors were the opt-in exception?</blockquote><p name="1f39" id="1f39" class="graf--p">What if we could write this instead?</p><pre name="b489" id="b489" class="graf--pre">.backdrop { … }</pre><pre name="0cd8" id="0cd8" class="graf--pre">.root_isCollapsed .backdrop { … }</pre><pre name="c435" id="c435" class="graf--pre">.field { … }</pre><pre name="d04d" id="d04d" class="graf--pre">.field:focus { … }</pre><p name="9c8b" id="9c8b" class="graf--p">While these selectors would normally be too vague, transforming them into css-loader’s local scope format would eliminate this issue and ensure they remain scoped to the module in which they were used.</p><p name="6dd0" id="6dd0" class="graf--p">For those few cases where we couldn’t avoid global styles, we could explicitly mark them with a special <em class="markup--em markup--p-em">:global</em> syntax.</p><p name="476e" id="476e" class="graf--p">For example, when styling the un-scoped classes generated by <a href="https://facebook.github.io/react/docs/animation.html#high-level-api-reactcsstransitiongroup" data-href="https://facebook.github.io/react/docs/animation.html#high-level-api-reactcsstransitiongroup" class="markup--anchor markup--p-anchor" rel="nofollow">ReactCSSTransitionGroup</a>:</p><pre name="25f9" id="25f9" class="graf--pre">.panel <strong class="markup--strong markup--pre-strong">:global</strong> .transition-active-enter { … }</pre><p name="4f2d" id="4f2d" class="graf--p graf--last">In this case, we’re not just scoping the local <em class="markup--em markup--p-em">panel</em> identifier to our module — we’re also styling a global class that is outside of our control.</p></div></div></section><section name="e9db" class=" section--content"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="61be" id="61be" class="graf--p graf--first">Once we started investigating how me might implement this local-by-default class syntax, we realised that it wouldn’t be too difficult.</p><p name="eee8" id="eee8" class="graf--p">To achieve this, we leveraged <a href="https://github.com/postcss/postcss" data-href="https://github.com/postcss/postcss" class="markup--anchor markup--p-anchor" rel="nofollow">PostCSS</a> — a fantastic tool that allows you to write custom CSS transformers as plugins. One of the most popular CSS build tools today — <a href="https://github.com/postcss/autoprefixer" data-href="https://github.com/postcss/autoprefixer" class="markup--anchor markup--p-anchor" rel="nofollow">Autoprefixer</a> — is actually a PostCSS plugin that doubles as a standalone tool.</p><p name="7af3" id="7af3" class="graf--p"><strong class="markup--strong markup--p-strong">To formalise the usage of local CSS, I’ve open sourced a highly<br>experimental plugin for PostCSS called </strong><a href="https://github.com/markdalgleish/postcss-local-scope" data-href="https://github.com/markdalgleish/postcss-local-scope" class="markup--anchor markup--p-anchor" rel="nofollow"><strong class="markup--strong markup--p-strong">postcss-local-scope</strong></a><strong class="markup--strong markup--p-strong">. It’s still under heavy development, so use it in production at your own risk.</strong></p><p name="8241" id="8241" class="graf--p">If you’re using Webpack, it’s a relatively straightforward process to hook <a href="https://github.com/postcss/postcss-loader" data-href="https://github.com/postcss/postcss-loader" class="markup--anchor markup--p-anchor" rel="nofollow">postcss-loader</a> and <a href="https://github.com/markdalgleish/postcss-local-scope" data-href="https://github.com/markdalgleish/postcss-local-scope" class="markup--anchor markup--p-anchor" rel="nofollow">postcss-local-scope</a> up to your CSS build process. Rather than document it here, I’ve created an example repository — <a href="https://github.com/markdalgleish/postcss-local-scope-example" data-href="https://github.com/markdalgleish/postcss-local-scope-example" class="markup--anchor markup--p-anchor" rel="nofollow">postcss-local-scope-example</a>—that shows a small working example.</p><figure name="72b6" id="72b6" class="graf--figure graf--iframe graf--last"><div class="iframeContainer"><iframe width="700" height="250" src="/media/48009fcff6d0175040817c911d25707d?maxWidth=700" data-media-id="48009fcff6d0175040817c911d25707d" frameborder="0"></iframe></div></figure></div></div></section><section name="f940" class=" section--content"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="a4ed" id="a4ed" class="graf--p graf--first">Excitingly, introducing local scope is really just the beginning.</p><p name="0317" id="0317" class="graf--p">Letting the build tool handle the generation of class names has some potentially huge implications. In the long term, we could decide to stop being <em class="markup--em markup--p-em">human compilers</em> and let the computer optimise the output.</p><blockquote name="4918" id="4918" class="graf--pullquote pullquote graf--last">In the future, we could start generating shared classes between components automatically, treating style re-use as an optimisation at compile time.</blockquote></div></div></section><section name="720e" class=" section--content"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="ca7a" id="ca7a" class="graf--p graf--first">Once you’ve tried working with local CSS, there’s really no going back. Experiencing true local scope in our style sheets — in a way that works across all browsers— is not something to be easily ignored.</p><p name="52ec" id="52ec" class="graf--p">Introducing local scope has had a significant ripple effect on how we approach our CSS. Naming conventions, patterns of re-use, and the potential extraction of styles into separate packages are all directly affected by this shift, and we’re only at the beginning of this new era of local CSS.</p><p name="59a2" id="59a2" class="graf--p">Understanding the ramifications of this shift is something that we’re still working through. With your valuable input and experimentation, I’m hoping that this is a conversation we can have together as a larger community.</p><blockquote name="cc52" id="cc52" class="graf--pullquote pullquote">To get involved, make sure you see it with your own eyes by<br>checking out <a href="https://github.com/markdalgleish/postcss-local-scope-example" data-href="https://github.com/markdalgleish/postcss-local-scope-example" class="markup--anchor markup--pullquote-anchor" rel="nofollow">postcss-local-scope-example</a>.</blockquote><p name="edfa" id="edfa" class="graf--p graf--last">Once you’ve seen it in action, I think you’ll agree that it’s not just hyperbole — the days of global CSS are coming to an end. The future of CSS is <em class="markup--em markup--p-em">local</em>.</p></div></div></section><section name="b08a" class=" section--content section--last"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="ee6f" id="ee6f" class="graf--p graf--first"><em class="markup--em markup--p-em">Note: Automatically optimising style re-use between components would be an amazing step forward, but it definitely requires help from people a lot smarter than me. Hopefully, that’s where you come in ☺</em></p></p>
- </article>
- </section>
-
-
- <nav id="jumpto">
- <p>
- <a href="/david/blog/">Accueil du blog</a> |
- <a href="https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284">Source originale</a> |
- <a href="/david/stream/2019/">Accueil du flux</a>
- </p>
- </nav>
-
- <footer>
- <div>
- <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
- <p>
- Bonjour/Hi!
- Je suis <a href="/david/" title="Profil public">David Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
- 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>).
- </p>
- <p>
- 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>.
- </p>
-
- <p>
- Voici quelques articles choisis :
- <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
- <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
- <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
- <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
- <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
- <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
- <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
- <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
- <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
- <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
- <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
- <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
- <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
- <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
- <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
- et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
- </p>
- <p>
- 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>.
- </p>
- <p>
- Je ne traque pas ta navigation mais mon
- <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
- conserve des logs d’accès.
- </p>
- </div>
- </footer>
- <script type="text/javascript">
- ;(_ => {
- const jumper = document.getElementById('jumper')
- jumper.addEventListener('click', e => {
- e.preventDefault()
- const anchor = e.target.getAttribute('href')
- const targetEl = document.getElementById(anchor.substring(1))
- targetEl.scrollIntoView({behavior: 'smooth'})
- })
- })()
- </script>
|