lots of improvements

This commit is contained in:
Fred K. Schott 2021-03-21 00:44:42 -07:00
parent 2082001ff8
commit 417657f138
31 changed files with 514 additions and 476 deletions

View file

@ -1,12 +1,10 @@
<Component>
<section class="grid-banner">
<div class="notification">
<div class="container">
Snowpack 3.0 is out now!
<a href="/posts/2021-01-13-snowpack-3-0">
Read the announcement post →
</a>
</div>
<section class="grid-banner">
<div class="notification">
<div class="container">
Snowpack 3.0 is out now!
<a href="/posts/2021-01-13-snowpack-3-0">
Read the announcement post →
</a>
</div>
</section>
</Component>
</div>
</section>

View file

@ -1,5 +1,4 @@
<Component>
<div class="hero">
<div class="hero">
<div class="section">
<h1 class="header-snowpack">Snowpack</h1>
<p class="header-snowpack-subtitle">The faster frontend build tool.</p>
@ -7,11 +6,15 @@
<div class="hero-cta">
<a href="/tutorials/quick-start" class="button button-primary">Get started</a>
<div style="margin: 0.5rem"></div>
<button id="copy-button" class="copy-button hidden-mobile" data-clipboard-text="npm install snowpack">
<button id="copy-button" class="copy-button hidden-mobile" data-clipboard-text="npm install snowpack">
<span class="faded" style="margin-right: 0.75rem;">$</span>
<span id="copy-button-text">npm install snowpack</span>
<svg style="height: 22px;width: 22px;margin: -0.1rem -0.1rem 0 0.75rem;" aria-hidden="true" focusable="false" data-prefix="far" data-icon="clone" class="svg-inline--fa fa-clone fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 512 512">
<path fill="currentColor" d="M464 0H144c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h320c26.51 0 48-21.49 48-48v-48h48c26.51 0 48-21.49 48-48V48c0-26.51-21.49-48-48-48zM362 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h42v224c0 26.51 21.49 48 48 48h224v42a6 6 0 0 1-6 6zm96-96H150a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h308a6 6 0 0 1 6 6v308a6 6 0 0 1-6 6z"></path>
<svg style="height: 22px;width: 22px;margin: -0.1rem -0.1rem 0 0.75rem;" aria-hidden="true" focusable="false"
data-prefix="far" data-icon="clone" class="svg-inline--fa fa-clone fa-w-16" role="img"
xmlns="http://www.w3.org/2000/svg" viewbox="0 0 512 512">
<path fill="currentColor"
d="M464 0H144c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h320c26.51 0 48-21.49 48-48v-48h48c26.51 0 48-21.49 48-48V48c0-26.51-21.49-48-48-48zM362 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h42v224c0 26.51 21.49 48 48 48h224v42a6 6 0 0 1-6 6zm96-96H150a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h308a6 6 0 0 1 6 6v308a6 6 0 0 1-6 6z">
</path>
</svg>
</button>
<script type="module">
@ -19,10 +22,10 @@
const clippy = new Clipboard('.copy-button');
const copyText = document.getElementById('copy-button').innerHTML;
clippy.on('success', function (e) {
document.getElementById('copy-button').style.minWidth = document.getElementById('copy-button').offsetWidth;
document.getElementById('copy-button').style.minWidth = document.getElementById('copy-button').offsetWidth;
console.info('Trigger:', e);
document.getElementById('copy-button').innerHTML = '<span>copied to clipboard!</span>';
document.getElementById('copy-button').addEventListener("mouseleave", function( event ) {
document.getElementById('copy-button').addEventListener("mouseleave", function (event) {
document.getElementById('copy-button').innerHTML = copyText;
}, false);
@ -39,9 +42,9 @@
<div class="hero-cta">
<!-- Place this tag where you want the button to render. -->
<div class="hidden-mobile" style="text-align: center; height: 36px;">
<a class="github-button" href="https://github.com/snowpackjs/snowpack" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star snowpackjs/snowpack on GitHub">Star</a >
<a class="github-button" href="https://github.com/snowpackjs/snowpack" data-icon="octicon-star"
data-size="large" data-show-count="true" aria-label="Star snowpackjs/snowpack on GitHub">Star</a>
</div>
</div>
</div>
</div>
</Component>
</div>

View file

@ -1,4 +1,3 @@
<Component>
<nav class="snow-toc">
<ol class="snow-toc-contents">
<li class="snow-toc-section">
@ -83,4 +82,3 @@
</li>
</ol>
</nav>
</Component>

View file

@ -1,21 +1,27 @@
<Component>
<nav class="snow-nav">
<button id="toc-drawer-button" class="snow-nav-mobile-open" type="button" aria-expanded="false" aria-controls="nav-primary">
<button id="toc-drawer-button" class="snow-nav-mobile-open" type="button" aria-expanded="false"
aria-controls="nav-primary">
<svg focusable="false" class="snow-icon" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 448 512">
<title>Toggle mobile menu</title>
<path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"></path>
<path
d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z">
</path>
</svg>
</button>
<a class="snow-nav-logo snow-logo" href="/">
<svg class="snow-logo-icon" viewbox="0 0 640 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg class="snow-logo-icon" viewbox="0 0 640 512" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g transform="translate(-1.000000, 0.000000)" fill-rule="nonzero">
<path d="M635.92,462.7 L347.92,14.7 C342.03,5.54 331.89,0 321,0 C310.11,0 299.97,5.54 294.08,14.7 L6.08,462.7 C-0.250773249,472.547007 -0.699487627,485.064987 4.91,495.34 C10.522069,505.612419 21.2945349,512 33,512 L609,512 C620.71,512 631.48,505.61 637.09,495.33 C642.699457,485.058495 642.250708,472.543372 635.92,462.7 Z M321,91.18 L406.39,224 L321,224 L257,288 L218.94,249.94 L321,91.18 Z" id="Shape"></path>
<path
d="M635.92,462.7 L347.92,14.7 C342.03,5.54 331.89,0 321,0 C310.11,0 299.97,5.54 294.08,14.7 L6.08,462.7 C-0.250773249,472.547007 -0.699487627,485.064987 4.91,495.34 C10.522069,505.612419 21.2945349,512 33,512 L609,512 C620.71,512 631.48,505.61 637.09,495.33 C642.699457,485.058495 642.250708,472.543372 635.92,462.7 Z M321,91.18 L406.39,224 L321,224 L257,288 L218.94,249.94 L321,91.18 Z"
id="Shape"></path>
</g>
</svg>
<span class="snow-logo-type">Snowpack</span>
</a>
<div class="search-form">
<input type="text" name="search" placeholder="Search documentation..." class="search-form-input" id="search-form-input">
<input type="text" name="search" placeholder="Search documentation..." class="search-form-input"
id="search-form-input">
<span class="search-form-hint">
<span class="sr-only">Press </span>
<kbd class="font-sans"><abbr title="Command" style="text-decoration: none;">⌘</abbr></kbd>
@ -29,64 +35,75 @@
{`v${props.version}`}
</a>
<a href="https://github.com/snowpackjs/snowpack" target="_blank" class="snow-nav-link snow-nav-link__desktop">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="snow-icon" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 496 512">
<path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path>
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="snow-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewbox="0 0 496 512">
<path fill="currentColor"
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z">
</path>
</svg>
</a>
<a href="https://twitter.com/snowpackjs" target="_blank" class="snow-nav-link snow-nav-link__desktop">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="snow-icon" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 512 512">
<path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path>
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="snow-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewbox="0 0 512 512">
<path fill="currentColor"
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z">
</path>
</svg>
</a>
<a href="https://discord.gg/snowpack" target="_blank" class="snow-nav-link snow-nav-link__desktop">
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="discord" class="snow-icon" role="img" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 210 240"><path d="M84.79 90.45c-6.45 0-11.55 5.66-11.55 12.57s5.21 12.57 11.55 12.57c6.45 0 11.55-5.66 11.55-12.57.11-6.91-5.09-12.57-11.55-12.57zm41.32 0c-6.45 0-11.55 5.66-11.55 12.57s5.21 12.57 11.55 12.57c6.45 0 11.55-5.66 11.55-12.57s-5.09-12.57-11.55-12.57z"/><path fill="currentColor" d="M185.4 0H24.6C11.04 0 0 11.04 0 24.72v162.24c0 13.68 11.04 24.72 24.6 24.72h136.08l-6.36-22.2 15.36 14.28 14.52 13.44L210 240V24.72C210 11.04 198.96 0 185.4 0zm-46.32 156.72s-4.32-5.16-7.92-9.72c15.72-4.44 21.72-14.28 21.72-14.28-4.92 3.24-9.6 5.52-13.8 7.08-6 2.52-11.76 4.2-17.4 5.16-11.52 2.16-22.08 1.56-31.08-.12-6.84-1.32-12.72-3.24-17.64-5.16-2.76-1.08-5.76-2.4-8.76-4.08-.36-.24-.72-.36-1.08-.6-.24-.12-.36-.24-.48-.36-2.16-1.2-3.36-2.04-3.36-2.04s5.76 9.6 21 14.16c-3.6 4.56-8.04 9.96-8.04 9.96-26.52-.84-36.6-18.24-36.6-18.24 0-38.64 17.28-69.96 17.28-69.96 17.28-12.96 33.72-12.6 33.72-12.6l1.2 1.44c-21.6 6.24-31.56 15.72-31.56 15.72s2.64-1.44 7.08-3.48c12.84-5.64 23.04-7.2 27.24-7.56.72-.12 1.32-.24 2.04-.24 7.32-.96 15.6-1.2 24.24-.24 11.4 1.32 23.64 4.68 36.12 11.52 0 0-9.48-9-29.88-15.24l1.68-1.92s16.44-.36 33.72 12.6c0 0 17.28 31.32 17.28 69.96 0 0-10.2 17.4-36.72 18.24z"/></svg>
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="discord" class="snow-icon" role="img"
xmlns="http://www.w3.org/2000/svg" viewbox="0 0 210 240">
<path
d="M84.79 90.45c-6.45 0-11.55 5.66-11.55 12.57s5.21 12.57 11.55 12.57c6.45 0 11.55-5.66 11.55-12.57.11-6.91-5.09-12.57-11.55-12.57zm41.32 0c-6.45 0-11.55 5.66-11.55 12.57s5.21 12.57 11.55 12.57c6.45 0 11.55-5.66 11.55-12.57s-5.09-12.57-11.55-12.57z" />
<path fill="currentColor"
d="M185.4 0H24.6C11.04 0 0 11.04 0 24.72v162.24c0 13.68 11.04 24.72 24.6 24.72h136.08l-6.36-22.2 15.36 14.28 14.52 13.44L210 240V24.72C210 11.04 198.96 0 185.4 0zm-46.32 156.72s-4.32-5.16-7.92-9.72c15.72-4.44 21.72-14.28 21.72-14.28-4.92 3.24-9.6 5.52-13.8 7.08-6 2.52-11.76 4.2-17.4 5.16-11.52 2.16-22.08 1.56-31.08-.12-6.84-1.32-12.72-3.24-17.64-5.16-2.76-1.08-5.76-2.4-8.76-4.08-.36-.24-.72-.36-1.08-.6-.24-.12-.36-.24-.48-.36-2.16-1.2-3.36-2.04-3.36-2.04s5.76 9.6 21 14.16c-3.6 4.56-8.04 9.96-8.04 9.96-26.52-.84-36.6-18.24-36.6-18.24 0-38.64 17.28-69.96 17.28-69.96 17.28-12.96 33.72-12.6 33.72-12.6l1.2 1.44c-21.6 6.24-31.56 15.72-31.56 15.72s2.64-1.44 7.08-3.48c12.84-5.64 23.04-7.2 27.24-7.56.72-.12 1.32-.24 2.04-.24 7.32-.96 15.6-1.2 24.24-.24 11.4 1.32 23.64 4.68 36.12 11.52 0 0-9.48-9-29.88-15.24l1.68-1.92s16.44-.36 33.72 12.6c0 0 17.28 31.32 17.28 69.96 0 0-10.2 17.4-36.72 18.24z" />
</svg>
</a>
</nav>
<script>
function handleMobileNav(evt) {
evt.preventDefault();
/*If hidden-mobile class is enabled that means we are on desktop do overflow normal but we
if we are at mobile fixed body position, so that its not scrollable(which currently causing bug) and navbar handling its
owns scroll. Case to consider there are chance use can open navbar using toggle button and user when click on any link
body postion should be unset
*/
document.body.classList.toggle('is-nav-open');
const isOpen = document.body.classList.contains('is-nav-open');
if (isOpen) {
evt.target.setAttribute('aria-expanded', 'true');
} else {
evt.target.setAttribute('aria-expanded', 'false');
}
<script>
function handleMobileNav(evt) {
evt.preventDefault();
/*If hidden-mobile class is enabled that means we are on desktop do overflow normal but we
if we are at mobile fixed body position, so that its not scrollable(which currently causing bug) and navbar handling its
owns scroll. Case to consider there are chance use can open navbar using toggle button and user when click on any link
body postion should be unset
*/
document.body.classList.toggle('is-nav-open');
const isOpen = document.body.classList.contains('is-nav-open');
if (isOpen) {
evt.target.setAttribute('aria-expanded', 'true');
} else {
evt.target.setAttribute('aria-expanded', 'false');
}
}
const mobileNavBtn = document.getElementById('toc-drawer-button');
mobileNavBtn.addEventListener('click', handleMobileNav);
mobileNavBtn.addEventListener('touchend', handleMobileNav);
if (window.location.pathname.startsWith('/posts')) {
mobileNavBtn.style.display = 'none';
}
const mobileNavBtn = document.getElementById('toc-drawer-button');
mobileNavBtn.addEventListener('click', handleMobileNav);
mobileNavBtn.addEventListener('touchend', handleMobileNav);
if (window.location.pathname.startsWith('/posts')) {
mobileNavBtn.style.display = 'none';
}
const searchFormInputEl = document.getElementById('search-form-input');
searchFormInputEl.addEventListener('keyup', () => {
const searchFormInputEl = document.getElementById('search-form-input');
searchFormInputEl.addEventListener('keyup', () => {
const gridTocEl = document.querySelector('#nav-primary');
if (searchFormInputEl.value) {
gridTocEl.classList.add('is-mobile-hidden');
} else {
gridTocEl.classList.remove('is-mobile-hidden');
}
});
if (searchFormInputEl.value) {
gridTocEl.classList.add('is-mobile-hidden');
} else {
gridTocEl.classList.remove('is-mobile-hidden');
}
});
document.onkeydown = function (e) {
if ((e.ctrlKey || e.metaKey) && e.which == 75) {
e.preventDefault();
searchFormInputEl.focus();
}
};
document.onkeydown = function (e) {
if ((e.ctrlKey || e.metaKey) && e.which == 75) {
e.preventDefault();
searchFormInputEl.focus();
}
};
</script>
<script type="module" defer>
import docsearch from 'docsearch.js/dist/cdn/docsearch.min.js';
docsearch({
apiKey: '562139304880b94536fc53f5d65c5c19', indexName: 'snowpack', inputSelector: '.search-form-input', debug: true // Set debug to true if you want to inspect the dropdown
});
</script>
</Component>
</script>

View file

@ -1,19 +1,21 @@
<Component>
<script type="module" defer src="/js/index.js"></script>
<aside class="snow-toc snow-toc__subnav snow-view-subnav">
<h2 class="content-title">
{props.title}
</h2>
<script type="module" defer src="/js/index.js"></script>
<aside class="snow-toc snow-toc__subnav snow-view-subnav">
<h2 class="content-title">
{props.title}
</h2>
{props.headers.length > 0 && <div>
<h4 class="snow-toc-section-header">On this page</h4>
<nav class="toc">
<ol>
{props.headings.map((heading) => {
return <li><a href={heading.url}>{heading.text}</a></li>
{props.headers.map((heading) => {
return <li><a href={"#" + heading.slug}>{heading.text}</a></li>
})}
</ol>
</nav>
<hr />
<h4 class="snow-toc-section-header">Suggest a change</h4>
<a href="https://github.com/snowpackjs/snowpack/blob/main/www/{props.inputPath}">Edit this page on GitHub</a>
</aside>
</Component>
</div>}
<h4 class="snow-toc-section-header">Suggest a change</h4>
<a href="https://github.com/snowpackjs/snowpack/blob/main/www/{props.inputPath}">Edit this page on GitHub</a>
</aside>

View file

@ -1,56 +1,56 @@
<script hmx="setup">
<script astro>
import Banner from '../components/Banner.hmx';
import Nav from '../components/Nav.hmx';
export function setup() {
export function setup({ context }) {
return {
title: 'Snowpack',
description: 'Snowpack is a lightning-fast frontend build tool, designed for the modern web.',
props: {
version: '3.0.13',
context: {
title: 'Snowpack',
description: 'Snowpack is a lightning-fast frontend build tool, designed for the modern web.',
currentSnowpackVersion: '3.0.13',
}
};
}
</script>
<head>
<slot:head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png"/>
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
<link rel="manifest" href="/favicon/site.webmanifest" />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content="{description}" />
<title>{context.title}</title>
<meta name="title" content={context.title} />
<meta name="description" content="{context.description}" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://www.snowpack.dev{props.permalink}"/>
<meta property="og:title" content={title}/>
<meta property="og:description" content={description}/>
<meta property="og:image" content="https://www.snowpack.dev/img/social-2.jpg"/>
<meta property="og:type" content="website" />
<meta property="og:url" content={context.permalink}/>
<meta property="og:title" content={context.title} />
<meta property="og:description" content={context.description} />
<meta property="og:image" content="https://www.snowpack.dev/img/social-2.jpg" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image"/>
<meta property="twitter:url" content="https://www.snowpack.dev{props.permalink}"/>
<meta property="twitter:title" content={title}/>
<meta property="twitter:description" content={description}/>
<meta property="twitter:image" content="https://www.snowpack.dev/img/social-2.jpg"/>
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={context.permalink}/>
<meta property="twitter:title" content={context.title} />
<meta property="twitter:description" content={context.description} />
<meta property="twitter:image" content="https://www.snowpack.dev/img/social-2.jpg" />
<!-- Global Stylesheets -->
<link rel="stylesheet" href="/css/app.css" />
<link href="https://fonts.googleapis.com/css2?family=Overpass:wght@400;700;900&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Overpass:wght@400;700;900&display=swap" rel="stylesheet" />
<!-- Note: You can then add additional, custom things here as well. -->
<!-- if no slot given, assume add to bottom -->
<slot></slot>
</head>
</slot:head>
<body class="base 2">
<slot:body>
<Banner></Banner>
<Nav version={props.version} />
<Nav version={context.currentSnowpackVersion} />
<!-- if no slot given, assume add to bottom -->
<slot></slot>
@ -65,4 +65,4 @@
gtag('js', new Date());
gtag('config', 'UA-130280175-9', { page_path: location.pathname === '/' ? (location.pathname + location.hash) : (location.pathname) });
</script>
</body>
</slot:body>

View file

@ -1,12 +1,14 @@
<script hmx="setup">
<script astro>
import Menu from '../components/Menu.hmx';
import Subnav from '../components/Subnav.hmx';
export function setup() {
return {layout: 'layouts/base.hmx', props: {}};
export const layout = 'layouts/base.hmx';
export function setup({ context }) {
return {};
}
</script>
<head>
<slot:head>
<style>
.cover-wrapper {
width: 100%;
@ -47,13 +49,13 @@
</style>
<slot></slot>
</head>
</slot:head>
<body>
<slot:body>
<div class="cover-wrapper">
<img class="cover-blur" src={props.cover} alt=""/>
<img class="cover" src={props.cover} alt=""/>
<img class="cover-blur" src={context.cover} alt=""/>
<img class="cover" src={context.cover} alt=""/>
</div>
<div class="container">
@ -66,7 +68,7 @@
<article class="snow-view-main">
<div class="content">
<h2 class="content-title">
{title}
{context.title}
</h2>
<div class="content-layout">
<div class="content-body">
@ -76,7 +78,7 @@
</div>
</article>
<Subnav title={title} />
<Subnav title={context.title} headers={context.content.headers} />
</section>
</div>
</body>
</slot:body>

View file

@ -1,33 +1,22 @@
<script hmx="setup">
<script astro>
import Subnav from '../components/Subnav.hmx';
import Menu from '../components/Menu.hmx';
import markdownToAst from "mdast-util-from-markdown";
import markdownTableOfContents from "mdast-util-toc";
export function setup({content}) {
const mdAst = markdownToAst(content.source);
// This is super gross, and just me trying to get an existing table of contents generator to work
// we can either do this internally as helpful sugar, or build a simpler one as a plugin
const headingsAst = markdownTableOfContents(mdAst);
const headings = headingsAst.map ? headingsAst.map.children.map(ch => {
return {depth: 1, url: ch.children[0].children[0].url, text: ch.children[0].children[0].children[0].value};
}) : [];
return {
layout: 'layouts/base.hmx',
props: {
headings,
}
export const layout = 'layouts/base.hmx';
export function setup({ context }) {
return {
context: {
}
};
}
</script>
<head>
<slot:head>
<slot></slot>
</head>
</slot:head>
<body>
<slot:body>
<div class="container">
<section class="snow-view__docs has-subnav">
@ -35,12 +24,12 @@
<Menu />
</aside>
<Subnav title={title} headings={props.headings} />
<Subnav title={context.title} headers={context.content.headers} />
<article class="snow-view-main">
<div class="content">
<h2 class="content-title">
{title}
{context.title}
</h2>
<div class="content-layout">
<div class="content-body">
@ -52,4 +41,4 @@
</section>
</div>
</body>
</slot:body>

View file

@ -1,20 +1,18 @@
<script hmx="setup">
<script astro>
import Menu from '../components/Menu.hmx';
export function setup() {
return {
layout: 'layouts/base.hmx',
props: {}
};
export const layout = 'layouts/base.hmx';
export function setup({ context }) {
return {};
}
</script>
<head>
<slot:head>
<!-- hi -->
<slot></slot>
</head>
</slot:head>
<body class="base 2">
<slot:body>
<div class="container">
<section class="snow-view__docs is-full">
@ -28,4 +26,4 @@
</section>
</div>
</body>
</slot:body>

View file

@ -1,11 +1,12 @@
<script hmx="setup">
<script astro>
import {format as formatDate, parseISO} from 'date-fns';
export function setup() {
return { layout: 'layouts/base.hmx', props: {} };
export const layout = 'layouts/base.hmx';
export function setup({ context }) {
return {};
}
</script>
<head>
<slot:head>
<link rel="stylesheet" href="/css/legacy-post.css" />
<style>
.markdown-body img,
@ -99,10 +100,9 @@
}
</style>
<slot></slot>
</head>
<body>
</slot:head>
<slot:body>
<div class="grid-extra-space">
<div class="grid-body-header">
<svg height="80px" style="padding-left: 8px;" viewBox="0 0 640 512" version="1.1"
@ -115,7 +115,7 @@
</g>
</g>
</svg>
<h1 class="header-snowpack">{title}</h1>
<h1 class="header-snowpack">{context.title}</h1>
<p>
@ -140,4 +140,4 @@
<slot></slot>
</article>
</div>
</body>
</slot:body>

View file

@ -1,16 +1,17 @@
<script hmx="setup">
export function setup() {
<script astro>
export const layout = 'layouts/main.hmx';
export function setup({ context }) {
return {
title: '404 - Not Found',
layout: 'layouts/main.hmx',
props: {
context: {
title: '404 - Not Found',
}
};
}
</script>
<h2 class="content-title">
{title}
{context.title}
</h2>
<div class="content">

View file

@ -1,52 +1,56 @@
<script hmx="setup">
<script astro>
import Card from '../components/Card.jsx';
export const layout = 'layouts/main.hmx';
// mocked for now, to be added later
// 1. import {paginate} from 'magicthing';
// 2. export default function ({paginate}) {
function paginate(options) {
if (options.tag === 'guide') {
return [
{title: 'Test guide 1', href:"#"},
{title: 'Test guide 2', href:"#"},
{ title: 'Test guide 1', href: "#" },
{ title: 'Test guide 2', href: "#" },
];
}
if (options.tag === 'communityGuides') {
return [{title: 'Test communityGuides', href:"#"}];
return [{ title: 'Test communityGuides', href: "#" }];
}
return [];
}
export function setup({/* paginate */}) {
export function setup({ context, /* paginate */ }) {
return {
title: 'Guides',
description: "Snowpack's usage and integration guides.",
layout: 'layouts/main.hmx',
props: {
guides: paginate({
files: '/posts/guides/*.md',
// sort: ((a, b) => new Date(b) - new Date(a)),
tag: 'guide',
limit: 10,
// page: query.page,
}),
communityGuides: paginate({
files: '/posts/guides/*.md',
// sort: ((a, b) => new Date(b) - new Date(a)),
tag: 'communityGuides',
limit: 10,
}),
context: {
title: 'Guides',
description: "Snowpack's usage and integration guides.",
props: {
guides: paginate({
files: '/posts/guides/*.md',
// sort: ((a, b) => new Date(b) - new Date(a)),
tag: 'guide',
limit: 10,
// page: query.page,
}),
communityGuides: paginate({
files: '/posts/guides/*.md',
// sort: ((a, b) => new Date(b) - new Date(a)),
tag: 'communityGuides',
limit: 10,
}),
}
}
};
}
</script>
<head>
<slot:head>
</head>
</slot:head>
<body>
<slot:body>
<h2 class="content-title">
{title}
{context.title}
</h2>
<h3 class="content-title">
@ -56,13 +60,13 @@
<div class="content">
<ul>
{props.guides.map((post) => {
return <li><a href={post.href}>{post.title}</a></li>;
return <li><a href={post.href}>{post.title}</a></li>;
})}
</ul>
</div>
<br/>
<br/>
<br />
<br />
<h3 class="content-title">
Popular Integration Guides
@ -70,7 +74,8 @@
<div class="card-grid card-grid-4">
{props.communityGuides.map((post) => {
return <Card item={post} />;
return
<Card item={post} />;
})}
</div>
</body>
</slot:body>

View file

@ -1,22 +1,21 @@
<script hmx="setup">
<script astro>
import Menu from '../components/Menu.hmx';
import Hero from '../components/Hero.hmx';
export function setup() {
return {
layout: 'layouts/base.hmx',
props: {}
}
export const layout = 'layouts/base.hmx';
export function setup({context}) {
return {}
}
</script>
<head>
<!-- Head Stuff -->
</head>
<slot:head>
<meta charset="AAA" />
</slot:head>
<body>
<Hero bar={title}></Hero>
<slot:body>
<Hero bar={context.title}></Hero>
<div foo={title} class="container" style="margin: 0 auto">
<div foo={context.title} class="container" style="margin: 0 auto">
<section class="snow-view__docs is-full is-home">
<aside id="nav-primary" class="snow-view-nav">
@ -95,4 +94,4 @@
<!-- Place this tag in your head or just before your close body tag. -->
<script async="async" defer="defer" src="https://buttons.github.io/buttons.js"></script>
</body>
</slot:body>

View file

@ -1,31 +1,33 @@
<script hmx="setup">
<script astro>
import Card from '../components/Card.jsx';
import CompanyLogo from '../components/CompanyLogo.jsx';
import NewsAssets from '../components/NewsAssets.svelte';
import NewsTitle from '../components/NewsTitle.vue';
export const layout = 'layouts/main.hmx';
import news from '../data/news.json';
import users from '../data/users.json';
export function setup({ findContent }) {
export function setup({ context, request }) {
console.log(request);
return {
title: 'Community & News',
description: "Snowpack community news and companies that use Snowpack.",
layout: 'layouts/main.hmx',
// Using Snowpack? Want to be featured on snowpack.dev?
// Add your project, organization, or company to the end of this list!
props: {
context: {
title: 'Community & News',
description: "Snowpack community news and companies that use Snowpack.",
// Using Snowpack? Want to be featured on snowpack.dev?
// Add your project, organization, or company to the end of this list!
news,
users,
}
};
}
}
</script>
<head> </head>
<slot:head> </slot:head>
<body>
<NewsTitle title={title} />
<slot:body>
<NewsTitle title={context.title} />
<p>
Get the latest news, blog posts, and tutorials on Snowpack. <a href="/feed.xml">Also available via RSS.</a>
@ -45,7 +47,8 @@
working on!</div>
</article>
{props.news.reverse().map((item) => <Card:dynamic item={item} />)}
{context.news.reverse().map((item) =>
<Card:dynamic item={item} />)}
</div>
<div class="content">
@ -54,7 +57,8 @@
<div class="company-logos">
{props.users.map((user) => <CompanyLogo user={user} />)}
{context.users.map((user) =>
<CompanyLogo user={user} />)}
<a href="https://github.com/snowpackjs/snowpack/edit/main/www/_template/news.md" target="_blank"
title="Add Your Project/Company!" class="add-company-button">
@ -70,4 +74,4 @@
<NewsAssets />
</div>
</body>
</slot:body>

View file

@ -1,29 +1,24 @@
<script hmx="setup">
import news from '../data/news.json';
import users from '../data/users.json';
<script astro>
import PluginSearchPage from '../components/PluginSearchPage.jsx';
export function setup({ findContent }) {
export const layout = 'layouts/main.hmx';
export function setup({ context }) {
return {
title: 'The Snowpack Plugin Catalog',
description: 'Snowpack plugins allow for configuration-minimal tooling integration.',
layout: 'layouts/main.hmx',
// Using Snowpack? Want to be featured on snowpack.dev?
// Add your project, organization, or company to the end of this list!
props: {
news,
users,
context: {
title: 'The Snowpack Plugin Catalog',
description: 'Snowpack plugins allow for configuration-minimal tooling integration.',
}
};
}
</script>
<head>
</head>
<body>
<slot:head>
</slot:head>
<slot:body>
<h2 class="content-title">
{ title }
{ context.title }
</h2>
<h3 class="pluginPage-subheading">Customize Snowpack with optimized build plugins.</h3>
@ -33,4 +28,4 @@
</p>
<PluginSearchPage:dynamic />
</body>
</slot:body>

View file

@ -1,4 +1,4 @@
<script hmx="setup">
<script astro>
import Subnav from '../components/Subnav.hmx';
import { content as Menu } from '../components/Menu.hmx';
// import contentful from 'skypack:contentful';
@ -16,12 +16,12 @@
<Menu />
</aside>
<Subnav title={title} />
<Subnav title={context.title} headers={context.content.headers} />
<article class="snow-view-main">
<div class="content">
<h2 class="content-title">
{title}
{context.title}
</h2>
<div class="content-layout">
<div class="content-body">

21
package-lock.json generated
View file

@ -167,6 +167,12 @@
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
"integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg=="
},
"@types/github-slugger": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz",
"integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
@ -1562,6 +1568,21 @@
"pump": "^3.0.0"
}
},
"github-slugger": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz",
"integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==",
"requires": {
"emoji-regex": ">=6.0.0 <=6.1.1"
},
"dependencies": {
"emoji-regex": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz",
"integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4="
}
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",

View file

@ -36,6 +36,7 @@
"deepmerge": "^4.2.2",
"domhandler": "^4.0.0",
"es-module-lexer": "^0.4.1",
"github-slugger": "^1.3.0",
"gray-matter": "^4.0.2",
"htmlparser2": "^6.0.0",
"kleur": "^4.1.4",
@ -54,6 +55,7 @@
"yargs-parser": "^20.2.7"
},
"devDependencies": {
"@types/github-slugger": "^1.3.0",
"@types/sass": "^1.16.0",
"@types/yargs-parser": "^20.2.0",
"@typescript-eslint/eslint-plugin": "^4.18.0",

View file

@ -12,58 +12,10 @@ module.exports = function (snowpackConfig, { resolve } = {}) {
output: ['.js'],
},
async load({ filePath }) {
const { compilePage, compileComponent } = await transformPromise;
const { compileComponent } = await transformPromise;
const projectRoot = snowpackConfig.root;
const contents = await readFile(filePath, 'utf-8');
if (!filePath.includes('/pages/') && !filePath.includes('/layouts/')) {
const result = await compileComponent(contents, { compileOptions: { resolve }, filename: filePath, projectRoot });
return result.contents;
}
const result = await compilePage(contents, {
compileOptions: { resolve },
filename: filePath,
projectRoot,
});
try {
return /* js */ `
${result.contents}
export default async (childDatas, childRenderFns) => {
// Kind of hacky, can clean up if this works
const renderHmx = {setup, head, body};
const merge = (await import('deepmerge')).default;
const content = childDatas && childDatas[0].content;
const _data = await renderHmx.setup({content});
if (_data.layout) {
const renderLayout = (await import('/_hmx/layouts/' + _data.layout.replace(/.*layouts\\//, "").replace(/\.hmx$/, '.js'))).default;
return renderLayout(
[...(childDatas || []), _data],
[...(childRenderFns || []), renderHmx]
);
}
const data = merge.all([_data, ...(childDatas || [])]);
let headResult;
let bodyResult;
for (const renderFn of (childRenderFns || [])) {
let headAndBody = await Promise.all([
renderFn.head(data, headResult),
renderFn.body(data, bodyResult)
]);
headResult = headAndBody[0];
bodyResult = headAndBody[1];
}
return h(Fragment, null, [
renderHmx.head(data, headResult, true),
renderHmx.body(data, bodyResult, true),
]);
};
`;
} catch (err) {
console.error(err);
}
return result.contents;
},
};

View file

@ -17,9 +17,12 @@ export interface JsxItem {
export interface TransformResult {
script: string;
head: JsxItem | undefined;
body: JsxItem | undefined;
items: JsxItem[];
}
export interface CompileResult {
result: TransformResult;
contents: string;
}

View file

@ -53,6 +53,10 @@ function getAttributes(attrs: Attribute[]): Record<string, string> {
continue;
}
const val: TemplateNode = attr.value[0];
if (!val) {
result[attr.name] = '(' + val + ')';
continue;
}
switch (val.type) {
case 'MustacheTag':
result[attr.name] = '(' + val.expression + ')';
@ -143,33 +147,37 @@ function getComponentWrapper(_name: string, { type, url }: { type: string; url:
}
function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string {
let compiledCode = compileExpressionSafe(raw, loader);
// esbuild treeshakes unused imports. In our case these are components, so let's keep them.
const imports = eslexer
.parse(raw)[0]
.filter(({ d }) => d === -1)
.map((i: any) => raw.substring(i.ss, i.se));
.parse(raw)[0]
.filter(({ d }) => d === -1)
.map((i) => raw.substring(i.ss, i.se));
for (let importStatement of imports) {
if (!compiledCode.includes(importStatement)) {
compiledCode = importStatement + '\n' + compiledCode;
}
}
return compiledCode;
}
function compileExpressionSafe(raw: string, loader: 'jsx' | 'tsx'): string {
let { code } = transformSync(raw, {
loader,
jsxFactory: 'h',
jsxFragment: 'Fragment',
charset: 'utf8',
});
for (let importStatement of imports) {
if (!code.includes(importStatement)) {
code = importStatement + '\n' + code;
}
}
return code;
}
export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise<TransformResult> {
await eslexer.init;
// Compile scripts as TypeScript, always
const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx');
const script = compileScriptSafe(ast.module ? ast.module.content : '', 'tsx');
// Todo: Validate that `h` and `Fragment` aren't defined in the script
const [scriptImports] = eslexer.parse(script, 'optional-sourcename');
@ -182,6 +190,8 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
);
const additionalImports = new Set<string>();
let headItem: JsxItem | undefined;
let bodyItem: JsxItem | undefined;
let items: JsxItem[] = [];
let mode: 'JSX' | 'SCRIPT' | 'SLOT' = 'JSX';
let collectionItem: JsxItem | undefined;
@ -192,7 +202,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
enter(node: TemplateNode) {
switch (node.type) {
case 'MustacheTag':
let code = compileScriptSafe(node.expression, 'jsx');
let code = compileExpressionSafe(node.expression, 'jsx');
let matches: RegExpExecArray[] = [];
let match: RegExpExecArray | null | undefined;
@ -230,8 +240,12 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
return;
}
break;
case 'Head':
case 'Body':
case 'InlineComponent':
case 'Element':
case 'Title':
case 'Element': {
const name: string = node.name;
if (!name) {
throw new Error('AHHHH');
@ -241,6 +255,16 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
currentItemName = name;
if (!collectionItem) {
collectionItem = { name, jsx: '' };
if (node.type === 'Head') {
collectionItem.jsx += `h(Fragment, null`;
headItem = collectionItem;
return;
}
if (node.type === 'Body') {
collectionItem.jsx += `h(Fragment, null`;
bodyItem = collectionItem;
return;
}
items.push(collectionItem);
}
collectionItem.jsx += collectionItem.jsx === '' ? '' : ',';
@ -249,10 +273,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
collectionItem.jsx += `h("${name}", ${attributes ? generateAttributes(attributes) : 'null'}`;
return;
}
if (name === 'Component') {
collectionItem.jsx += `h(Fragment, null`;
return;
}
const [componentName, componentKind] = name.split(':');
const componentImportData = components[componentName];
if (!componentImportData) {
@ -265,6 +285,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
collectionItem.jsx += `h(${wrapper}, ${attributes ? generateAttributes(attributes) : 'null'}`;
return;
}
case 'Attribute': {
this.skip();
return;
@ -293,7 +314,7 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
return;
}
default:
throw new Error('Unexpected node type: ' + node.type);
throw new Error('Unexpected (enter) node type: ' + node.type);
}
},
leave(node, parent, prop, index) {
@ -314,6 +335,9 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
if (!collectionItem) {
return;
}
case 'Head':
case 'Body':
case 'Title':
case 'Element':
case 'InlineComponent':
if (!collectionItem) {
@ -329,13 +353,15 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro
return;
}
default:
throw new Error('Unexpected node type: ' + node.type);
throw new Error('Unexpected (leave) node type: ' + node.type);
}
},
});
return {
script: script + '\n' + Array.from(additionalImports).join('\n'),
head: headItem,
body: bodyItem,
items,
};
}

View file

@ -58,7 +58,7 @@ export interface Parser {
export interface Script extends BaseNode {
type: 'Script';
context: string;
context: 'runtime' | 'setup';
content: string;
}
@ -75,8 +75,8 @@ export interface Style extends BaseNode {
export interface Ast {
html: TemplateNode;
css: Style;
instance: Script;
module: Script;
// instance: Script;
}
export interface Warning {
@ -94,38 +94,6 @@ export type ModuleFormat = 'esm' | 'cjs';
export type CssHashGetter = (args: { name: string; filename: string | undefined; css: string; hash: (input: string) => string }) => string;
export interface CompileOptions {
format?: ModuleFormat;
name?: string;
filename?: string;
generate?: 'dom' | 'ssr' | false;
sourcemap?: object | string;
outputFilename?: string;
cssOutputFilename?: string;
sveltePath?: string;
dev?: boolean;
accessors?: boolean;
immutable?: boolean;
hydratable?: boolean;
legacy?: boolean;
customElement?: boolean;
tag?: string;
css?: boolean;
loopGuardTimeout?: number;
namespace?: string;
cssHash?: CssHashGetter;
preserveComments?: boolean;
preserveWhitespace?: boolean;
}
export interface ParserOptions {
filename?: string;
customElement?: boolean;
}
export interface Visitor {
enter: (node: Node) => void;
leave?: (node: Node) => void;

View file

@ -232,33 +232,34 @@ export default function parse(template: string, options: ParserOptions = {}): As
);
}
const instance_scripts = parser.js.filter((script) => script.context === 'default');
const module_scripts = parser.js.filter((script) => script.context === 'module');
// const instance_scripts = parser.js.filter((script) => script.context === 'default');
// const module_scripts = parser.js.filter((script) => script.context === 'module');
const hmx_scripts = parser.js.filter((script) => script.context === 'setup');
if (instance_scripts.length > 1) {
if (hmx_scripts.length > 1) {
parser.error(
{
code: 'invalid-script',
message: 'A component can only have one instance-level <script> element',
message: 'A component can only have one <script astro> element',
},
instance_scripts[1].start
hmx_scripts[1].start
);
}
if (module_scripts.length > 1) {
parser.error(
{
code: 'invalid-script',
message: 'A component can only have one <script context="module"> element',
},
module_scripts[1].start
);
}
// if (module_scripts.length > 1) {
// parser.error(
// {
// code: 'invalid-script',
// message: 'A component can only have one <script context="module"> element',
// },
// module_scripts[1].start
// );
// }
return {
html: parser.html,
css: parser.css[0],
instance: instance_scripts[0],
module: module_scripts[0],
// instance: instance_scripts[0],
module: hmx_scripts[0],
};
}

View file

@ -7,15 +7,16 @@ import { Node, Program } from 'estree';
const script_closing_tag = '</script>';
function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find((attribute) => attribute.name === 'context');
if (!context) return 'default';
function get_context(parser: Parser, attributes: any[], start: number): 'runtime' | 'setup' {
const context = attributes.find((attribute) => attribute.name === 'astro');
if (!context) return 'runtime';
if (context.value === true) return 'setup';
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
parser.error(
{
code: 'invalid-script',
message: 'context attribute must be static',
message: 'astro attribute must be static',
},
start
);
@ -23,11 +24,11 @@ function get_context(parser: Parser, attributes: any[], start: number): string {
const value = context.value[0].data;
if (value !== 'module') {
if (value !== 'setup') {
parser.error(
{
code: 'invalid-script',
message: 'If the context attribute is supplied, its value must be "module"',
message: 'If the "astro" attribute has a value, its value must be "setup"',
},
context.start
);

View file

@ -14,13 +14,14 @@ import list from '../../utils/list.js';
const valid_tag_name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const meta_tags = new Map([
['svelte:head', 'Head'],
['svelte:options', 'Options'],
['svelte:window', 'Window'],
['svelte:body', 'Body'],
['slot:head', 'Head'],
['slot:body', 'Body'],
// ['astro:options', 'Options'],
// ['astro:window', 'Window'],
// ['astro:body', 'Body'],
]);
const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment');
const valid_meta_tags = Array.from(meta_tags.keys()); //.concat('astro:self', 'astro:component', 'astro:fragment');
const specials = new Map([
[
@ -39,9 +40,10 @@ const specials = new Map([
],
]);
const SELF = /^svelte:self(?=[\s/>])/;
const COMPONENT = /^svelte:component(?=[\s/>])/;
const SLOT = /^svelte:fragment(?=[\s/>])/;
const SELF = /^astro:self(?=[\s/>])/;
const COMPONENT = /^astro:component(?=[\s/>])/;
const SLOT = /^astro:fragment(?=[\s/>])/;
const HEAD = /^head(?=[\s/>])/;
function parent_is_head(stack) {
let i = stack.length;
@ -79,7 +81,7 @@ export default function tag(parser: Parser) {
if (meta_tags.has(name)) {
const slug = meta_tags.get(name).toLowerCase();
if (is_closing_tag) {
if ((name === 'svelte:window' || name === 'svelte:body') && parser.current().children.length) {
if ((name === 'astro:window' || name === 'astro:body') && parser.current().children.length) {
parser.error(
{
code: `invalid-${slug}-content`,
@ -115,9 +117,9 @@ export default function tag(parser: Parser) {
const type = meta_tags.has(name)
? meta_tags.get(name)
: /[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component'
: /[A-Z]/.test(name[0]) || name === 'astro:self' || name === 'astro:component'
? 'InlineComponent'
: name === 'svelte:fragment'
: name === 'astro:fragment'
? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack)
? 'Title'
@ -197,13 +199,13 @@ export default function tag(parser: Parser) {
parser.allow_whitespace();
}
if (name === 'svelte:component') {
if (name === 'astro:component') {
const index = element.attributes.findIndex((attr) => attr.type === 'Attribute' && attr.name === 'this');
if (!~index) {
parser.error(
{
code: 'missing-component-definition',
message: "<svelte:component> must have a 'this' attribute",
message: "<astro:component> must have a 'this' attribute",
},
start
);
@ -281,27 +283,29 @@ function read_tag_name(parser: Parser) {
parser.error(
{
code: 'invalid-self-placement',
message: '<svelte:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components',
message: '<astro:self> components can only exist inside {#if} blocks, {#each} blocks, or slots passed to components',
},
start
);
}
return 'svelte:self';
return 'astro:self';
}
if (parser.read(COMPONENT)) return 'svelte:component';
if (parser.read(COMPONENT)) return 'astro:component';
if (parser.read(SLOT)) return 'svelte:fragment';
if (parser.read(SLOT)) return 'astro:fragment';
if (parser.read(HEAD)) return 'head';
const name = parser.read_until(/(\s|\/|>)/);
if (meta_tags.has(name)) return name;
if (name.startsWith('svelte:')) {
if (name.startsWith('astro:')) {
const match = fuzzymatch(name.slice(7), valid_meta_tags);
let message = `Valid <svelte:...> tag names are ${list(valid_meta_tags)}`;
let message = `Valid <astro:...> tag names are ${list(valid_meta_tags)}`;
if (match) message += ` (did you mean '${match}'?)`;
parser.error(

View file

@ -52,6 +52,7 @@ export default async function (astroConfig: AstroConfig) {
await writeFile(outPath, html, 'utf-8');
} catch (err) {
console.error('Unable to generate page', rel);
console.error(err);
}
}

View file

@ -1,32 +0,0 @@
import type { HtmlExtension, Token } from 'micromark/dist/shared-types';
const characterReferences = {
'"': 'quot',
'&': 'amp',
'<': 'lt',
'>': 'gt',
'{': 'lbrace',
'}': 'rbrace',
};
type EncodedChars = '"' | '&' | '<' | '>' | '{' | '}';
function encode(value: string): string {
return value.replace(/["&<>{}]/g, (raw: string) => {
return '&' + characterReferences[raw as EncodedChars] + ';';
});
}
const plugin: HtmlExtension = {
exit: {
codeFlowValue() {
const token: Token = arguments[0];
const serialize = (this.sliceSerialize as unknown) as (t: Token) => string;
const raw = (this.raw as unknown) as (s: string) => void;
const value = serialize(token);
raw(encode(value));
},
},
};
export { plugin as default };

View file

@ -0,0 +1,35 @@
import slugger from 'github-slugger';
// NOTE: micromark has terrible TS types. Instead of fighting with the
// limited/broken TS types that they ship, we just reach for our good friend, "any".
export function createMarkdownHeadersCollector() {
const headers: any[] = [];
let currentHeader: any;
return {
headers,
headersExtension: {
enter: {
atxHeading(node: any) {
currentHeader = {};
headers.push(currentHeader);
},
atxHeadingSequence(node: any) {
currentHeader.depth = this.sliceSerialize(node).length;
},
atxHeadingText(node: any) {
currentHeader.text = this.sliceSerialize(node);
},
} as any,
exit: {
atxHeading(node: any) {
currentHeader.slug = slugger.slug(currentHeader.text);
this.tag(`<h${currentHeader.depth} id="${currentHeader.slug}">`);
this.raw(currentHeader.text);
this.tag(`</h${currentHeader.depth}>`);
// console.log(this.sliceSerialize(node));
},
} as any,
} as any,
};
}

View file

@ -58,7 +58,14 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
try {
const mod = await snowpackRuntime.importModule(selectedPageUrl);
const html = (await mod.exports.default()) as string;
const html = (await mod.exports.__renderPage({
request: {
host: fullurl.hostname,
path: fullurl.pathname,
href: fullurl.toString(),
},
children: [],
})) as string;
return {
statusCode: 200,

View file

@ -7,7 +7,7 @@ import matter from 'gray-matter';
import gfmHtml from 'micromark-extension-gfm/html.js';
import { CompileResult, TransformResult } from './@types/astro';
import { parse } from './compiler/index.js';
import markdownEncode from './markdown-encode.js';
import { createMarkdownHeadersCollector } from './micromark-collect-headers.js';
import { defaultLogOptions } from './logger.js';
import { optimize } from './optimize/index.js';
import { codegen } from './codegen/index.js';
@ -51,33 +51,35 @@ async function convertMdToJsx(
contents: string,
{ compileOptions, filename, fileID }: { compileOptions: CompileOptions; filename: string; fileID: string }
): Promise<TransformResult> {
// This doesn't work.
const { data: _frontmatterData, content } = matter(contents);
const {headers, headersExtension} = createMarkdownHeadersCollector();
const mdHtml = micromark(content, {
extensions: [gfmSyntax()],
htmlExtensions: [gfmHtml, markdownEncode],
htmlExtensions: [gfmHtml, headersExtension],
});
const setupData = {
title: _frontmatterData.title,
description: _frontmatterData.description,
layout: _frontmatterData.layout,
console.log("headers", headers);
const setupContext = {
..._frontmatterData,
content: {
frontmatter: _frontmatterData,
// This is an awful hack due to Svelte parser disliking script tags badly.
source: content.replace(/<\/?script/g, '<SCRIPT'),
headers,
source: content,
html: mdHtml,
},
props: {
..._frontmatterData,
},
};
// </script> can't be anywhere inside of a JS string, otherwise the HTML parser fails.
// Break it up here so that the HTML parser won't detect it.
const stringifiedSetupContext = JSON.stringify(setupContext).replace(/\<\/script\>/g, `</scrip" + "t>`);
return convertHmxToJsx(
`<script hmx="setup">export function setup() {
return ${JSON.stringify(setupData)};
}</script><head></head><body>${mdHtml}</body>`,
`<script astro>
${_frontmatterData.layout ? `export const layout = ${JSON.stringify(_frontmatterData.layout)};` : ''}
export function setup({context}) {
return {context: ${stringifiedSetupContext} };
}
</script><slot:head></slot:head><slot:body><section>{${JSON.stringify(mdHtml)}}</section></slot:body>`,
{ compileOptions, filename, fileID }
);
}
@ -97,49 +99,85 @@ async function transformFromSource(
}
}
export async function compilePage(
source: string,
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
): Promise<CompileResult> {
const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
const headItem = sourceJsx.items.find((item) => item.name === 'head');
const bodyItem = sourceJsx.items.find((item) => item.name === 'body');
const headItemJsx = !headItem ? 'null' : headItem.jsx.replace('"head"', 'isRoot ? "head" : Fragment');
const bodyItemJsx = !bodyItem ? 'null' : bodyItem.jsx.replace('"head"', 'isRoot ? "body" : Fragment');
const modJsx = `
${sourceJsx.script}
import { h, Fragment } from '${internalImport('h.js')}';
export function head({title, description, props}, child, isRoot) { return (${headItemJsx}); }
export function body({title, description, props}, child, isRoot) { return (${bodyItemJsx}); }
`.trim();
return {
contents: modJsx,
};
}
export async function compileComponent(
source: string,
{ compileOptions = defaultCompileOptions, filename, projectRoot }: { compileOptions: CompileOptions; filename: string; projectRoot: string }
): Promise<CompileResult> {
const sourceJsx = await transformFromSource(source, { compileOptions, filename, projectRoot });
// throw error if <Component /> missing
if (!sourceJsx.items.find(({ name }) => name === 'Component')) throw new Error(`${filename} <Component> expected!`);
const headItem = sourceJsx.head;
const bodyItem = sourceJsx.body;
const headItemJsx = !headItem ? 'null' : headItem.jsx;
const bodyItemJsx = !bodyItem ? 'null' : bodyItem.jsx;
// sort <style> tags first
// TODO: remove these and inject in <head>
sourceJsx.items.sort((a, b) => (a.name === 'style' && b.name !== 'style' ? -1 : 0));
// return template
const modJsx = `
import { h, Fragment } from '${internalImport('h.js')}';
export default function(props) { return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); }
`.trim();
let modJsx = `
// <script astro></script>
${sourceJsx.script}
// \`__render()\`: Render the contents of the HMX module. "<slot:*>" elements are not
// included (see below).
import { h, Fragment } from '${internalImport('h.js')}';
export default function __render(props) { return h(Fragment, null, ${sourceJsx.items.map(({ jsx }) => jsx).join(',')}); }
// <slot:*> render functions
export function __slothead(context, child) { return h(Fragment, null, ${headItemJsx}); }
export function __slotbody(context, child) { return h(Fragment, null, ${bodyItemJsx}); }
`;
if (headItemJsx || bodyItemJsx) {
modJsx += `
// \`__renderPage()\`: Render the contents of the HMX module as a page. This is a special flow,
// triggered by loading a component directly by URL.
// If the page exports a defined "layout", then load + render those first. "context", "slot:head",
// and "slot:body" should all inherit from parent layouts, merging together in the correct order.
export async function __renderPage({request, children}) {
const currentChild = {
__slothead,
__slotbody,
setup: typeof setup === 'undefined' ? (passthrough) => passthrough : setup,
layout: typeof layout === 'undefined' ? undefined : layout,
};
// find all layouts, going up the layout chain.
if (currentChild.layout) {
const layoutComponent = (await import('/_hmx/layouts/' + layout.replace(/.*layouts\\//, "").replace(/\.hmx$/, '.js')));
return layoutComponent.__renderPage({
request,
children: [currentChild, ...children],
});
}
const isRoot = true;
const merge = (await import('deepmerge')).default;
// call all children setup scripts, in order, and return.
let mergedContext = {};
for (const child of [currentChild, ...children]) {
const childSetupResult = await child.setup({request, context: mergedContext});
mergedContext = childSetupResult.context ? merge(mergedContext, childSetupResult.context) : mergedContext;
}
Object.freeze(mergedContext);
let headResult;
let bodyResult;
for (const child of children.reverse()) {
headResult = await child.__slothead(mergedContext, headResult);
bodyResult = await child.__slotbody(mergedContext, bodyResult);
}
return h(Fragment, null, [
h("head", null, currentChild.__slothead(mergedContext, headResult)),
h("body", null, currentChild.__slotbody(mergedContext, bodyResult)),
]);
};\n`;
}
return {
result: sourceJsx,
contents: modJsx,
};
}

View file

@ -1,4 +1,4 @@
<script hmx="setup">
<script astro>
export function setup() {
return {
props: {}
@ -6,10 +6,10 @@
}
</script>
<head>
<slot:head>
<!-- Head Stuff -->
</head>
</slot:head>
<body>
<slot:body>
<h1>Hello world!</h1>
</body>
</slot:body>