@@ -0,0 +1,432 @@ | |||
<!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` element | |||
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,initial-scale=1"> | |||
<!-- Required to make a valid HTML5 document. --> | |||
<title>Using the platform (archive) — David Larlet</title> | |||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||
<!-- That good ol' feed, subscribe :). --> | |||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||
<!-- Documented, feel free to shoot an email. --> | |||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<script> | |||
function toggleTheme(themeName) { | |||
document.documentElement.classList.toggle( | |||
'forced-dark', | |||
themeName === 'dark' | |||
) | |||
document.documentElement.classList.toggle( | |||
'forced-light', | |||
themeName === 'light' | |||
) | |||
} | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme !== 'undefined') { | |||
toggleTheme(selectedTheme) | |||
} | |||
</script> | |||
<meta name="robots" content="noindex, nofollow"> | |||
<meta content="origin-when-cross-origin" name="referrer"> | |||
<!-- Canonical URL for SEO purposes --> | |||
<link rel="canonical" href="https://elisehe.in/2021/08/22/using-the-platform"> | |||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||
<article> | |||
<header> | |||
<h1>Using the platform</h1> | |||
</header> | |||
<nav> | |||
<p class="center"> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="https://elisehe.in/2021/08/22/using-the-platform" title="Lien vers le contenu original">Source originale</a> | |||
</p> | |||
</nav> | |||
<hr> | |||
<p>I recently came across a series of articles by Daniel Kehoe where he introduces <a href="https://tutorials.yax.com/articles/build-websites-the-yax-way/quicktakes/what-is-the-yax-way.html"><em>The Stackless Way</em></a>, an optimistic take on web development that proposes we “use the platform” (modern features built into the language) instead of frameworks and build tools that keep getting replaced every few years.</p> | |||
<p>It was good timing. While I’m a front-end developer at heart, I’ve rarely had the luxury of focusing on it full time. I’ve been dipping in and out of JavaScript, never fully caught up, always trying to navigate the ecosystem all over again each time a project came up. And framework fatigue is real!</p> | |||
<p>So, instead of finally getting into Rollup to replace an ancient Browserify build on an old codebase (which could also really use that upgrade from Polymer to LitElement…), I decided to go “stackless”. I took a long-time idea for a motion design project and built it using nothing but features native to the browser: vanilla JS, ES6 modules and web components.</p> | |||
<p>Working on a codebase with no dependencies has been a way of rediscovering exactly what I get for free in 2021, and what value I’m adding by bringing frameworks, transpilers and bundlers to the mix. I’d like to share what I learned (and what I needed to unlearn) in the process.</p> | |||
<h3 id="the-project">The project</h3> | |||
<p>The project itself, <a href="https://schematics.elisehe.in"><em>Schematics: A Love Story</em></a>, is a collection of animated diagrams from a visual poetry book by the same name. The animations dotted throughout this write-up are examples from the project: SVG created programmatically with modular, vanilla JavaScript, encapsulated in a web component, sitting inside a framework-free codebase.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/0d2f110a4e442af3495fdc556c2dbd92accc9255/5a69e/assets/post-assets/stackless/fig14-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/d20fa5f0b97d59400afd9bae5611ceb9b1437c93/ed6db/assets/post-assets/stackless/fig14-mobile.gif" alt="Figure 14: A line spiralling upwards along a the time axis in a 3-dimensional coordinate system."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<h2 id="the-stackless-way">The Stackless Way</h2> | |||
<p>Daniel Kehoe is not alone in his push-back on the complexity of the modern web. Frank Chimero’s <a href="https://frankchimero.com/blog/2018/everything-easy/"><em>Everything Easy is Hard Again</em></a> gets to me every time, and, more recently, I enjoyed <a href="https://medium.com/codex/youre-missing-out-on-vanilla-js-91aceec917d6">this lighthearted rant</a> about the lack of appreciation for vanilla JS.</p> | |||
<p>Appeals for external JS dependencies to be added with thoughtful intent have become common, but some schools of thought question the concept of single-page apps as a whole: server-side rendering is still a thing, after all. Pages can also be pre-built into a fully static site and served from a CDN (see <a href="https://jamstack.org/what-is-jamstack/">Jamstack</a>). These approaches recognise that we can move some of the complexity currently managed by front-end frameworks elsewhere on the stack.</p> | |||
<p>But Kehoe’s series of articles on going stackless feels a little different. It’s not just about JavaScript — it’s a wholehearted devotion to the native features of the web as a platform (Routing? Just make sure every URL matches a .html file!)</p> | |||
<p>There are limits to this kind of purism, of course. The Stackless Way, to me, is less of a realistic approach to building production web apps and more of a learning and introspection tool, a way to take a step back and fall in love with the platform again.</p> | |||
<p>Being all about motion design, my own project relied on many a <code class="language-plaintext highlighter-rouge">setTimeout</code> and page transitions — a single-page app, really… just handcrafted. The two main technologies that made it possible are ES6 modules and web components, which, along with module CDNs, form the pillars of The Stackless Way.</p> | |||
<h2 id="es6-modules">ES6 modules</h2> | |||
<p>If you’ve been developing for the web for some time, you might remember doing this:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/vendor/jquery.min.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/main.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/subscribe.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/gallery.js"</span><span class="nt">></script></span> | |||
<span class="c"><!-- Many more script tags whose specific ordering was a source of bugs and frustration --></span> | |||
<span class="nt"></head></span> | |||
</code></pre></div></div> | |||
<p>Listing all required scripts separately in the document <code class="language-plaintext highlighter-rouge"><head></code> was all we could do given the lack of support for a native import/export mechanism in JavaScript. This was, of course, suboptimal:</p> | |||
<ul> | |||
<li>each script tag initiates a HTTP request;</li> | |||
<li>there is no namespacing (all scripts exist in the global scope);</li> | |||
<li>the order of execution is linear, and maps directly onto the ordering of the script tags.</li> | |||
</ul> | |||
<p>To combat this, approaches to modularity in JavaScript have proliferated over the years (AMD, UMD, CommonJS), and along with them, build tools and bundlers to convert the modular code into something that the browser understands.</p> | |||
<p>ECMAScript Modules (also referred to as ESM or ES6 Modules) is the first <em>native</em> standard for modules in JavaScript. I emphasise <em>native</em> here because that means we can ditch the build tools and bundlers in favour of <code class="language-plaintext highlighter-rouge"><script type=“module”></code> in HTML and <code class="language-plaintext highlighter-rouge">import</code>/<code class="language-plaintext highlighter-rouge">export</code> in JS:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span> | |||
<span class="c"><!-- Single entry point for a dependency graph of any depth --></span> | |||
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"main.js"</span><span class="nt">></script></span> | |||
<span class="nt"></head></span> | |||
</code></pre></div></div> | |||
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.js</span> | |||
<span class="k">import</span> <span class="nx">Gallery</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Gallery.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="k">import</span> <span class="nx">SubscriptionForm</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./SubscriptionForm.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="c1">// Gallery.js</span> | |||
<span class="k">import</span> <span class="nx">Swipe</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./helpers/gestures.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Gallery</span> <span class="p">{}</span> | |||
</code></pre></div></div> | |||
<p><strong>Native support for modularity is the most important step towards a build-free codebase</strong>. If I had access to only one <a href="http://es6-features.org/">ES6 feature</a> for the rest of my life, I’m confident that modules would take me most of the way there when it comes to well-structured native JavaScript.</p> | |||
<p>And not having to build your JS app is magical. Setting up the directory structure for <em>Schematics</em>, I was giddy with excitement skipping <code class="language-plaintext highlighter-rouge">npm run build</code> and seeing my source files mirrored — as is! — in the browser. It reminded me of when I first began building websites and double-clicking <code class="language-plaintext highlighter-rouge">index.html</code> was enough to see your work in the browser.</p> | |||
<p>You do need a local server if you’re using ES6 modules (so double-clicking an HTML file might be a thing of the past for good). But there’s no lag between editing your code and seeing changes in the browser, and the source code you see in the inspector is exactly what you typed into your editor (no sourcemaps!).</p> | |||
<p>Nevermind the faster edit-compile-debug cycle — this speaks directly to Chimero’s concern about the <a href="https://frankchimero.com/blog/2018/everything-easy/">lack of legibility in today’s codebases as an obstacle to learning the craft</a>:</p> | |||
<blockquote> | |||
<p>Before, the websites could explain themselves; now, someone needs to walk you through it. Illegibility comes from complexity without clarity. I believe that the legibility of the source is one of the most important properties of the web. […] the best way to help someone <em>write</em> markup is to make sure they can <em>read</em> markup. (<a href="https://frankchimero.com/blog/2018/everything-easy/">Frank Chimero</a>)</p> | |||
</blockquote> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/5d8af1fbafa273e967948ac4fef631cb65230ef8/c1472/assets/post-assets/stackless/fig18-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/17fab52ccc9fc287c87cb7cd801de4d371344419/e0067/assets/post-assets/stackless/fig18-mobile.gif" alt="Figure 18: A flow diagram for love. Good? Yes. More? Yes. Do it."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<p>I recommend reading Kehoe’s <a href="https://tutorials.yax.com/articles/javascript/import/index.html"><em>Javascript import explained</em></a> or Ayodeji’s <a href="https://medium.com/backticks-tildes/introduction-to-es6-modules-49956f580da"><em>Introduction to ES6 modules</em></a> for a historic overview of approaches to modularity, how we got to ESM, and how to write your own modules.</p> | |||
<h3 id="the-future-of-app-bundles">The future of app bundles</h3> | |||
<p>Just because you don’t strictly need a build tool to <em>run</em> your code, doesn’t mean you shouldn’t use one for production builds to optimise performance. I’m referring here not to transpilation, but to minification, mangling, tree-shaking, etc.</p> | |||
<p>I’ve been bundling JavaScript into a single <code class="language-plaintext highlighter-rouge">app.js</code> file for such a long time that my first instinct was to concatenate everything into a single script on <em>Schematics</em>, too. This, of course, will not work with ES6 modules — the concept of files is what creates boundaries between different modules, and there is no way to specify more than one module per file.</p> | |||
<p>Luckily, you don’t need to bundle your ES6 modules into a single file to improve performance. Let me repeat: <strong>you don’t need to bundle your ES6 modules into a single file to improve performance</strong>.</p> | |||
<p>Here’s why:</p> | |||
<ol> | |||
<li><code class="language-plaintext highlighter-rouge"><script type=“module"></code> requests are deferred by default: they won’t block document parsing.</li> | |||
<li>Serving all modules from separate source files is great for caching, as the modules unaffected by some change can continue being retrieved from the cache. When you serve a single bundle, any one change will invalidate the whole bundle.</li> | |||
<li>You can lazy load parts of your code by making use of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports">dynamic imports</a> — importing modules during runtime at the point where you actually need them.</li> | |||
<li>You can preload critical modules with <a href="https://developers.google.com/web/updates/2017/12/modulepreload"><code class="language-plaintext highlighter-rouge">modulepreload</code></a>. This starts parsing and compiling the linked modules immediately, off the main thread.</li> | |||
<li>Finally, HTTP2 may in the future allow us to resolve the entire dependency graph from the first <code class="language-plaintext highlighter-rouge"><script type=“module”></code> request, and send all required files back in a single response. For the time being, this requires bespoke logic written on the server side.</li> | |||
</ol> | |||
<p>And don’t forget about bundle size:</p> | |||
<blockquote> | |||
<p>If you inspect the output code generated by most popular bundlers, you’ll find a lot of boilerplate whose only purpose is to dynamically load other code and manage dependencies, but none of that would be needed if we just used modules with import and export statements! (<a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today">Philip Walton</a>)</p> | |||
</blockquote> | |||
<p>I still have some work to do to go against my first instinct to bundle. It’s not uncommon for a SPA to have hundreds of JavaScript files, and the prospect of serving them all separately seems counterintuitive. Even having read up on all the reasons why I don’t need to bundle my modules, I occasionally hesitated before separating some logic into its own file to avoid the extra request.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/ea47b365638fd85ec7952a63fafb1997530db2b2/b2b8c/assets/post-assets/stackless/fig20-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/3835a734f4f22ce2dddee7654ebd561f17e23042/cceca/assets/post-assets/stackless/fig20-mobile.gif" alt="Figure 20: An illustration of the propagation of sound waves across arrays of vertical lines."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<p>For more details and interesting discussion around ES6 module load performance and app bundles, see <a href="https://esdiscuss.org/topic/fwd-are-es6-modules-in-browsers-going-to-get-loaded-level-by-level">this thread on the ECMAScript Discussion Archives</a>. The spec is not set in stone yet, and there are plenty of ideas floating around, some more daring than others (my personal favourite is the proposal to serve the entire module dependency graph as a .zip file).</p> | |||
<blockquote> | |||
<p>There’s plenty of ongoing module work happening in Chrome, though, so we’re getting closer to giving bundlers their well-earned rest! (<a href="https://developers.google.com/web/updates/2017/12/modulepreload">Sérgio Gomes</a>)</p> | |||
</blockquote> | |||
<p>I also recommend reading <a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today"><em>Using Native JavaScript Modules in Production Today</em></a> by Philip Walton. It explains how to serve optimised module files alongside a standard fallback single-file bundle for older browsers.</p> | |||
<p>Finally, if you’re looking for an alternative to webpack that supports native modules, check out <a href="https://vitejs.dev">Vite</a> or <a href="https://rollupjs.org/">Rollup</a>.</p> | |||
<h2 id="web-components">Web components</h2> | |||
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a> is an umbrella term for several native technologies (Custom Elements, Shadow DOM and HTML templates) that let us bundle the markup and dynamic behaviour of an element into a reusable piece of code, on a high level no different to the React or Vue component. As of May 2021, <a href="https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/"><em>All the Ways to Make a Web Component</em></a> lists 55 variants of a hypothetical <code class="language-plaintext highlighter-rouge"><my-counter></code> component for comparison across bundle size, coding style and performance. Native web components, of course, stand out because they have no external dependencies (and, consequently, an unmatched tiny bundle size).</p> | |||
<p>Here are some of my own impressions of web components as compared to external frameworks when it comes to DX and architecture.</p> | |||
<h3 id="shadow-dom-and-template">Shadow DOM and <code class="language-plaintext highlighter-rouge"><template></code></h3> | |||
<p>The shadow DOM, a way of keeping the internals of a component inaccessible to the main document, is a neat feature in theory, but a natural use case for it didn’t really arise when working on <em>Schematics</em>. I’ve played around with the shadow DOM in the past as part of a Polymer (now LitElement) project, and I mostly remember it creating more problems than it solved, especially when it came to styling. I can see the shadow DOM being useful in cases where a component is reused across many sites and in unpredictable contexts (such as elements in a UI library); for your standard <code class="language-plaintext highlighter-rouge"><site-specific-header-dropdown></code>, it doesn’t add much value.</p> | |||
<p>The <code class="language-plaintext highlighter-rouge"><template></code> and <code class="language-plaintext highlighter-rouge"><slot></code> elements (the latter only useful if you’re using the shadow DOM) are presented as a means of adding hidden markup inside JS, but they feel clunky if you’re used to something like JSX. Additionally, the <code class="language-plaintext highlighter-rouge"><template></code> tag <a href="https://stackoverflow.com/a/53317973">seems counter-inuitive if the aim is to create portable components</a> that can be included in an app with a single import. When declared in the HTML, any JavaScript making use of the <code class="language-plaintext highlighter-rouge"><template></code> makes assumptions about what’s available to it in the main document. Indeed, at times the <code class="language-plaintext highlighter-rouge">index.html</code> in Schematics felt like a dumping ground for various <code class="language-plaintext highlighter-rouge"><template></code>s, rather than a neat overview of the page structure.</p> | |||
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/HTML_Imports">HTML Imports</a> was a proposal that might have allowed us to package JS and HTML together into a single bundle, making <code class="language-plaintext highlighter-rouge"><template></code> the star of the show. Unfortunately, the feature never took off.</p> | |||
<p>Another <a href="https://github.com/whatwg/html/issues/2254">proposed feature</a> is a means to add variables, simple statements and event handlers to nodes in a <code class="language-plaintext highlighter-rouge"><template></code> tag. The gist of it is similar to what you’d currently see in front-end frameworks:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template</span> <span class="na">id=</span><span class="s">"card"</span><span class="nt">></span> | |||
<span class="nt"><card></span> | |||
<span class="nt"><h2></span>{{title}}<span class="nt"></h2></span> | |||
<span class="nt"><div></span>{{description}}<span class="nt"></div></span> | |||
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/card/{{id}}"</span><span class="nt">></span>Read more<span class="nt"></a></span> | |||
<span class="nt"><button</span> <span class="na">handler=</span><span class="s">"onEdit"</span><span class="nt">></span>Edit<span class="nt"></button></span> | |||
<span class="nt"></card></span> | |||
<span class="nt"></temlate></span> | |||
</code></pre></div></div> | |||
<p>Being accustomed to built-in data-binding in frameworks, until the variables above are automatically updated, the feature feels half-baked. As it stands, it’s difficult to gauge the full, declarative markup of a component in one place, as update logic is scattered throughout the class with query selectors, <code class="language-plaintext highlighter-rouge">innerHTML</code>, document fragments and the like.</p> | |||
<p>If you’d like variable and event handler support in <code class="language-plaintext highlighter-rouge"><template></code> now, GitHub, who <a href="https://github.blog/2021-05-04-how-we-use-web-components-at-github/">use vanilla web components internally</a>, provide <a href="https://github.com/github/template-parts">a polyfill</a> for the minimum viable bits of the proposal. As for data binding, Danny Moerkerke describes one approach to implementing it yourself: <a href="https://medium.com/swlh/https-medium-com-drmoerkerke-data-binding-for-web-components-in-just-a-few-lines-of-code-33f0a46943b3"><em>Data binding for Web Components in just a few lines of code</em></a>.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/d85e81c35756d05361c49b19e45743d3f40f6567/75587/assets/post-assets/stackless/fig36-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/d0c8e2853dbe0b9a57cdfc78906026bff470b269/dd0d7/assets/post-assets/stackless/fig36-mobile.gif" alt="Figure 36: A swinging pendulum."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<h3 id="extending-native-elements">Extending native elements</h3> | |||
<p>The web components feature I was most excited about, and one that a third-party framework inherently cannot provide, is <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#customized_built-in_elements">the ability to extend native HTML elements</a>. This allows you to inherit any built-in behaviour for that element in your component, including accessibility-specific properties. Unfortunatley, <a href="https://dev.to/lkraav/comment/ad06">Apple have decided not to support customizable elements in Safari</a>.</p> | |||
<p>This is disappointing. Using front-end frameworks, we often implement our own, more snazzy versions of native HTML elements. Input elements especially, such as dropdowns and radio buttons, are tricky to style using CSS and often end up reproduced with JS. But reproducing the required ARIA attributes, keyboard navigation and the like requires extra effort — something we would get for free with customizable elements.</p> | |||
<h3 id="components-all-the-way-down">Components all the way down?</h3> | |||
<p>While ES modules sparked excitement for the future, I was left underwhelmed by the prospect of native web components replacing traditional front-end frameworks. In hindsight, weighing up web components against JS frameworks was missing the point of the exercise. It’s not about making more of your JavaScript vanilla; it’s spotting opportunities to forego JavaScript in the first place.</p> | |||
<p>Being a React user, attaching my <code class="language-plaintext highlighter-rouge">App.js</code> root component to a <code class="language-plaintext highlighter-rouge"><div id=“app”></code> root node and working down the tree, in smaller components, is how I’m used to thinking about SPAs. Though you <em>can</em> attach your component onto individual DOM nodes (and you could easily mix and match frameworks, too), the more standard approach is to let the framework drive the whole page.</p> | |||
<p>This will quickly fall apart when using web components. At the level of the atomic UI element, they shine; for the business logic and user flows, you should defer to a different part of your stack. As for static content — why render it with JavaScript if raw markup does the job perfectly?</p> | |||
<p>And so, I caught my mindset shifting from an app that’s just one big component to <em>not</em> using components by default, breaking the link between component and framework in the process.</p> | |||
<p>In the case of <em>Schematics</em>, I ended up with just a couple of main custom elements sitting neatly in the middle of native HTML tags. A simplified illustration of the idea:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><body></span> | |||
<span class="nt"><header></header></span> | |||
<span class="nt"><aside></aside></span> | |||
<span class="nt"><main></span> | |||
<span class="nt"><schematics-figure></schematics-figure></span> | |||
<span class="nt"><schematics-figure-toolbar></schematics-figure-toolbar></span> | |||
<span class="nt"></main></span> | |||
<span class="nt"><footer></footer></span> | |||
<span class="nt"></body></span> | |||
</code></pre></div></div> | |||
<p>It seems obvious in hindsight that a component on a website should encapsulate the behaviour and appearance of some bit of UI. But, as <a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">this article nicely puts it</a>, with the proliferation of JS frameworks, “we’ve landed over in the ditch on the other side of the road: putting all sorts of behaviour into the same JSX-denoted structure as Components”.</p> | |||
<blockquote> | |||
<p>“React renders your UI and responds to events” was <a href="https://youtu.be/x7cQ3mrcKaY?t=79">how it was introduced</a>. Not: “React is a way to transform and execute all your logic in a hierarchy of declarative markup”, as it has devolved into in many cases. (<a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">Magne</a>)</p> | |||
</blockquote> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/6d77f50ef261b769e44844c897ce310990d23906/7d632/assets/post-assets/stackless/fig43-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/f544521286fe6c3c943b2adea827785c25f4ba91/21968/assets/post-assets/stackless/fig43-mobile.gif" alt="Figure 43: A 3D cube erratically rotating and changing size."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<hr> | |||
<p>The above thoughts are mostly based on first impressions. If you’re interested in a more detailed review of the state of the web components spec, I recommend <a href="https://webreflection.medium.com/about-web-components-cc3e8b4035b0">this excellent article by Andrea Giammarchi</a> which takes a more critical look of the feature in the context of the 30-year history of the web.</p> | |||
<h2 id="the-future-of-stackless">The future of stackless</h2> | |||
<p><em>Schematics</em> was a simple project as far as architecture goes, and web components and ES6 modules took me most of the way there. I can’t see myself building a complex app without a framework. Fully static sites, too, are simpler to build using a generator like Jekyll: something like markup reuse across pages is impossible when you only have the browser and text editor to work with (the humble <code class="language-plaintext highlighter-rouge"><iframe></code> might disagree with me here…).</p> | |||
<p>Technical limitations aside, an issue I became aware of during this exercise was a lack of standards in vanilla JavaScript. Because we’re so used to the framework dictating how to structure our codebase, there aren’t many established guidelines should you decide to handcraft your JavaScript. Never mind the high-level architecture — even the way you define a class, use static variables, or implement composition all have a range of approaches to them. Web components fill in the gap when it comes to the UI; perhaps if more developers dared to go framework-free (when appropriate!) discussions around best practices would be more productive.</p> | |||
<h2 id="know-the-platform">Know the platform</h2> | |||
<p>Developing web apps without frameworks or build tools is not an end goal in and of itself. As Daniel Kehoe put it in the stackless newsletter:</p> | |||
<blockquote> | |||
<p>I don’t think we’ll be talking about “stackless” in a few years. It’s just going to be part of any web developer’s professional bag of tricks.</p> | |||
</blockquote> | |||
<p>I’d love to see that happen. I have a special disdain for beginner JavaScript tutorials that have you run <code class="language-plaintext highlighter-rouge">create-react-app</code> as the first step, and this exercise has only strengthened my conviction that every beginner programmer should get to grips with HTML, CSS and vanilla JS before delving into frameworks. Features native to the web are what all frameworks share, and knowing the platform makes for a stronger foundation in the face of change.</p> | |||
<hr> | |||
<h3 id="postscriptum-stackless-stylesheets">Postscriptum: Stackless stylesheets?</h3> | |||
<p>I’m so used to writing LESS or SCSS, or, more recently, setting up a set of PostCSS plugins, that <em>not</em> having a preprocessor didn’t cross my mind at first.</p> | |||
<p>Indeed, Kehoe encourages the use of CSS frameworks and/or preprocessors <a href="https://tutorials.yax.com/articles/the-yax-way/2.html">“until libraries of custom UI elements are more broadly available”</a>.</p> | |||
<p>But such a big part of the appeal of stackless for me was running <em>all</em> source code in the browser as is — having a preprocessing step for CSS would ruin that. So, in the spirit of the exercise I limited myself to vanilla CSS.</p> | |||
<p>I persisted with this right up until the first media query came along, and things began to look nasty. Yes, modern CSS does get you 90% there; custom properties are a real workhorse (don’t forget, no preprocessor can give you properties that update during runtime, or that you can set/get via JavaScript). But the lack of native support for nested selectors (along with the parent selector) I cannot live without, and that extra 10% makes a world’s difference.</p> | |||
<p>Stackless CSS isn’t there yet. Use a preprocessor.</p> | |||
</article> | |||
<hr> | |||
<footer> | |||
<p> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-rss2"></use> | |||
</svg> Suivre</a> • | |||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-user-tie"></use> | |||
</svg> Pro</a> • | |||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-mail"></use> | |||
</svg> Email</a> • | |||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-hammer2"></use> | |||
</svg> Légal</abbr> | |||
</p> | |||
<template id="theme-selector"> | |||
<form> | |||
<fieldset> | |||
<legend><svg class="icon icon-brightness-contrast"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-brightness-contrast"></use> | |||
</svg> Thème</legend> | |||
<label> | |||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||
</label> | |||
<label> | |||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||
</label> | |||
<label> | |||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||
</label> | |||
</fieldset> | |||
</form> | |||
</template> | |||
</footer> | |||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||
<script> | |||
function loadThemeForm(templateName) { | |||
const themeSelectorTemplate = document.querySelector(templateName) | |||
const form = themeSelectorTemplate.content.firstElementChild | |||
themeSelectorTemplate.replaceWith(form) | |||
form.addEventListener('change', (e) => { | |||
const chosenColorScheme = e.target.value | |||
localStorage.setItem('theme', chosenColorScheme) | |||
toggleTheme(chosenColorScheme) | |||
}) | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme && selectedTheme !== 'undefined') { | |||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||
} | |||
} | |||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||
window.addEventListener('load', () => { | |||
let hasDarkRules = false | |||
for (const styleSheet of Array.from(document.styleSheets)) { | |||
let mediaRules = [] | |||
for (const cssRule of styleSheet.cssRules) { | |||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||
continue | |||
} | |||
// WARNING: Safari does not have/supports `conditionText`. | |||
if (cssRule.conditionText) { | |||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||
continue | |||
} | |||
} else { | |||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||
continue | |||
} | |||
} | |||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||
} | |||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||
// currently iterating on, otherwise the browser will be stuck | |||
// in a infinite loop… | |||
for (const mediaRule of mediaRules) { | |||
styleSheet.insertRule(mediaRule.cssText) | |||
hasDarkRules = true | |||
} | |||
} | |||
if (hasDarkRules) { | |||
loadThemeForm('#theme-selector') | |||
} | |||
}) | |||
</script> | |||
</body> | |||
</html> |
@@ -0,0 +1,267 @@ | |||
title: Using the platform | |||
url: https://elisehe.in/2021/08/22/using-the-platform | |||
hash_url: 0f1f65d09181752916216e892bb192d8 | |||
<p>I recently came across a series of articles by Daniel Kehoe where he introduces <a href="https://tutorials.yax.com/articles/build-websites-the-yax-way/quicktakes/what-is-the-yax-way.html"><em>The Stackless Way</em></a>, an optimistic take on web development that proposes we “use the platform” (modern features built into the language) instead of frameworks and build tools that keep getting replaced every few years.</p> | |||
<p>It was good timing. While I’m a front-end developer at heart, I’ve rarely had the luxury of focusing on it full time. I’ve been dipping in and out of JavaScript, never fully caught up, always trying to navigate the ecosystem all over again each time a project came up. And framework fatigue is real!</p> | |||
<p>So, instead of finally getting into Rollup to replace an ancient Browserify build on an old codebase (which could also really use that upgrade from Polymer to LitElement…), I decided to go “stackless”. I took a long-time idea for a motion design project and built it using nothing but features native to the browser: vanilla JS, ES6 modules and web components.</p> | |||
<p>Working on a codebase with no dependencies has been a way of rediscovering exactly what I get for free in 2021, and what value I’m adding by bringing frameworks, transpilers and bundlers to the mix. I’d like to share what I learned (and what I needed to unlearn) in the process.</p> | |||
<h3 id="the-project">The project</h3> | |||
<p>The project itself, <a href="https://schematics.elisehe.in"><em>Schematics: A Love Story</em></a>, is a collection of animated diagrams from a visual poetry book by the same name. The animations dotted throughout this write-up are examples from the project: SVG created programmatically with modular, vanilla JavaScript, encapsulated in a web component, sitting inside a framework-free codebase.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/0d2f110a4e442af3495fdc556c2dbd92accc9255/5a69e/assets/post-assets/stackless/fig14-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/d20fa5f0b97d59400afd9bae5611ceb9b1437c93/ed6db/assets/post-assets/stackless/fig14-mobile.gif" alt="Figure 14: A line spiralling upwards along a the time axis in a 3-dimensional coordinate system."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<h2 id="the-stackless-way">The Stackless Way</h2> | |||
<p>Daniel Kehoe is not alone in his push-back on the complexity of the modern web. Frank Chimero’s <a href="https://frankchimero.com/blog/2018/everything-easy/"><em>Everything Easy is Hard Again</em></a> gets to me every time, and, more recently, I enjoyed <a href="https://medium.com/codex/youre-missing-out-on-vanilla-js-91aceec917d6">this lighthearted rant</a> about the lack of appreciation for vanilla JS.</p> | |||
<p>Appeals for external JS dependencies to be added with thoughtful intent have become common, but some schools of thought question the concept of single-page apps as a whole: server-side rendering is still a thing, after all. Pages can also be pre-built into a fully static site and served from a CDN (see <a href="https://jamstack.org/what-is-jamstack/">Jamstack</a>). These approaches recognise that we can move some of the complexity currently managed by front-end frameworks elsewhere on the stack.</p> | |||
<p>But Kehoe’s series of articles on going stackless feels a little different. It’s not just about JavaScript — it’s a wholehearted devotion to the native features of the web as a platform (Routing? Just make sure every URL matches a .html file!)</p> | |||
<p>There are limits to this kind of purism, of course. The Stackless Way, to me, is less of a realistic approach to building production web apps and more of a learning and introspection tool, a way to take a step back and fall in love with the platform again.</p> | |||
<p>Being all about motion design, my own project relied on many a <code class="language-plaintext highlighter-rouge">setTimeout</code> and page transitions — a single-page app, really… just handcrafted. The two main technologies that made it possible are ES6 modules and web components, which, along with module CDNs, form the pillars of The Stackless Way.</p> | |||
<h2 id="es6-modules">ES6 modules</h2> | |||
<p>If you’ve been developing for the web for some time, you might remember doing this:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/vendor/jquery.min.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/main.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/subscribe.js"</span><span class="nt">></script></span> | |||
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/js/gallery.js"</span><span class="nt">></script></span> | |||
<span class="c"><!-- Many more script tags whose specific ordering was a source of bugs and frustration --></span> | |||
<span class="nt"></head></span> | |||
</code></pre></div></div> | |||
<p>Listing all required scripts separately in the document <code class="language-plaintext highlighter-rouge"><head></code> was all we could do given the lack of support for a native import/export mechanism in JavaScript. This was, of course, suboptimal:</p> | |||
<ul> | |||
<li>each script tag initiates a HTTP request;</li> | |||
<li>there is no namespacing (all scripts exist in the global scope);</li> | |||
<li>the order of execution is linear, and maps directly onto the ordering of the script tags.</li> | |||
</ul> | |||
<p>To combat this, approaches to modularity in JavaScript have proliferated over the years (AMD, UMD, CommonJS), and along with them, build tools and bundlers to convert the modular code into something that the browser understands.</p> | |||
<p>ECMAScript Modules (also referred to as ESM or ES6 Modules) is the first <em>native</em> standard for modules in JavaScript. I emphasise <em>native</em> here because that means we can ditch the build tools and bundlers in favour of <code class="language-plaintext highlighter-rouge"><script type=“module”></code> in HTML and <code class="language-plaintext highlighter-rouge">import</code>/<code class="language-plaintext highlighter-rouge">export</code> in JS:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span> | |||
<span class="c"><!-- Single entry point for a dependency graph of any depth --></span> | |||
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"main.js"</span><span class="nt">></script></span> | |||
<span class="nt"></head></span> | |||
</code></pre></div></div> | |||
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.js</span> | |||
<span class="k">import</span> <span class="nx">Gallery</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Gallery.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="k">import</span> <span class="nx">SubscriptionForm</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./SubscriptionForm.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="c1">// Gallery.js</span> | |||
<span class="k">import</span> <span class="nx">Swipe</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./helpers/gestures.js</span><span class="dl">"</span><span class="p">;</span> | |||
<span class="k">export</span> <span class="k">default</span> <span class="kd">class</span> <span class="nx">Gallery</span> <span class="p">{}</span> | |||
</code></pre></div></div> | |||
<p><strong>Native support for modularity is the most important step towards a build-free codebase</strong>. If I had access to only one <a href="http://es6-features.org/">ES6 feature</a> for the rest of my life, I’m confident that modules would take me most of the way there when it comes to well-structured native JavaScript.</p> | |||
<p>And not having to build your JS app is magical. Setting up the directory structure for <em>Schematics</em>, I was giddy with excitement skipping <code class="language-plaintext highlighter-rouge">npm run build</code> and seeing my source files mirrored — as is! — in the browser. It reminded me of when I first began building websites and double-clicking <code class="language-plaintext highlighter-rouge">index.html</code> was enough to see your work in the browser.</p> | |||
<p>You do need a local server if you’re using ES6 modules (so double-clicking an HTML file might be a thing of the past for good). But there’s no lag between editing your code and seeing changes in the browser, and the source code you see in the inspector is exactly what you typed into your editor (no sourcemaps!).</p> | |||
<p>Nevermind the faster edit-compile-debug cycle — this speaks directly to Chimero’s concern about the <a href="https://frankchimero.com/blog/2018/everything-easy/">lack of legibility in today’s codebases as an obstacle to learning the craft</a>:</p> | |||
<blockquote> | |||
<p>Before, the websites could explain themselves; now, someone needs to walk you through it. Illegibility comes from complexity without clarity. I believe that the legibility of the source is one of the most important properties of the web. […] the best way to help someone <em>write</em> markup is to make sure they can <em>read</em> markup. (<a href="https://frankchimero.com/blog/2018/everything-easy/">Frank Chimero</a>)</p> | |||
</blockquote> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/5d8af1fbafa273e967948ac4fef631cb65230ef8/c1472/assets/post-assets/stackless/fig18-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/17fab52ccc9fc287c87cb7cd801de4d371344419/e0067/assets/post-assets/stackless/fig18-mobile.gif" alt="Figure 18: A flow diagram for love. Good? Yes. More? Yes. Do it."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<p>I recommend reading Kehoe’s <a href="https://tutorials.yax.com/articles/javascript/import/index.html"><em>Javascript import explained</em></a> or Ayodeji’s <a href="https://medium.com/backticks-tildes/introduction-to-es6-modules-49956f580da"><em>Introduction to ES6 modules</em></a> for a historic overview of approaches to modularity, how we got to ESM, and how to write your own modules.</p> | |||
<h3 id="the-future-of-app-bundles">The future of app bundles</h3> | |||
<p>Just because you don’t strictly need a build tool to <em>run</em> your code, doesn’t mean you shouldn’t use one for production builds to optimise performance. I’m referring here not to transpilation, but to minification, mangling, tree-shaking, etc.</p> | |||
<p>I’ve been bundling JavaScript into a single <code class="language-plaintext highlighter-rouge">app.js</code> file for such a long time that my first instinct was to concatenate everything into a single script on <em>Schematics</em>, too. This, of course, will not work with ES6 modules — the concept of files is what creates boundaries between different modules, and there is no way to specify more than one module per file.</p> | |||
<p>Luckily, you don’t need to bundle your ES6 modules into a single file to improve performance. Let me repeat: <strong>you don’t need to bundle your ES6 modules into a single file to improve performance</strong>.</p> | |||
<p>Here’s why:</p> | |||
<ol> | |||
<li><code class="language-plaintext highlighter-rouge"><script type=“module"></code> requests are deferred by default: they won’t block document parsing.</li> | |||
<li>Serving all modules from separate source files is great for caching, as the modules unaffected by some change can continue being retrieved from the cache. When you serve a single bundle, any one change will invalidate the whole bundle.</li> | |||
<li>You can lazy load parts of your code by making use of <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports">dynamic imports</a> — importing modules during runtime at the point where you actually need them.</li> | |||
<li>You can preload critical modules with <a href="https://developers.google.com/web/updates/2017/12/modulepreload"><code class="language-plaintext highlighter-rouge">modulepreload</code></a>. This starts parsing and compiling the linked modules immediately, off the main thread.</li> | |||
<li>Finally, HTTP2 may in the future allow us to resolve the entire dependency graph from the first <code class="language-plaintext highlighter-rouge"><script type=“module”></code> request, and send all required files back in a single response. For the time being, this requires bespoke logic written on the server side.</li> | |||
</ol> | |||
<p>And don’t forget about bundle size:</p> | |||
<blockquote> | |||
<p>If you inspect the output code generated by most popular bundlers, you’ll find a lot of boilerplate whose only purpose is to dynamically load other code and manage dependencies, but none of that would be needed if we just used modules with import and export statements! (<a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today">Philip Walton</a>)</p> | |||
</blockquote> | |||
<p>I still have some work to do to go against my first instinct to bundle. It’s not uncommon for a SPA to have hundreds of JavaScript files, and the prospect of serving them all separately seems counterintuitive. Even having read up on all the reasons why I don’t need to bundle my modules, I occasionally hesitated before separating some logic into its own file to avoid the extra request.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/ea47b365638fd85ec7952a63fafb1997530db2b2/b2b8c/assets/post-assets/stackless/fig20-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/3835a734f4f22ce2dddee7654ebd561f17e23042/cceca/assets/post-assets/stackless/fig20-mobile.gif" alt="Figure 20: An illustration of the propagation of sound waves across arrays of vertical lines."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<p>For more details and interesting discussion around ES6 module load performance and app bundles, see <a href="https://esdiscuss.org/topic/fwd-are-es6-modules-in-browsers-going-to-get-loaded-level-by-level">this thread on the ECMAScript Discussion Archives</a>. The spec is not set in stone yet, and there are plenty of ideas floating around, some more daring than others (my personal favourite is the proposal to serve the entire module dependency graph as a .zip file).</p> | |||
<blockquote> | |||
<p>There’s plenty of ongoing module work happening in Chrome, though, so we’re getting closer to giving bundlers their well-earned rest! (<a href="https://developers.google.com/web/updates/2017/12/modulepreload">Sérgio Gomes</a>)</p> | |||
</blockquote> | |||
<p>I also recommend reading <a href="https://philipwalton.com/articles/using-native-javascript-modules-in-production-today"><em>Using Native JavaScript Modules in Production Today</em></a> by Philip Walton. It explains how to serve optimised module files alongside a standard fallback single-file bundle for older browsers.</p> | |||
<p>Finally, if you’re looking for an alternative to webpack that supports native modules, check out <a href="https://vitejs.dev">Vite</a> or <a href="https://rollupjs.org/">Rollup</a>.</p> | |||
<h2 id="web-components">Web components</h2> | |||
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Components</a> is an umbrella term for several native technologies (Custom Elements, Shadow DOM and HTML templates) that let us bundle the markup and dynamic behaviour of an element into a reusable piece of code, on a high level no different to the React or Vue component. As of May 2021, <a href="https://webcomponents.dev/blog/all-the-ways-to-make-a-web-component/"><em>All the Ways to Make a Web Component</em></a> lists 55 variants of a hypothetical <code class="language-plaintext highlighter-rouge"><my-counter></code> component for comparison across bundle size, coding style and performance. Native web components, of course, stand out because they have no external dependencies (and, consequently, an unmatched tiny bundle size).</p> | |||
<p>Here are some of my own impressions of web components as compared to external frameworks when it comes to DX and architecture.</p> | |||
<h3 id="shadow-dom-and-template">Shadow DOM and <code class="language-plaintext highlighter-rouge"><template></code></h3> | |||
<p>The shadow DOM, a way of keeping the internals of a component inaccessible to the main document, is a neat feature in theory, but a natural use case for it didn’t really arise when working on <em>Schematics</em>. I’ve played around with the shadow DOM in the past as part of a Polymer (now LitElement) project, and I mostly remember it creating more problems than it solved, especially when it came to styling. I can see the shadow DOM being useful in cases where a component is reused across many sites and in unpredictable contexts (such as elements in a UI library); for your standard <code class="language-plaintext highlighter-rouge"><site-specific-header-dropdown></code>, it doesn’t add much value.</p> | |||
<p>The <code class="language-plaintext highlighter-rouge"><template></code> and <code class="language-plaintext highlighter-rouge"><slot></code> elements (the latter only useful if you’re using the shadow DOM) are presented as a means of adding hidden markup inside JS, but they feel clunky if you’re used to something like JSX. Additionally, the <code class="language-plaintext highlighter-rouge"><template></code> tag <a href="https://stackoverflow.com/a/53317973">seems counter-inuitive if the aim is to create portable components</a> that can be included in an app with a single import. When declared in the HTML, any JavaScript making use of the <code class="language-plaintext highlighter-rouge"><template></code> makes assumptions about what’s available to it in the main document. Indeed, at times the <code class="language-plaintext highlighter-rouge">index.html</code> in Schematics felt like a dumping ground for various <code class="language-plaintext highlighter-rouge"><template></code>s, rather than a neat overview of the page structure.</p> | |||
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/HTML_Imports">HTML Imports</a> was a proposal that might have allowed us to package JS and HTML together into a single bundle, making <code class="language-plaintext highlighter-rouge"><template></code> the star of the show. Unfortunately, the feature never took off.</p> | |||
<p>Another <a href="https://github.com/whatwg/html/issues/2254">proposed feature</a> is a means to add variables, simple statements and event handlers to nodes in a <code class="language-plaintext highlighter-rouge"><template></code> tag. The gist of it is similar to what you’d currently see in front-end frameworks:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template</span> <span class="na">id=</span><span class="s">"card"</span><span class="nt">></span> | |||
<span class="nt"><card></span> | |||
<span class="nt"><h2></span>{{title}}<span class="nt"></h2></span> | |||
<span class="nt"><div></span>{{description}}<span class="nt"></div></span> | |||
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/card/{{id}}"</span><span class="nt">></span>Read more<span class="nt"></a></span> | |||
<span class="nt"><button</span> <span class="na">handler=</span><span class="s">"onEdit"</span><span class="nt">></span>Edit<span class="nt"></button></span> | |||
<span class="nt"></card></span> | |||
<span class="nt"></temlate></span> | |||
</code></pre></div></div> | |||
<p>Being accustomed to built-in data-binding in frameworks, until the variables above are automatically updated, the feature feels half-baked. As it stands, it’s difficult to gauge the full, declarative markup of a component in one place, as update logic is scattered throughout the class with query selectors, <code class="language-plaintext highlighter-rouge">innerHTML</code>, document fragments and the like.</p> | |||
<p>If you’d like variable and event handler support in <code class="language-plaintext highlighter-rouge"><template></code> now, GitHub, who <a href="https://github.blog/2021-05-04-how-we-use-web-components-at-github/">use vanilla web components internally</a>, provide <a href="https://github.com/github/template-parts">a polyfill</a> for the minimum viable bits of the proposal. As for data binding, Danny Moerkerke describes one approach to implementing it yourself: <a href="https://medium.com/swlh/https-medium-com-drmoerkerke-data-binding-for-web-components-in-just-a-few-lines-of-code-33f0a46943b3"><em>Data binding for Web Components in just a few lines of code</em></a>.</p> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/d85e81c35756d05361c49b19e45743d3f40f6567/75587/assets/post-assets/stackless/fig36-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/d0c8e2853dbe0b9a57cdfc78906026bff470b269/dd0d7/assets/post-assets/stackless/fig36-mobile.gif" alt="Figure 36: A swinging pendulum."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<h3 id="extending-native-elements">Extending native elements</h3> | |||
<p>The web components feature I was most excited about, and one that a third-party framework inherently cannot provide, is <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#customized_built-in_elements">the ability to extend native HTML elements</a>. This allows you to inherit any built-in behaviour for that element in your component, including accessibility-specific properties. Unfortunatley, <a href="https://dev.to/lkraav/comment/ad06">Apple have decided not to support customizable elements in Safari</a>.</p> | |||
<p>This is disappointing. Using front-end frameworks, we often implement our own, more snazzy versions of native HTML elements. Input elements especially, such as dropdowns and radio buttons, are tricky to style using CSS and often end up reproduced with JS. But reproducing the required ARIA attributes, keyboard navigation and the like requires extra effort — something we would get for free with customizable elements.</p> | |||
<h3 id="components-all-the-way-down">Components all the way down?</h3> | |||
<p>While ES modules sparked excitement for the future, I was left underwhelmed by the prospect of native web components replacing traditional front-end frameworks. In hindsight, weighing up web components against JS frameworks was missing the point of the exercise. It’s not about making more of your JavaScript vanilla; it’s spotting opportunities to forego JavaScript in the first place.</p> | |||
<p>Being a React user, attaching my <code class="language-plaintext highlighter-rouge">App.js</code> root component to a <code class="language-plaintext highlighter-rouge"><div id=“app”></code> root node and working down the tree, in smaller components, is how I’m used to thinking about SPAs. Though you <em>can</em> attach your component onto individual DOM nodes (and you could easily mix and match frameworks, too), the more standard approach is to let the framework drive the whole page.</p> | |||
<p>This will quickly fall apart when using web components. At the level of the atomic UI element, they shine; for the business logic and user flows, you should defer to a different part of your stack. As for static content — why render it with JavaScript if raw markup does the job perfectly?</p> | |||
<p>And so, I caught my mindset shifting from an app that’s just one big component to <em>not</em> using components by default, breaking the link between component and framework in the process.</p> | |||
<p>In the case of <em>Schematics</em>, I ended up with just a couple of main custom elements sitting neatly in the middle of native HTML tags. A simplified illustration of the idea:</p> | |||
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><body></span> | |||
<span class="nt"><header></header></span> | |||
<span class="nt"><aside></aside></span> | |||
<span class="nt"><main></span> | |||
<span class="nt"><schematics-figure></schematics-figure></span> | |||
<span class="nt"><schematics-figure-toolbar></schematics-figure-toolbar></span> | |||
<span class="nt"></main></span> | |||
<span class="nt"><footer></footer></span> | |||
<span class="nt"></body></span> | |||
</code></pre></div></div> | |||
<p>It seems obvious in hindsight that a component on a website should encapsulate the behaviour and appearance of some bit of UI. But, as <a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">this article nicely puts it</a>, with the proliferation of JS frameworks, “we’ve landed over in the ditch on the other side of the road: putting all sorts of behaviour into the same JSX-denoted structure as Components”.</p> | |||
<blockquote> | |||
<p>“React renders your UI and responds to events” was <a href="https://youtu.be/x7cQ3mrcKaY?t=79">how it was introduced</a>. Not: “React is a way to transform and execute all your logic in a hierarchy of declarative markup”, as it has devolved into in many cases. (<a href="https://dev.to/redbar0n/what-happened-to-components-being-just-a-visual-thing-22hc">Magne</a>)</p> | |||
</blockquote> | |||
<figure class="image"> | |||
<picture> | |||
<source srcset="https://d33wubrfki0l68.cloudfront.net/6d77f50ef261b769e44844c897ce310990d23906/7d632/assets/post-assets/stackless/fig43-desktop.gif" media="(min-width: 750px)"></source> | |||
<img src="https://d33wubrfki0l68.cloudfront.net/f544521286fe6c3c943b2adea827785c25f4ba91/21968/assets/post-assets/stackless/fig43-mobile.gif" alt="Figure 43: A 3D cube erratically rotating and changing size."> | |||
</picture> | |||
<figcaption> | |||
A diagram reproduced from <a href="https://julianhibbard.com">Julian Hibbard’s</a> <em>Schematics: A Love Story</em>, handcrafted with modern JavaScript.<br><a href="https://schematics.elisehe.in">See full collection</a> | |||
</figcaption> | |||
</figure> | |||
<hr> | |||
<p>The above thoughts are mostly based on first impressions. If you’re interested in a more detailed review of the state of the web components spec, I recommend <a href="https://webreflection.medium.com/about-web-components-cc3e8b4035b0">this excellent article by Andrea Giammarchi</a> which takes a more critical look of the feature in the context of the 30-year history of the web.</p> | |||
<h2 id="the-future-of-stackless">The future of stackless</h2> | |||
<p><em>Schematics</em> was a simple project as far as architecture goes, and web components and ES6 modules took me most of the way there. I can’t see myself building a complex app without a framework. Fully static sites, too, are simpler to build using a generator like Jekyll: something like markup reuse across pages is impossible when you only have the browser and text editor to work with (the humble <code class="language-plaintext highlighter-rouge"><iframe></code> might disagree with me here…).</p> | |||
<p>Technical limitations aside, an issue I became aware of during this exercise was a lack of standards in vanilla JavaScript. Because we’re so used to the framework dictating how to structure our codebase, there aren’t many established guidelines should you decide to handcraft your JavaScript. Never mind the high-level architecture — even the way you define a class, use static variables, or implement composition all have a range of approaches to them. Web components fill in the gap when it comes to the UI; perhaps if more developers dared to go framework-free (when appropriate!) discussions around best practices would be more productive.</p> | |||
<h2 id="know-the-platform">Know the platform</h2> | |||
<p>Developing web apps without frameworks or build tools is not an end goal in and of itself. As Daniel Kehoe put it in the stackless newsletter:</p> | |||
<blockquote> | |||
<p>I don’t think we’ll be talking about “stackless” in a few years. It’s just going to be part of any web developer’s professional bag of tricks.</p> | |||
</blockquote> | |||
<p>I’d love to see that happen. I have a special disdain for beginner JavaScript tutorials that have you run <code class="language-plaintext highlighter-rouge">create-react-app</code> as the first step, and this exercise has only strengthened my conviction that every beginner programmer should get to grips with HTML, CSS and vanilla JS before delving into frameworks. Features native to the web are what all frameworks share, and knowing the platform makes for a stronger foundation in the face of change.</p> | |||
<hr> | |||
<h3 id="postscriptum-stackless-stylesheets">Postscriptum: Stackless stylesheets?</h3> | |||
<p>I’m so used to writing LESS or SCSS, or, more recently, setting up a set of PostCSS plugins, that <em>not</em> having a preprocessor didn’t cross my mind at first.</p> | |||
<p>Indeed, Kehoe encourages the use of CSS frameworks and/or preprocessors <a href="https://tutorials.yax.com/articles/the-yax-way/2.html">“until libraries of custom UI elements are more broadly available”</a>.</p> | |||
<p>But such a big part of the appeal of stackless for me was running <em>all</em> source code in the browser as is — having a preprocessing step for CSS would ruin that. So, in the spirit of the exercise I limited myself to vanilla CSS.</p> | |||
<p>I persisted with this right up until the first media query came along, and things began to look nasty. Yes, modern CSS does get you 90% there; custom properties are a real workhorse (don’t forget, no preprocessor can give you properties that update during runtime, or that you can set/get via JavaScript). But the lack of native support for nested selectors (along with the parent selector) I cannot live without, and that extra 10% makes a world’s difference.</p> | |||
<p>Stackless CSS isn’t there yet. Use a preprocessor.</p> |
@@ -0,0 +1,244 @@ | |||
<!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` element | |||
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,initial-scale=1"> | |||
<!-- Required to make a valid HTML5 document. --> | |||
<title>this website as a learning and reflection tool (archive) — David Larlet</title> | |||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||
<!-- That good ol' feed, subscribe :). --> | |||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||
<!-- Documented, feel free to shoot an email. --> | |||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<script> | |||
function toggleTheme(themeName) { | |||
document.documentElement.classList.toggle( | |||
'forced-dark', | |||
themeName === 'dark' | |||
) | |||
document.documentElement.classList.toggle( | |||
'forced-light', | |||
themeName === 'light' | |||
) | |||
} | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme !== 'undefined') { | |||
toggleTheme(selectedTheme) | |||
} | |||
</script> | |||
<meta name="robots" content="noindex, nofollow"> | |||
<meta content="origin-when-cross-origin" name="referrer"> | |||
<!-- Canonical URL for SEO purposes --> | |||
<link rel="canonical" href="https://winnielim.org/experiments/website/this-website-as-a-learning-and-reflection-tool/"> | |||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||
<article> | |||
<header> | |||
<h1>this website as a learning and reflection tool</h1> | |||
</header> | |||
<nav> | |||
<p class="center"> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="https://winnielim.org/experiments/website/this-website-as-a-learning-and-reflection-tool/" title="Lien vers le contenu original">Source originale</a> | |||
</p> | |||
</nav> | |||
<hr> | |||
<h2><span class="ez-toc-section" id="What_led_to_this"></span>What led to this<span class="ez-toc-section-end"></span></h2> | |||
<p>Once in a while I’ll stumble upon <a href="https://medium.com/the-experimental-year/the-year-i-broke-up-with-myself-a-review-of-2015-with-year-long-data-cf284e22f1f9">my 20,000+ word</a> review of 2015 and I would learn different lessons from it each time. 2015 is probably the most transformational year I’ve had so far in my life, and even 6 years later it was intriguing to see how it all happened, how it was all happening before I knew what was happening, and how I wrote my meta-thoughts on those events after the year was over. </p> | |||
<p>I spent a really long time assembling that document manually, and I never found the will or the energy to do it again. But in recent times I’ve been having a lot of insights simply by reviewing the “on this day” function on <a href="https://dayoneapp.com/">dayone</a>. That only covers my private journal entries, and I want to go beyond surfacing entries “on this day”, so I started thinking how I want to build the functionality to somewhat automate this on this website.</p> | |||
<p>Basically, I want to programatically surface different views of my data and writing on top of the standard reverse-chronological. I am starting by sorting them annually first, but I could also surface views by tags, related content, location, etc. </p> | |||
<p>In parallel, I also wanted a space where I can <a href="https://winnielim.org/notes/first-note/" data-type="note" data-id="3888">write short-form thoughts</a> and store meaningful social media data like <a href="https://winnielim.org/notes/54-months/" data-type="note" data-id="3896">important instagram posts</a>. </p> | |||
<p>(Feel free to skip the next section if you want to know more details about the why instead </p> | |||
<h2><span class="ez-toc-section" id="Deciding_on_the_structure"></span>Deciding on the structure<span class="ez-toc-section-end"></span></h2> | |||
<p>I thought they could all exist as the same thing, so at first I started building <a href="https://winnielim.org/archive/log/?y=2015">a page</a> where I could view everything I have done on this website – the default archive view surfaces my writing only, whereas I post other stuff like <a href="/library/collections">collections</a>, <a href="/library/resources">resources</a>, <a href="/notebooks">notebooks</a>, etc. </p> | |||
<p>But I quickly realised I wanted a section purely reserved for short-form content. Along the way I was also wondering if I was over-complicating everything – perhaps I should just post short-form content under a different category under /journal. But the additional custom fields I wanted for short-form content would add unnecessary clutter to the default journal entry form, and if one day I decide to kill the new section I could do so cleanly if it was simply another post type in wordpress.</p> | |||
<p>So now I have two new sections: <a href="/notes">/notes</a> for short-form content and <a href="/archive/log">/archive/log</a> for logging everything. At first I wasn’t sure about /notes because I already have <a href="/notebooks">/notebooks</a>, and a more technically correct term would be /microblog, but no one outside of tech knows what is a /microblog and to me /notebooks are the structured containers to all my content. I considered “meta” for a while, that was before the whole facebook debacle, but I guess “meta” is also not a very relatable word in the mainstream consciousness. My twitter friend also helped!</p> | |||
<p>To be honest, at the end I don’t think anyone really cares but myself. In general from analysing my stats most people only either view the homepage if they happen to stumble upon my site from elsewhere, or the single journal entries when I publish them on social media. Only a small percentage of people explore the other sections. It is this small percentage of people who visit tens or hundreds of my pages in one visit (whoever you are, thank you for making me feel less alone in this world). I am mostly building this site for myself, but also this very small group of people.</p> | |||
<h2><span class="ez-toc-section" id="A_tool_for_self-reflection"></span>A tool for self-reflection<span class="ez-toc-section-end"></span></h2> | |||
<p>Our brains are limited in the way we can recall information. No one remembers what happened last week, much less specifically on a day three years ago. Our psyches also actively edit the past. Sometimes they may get romanticised, sometimes they get exaggerated. My memories of something actively influences how I interact in the present. </p> | |||
<p>For example, when I decided to quit a job I thought I had loved I wrote down all the reasons why I had quit, precisely knowing that my future self would forget and romanticise the job. It turned out I was right, every now and then periodically I would be overcome by nostalgia which in turn invited regret. That particular journal entry was very helpful in nudging me back into reality and dispersing the romanticised regret.</p> | |||
<p>Sometimes I feel lost and I start wondering why I am where I am. My old entries serve as a powerful reminder of why I chose to do things a certain way. It is not easy to walk on an uncommon path. There are no signposts along the way, I have to leave breadcrumbs for myself. You would think we would remember everything about our selves, but I often forget who I am and who I want to be. Societal conditioning is immense.</p> | |||
<p>This website is essentially a repository of my memories, lessons I’ve learnt, insights I’ve discovered, a changelog of my previous selves. Most people build a map of things they have learnt, I am building a map of how I have come to be, in case I may get lost again. Maybe someone else interested in a similar lonely path will feel less alone with my documented footprints. Maybe that someone else would be me in the future. Maybe all of this would be interesting when I am dead, <a href="https://winnielim.org/journal/on-writing-to-exist-and-website-graveyards/" data-type="post" data-id="3347">assuming I find a way</a> to keep this site alive. </p> | |||
<p>Reading my past entries also allow me to understand the distance I’ve travelled between my past selves and my current self. Sometimes it feels like I’m still stuck in my old unhealthy patterns and reading my past entries enable me to see otherwise. Some things used to haunt me greatly and I feel ambivalent or amused about them now. I like that I can relive certain experiences because I took the effort to write them down, and understand how I relate to them now.</p> | |||
<p>Some of my best writing (to me) appears unintentionally <a href="https://www.instagram.com/p/BoYVRTanQDP/?utm_medium=copy_link">on a spontaneous photo</a> on instagram, and I want them to exist outside of a reverse chronological timeline that no one ever revisits again. I would not be re-reading these posts if not for dayone or timehop, and re-reading them lights up something in me, especially if I am in a period of existential slump, which occurs more often than I would like. Sometimes I forget I can be capable of writing in more dimensions than my usual stream of consciousness. I could reduce the time I spend in existential slumps if I could access these little sparks of my old selves in a more timely and accessible manner.</p> | |||
<p>I store links, books and highlights on this site, so I can see how they all come together with my writing and how they influence my selves. Some books are worth revisiting over and over again, and I would like to store key highlights from all my life-changing books in one place.</p> | |||
<p>I find repetition extremely useful when it comes to wanting to reinforce new patterns or behaviours, so this website serves as my repetition tool. </p> | |||
<h2><span class="ez-toc-section" id="An_interactive_interface_for_programmatically_querying_my_selves"></span>An interactive interface for programmatically querying my selves<span class="ez-toc-section-end"></span></h2> | |||
<p>It is also useful for me to have an interactive interface of everything I have learnt and written about. The queries available now are rudimentary but still more useful than purely chronological. I can see everything tagged with “<a href="https://winnielim.org/tag/zen/">zen</a>“, or search everything that mentioned the word “<a href="https://winnielim.org/?s=meditation">meditation</a>“. I built an “<a href="https://winnielim.org/archive/on-this-day/">on this day</a>” function for everything published on this website. I try to link posts up if they are related to a chain of thoughts, so if I happen to read one I’ll see the others. As long as it is queryable though SQL, I can build an interface for it (this is why I am using wordpress instead of static-site generators). Maybe one day I can build a dashboard or visualisation of my entries. I guess it is like externalising my mind on a website. </p> | |||
<figure class="wp-block-image size-medium"><img loading="lazy" src="https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-700x350.png" alt="illustration: a search box querying my selves" class="wp-image-3922" srcset="https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-700x350.png 700w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-1600x800.png 1600w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-300x150.png 300w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-768x384.png 768w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-1536x768.png 1536w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-2048x1024.png 2048w" sizes="(max-width: 700px) 100vw, 700px"></figure> | |||
<h2><span class="ez-toc-section" id="Increasing_opportunities_for_relearning"></span>Increasing opportunities for relearning<span class="ez-toc-section-end"></span></h2> | |||
<p>Something like “on this day” or <a href="https://readwise.io/">Readwise</a>‘s emails of random highlights from our kindle library are just opportunities to re-expose ourselves to information we have stored. The question is how do I increase these opportunities beyond the context of time or randomness? I think it would have to involve some manual curation and also tools like taxonomies. I will probably discover more meaningful queries I can use regularly as I go along.</p> | |||
<p>Many people treat their journals as something they write and they may never read them again. Same with books. We keep reading, we have all these insights from reading, then over time we forget about them. My 2015 review contained many insights and lessons that are still very relevant to me today, but I have forgotten most of them. Each time I read it I’m like <em>wow this makes so much sense</em>, then I forget, then I read it again – <a href="https://winnielim.org/journal/remembering-forgotten-lessons-and-selves/" data-type="post" data-id="2463">this loop has happened multiple times</a>. How do I store these insights in a meaningful, accessible manner that doesn’t rely on a random impulse to access it? I could set a calendar reminder, or is it possible to preempt the possible scenarios when I would need the relevant insights the most? </p> | |||
<h2><span class="ez-toc-section" id="This_website_is_my_living_book"></span>This website is my living book<span class="ez-toc-section-end"></span></h2> | |||
<p>Some people have asked me whether I’m ever going to write a book, since I’ve written so much and they have found some of my writing meaningful to them. This website is the book. I don’t particularly believe in books actually (I know the irony, considering the amount of books I read), especially for learning. They are linear and it is difficult to sift out the meaningful bits among the huge chunks of content. </p> | |||
<p>But a website is interactive. Perhaps it is still chaotic now, but hopefully one day in the future people can jump directly into the parts meaningful to them and go on their own navigation path supported by relevance instead of chronology or a pre-determined linear scaffolding. They can perhaps perform their own interactive queries. You can’t do that with a book. The information in a book is also pretty much dead, whereas a website can be continually updated.</p> | |||
<p>The thing about a website is that it is not as portable as a book in terms of the reading experience. You still have to do the work to navigate or search for what you want, whereas a book is just a page after page reading experience. I hope I can circumvent some of that with my notebooks, where I am planning to offer a more structured thematic reading experience.</p> | |||
<h2><span class="ez-toc-section" id="Catering_to_an_audience_versus_myself"></span>Catering to an audience versus myself<span class="ez-toc-section-end"></span></h2> | |||
<p>I do feel self-conscious in general whenever I write or make something. I think this self-consciousness is holding me back. Writing a post like this, I can’t help but wonder if people are going to think I’m super weird for putting so much effort into a personal website (well the other day I found a <a href="https://www.fieggen.com/shoelace/">site on shoelaces</a> so maybe I am not that weird after all). </p> | |||
<p>But I realised I have to put aside this self-consciousness and work on this website as though I am only catering to myself if I want to create something that is really a true representation of how I think and create versus a false image. This website may be chaotic in its current form but it is how I work in progress. </p> | |||
<p>I think it is a meaningful thing, to be able to put something true out there, something that people and myself can interact with, in a world where we’re actively encouraged not to be our selves.</p> | |||
<p>This website, is my rebellion against that.</p> | |||
<hr class="wp-block-separator"> | |||
<p><em>Note: it turns out I’ve already written <a href="https://winnielim.org/journal/remembering-forgotten-lessons-and-selves/">something similar but different</a> 2 years ago, which reinforces my point I guess: which is that I don’t remember what I’ve written, how do I make remembering easier, and it was also very interesting to see the differences in tone between that piece and this.</em></p> | |||
</article> | |||
<hr> | |||
<footer> | |||
<p> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-rss2"></use> | |||
</svg> Suivre</a> • | |||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-user-tie"></use> | |||
</svg> Pro</a> • | |||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-mail"></use> | |||
</svg> Email</a> • | |||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-hammer2"></use> | |||
</svg> Légal</abbr> | |||
</p> | |||
<template id="theme-selector"> | |||
<form> | |||
<fieldset> | |||
<legend><svg class="icon icon-brightness-contrast"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-brightness-contrast"></use> | |||
</svg> Thème</legend> | |||
<label> | |||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||
</label> | |||
<label> | |||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||
</label> | |||
<label> | |||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||
</label> | |||
</fieldset> | |||
</form> | |||
</template> | |||
</footer> | |||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||
<script> | |||
function loadThemeForm(templateName) { | |||
const themeSelectorTemplate = document.querySelector(templateName) | |||
const form = themeSelectorTemplate.content.firstElementChild | |||
themeSelectorTemplate.replaceWith(form) | |||
form.addEventListener('change', (e) => { | |||
const chosenColorScheme = e.target.value | |||
localStorage.setItem('theme', chosenColorScheme) | |||
toggleTheme(chosenColorScheme) | |||
}) | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme && selectedTheme !== 'undefined') { | |||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||
} | |||
} | |||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||
window.addEventListener('load', () => { | |||
let hasDarkRules = false | |||
for (const styleSheet of Array.from(document.styleSheets)) { | |||
let mediaRules = [] | |||
for (const cssRule of styleSheet.cssRules) { | |||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||
continue | |||
} | |||
// WARNING: Safari does not have/supports `conditionText`. | |||
if (cssRule.conditionText) { | |||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||
continue | |||
} | |||
} else { | |||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||
continue | |||
} | |||
} | |||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||
} | |||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||
// currently iterating on, otherwise the browser will be stuck | |||
// in a infinite loop… | |||
for (const mediaRule of mediaRules) { | |||
styleSheet.insertRule(mediaRule.cssText) | |||
hasDarkRules = true | |||
} | |||
} | |||
if (hasDarkRules) { | |||
loadThemeForm('#theme-selector') | |||
} | |||
}) | |||
</script> | |||
</body> | |||
</html> |
@@ -0,0 +1,153 @@ | |||
title: this website as a learning and reflection tool | |||
url: https://winnielim.org/experiments/website/this-website-as-a-learning-and-reflection-tool/ | |||
hash_url: 532af45eef948b36907a25f63c6eb95f | |||
<h2><span class="ez-toc-section" id="What_led_to_this"></span>What led to this<span class="ez-toc-section-end"></span></h2> | |||
<p>Once in a while I’ll stumble upon <a href="https://medium.com/the-experimental-year/the-year-i-broke-up-with-myself-a-review-of-2015-with-year-long-data-cf284e22f1f9">my 20,000+ word</a> review of 2015 and I would learn different lessons from it each time. 2015 is probably the most transformational year I’ve had so far in my life, and even 6 years later it was intriguing to see how it all happened, how it was all happening before I knew what was happening, and how I wrote my meta-thoughts on those events after the year was over. </p> | |||
<p>I spent a really long time assembling that document manually, and I never found the will or the energy to do it again. But in recent times I’ve been having a lot of insights simply by reviewing the “on this day” function on <a href="https://dayoneapp.com/">dayone</a>. That only covers my private journal entries, and I want to go beyond surfacing entries “on this day”, so I started thinking how I want to build the functionality to somewhat automate this on this website.</p> | |||
<p>Basically, I want to programatically surface different views of my data and writing on top of the standard reverse-chronological. I am starting by sorting them annually first, but I could also surface views by tags, related content, location, etc. </p> | |||
<p>In parallel, I also wanted a space where I can <a href="https://winnielim.org/notes/first-note/" data-type="note" data-id="3888">write short-form thoughts</a> and store meaningful social media data like <a href="https://winnielim.org/notes/54-months/" data-type="note" data-id="3896">important instagram posts</a>. </p> | |||
<p>(Feel free to skip the next section if you want to know more details about the why instead </p> | |||
<h2><span class="ez-toc-section" id="Deciding_on_the_structure"></span>Deciding on the structure<span class="ez-toc-section-end"></span></h2> | |||
<p>I thought they could all exist as the same thing, so at first I started building <a href="https://winnielim.org/archive/log/?y=2015">a page</a> where I could view everything I have done on this website – the default archive view surfaces my writing only, whereas I post other stuff like <a href="/library/collections">collections</a>, <a href="/library/resources">resources</a>, <a href="/notebooks">notebooks</a>, etc. </p> | |||
<p>But I quickly realised I wanted a section purely reserved for short-form content. Along the way I was also wondering if I was over-complicating everything – perhaps I should just post short-form content under a different category under /journal. But the additional custom fields I wanted for short-form content would add unnecessary clutter to the default journal entry form, and if one day I decide to kill the new section I could do so cleanly if it was simply another post type in wordpress.</p> | |||
<p>So now I have two new sections: <a href="/notes">/notes</a> for short-form content and <a href="/archive/log">/archive/log</a> for logging everything. At first I wasn’t sure about /notes because I already have <a href="/notebooks">/notebooks</a>, and a more technically correct term would be /microblog, but no one outside of tech knows what is a /microblog and to me /notebooks are the structured containers to all my content. I considered “meta” for a while, that was before the whole facebook debacle, but I guess “meta” is also not a very relatable word in the mainstream consciousness. My twitter friend also helped!</p> | |||
<p>To be honest, at the end I don’t think anyone really cares but myself. In general from analysing my stats most people only either view the homepage if they happen to stumble upon my site from elsewhere, or the single journal entries when I publish them on social media. Only a small percentage of people explore the other sections. It is this small percentage of people who visit tens or hundreds of my pages in one visit (whoever you are, thank you for making me feel less alone in this world). I am mostly building this site for myself, but also this very small group of people.</p> | |||
<h2><span class="ez-toc-section" id="A_tool_for_self-reflection"></span>A tool for self-reflection<span class="ez-toc-section-end"></span></h2> | |||
<p>Our brains are limited in the way we can recall information. No one remembers what happened last week, much less specifically on a day three years ago. Our psyches also actively edit the past. Sometimes they may get romanticised, sometimes they get exaggerated. My memories of something actively influences how I interact in the present. </p> | |||
<p>For example, when I decided to quit a job I thought I had loved I wrote down all the reasons why I had quit, precisely knowing that my future self would forget and romanticise the job. It turned out I was right, every now and then periodically I would be overcome by nostalgia which in turn invited regret. That particular journal entry was very helpful in nudging me back into reality and dispersing the romanticised regret.</p> | |||
<p>Sometimes I feel lost and I start wondering why I am where I am. My old entries serve as a powerful reminder of why I chose to do things a certain way. It is not easy to walk on an uncommon path. There are no signposts along the way, I have to leave breadcrumbs for myself. You would think we would remember everything about our selves, but I often forget who I am and who I want to be. Societal conditioning is immense.</p> | |||
<p>This website is essentially a repository of my memories, lessons I’ve learnt, insights I’ve discovered, a changelog of my previous selves. Most people build a map of things they have learnt, I am building a map of how I have come to be, in case I may get lost again. Maybe someone else interested in a similar lonely path will feel less alone with my documented footprints. Maybe that someone else would be me in the future. Maybe all of this would be interesting when I am dead, <a href="https://winnielim.org/journal/on-writing-to-exist-and-website-graveyards/" data-type="post" data-id="3347">assuming I find a way</a> to keep this site alive. </p> | |||
<p>Reading my past entries also allow me to understand the distance I’ve travelled between my past selves and my current self. Sometimes it feels like I’m still stuck in my old unhealthy patterns and reading my past entries enable me to see otherwise. Some things used to haunt me greatly and I feel ambivalent or amused about them now. I like that I can relive certain experiences because I took the effort to write them down, and understand how I relate to them now.</p> | |||
<p>Some of my best writing (to me) appears unintentionally <a href="https://www.instagram.com/p/BoYVRTanQDP/?utm_medium=copy_link">on a spontaneous photo</a> on instagram, and I want them to exist outside of a reverse chronological timeline that no one ever revisits again. I would not be re-reading these posts if not for dayone or timehop, and re-reading them lights up something in me, especially if I am in a period of existential slump, which occurs more often than I would like. Sometimes I forget I can be capable of writing in more dimensions than my usual stream of consciousness. I could reduce the time I spend in existential slumps if I could access these little sparks of my old selves in a more timely and accessible manner.</p> | |||
<p>I store links, books and highlights on this site, so I can see how they all come together with my writing and how they influence my selves. Some books are worth revisiting over and over again, and I would like to store key highlights from all my life-changing books in one place.</p> | |||
<p>I find repetition extremely useful when it comes to wanting to reinforce new patterns or behaviours, so this website serves as my repetition tool. </p> | |||
<h2><span class="ez-toc-section" id="An_interactive_interface_for_programmatically_querying_my_selves"></span>An interactive interface for programmatically querying my selves<span class="ez-toc-section-end"></span></h2> | |||
<p>It is also useful for me to have an interactive interface of everything I have learnt and written about. The queries available now are rudimentary but still more useful than purely chronological. I can see everything tagged with “<a href="https://winnielim.org/tag/zen/">zen</a>“, or search everything that mentioned the word “<a href="https://winnielim.org/?s=meditation">meditation</a>“. I built an “<a href="https://winnielim.org/archive/on-this-day/">on this day</a>” function for everything published on this website. I try to link posts up if they are related to a chain of thoughts, so if I happen to read one I’ll see the others. As long as it is queryable though SQL, I can build an interface for it (this is why I am using wordpress instead of static-site generators). Maybe one day I can build a dashboard or visualisation of my entries. I guess it is like externalising my mind on a website. </p> | |||
<figure class="wp-block-image size-medium"><img loading="lazy" src="https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-700x350.png" alt="illustration: a search box querying my selves" class="wp-image-3922" srcset="https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-700x350.png 700w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-1600x800.png 1600w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-300x150.png 300w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-768x384.png 768w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-1536x768.png 1536w, https://winnielim.org/wp-content/uploads/2021/11/winnielim-query-my-selves-2048x1024.png 2048w" sizes="(max-width: 700px) 100vw, 700px"></figure> | |||
<h2><span class="ez-toc-section" id="Increasing_opportunities_for_relearning"></span>Increasing opportunities for relearning<span class="ez-toc-section-end"></span></h2> | |||
<p>Something like “on this day” or <a href="https://readwise.io/">Readwise</a>‘s emails of random highlights from our kindle library are just opportunities to re-expose ourselves to information we have stored. The question is how do I increase these opportunities beyond the context of time or randomness? I think it would have to involve some manual curation and also tools like taxonomies. I will probably discover more meaningful queries I can use regularly as I go along.</p> | |||
<p>Many people treat their journals as something they write and they may never read them again. Same with books. We keep reading, we have all these insights from reading, then over time we forget about them. My 2015 review contained many insights and lessons that are still very relevant to me today, but I have forgotten most of them. Each time I read it I’m like <em>wow this makes so much sense</em>, then I forget, then I read it again – <a href="https://winnielim.org/journal/remembering-forgotten-lessons-and-selves/" data-type="post" data-id="2463">this loop has happened multiple times</a>. How do I store these insights in a meaningful, accessible manner that doesn’t rely on a random impulse to access it? I could set a calendar reminder, or is it possible to preempt the possible scenarios when I would need the relevant insights the most? </p> | |||
<h2><span class="ez-toc-section" id="This_website_is_my_living_book"></span>This website is my living book<span class="ez-toc-section-end"></span></h2> | |||
<p>Some people have asked me whether I’m ever going to write a book, since I’ve written so much and they have found some of my writing meaningful to them. This website is the book. I don’t particularly believe in books actually (I know the irony, considering the amount of books I read), especially for learning. They are linear and it is difficult to sift out the meaningful bits among the huge chunks of content. </p> | |||
<p>But a website is interactive. Perhaps it is still chaotic now, but hopefully one day in the future people can jump directly into the parts meaningful to them and go on their own navigation path supported by relevance instead of chronology or a pre-determined linear scaffolding. They can perhaps perform their own interactive queries. You can’t do that with a book. The information in a book is also pretty much dead, whereas a website can be continually updated.</p> | |||
<p>The thing about a website is that it is not as portable as a book in terms of the reading experience. You still have to do the work to navigate or search for what you want, whereas a book is just a page after page reading experience. I hope I can circumvent some of that with my notebooks, where I am planning to offer a more structured thematic reading experience.</p> | |||
<h2><span class="ez-toc-section" id="Catering_to_an_audience_versus_myself"></span>Catering to an audience versus myself<span class="ez-toc-section-end"></span></h2> | |||
<p>I do feel self-conscious in general whenever I write or make something. I think this self-consciousness is holding me back. Writing a post like this, I can’t help but wonder if people are going to think I’m super weird for putting so much effort into a personal website (well the other day I found a <a href="https://www.fieggen.com/shoelace/">site on shoelaces</a> so maybe I am not that weird after all). </p> | |||
<p>But I realised I have to put aside this self-consciousness and work on this website as though I am only catering to myself if I want to create something that is really a true representation of how I think and create versus a false image. This website may be chaotic in its current form but it is how I work in progress. </p> | |||
<p>I think it is a meaningful thing, to be able to put something true out there, something that people and myself can interact with, in a world where we’re actively encouraged not to be our selves.</p> | |||
<p>This website, is my rebellion against that.</p> | |||
<hr class="wp-block-separator"> | |||
<p><em>Note: it turns out I’ve already written <a href="https://winnielim.org/journal/remembering-forgotten-lessons-and-selves/">something similar but different</a> 2 years ago, which reinforces my point I guess: which is that I don’t remember what I’ve written, how do I make remembering easier, and it was also very interesting to see the differences in tone between that piece and this.</em></p> |
@@ -0,0 +1,212 @@ | |||
<!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` element | |||
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,initial-scale=1"> | |||
<!-- Required to make a valid HTML5 document. --> | |||
<title>The failed promise of Web Components (archive) — David Larlet</title> | |||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||
<!-- That good ol' feed, subscribe :). --> | |||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||
<!-- Documented, feel free to shoot an email. --> | |||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<script> | |||
function toggleTheme(themeName) { | |||
document.documentElement.classList.toggle( | |||
'forced-dark', | |||
themeName === 'dark' | |||
) | |||
document.documentElement.classList.toggle( | |||
'forced-light', | |||
themeName === 'light' | |||
) | |||
} | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme !== 'undefined') { | |||
toggleTheme(selectedTheme) | |||
} | |||
</script> | |||
<meta name="robots" content="noindex, nofollow"> | |||
<meta content="origin-when-cross-origin" name="referrer"> | |||
<!-- Canonical URL for SEO purposes --> | |||
<link rel="canonical" href="https://lea.verou.me/2020/09/the-failed-promise-of-web-components/"> | |||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||
<article> | |||
<header> | |||
<h1>The failed promise of Web Components</h1> | |||
</header> | |||
<nav> | |||
<p class="center"> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="https://lea.verou.me/2020/09/the-failed-promise-of-web-components/" title="Lien vers le contenu original">Source originale</a> | |||
</p> | |||
</nav> | |||
<hr> | |||
<p>Web Components had so much potential to empower HTML to do more, and make web development more accessible to non-programmers and easier for programmers. Remember how exciting it was every time we got new shiny HTML elements that actually <em>do stuff</em>? Remember how exciting it was to be able to do sliders, color pickers, dialogs, disclosure widgets straight in the HTML, without having to include any widget libraries?</p> | |||
<p>The promise of Web Components was that we’d get this convenience, but for a much wider range of HTML elements, developed much faster, as nobody needs to wait for the full spec + implementation process. We’d just include a script, and boom, we have more elements at our disposal!</p> | |||
<p>Or, that was the idea. Somewhere along the way, the space got flooded by JS frameworks aficionados, who revel in complex APIs, overengineered build processes and dependency graphs that look like the roots of a banyan tree.</p> | |||
<figure class="wp-block-image"><img src="https://live.staticflickr.com/2025/32441377780_e3acf6de12_b.jpg" alt=""><figcaption>This is what the roots of a Banyan tree look like. <a href="https://www.flickr.com/photos/79721788@N00/32441377780/">Photo by David Stanley on Flickr (CC-BY)</a>. </figcaption></figure> | |||
<p><span id="more-3147"></span></p> | |||
<p>Perusing the components on <a href="https://www.webcomponents.org/">webcomponents.org</a> fills me with anxiety, and I’m perfectly comfortable writing JS — I write JS for a living! What hope do those who can’t write JS have? Using a custom element from the directory often needs to be preceded by a ritual of npm flugelhorn, import clownshoes, build quux, all completely unapologetically because “here is my truckload of dependencies, yeah, what”. Many steps are even omitted, likely because they are “obvious”. Often, you wade through the maze only to find the component doesn’t work anymore, or is not fit for your purpose.</p> | |||
<p>Besides setup, the main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but <em>expect</em> JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS. I recall <a href="https://adactio.com/articles/12839#webcomponents">a wonderful talk by Jeremy Keith</a> a few years ago about this very phenomenon, where he discussed <a href="https://shop.polymer-project.org/">this e-shop Web components demo by Google</a>, which is the poster child of this practice. These are the entire contents of its <code><body></code> element:</p> | |||
<pre class="wp-block-code language-markup"><code><body> | |||
<shop-app unresolved="">SHOP</shop-app> | |||
<script src="node_assets/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script> | |||
<script type="module" src="src/shop-app.js"></script> | |||
<script>window.performance&&performance.mark&&performance.mark("index.html");</script> | |||
</body></code></pre> | |||
<p>If this is how Google is leading the way, how can we hope for contributors to design components that follow established HTML conventions?</p> | |||
<p>Jeremy criticized this practice from the aspect of backwards compatibility: when JS is broken or not enabled, or the browser doesn’t support Web Components, the entire website is blank. While this is indeed a serious concern, my primary concern is one of <strong>usability</strong>: <strong>HTML is a lower barrier to entry language</strong>. Far more people can write HTML than JS. Even for those who do eventually write JS, it often comes after spending years writing HTML & CSS.</p> | |||
<p>If components are designed in a way that requires JS, this excludes thousands of people from using them. And even for those who <em>can</em> write JS, HTML is often easier: you don’t see many people rolling their own sliders or using JS-based ones once <code><input type="range"></code> became widely supported, right?</p> | |||
<p>Even when JS is unavoidable, it’s not black and white. A well designed HTML element can reduce the amount and complexity of JS needed to a minimum. Think of the <code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><dialog></a></code> element: it usually does require *some* JS, but it’s usually rather simple JS. Similarly, the <code><video></code> element is perfectly usable just by writing HTML, and has a comprehensive JS API for anyone who wants to do fancy custom things.</p> | |||
<p>The other day I was looking for a simple, dependency free, tabs component. You know, the canonical example of something that is easy to do with Web Components, the example 50% of tutorials mention. I didn’t even care what it looked like, it was for a testing interface. I just wanted something that is small and works like a normal HTML element. Yet, it proved so hard I ended up writing my own!</p> | |||
<h3><a href="#can-we-fix-this" aria-hidden="true" class="aal_anchor" id="can-we-fix-this"><svg aria-hidden="true" class="aal_svg" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Can we fix this?</h3> | |||
<p>I’m not sure if this is a design issue, or a documentation issue. Perhaps for many of these web components, there are easier ways to use them. Perhaps there are vanilla web components out there that I just can’t find. Perhaps I’m looking in the wrong place and there is another directory somewhere with different goals and a different target audience. </p> | |||
<p>But if not, and if I’m not alone in feeling this way, we need a directory of web components with strict inclusion criteria:</p> | |||
<ul><li><strong>Plug and play.</strong> No dependencies, no setup beyond including one <code><script></code> tag. If a dependency is absolutely <em>needed</em> (e.g. in a map component it doesn’t make sense to draw your own maps), the component loads it automatically if it’s not already loaded.</li><li>Syntax and API follows <a href="https://www.smashingmagazine.com/2017/02/designing-html-apis/"><strong>conventions established by built-in HTML elements</strong></a> and anything that <em>can</em> be done without the component user writing JS, <em>is</em> doable without JS, per <a href="https://www.w3.org/2001/tag/doc/leastPower.html">the W3C principle of least power</a>.</li><li><strong>Accessible by default</strong> via sensible ARIA defaults, just like normal HTML elements.</li><li><strong>Themable</strong> via <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part">::part()</a></code>, selective inheritance and custom properties. Very minimal style by default. Normal CSS properties should just “work” to the the extent possible.</li><li><strong>Only one component of a given type</strong> in the directory, that is <strong>flexible</strong> and <strong>extensible</strong> and continuously iterated on and improved by the community. Not 30 different sliders and 15 different tabs that users have to wade through. No branding, no silos of “component libraries”. Only elements that are designed as closely as possible to what a browser would implement in every way the current technology allows.</li></ul> | |||
<p>I would be up for working on this if others feel the same way, since that is not a project for one person to tackle. <em>Who’s with me?</em></p> | |||
<p><strong>UPDATE:</strong> Wow this post blew up! Thank you all for your interest in participating in a potential future effort. I’m currently talking to stakeholders of some of the existing efforts to see if there are any potential collaborations before I go off and create a new one. <a href="https://twitter.com/leaverou">Follow me on Twitter to hear about the outcome</a>!</p> | |||
</article> | |||
<hr> | |||
<footer> | |||
<p> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-rss2"></use> | |||
</svg> Suivre</a> • | |||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-user-tie"></use> | |||
</svg> Pro</a> • | |||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-mail"></use> | |||
</svg> Email</a> • | |||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-hammer2"></use> | |||
</svg> Légal</abbr> | |||
</p> | |||
<template id="theme-selector"> | |||
<form> | |||
<fieldset> | |||
<legend><svg class="icon icon-brightness-contrast"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-brightness-contrast"></use> | |||
</svg> Thème</legend> | |||
<label> | |||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||
</label> | |||
<label> | |||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||
</label> | |||
<label> | |||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||
</label> | |||
</fieldset> | |||
</form> | |||
</template> | |||
</footer> | |||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||
<script> | |||
function loadThemeForm(templateName) { | |||
const themeSelectorTemplate = document.querySelector(templateName) | |||
const form = themeSelectorTemplate.content.firstElementChild | |||
themeSelectorTemplate.replaceWith(form) | |||
form.addEventListener('change', (e) => { | |||
const chosenColorScheme = e.target.value | |||
localStorage.setItem('theme', chosenColorScheme) | |||
toggleTheme(chosenColorScheme) | |||
}) | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme && selectedTheme !== 'undefined') { | |||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||
} | |||
} | |||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||
window.addEventListener('load', () => { | |||
let hasDarkRules = false | |||
for (const styleSheet of Array.from(document.styleSheets)) { | |||
let mediaRules = [] | |||
for (const cssRule of styleSheet.cssRules) { | |||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||
continue | |||
} | |||
// WARNING: Safari does not have/supports `conditionText`. | |||
if (cssRule.conditionText) { | |||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||
continue | |||
} | |||
} else { | |||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||
continue | |||
} | |||
} | |||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||
} | |||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||
// currently iterating on, otherwise the browser will be stuck | |||
// in a infinite loop… | |||
for (const mediaRule of mediaRules) { | |||
styleSheet.insertRule(mediaRule.cssText) | |||
hasDarkRules = true | |||
} | |||
} | |||
if (hasDarkRules) { | |||
loadThemeForm('#theme-selector') | |||
} | |||
}) | |||
</script> | |||
</body> | |||
</html> |
@@ -0,0 +1,82 @@ | |||
title: The failed promise of Web Components | |||
url: https://lea.verou.me/2020/09/the-failed-promise-of-web-components/ | |||
hash_url: 68cd338e37c60a8ed179222c6b1af01f | |||
<p>Web Components had so much potential to empower HTML to do more, and make web development more accessible to non-programmers and easier for programmers. Remember how exciting it was every time we got new shiny HTML elements that actually <em>do stuff</em>? Remember how exciting it was to be able to do sliders, color pickers, dialogs, disclosure widgets straight in the HTML, without having to include any widget libraries?</p> | |||
<p>The promise of Web Components was that we’d get this convenience, but for a much wider range of HTML elements, developed much faster, as nobody needs to wait for the full spec + implementation process. We’d just include a script, and boom, we have more elements at our disposal!</p> | |||
<p>Or, that was the idea. Somewhere along the way, the space got flooded by JS frameworks aficionados, who revel in complex APIs, overengineered build processes and dependency graphs that look like the roots of a banyan tree.</p> | |||
<figure class="wp-block-image"><img src="https://live.staticflickr.com/2025/32441377780_e3acf6de12_b.jpg" alt=""><figcaption>This is what the roots of a Banyan tree look like. <a href="https://www.flickr.com/photos/79721788@N00/32441377780/">Photo by David Stanley on Flickr (CC-BY)</a>. </figcaption></figure> | |||
<span id="more-3147"></span> | |||
<p>Perusing the components on <a href="https://www.webcomponents.org/">webcomponents.org</a> fills me with anxiety, and I’m perfectly comfortable writing JS — I write JS for a living! What hope do those who can’t write JS have? Using a custom element from the directory often needs to be preceded by a ritual of npm flugelhorn, import clownshoes, build quux, all completely unapologetically because “here is my truckload of dependencies, yeah, what”. Many steps are even omitted, likely because they are “obvious”. Often, you wade through the maze only to find the component doesn’t work anymore, or is not fit for your purpose.</p> | |||
<p>Besides setup, the main problem is that HTML is not treated with the appropriate respect in the design of these components. They are not designed as closely as possible to standard HTML elements, but <em>expect</em> JS to be written for them to do anything. HTML is simply treated as a shorthand, or worse, as merely a marker to indicate where the element goes in the DOM, with all parameters passed in via JS. I recall <a href="https://adactio.com/articles/12839#webcomponents">a wonderful talk by Jeremy Keith</a> a few years ago about this very phenomenon, where he discussed <a href="https://shop.polymer-project.org/">this e-shop Web components demo by Google</a>, which is the poster child of this practice. These are the entire contents of its <code><body></code> element:</p> | |||
<pre class="wp-block-code language-markup"><code><body> | |||
<shop-app unresolved="">SHOP</shop-app> | |||
<script src="node_assets/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script> | |||
<script type="module" src="src/shop-app.js"></script> | |||
<script>window.performance&&performance.mark&&performance.mark("index.html");</script> | |||
</body></code></pre> | |||
<p>If this is how Google is leading the way, how can we hope for contributors to design components that follow established HTML conventions?</p> | |||
<p>Jeremy criticized this practice from the aspect of backwards compatibility: when JS is broken or not enabled, or the browser doesn’t support Web Components, the entire website is blank. While this is indeed a serious concern, my primary concern is one of <strong>usability</strong>: <strong>HTML is a lower barrier to entry language</strong>. Far more people can write HTML than JS. Even for those who do eventually write JS, it often comes after spending years writing HTML & CSS.</p> | |||
<p>If components are designed in a way that requires JS, this excludes thousands of people from using them. And even for those who <em>can</em> write JS, HTML is often easier: you don’t see many people rolling their own sliders or using JS-based ones once <code><input type="range"></code> became widely supported, right?</p> | |||
<p>Even when JS is unavoidable, it’s not black and white. A well designed HTML element can reduce the amount and complexity of JS needed to a minimum. Think of the <code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><dialog></a></code> element: it usually does require *some* JS, but it’s usually rather simple JS. Similarly, the <code><video></code> element is perfectly usable just by writing HTML, and has a comprehensive JS API for anyone who wants to do fancy custom things.</p> | |||
<p>The other day I was looking for a simple, dependency free, tabs component. You know, the canonical example of something that is easy to do with Web Components, the example 50% of tutorials mention. I didn’t even care what it looked like, it was for a testing interface. I just wanted something that is small and works like a normal HTML element. Yet, it proved so hard I ended up writing my own!</p> | |||
<h3><a href="#can-we-fix-this" aria-hidden="true" class="aal_anchor" id="can-we-fix-this"><svg aria-hidden="true" class="aal_svg" version="1.1" viewbox="0 0 16 16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Can we fix this?</h3> | |||
<p>I’m not sure if this is a design issue, or a documentation issue. Perhaps for many of these web components, there are easier ways to use them. Perhaps there are vanilla web components out there that I just can’t find. Perhaps I’m looking in the wrong place and there is another directory somewhere with different goals and a different target audience. </p> | |||
<p>But if not, and if I’m not alone in feeling this way, we need a directory of web components with strict inclusion criteria:</p> | |||
<ul><li><strong>Plug and play.</strong> No dependencies, no setup beyond including one <code><script></code> tag. If a dependency is absolutely <em>needed</em> (e.g. in a map component it doesn’t make sense to draw your own maps), the component loads it automatically if it’s not already loaded.</li><li>Syntax and API follows <a href="https://www.smashingmagazine.com/2017/02/designing-html-apis/"><strong>conventions established by built-in HTML elements</strong></a> and anything that <em>can</em> be done without the component user writing JS, <em>is</em> doable without JS, per <a href="https://www.w3.org/2001/tag/doc/leastPower.html">the W3C principle of least power</a>.</li><li><strong>Accessible by default</strong> via sensible ARIA defaults, just like normal HTML elements.</li><li><strong>Themable</strong> via <code><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part">::part()</a></code>, selective inheritance and custom properties. Very minimal style by default. Normal CSS properties should just “work” to the the extent possible.</li><li><strong>Only one component of a given type</strong> in the directory, that is <strong>flexible</strong> and <strong>extensible</strong> and continuously iterated on and improved by the community. Not 30 different sliders and 15 different tabs that users have to wade through. No branding, no silos of “component libraries”. Only elements that are designed as closely as possible to what a browser would implement in every way the current technology allows.</li></ul> | |||
<p>I would be up for working on this if others feel the same way, since that is not a project for one person to tackle. <em>Who’s with me?</em></p> | |||
<p><strong>UPDATE:</strong> Wow this post blew up! Thank you all for your interest in participating in a potential future effort. I’m currently talking to stakeholders of some of the existing efforts to see if there are any potential collaborations before I go off and create a new one. <a href="https://twitter.com/leaverou">Follow me on Twitter to hear about the outcome</a>!</p> |
@@ -0,0 +1,272 @@ | |||
<!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` element | |||
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,initial-scale=1"> | |||
<!-- Required to make a valid HTML5 document. --> | |||
<title>Understanding Kristofer Joseph's Single File Web Component (archive) — David Larlet</title> | |||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||
<!-- That good ol' feed, subscribe :). --> | |||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||
<!-- Documented, feel free to shoot an email. --> | |||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<script> | |||
function toggleTheme(themeName) { | |||
document.documentElement.classList.toggle( | |||
'forced-dark', | |||
themeName === 'dark' | |||
) | |||
document.documentElement.classList.toggle( | |||
'forced-light', | |||
themeName === 'light' | |||
) | |||
} | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme !== 'undefined') { | |||
toggleTheme(selectedTheme) | |||
} | |||
</script> | |||
<meta name="robots" content="noindex, nofollow"> | |||
<meta content="origin-when-cross-origin" name="referrer"> | |||
<!-- Canonical URL for SEO purposes --> | |||
<link rel="canonical" href="https://til.simonwillison.net/web-components/understanding-single-file-web-component"> | |||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||
<article> | |||
<header> | |||
<h1>Understanding Kristofer Joseph's Single File Web Component</h1> | |||
</header> | |||
<nav> | |||
<p class="center"> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="https://til.simonwillison.net/web-components/understanding-single-file-web-component" title="Lien vers le contenu original">Source originale</a> | |||
</p> | |||
</nav> | |||
<hr> | |||
<p><a href="https://twitter.com/brianleroux/status/1453472609518034944" rel="nofollow">Via Brian LeRoux</a> I found <a href="https://gist.github.com/kristoferjoseph/c4e47389ae0f0447db175b914e471628">single-file-web-component.html</a> by Kristofer Joseph. It's really clever! It demonstrates how to build a <code><hello-world></hello-world></code> custom Web Component in a single HTML file, using some neat tricks.</p> | |||
<p>For my own education I spent some time picking it apart and built my own annotated version of the code showing what I learned.</p> | |||
<p>Justin Fagnani <a href="https://twitter.com/justinfagnani/status/1453531485240102916" rel="nofollow">provided useful feedback</a> on this on Twitter.</p> | |||
<div class="highlight highlight-text-html-basic"><pre><span class="pl-c1"><!DOCTYPE html<span class="pl-kos">></span></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">html</span> <span class="pl-c1">lang</span>="<span class="pl-s">en</span>"<span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">head</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">meta</span> <span class="pl-c1">charset</span>="<span class="pl-s">UTF-8</span>" /> | |||
<span class="pl-kos"><</span><span class="pl-ent">title</span><span class="pl-kos">></span>Single File Web Component<span class="pl-kos"></</span><span class="pl-ent">title</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-ent">body</span> { | |||
<span class="pl-c1">background-color</span><span class="pl-kos">:</span> <span class="pl-pds"><span class="pl-kos">#</span>eee</span>; | |||
<span class="pl-c1">font-family</span><span class="pl-kos">:</span> Helvetica<span class="pl-kos">,</span> sans-serif; | |||
} | |||
<span class="pl-ent">h1</span> { | |||
<span class="pl-c1">color</span><span class="pl-kos">:</span> blue; | |||
<span class="pl-c1">background-color</span><span class="pl-kos">:</span> pink; | |||
} | |||
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">head</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">body</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">template</span> <span class="pl-c1">id</span>="<span class="pl-s">single-file</span>"<span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> These styles affect only content inside the shadow DOM.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> Styles in the outside document mostly do not affect stuff</span> | |||
<span class="pl-c"> here, but there are some exceptions: font-family affects</span> | |||
<span class="pl-c"> this <h1> for example. I don't understand the rules here.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-ent">h1</span> { | |||
<span class="pl-c1">color</span><span class="pl-kos">:</span> red; | |||
} | |||
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>Hello world (web component)<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span> | |||
<span class="pl-c"><!--</span> | |||
<span class="pl-c"> This code still works if you remove the type="module" parameter.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> Using that parameter enables features such as 'import ... from'</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> More importantly it stops variables inside the script tag from</span> | |||
<span class="pl-c"> leaking out to the global scope - if you remove type="module"</span> | |||
<span class="pl-c"> from here then the HelloWorld class becomes visible.</span> | |||
<span class="pl-c"> --></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">script</span> <span class="pl-c1">type</span>="<span class="pl-s">module</span>"<span class="pl-kos">></span> | |||
<span class="pl-k">class</span> <span class="pl-v">HelloWorld</span> <span class="pl-k">extends</span> <span class="pl-v">HTMLElement</span> <span class="pl-kos">{</span> | |||
<span class="pl-en">constructor</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> If you remove the call to super() here Firefox shows an error:</span> | |||
<span class="pl-c"> "Uncaught ReferenceError: must call super constructor before</span> | |||
<span class="pl-c"> using 'this' in derived class constructor'"</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">super</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-k">const</span> <span class="pl-s1">template</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> mode: 'open' means you are allowed to access</span> | |||
<span class="pl-c"> document.querySelector('hello-world').shadowRoot to get</span> | |||
<span class="pl-c"> at the DOM inside. Set to 'closed' and the .shadowRoot</span> | |||
<span class="pl-c"> property will return null.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-en">attachShadow</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">mode</span>: <span class="pl-s">"open"</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span> | |||
<span class="pl-s1">template</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-en">cloneNode</span><span class="pl-kos">(</span><span class="pl-c1">true</span><span class="pl-kos">)</span> | |||
<span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/* </span> | |||
<span class="pl-c"> template.content is a 'DocumentFragment' array.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> template.content.cloneNode() without the true performs</span> | |||
<span class="pl-c"> a shallow clone, which provides a empty DocumentFragment</span> | |||
<span class="pl-c"> array.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> template.content.cloneNode(true) provides one with 6 items</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-en">connectedCallback</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> | |||
<span class="pl-c">// https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks</span> | |||
<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">"Why hello there 👋"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-s1">customElements</span><span class="pl-kos">.</span><span class="pl-en">define</span><span class="pl-kos">(</span><span class="pl-s">"hello-world"</span><span class="pl-kos">,</span> <span class="pl-v">HelloWorld</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">template</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>This is not a web component<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">hello-world</span><span class="pl-kos">></span><span class="pl-kos"></</span><span class="pl-ent">hello-world</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-k">const</span> <span class="pl-s1">sf</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> Before executing this line, sf.content.lastElementChild</span> | |||
<span class="pl-c"> is the <script type="module"> element hidden away inside</span> | |||
<span class="pl-c"> the <template> - we remove it from the template here and</span> | |||
<span class="pl-c"> append it to the document.body, causing it to execute in</span> | |||
<span class="pl-c"> the main document and activate the <hello-world> tag.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span><span class="pl-s1">sf</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-c1">lastElementChild</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">body</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">html</span><span class="pl-kos">></span></pre></div> | |||
<p class="created">Created 2021-10-27T17:33:08-07:00, updated 2021-10-27T19:48:23-07:00 · <a href="https://github.com/simonw/til/commits/main/web-components/understanding-single-file-web-component.md">History</a> · <a href="https://github.com/simonw/til/blob/main/web-components/understanding-single-file-web-component.md">Edit</a></p> | |||
</article> | |||
<hr> | |||
<footer> | |||
<p> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-rss2"></use> | |||
</svg> Suivre</a> • | |||
<a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-user-tie"></use> | |||
</svg> Pro</a> • | |||
<a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-mail"></use> | |||
</svg> Email</a> • | |||
<abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-hammer2"></use> | |||
</svg> Légal</abbr> | |||
</p> | |||
<template id="theme-selector"> | |||
<form> | |||
<fieldset> | |||
<legend><svg class="icon icon-brightness-contrast"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-brightness-contrast"></use> | |||
</svg> Thème</legend> | |||
<label> | |||
<input type="radio" value="auto" name="chosen-color-scheme" checked> Auto | |||
</label> | |||
<label> | |||
<input type="radio" value="dark" name="chosen-color-scheme"> Foncé | |||
</label> | |||
<label> | |||
<input type="radio" value="light" name="chosen-color-scheme"> Clair | |||
</label> | |||
</fieldset> | |||
</form> | |||
</template> | |||
</footer> | |||
<script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script> | |||
<script> | |||
function loadThemeForm(templateName) { | |||
const themeSelectorTemplate = document.querySelector(templateName) | |||
const form = themeSelectorTemplate.content.firstElementChild | |||
themeSelectorTemplate.replaceWith(form) | |||
form.addEventListener('change', (e) => { | |||
const chosenColorScheme = e.target.value | |||
localStorage.setItem('theme', chosenColorScheme) | |||
toggleTheme(chosenColorScheme) | |||
}) | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme && selectedTheme !== 'undefined') { | |||
form.querySelector(`[value="${selectedTheme}"]`).checked = true | |||
} | |||
} | |||
const prefersColorSchemeDark = '(prefers-color-scheme: dark)' | |||
window.addEventListener('load', () => { | |||
let hasDarkRules = false | |||
for (const styleSheet of Array.from(document.styleSheets)) { | |||
let mediaRules = [] | |||
for (const cssRule of styleSheet.cssRules) { | |||
if (cssRule.type !== CSSRule.MEDIA_RULE) { | |||
continue | |||
} | |||
// WARNING: Safari does not have/supports `conditionText`. | |||
if (cssRule.conditionText) { | |||
if (cssRule.conditionText !== prefersColorSchemeDark) { | |||
continue | |||
} | |||
} else { | |||
if (cssRule.cssText.startsWith(prefersColorSchemeDark)) { | |||
continue | |||
} | |||
} | |||
mediaRules = mediaRules.concat(Array.from(cssRule.cssRules)) | |||
} | |||
// WARNING: do not try to insert a Rule to a styleSheet you are | |||
// currently iterating on, otherwise the browser will be stuck | |||
// in a infinite loop… | |||
for (const mediaRule of mediaRules) { | |||
styleSheet.insertRule(mediaRule.cssText) | |||
hasDarkRules = true | |||
} | |||
} | |||
if (hasDarkRules) { | |||
loadThemeForm('#theme-selector') | |||
} | |||
}) | |||
</script> | |||
</body> | |||
</html> |
@@ -0,0 +1,106 @@ | |||
title: Understanding Kristofer Joseph's Single File Web Component | |||
url: https://til.simonwillison.net/web-components/understanding-single-file-web-component | |||
hash_url: 8016f83f512107dd26e5780b08ea7305 | |||
<p><a href="https://twitter.com/brianleroux/status/1453472609518034944" rel="nofollow">Via Brian LeRoux</a> I found <a href="https://gist.github.com/kristoferjoseph/c4e47389ae0f0447db175b914e471628">single-file-web-component.html</a> by Kristofer Joseph. It's really clever! It demonstrates how to build a <code><hello-world></hello-world></code> custom Web Component in a single HTML file, using some neat tricks.</p> | |||
<p>For my own education I spent some time picking it apart and built my own annotated version of the code showing what I learned.</p> | |||
<p>Justin Fagnani <a href="https://twitter.com/justinfagnani/status/1453531485240102916" rel="nofollow">provided useful feedback</a> on this on Twitter.</p> | |||
<div class="highlight highlight-text-html-basic"><pre><span class="pl-c1"><!DOCTYPE html<span class="pl-kos">></span></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">html</span> <span class="pl-c1">lang</span>="<span class="pl-s">en</span>"<span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">head</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">meta</span> <span class="pl-c1">charset</span>="<span class="pl-s">UTF-8</span>" /> | |||
<span class="pl-kos"><</span><span class="pl-ent">title</span><span class="pl-kos">></span>Single File Web Component<span class="pl-kos"></</span><span class="pl-ent">title</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-ent">body</span> { | |||
<span class="pl-c1">background-color</span><span class="pl-kos">:</span> <span class="pl-pds"><span class="pl-kos">#</span>eee</span>; | |||
<span class="pl-c1">font-family</span><span class="pl-kos">:</span> Helvetica<span class="pl-kos">,</span> sans-serif; | |||
} | |||
<span class="pl-ent">h1</span> { | |||
<span class="pl-c1">color</span><span class="pl-kos">:</span> blue; | |||
<span class="pl-c1">background-color</span><span class="pl-kos">:</span> pink; | |||
} | |||
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">head</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">body</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">template</span> <span class="pl-c1">id</span>="<span class="pl-s">single-file</span>"<span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> These styles affect only content inside the shadow DOM.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> Styles in the outside document mostly do not affect stuff</span> | |||
<span class="pl-c"> here, but there are some exceptions: font-family affects</span> | |||
<span class="pl-c"> this <h1> for example. I don't understand the rules here.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-ent">h1</span> { | |||
<span class="pl-c1">color</span><span class="pl-kos">:</span> red; | |||
} | |||
<span class="pl-kos"></</span><span class="pl-ent">style</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>Hello world (web component)<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span> | |||
<span class="pl-c"><!--</span> | |||
<span class="pl-c"> This code still works if you remove the type="module" parameter.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> Using that parameter enables features such as 'import ... from'</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> More importantly it stops variables inside the script tag from</span> | |||
<span class="pl-c"> leaking out to the global scope - if you remove type="module"</span> | |||
<span class="pl-c"> from here then the HelloWorld class becomes visible.</span> | |||
<span class="pl-c"> --></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">script</span> <span class="pl-c1">type</span>="<span class="pl-s">module</span>"<span class="pl-kos">></span> | |||
<span class="pl-k">class</span> <span class="pl-v">HelloWorld</span> <span class="pl-k">extends</span> <span class="pl-v">HTMLElement</span> <span class="pl-kos">{</span> | |||
<span class="pl-en">constructor</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> If you remove the call to super() here Firefox shows an error:</span> | |||
<span class="pl-c"> "Uncaught ReferenceError: must call super constructor before</span> | |||
<span class="pl-c"> using 'this' in derived class constructor'"</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">super</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-k">const</span> <span class="pl-s1">template</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> mode: 'open' means you are allowed to access</span> | |||
<span class="pl-c"> document.querySelector('hello-world').shadowRoot to get</span> | |||
<span class="pl-c"> at the DOM inside. Set to 'closed' and the .shadowRoot</span> | |||
<span class="pl-c"> property will return null.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">this</span><span class="pl-kos">.</span><span class="pl-en">attachShadow</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">mode</span>: <span class="pl-s">"open"</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span> | |||
<span class="pl-s1">template</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-en">cloneNode</span><span class="pl-kos">(</span><span class="pl-c1">true</span><span class="pl-kos">)</span> | |||
<span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/* </span> | |||
<span class="pl-c"> template.content is a 'DocumentFragment' array.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> template.content.cloneNode() without the true performs</span> | |||
<span class="pl-c"> a shallow clone, which provides a empty DocumentFragment</span> | |||
<span class="pl-c"> array.</span> | |||
<span class="pl-c"></span> | |||
<span class="pl-c"> template.content.cloneNode(true) provides one with 6 items</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-en">connectedCallback</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> | |||
<span class="pl-c">// https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks</span> | |||
<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">"Why hello there 👋"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-kos">}</span> | |||
<span class="pl-s1">customElements</span><span class="pl-kos">.</span><span class="pl-en">define</span><span class="pl-kos">(</span><span class="pl-s">"hello-world"</span><span class="pl-kos">,</span> <span class="pl-v">HelloWorld</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">template</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">h1</span><span class="pl-kos">></span>This is not a web component<span class="pl-kos"></</span><span class="pl-ent">h1</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">hello-world</span><span class="pl-kos">></span><span class="pl-kos"></</span><span class="pl-ent">hello-world</span><span class="pl-kos">></span> | |||
<span class="pl-kos"><</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-k">const</span> <span class="pl-s1">sf</span> <span class="pl-c1">=</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">getElementById</span><span class="pl-kos">(</span><span class="pl-s">"single-file"</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-c">/*</span> | |||
<span class="pl-c"> Before executing this line, sf.content.lastElementChild</span> | |||
<span class="pl-c"> is the <script type="module"> element hidden away inside</span> | |||
<span class="pl-c"> the <template> - we remove it from the template here and</span> | |||
<span class="pl-c"> append it to the document.body, causing it to execute in</span> | |||
<span class="pl-c"> the main document and activate the <hello-world> tag.</span> | |||
<span class="pl-c"> */</span> | |||
<span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-c1">body</span><span class="pl-kos">.</span><span class="pl-en">appendChild</span><span class="pl-kos">(</span><span class="pl-s1">sf</span><span class="pl-kos">.</span><span class="pl-c1">content</span><span class="pl-kos">.</span><span class="pl-c1">lastElementChild</span><span class="pl-kos">)</span><span class="pl-kos">;</span> | |||
<span class="pl-kos"></</span><span class="pl-ent">script</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">body</span><span class="pl-kos">></span> | |||
<span class="pl-kos"></</span><span class="pl-ent">html</span><span class="pl-kos">></span></pre></div> | |||
<p class="created">Created 2021-10-27T17:33:08-07:00, updated 2021-10-27T19:48:23-07:00 · <a href="https://github.com/simonw/til/commits/main/web-components/understanding-single-file-web-component.md">History</a> · <a href="https://github.com/simonw/til/blob/main/web-components/understanding-single-file-web-component.md">Edit</a></p> |
@@ -0,0 +1,204 @@ | |||
<!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` element | |||
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,initial-scale=1"> | |||
<!-- Required to make a valid HTML5 document. --> | |||
<title>HTML with Superpowers (archive) — David Larlet</title> | |||
<meta name="description" content="Publication mise en cache pour en conserver une trace."> | |||
<!-- That good ol' feed, subscribe :). --> | |||
<link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/"> | |||
<!-- Generated from https://realfavicongenerator.net/ such a mess. --> | |||
<link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png"> | |||
<link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png"> | |||
<link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png"> | |||
<link rel="manifest" href="/static/david/icons2/site.webmanifest"> | |||
<link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c"> | |||
<link rel="shortcut icon" href="/static/david/icons2/favicon.ico"> | |||
<meta name="msapplication-TileColor" content="#f7f7f7"> | |||
<meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml"> | |||
<meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)"> | |||
<meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)"> | |||
<!-- Documented, feel free to shoot an email. --> | |||
<link rel="stylesheet" href="/static/david/css/style_2021-01-20.css"> | |||
<!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. --> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin> | |||
<script> | |||
function toggleTheme(themeName) { | |||
document.documentElement.classList.toggle( | |||
'forced-dark', | |||
themeName === 'dark' | |||
) | |||
document.documentElement.classList.toggle( | |||
'forced-light', | |||
themeName === 'light' | |||
) | |||
} | |||
const selectedTheme = localStorage.getItem('theme') | |||
if (selectedTheme !== 'undefined') { | |||
toggleTheme(selectedTheme) | |||
} | |||
</script> | |||
<meta name="robots" content="noindex, nofollow"> | |||
<meta content="origin-when-cross-origin" name="referrer"> | |||
<!-- Canonical URL for SEO purposes --> | |||
<link rel="canonical" href="https://daverupert.com/2021/10/html-with-superpowers/"> | |||
<body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all"> | |||
<article> | |||
<header> | |||
<h1>HTML with Superpowers</h1> | |||
</header> | |||
<nav> | |||
<p class="center"> | |||
<a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home"> | |||
<use xlink:href="/static/david/icons2/symbol-defs.svg#icon-home"></use> | |||
</svg> Accueil</a> • | |||
<a href="https://daverupert.com/2021/10/html-with-superpowers/" title="Lien vers le contenu original">Source originale</a> | |||
</p> | |||
</nav> | |||
<hr> | |||
<p>Earlier this year I spoke at An Event Apart’s Spring Summit and today I’m happy to share with you that talk called <em>HTML with Superpowers</em>, which is all about Web Components.</p> | |||
<p><a href="https://noti.st/davatron5000/k6N2LP/html-with-superpowers" class="button">View slides on Notist</a></p> | |||
<p>This talk had two major objectives. Firstly, In January 2021 I joined the <a href="https://www.w3.org/community/webcomponents/">W3C Web Components Community Group</a>, which is hoping to advance Web Components and raise awareness. It’s a worthwhile cause to get over some of Web Components’ historical marketing problems. I also get a chance to provide feedback on nascent stage proposals. I’m a fringe member in this group, but happy to escalate any questions or feedback you have.</p> | |||
<p>The second reason I wanted to talk about Web Components is that I feel there’s an education gap. I noticed that nearly all the people I know that love HTML, CSS, and Design Systems… they don’t use Web Components. It makes sense when you realize a lot of web component education targets JavaScript developers. Every <em>How-To Web Components</em> post on <a href="http://Dev.to">Dev.to</a> starts with the class-based JavaScript API. That’s great… if you like JavaScript. But if I’m 100% honest, if you already have a JS framework that works for you, I don’t see much point in switching. That’s an intense amount of sunk costs and organizational entropy to overcome. I wanted to write a talk that approaches Web Components from a different angle.</p> | |||
<p>Emulating <a href="https://speaking.adactio.com/87IIn1#sn3gBTn">Jeremy Keith’s pacing layers for the Web</a>, which emulates Stuart Brand’s pacing layers for architecture, I start with the slower layers of the Web. The layers unlikely to change from under you and then build up to the “<a href="https://www.youtube.com/watch?v=RFm9ClqlGuo">Jeremy Beremy</a>” velocity of JavaScript. That means starting with HTML, then talk about CSS, then talk about JavaScript.</p> | |||
<h2>Part 1: How to use Web Components with HTML</h2> | |||
<p>Let’s start with HTML. Let’s use Web Components that someone else built. No build tools. No NPM install blah blah. Copy and paste a script tag from the web like the old days. I walk through half a dozen examples on how to use Web Components to make interactive sites, reusable components, and even 3D realms using HTML and a humble <code><script></code> tag.</p> | |||
<p>My favorite is <a href="https://github.com/GoogleChromeLabs/two-up"><code><two-up></code> from Google Chrome Labs</a> which you can use in your code today to make one of those neat before/after picture sliders.</p> | |||