Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

svelte "support" lol #154

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

svelte "support" lol #154

wants to merge 7 commits into from

Conversation

Prinzhorn
Copy link

@Prinzhorn Prinzhorn commented Jan 18, 2022

Listen, this "works" but I just wanted to get something working.

The following index.svelte works as expected:

<script>
  export let current;
</script>

<h1>Hi {current.source}</h1>

<style>
  h1 {
    color: red;
  }
</style>

The amazing thing about having Svelte support in Harp (besides using Svelte syntax, duh) would be scoped CSS. None of the other template languages give you that. The generated HTML for the above is currently this

<h1 class="svelte-1tb9iu1">Hi index</h1><style>h1.svelte-1tb9iu1{color:red}</style>

Two open questions, if we solve those we got ourselves the first Svelte iteration:

  1. What do we do about that temporary file I'm hacking around? The compile call returns (among other things) the JavaScript code (a string) for a CJS module. So there must be a better way. There is svelte/register, need to check how that works internally because it gives us the compiled component straight away.
  2. CSS! Every component returns its scoped CSS, which I'm currently injecting into a <style> tag. It "works" but it would probably nicer to write out a CSS file? But that would need to make it's way into the main root HTML file's head. There is <svelte:head>, need to look into that as wel.

I also noticed your JSX "implementation" is copy and paste of EJS without editing it, I assume you didn't commit what you tried?

I tried working with SvelteKit and the adapter-static but I'm just not happy with it. I don't like the UX of a client side router (it feels like nothing is happening, the browser doesn't show any indication), I just want static HTML pages and I'm good. I'd even accept the inline <style> for now.

Also the second iteration would obviously be to turn on hydration and see where that leads. Can literally spit out a inline <script> same way I'm doing with style for now.

I do have a website completely done in SvelteKit that I'd love to migrate, so we have a real world test case already.

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 19, 2022

I'm just playing around with it some more and even with this little code it's already crazy. Like, rendering Svelte inside ejs? WTF?

index.ejs

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />

	<title>Page title</title>
</head>

<body>
	<%- partial("_hello-world", {name: "Harp"}) %>
</body>

</html>

_hello-world.svelte

<script>
  export let name;
  export let current;
</script>

<h1>Hi {name} and ({current.source})</h1>

<style>
  h1 {
    color: red;
  }
</style>

I've missed Harp 😄

Let's go full circle: can I also include Jade inside Svelte?

<script>
  export let name;
  export let current;
  export let partial;
</script>

<h1>Hi {name} and ({current.source})</h1>

<div>{@html partial('_not-pug', {nested: "I'm not pug"})}</div>

<style>
  h1 {
    color: red;
  }
</style>

_not-pug.jade

h1 Jade says: #{nested}

Selection_1092


What a time to be alive. I hope I can infect you with Svelte 😏

I'll play with it some more and see what I come up with. There are some open question, e.g. importing Svelte inside Svelte currently doesn't have access to current etc., unless you explicitly pass them down as props. Which you definitely don't want. I'm not sure what the most ergonomic way would be that is still fun to use. We could wrap all top level Svelte rendering in a component that exposes all these things via context, but it doesn't sound fun to use. Ideally it would just work having export let current when you need it. Maybe there is a way to hack around that so that every Svelte component just gets these props (maybe we can hook into the component lifecycle and just spread them all into $$props)

@sintaxi
Copy link
Owner

sintaxi commented Jan 19, 2022

Wow. This is wild! Nice work. I'll circle back to you once I've had the opportunity to run it.

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 19, 2022

I'll leave my notes here and let you know when something cool happens.

So I've played with 'svelte/register' which hooks into require. But two issues arise:

  1. It couldn't find svelte/internal which the compiled Svelte component imports. Because the resolution algorithm searches for it where my project lies (relative to the Svelte file) and not in the terraform project
  2. require cache. This basically makes it unsuitable for Harp. Once it has seen a svelte file it won't re-compile when you reload the page. And I don't feel like messing with that cache.

So I'll go back to using compile manually and see if I find a way without an intermediate temporary file (https://stackoverflow.com/questions/17581830/load-node-js-module-from-string-in-memory).

Oh well, without registering the require hook importing other Svelte components won't work, e.g. import Nav from './_nav.svelte';. So yeah, we need a solution for this.

@Prinzhorn
Copy link
Author

nvm, requiring Svelte inside Svelte works now.

svelte.svelte

<script>
  export let current;

  import Nav from './_nav.svelte';
</script>

<Nav {current} />

<h1>Hello {current.source}</h1>

<style>
  h1 {
    color: red;
  }
</style>

_nav.svelte

<script>
  export let current;
</script>

<nav>
  <ul>
    <li>
      <a href="/" class:active={current.source === 'index'}>Home</a>
    </li>
    <li>
      <a href="/svelte" class:active={current.source === 'svelte'}>Svelte</a>
    </li>
  </ul>
</nav>

<style>
  nav {
    background: yellow;
  }

  ul {
    list-style: none;
  }

  .active {
    font-weight: bold;
  }
</style>

output with scoped styles

<nav class="svelte-1y9xqmb"><ul class="svelte-1y9xqmb"><li><a href="/" class="svelte-1y9xqmb">Home</a></li>
    <li><a href="/svelte" class="svelte-1y9xqmb active">Svelte</a></li></ul>
</nav>

<h1 class="svelte-1tb9iu1">Hello svelte</h1><style>h1.svelte-1tb9iu1{color:red}
nav.svelte-1y9xqmb{background:yellow}ul.svelte-1y9xqmb{list-style:none}.active.svelte-1y9xqmb{font-weight:bold}</style>

Selection_1093

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 19, 2022

I'm done for now. I've added very hacky support for hydration. Currently it requires the component to be wrapped in a div because I have no idea how hydration markers work in Svelte.

Also I'm sure there must be a way to detect if a component even needs hydration to avoid sending unnecessary script. For some reason esbuild also caches stuff even though they've changed. Not sure what that's about.

counter.svelte

<script>
  let count = 0;

  function handleClick() {
    count++;
  }
</script>

<button type="button" on:click={handleClick}>count = {count}</button>

output

<div style="display: contents;"><button type="button">count = 0</button></div><script>(()=>{function d(){}function v(t){return t()}function T(){return Object.create(null)}function _(t){t.forEach(v)}function N(t){return typeof t=="function"}function q(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}function R(t){return Object.keys(t).length===0}var pt=new Set;var y=!1;function X(){y=!0}function Z(){y=!1}function tt(t,e,n,r){for(;t<e;){let s=t+(e-t>>1);n(s)<=r?t=s+1:e=s}return t}function et(t){if(t.hydrate_init)return;t.hydrate_init=!0;let e=t.childNodes;if(t.nodeName==="HEAD"){let o=[];for(let c=0;c<e.length;c++){let f=e[c];f.claim_order!==void 0&&o.push(f)}e=o}let n=new Int32Array(e.length+1),r=new Int32Array(e.length);n[0]=-1;let s=0;for(let o=0;o<e.length;o++){let c=e[o].claim_order,f=(s>0&&e[n[s]].claim_order<=c?s+1:tt(1,s,m=>e[n[m]].claim_order,c))-1;r[o]=n[f]+1;let a=f+1;n[a]=o,s=Math.max(a,s)}let u=[],i=[],l=e.length-1;for(let o=n[s]+1;o!=0;o=r[o-1]){for(u.push(e[o-1]);l>=o;l--)i.push(e[l]);l--}for(;l>=0;l--)i.push(e[l]);u.reverse(),i.sort((o,c)=>o.claim_order-c.claim_order);for(let o=0,c=0;o<i.length;o++){for(;c<u.length&&i[o].claim_order>=u[c].claim_order;)c++;let f=c<u.length?u[c]:null;t.insertBefore(i[o],f)}}function g(t,e){if(y){for(et(t),(t.actual_end_child===void 0||t.actual_end_child!==null&&t.actual_end_child.parentElement!==t)&&(t.actual_end_child=t.firstChild);t.actual_end_child!==null&&t.actual_end_child.claim_order===void 0;)t.actual_end_child=t.actual_end_child.nextSibling;e!==t.actual_end_child?(e.claim_order!==void 0||e.parentNode!==t)&&t.insertBefore(e,t.actual_end_child):t.actual_end_child=e.nextSibling}else(e.parentNode!==t||e.nextSibling!==null)&&t.appendChild(e)}function B(t,e,n){y&&!n?g(t,e):(e.parentNode!==t||e.nextSibling!=n)&&t.insertBefore(e,n||null)}function b(t){t.parentNode.removeChild(t)}function x(t){return document.createElement(t)}function F(t){return document.createTextNode(t)}function L(t,e,n,r){return t.addEventListener(e,n,r),()=>t.removeEventListener(e,n,r)}function I(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function k(t){return Array.from(t.childNodes)}function nt(t){t.claim_info===void 0&&(t.claim_info={last_index:0,total_claimed:0})}function P(t,e,n,r,s=!1){nt(t);let u=(()=>{for(let i=t.claim_info.last_index;i<t.length;i++){let l=t[i];if(e(l)){let o=n(l);return o===void 0?t.splice(i,1):t[i]=o,s||(t.claim_info.last_index=i),l}}for(let i=t.claim_info.last_index-1;i>=0;i--){let l=t[i];if(e(l)){let o=n(l);return o===void 0?t.splice(i,1):t[i]=o,s?o===void 0&&t.claim_info.last_index--:t.claim_info.last_index=i,l}}return r()})();return u.claim_order=t.claim_info.total_claimed,t.claim_info.total_claimed+=1,u}function it(t,e,n,r){return P(t,s=>s.nodeName===e,s=>{let u=[];for(let i=0;i<s.attributes.length;i++){let l=s.attributes[i];n[l.name]||u.push(l.name)}u.forEach(i=>s.removeAttribute(i))},()=>r(e))}function z(t,e,n){return it(t,e,n,x)}function E(t,e){return P(t,n=>n.nodeType===3,n=>{let r=""+e;if(n.data.startsWith(r)){if(n.data.length!==r.length)return n.splitText(r.length)}else n.data=r},()=>F(e),!0)}function H(t,e){e=""+e,t.wholeText!==e&&(t.data=e)}var mt=new Map;var S;function h(t){S=t}var p=[];var W=[],$=[],G=[],rt=Promise.resolve(),C=!1;function ot(){C||(C=!0,rt.then(U))}function O(t){$.push(t)}var D=new Set,w=0;function U(){let t=S;do{for(;w<p.length;){let e=p[w];w++,h(e),st(e.$$)}for(h(null),p.length=0,w=0;W.length;)W.pop()();for(let e=0;e<$.length;e+=1){let n=$[e];D.has(n)||(D.add(n),n())}$.length=0}while(p.length);for(;G.length;)G.pop()();C=!1,D.clear(),h(t)}function st(t){if(t.fragment!==null){t.update(),_(t.before_update);let e=t.dirty;t.dirty=[-1],t.fragment&&t.fragment.p(t.ctx,e),t.after_update.forEach(O)}}var ct=new Set;function lt(t,e){t&&t.i&&(ct.delete(t),t.i(e))}var yt=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var gt=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function ut(t,e,n,r){let{fragment:s,on_mount:u,on_destroy:i,after_update:l}=t.$$;s&&s.m(e,n),r||O(()=>{let o=u.map(v).filter(N);i?i.push(...o):_(o),t.$$.on_mount=[]}),l.forEach(O)}function Y(t,e){let n=t.$$;n.fragment!==null&&(_(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function at(t,e){t.$$.dirty[0]===-1&&(p.push(t),ot(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<<e%31}function J(t,e,n,r,s,u,i,l=[-1]){let o=S;h(t);let c=t.$$={fragment:null,ctx:null,props:u,update:d,not_equal:s,bound:T(),on_mount:[],on_destroy:[],on_disconnect:[],before_update:[],after_update:[],context:new Map(e.context||(o?o.$$.context:[])),callbacks:T(),dirty:l,skip_bound:!1,root:e.target||o.$$.root};i&&i(c.root);let f=!1;if(c.ctx=n?n(t,e.props||{},(a,m,...M)=>{let j=M.length?M[0]:m;return c.ctx&&s(c.ctx[a],c.ctx[a]=j)&&(!c.skip_bound&&c.bound[a]&&c.bound[a](j),f&&at(t,a)),m}):[],c.update(),f=!0,_(c.before_update),c.fragment=r?r(c.ctx):!1,e.target){if(e.hydrate){X();let a=k(e.target);c.fragment&&c.fragment.l(a),a.forEach(b)}else c.fragment&&c.fragment.c();e.intro&&lt(t.$$.fragment),ut(t,e.target,e.anchor,e.customElement),Z(),U()}h(o)}var ft;typeof HTMLElement=="function"&&(ft=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(v).filter(N);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){_(this.$$.on_disconnect)}$destroy(){Y(this,1),this.$destroy=d}$on(t,e){let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let r=n.indexOf(e);r!==-1&&n.splice(r,1)}}$set(t){this.$$set&&!R(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var A=class{$destroy(){Y(this,1),this.$destroy=d}$on(e,n){let r=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return r.push(n),()=>{let s=r.indexOf(n);s!==-1&&r.splice(s,1)}}$set(e){this.$$set&&!R(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};function dt(t){let e,n,r,s,u;return{c(){e=x("button"),n=F("count = "),r=F(t[0]),this.h()},l(i){e=z(i,"BUTTON",{type:!0});var l=k(e);n=E(l,"count = "),r=E(l,t[0]),l.forEach(b),this.h()},h(){I(e,"type","button")},m(i,l){B(i,e,l),g(e,n),g(e,r),s||(u=L(e,"click",t[1]),s=!0)},p(i,[l]){l&1&&H(r,i[0])},i:d,o:d,d(i){i&&b(e),s=!1,u()}}}function _t(t,e,n){let r=0;function s(){n(0,r++,r)}return[r,s]}var V=class extends A{constructor(e){super();J(this,e,_t,dt,q,{})}},K=V;var Q=document.getElementsByTagName("script"),ht=Q[Q.length-1];new K({target:ht.previousElementSibling,hydrate:!0});})();
</script><style></style>
vokoscreen-2022-01-19_11-43-55.mp4

(the flickering screen comes from broken screen recording)

Edit: here's a PoC how a component is progressively enhanced. Button is disabled in the static HTML but becomes enabled once hydrated.

<script>
  import { onMount } from 'svelte';

  let count = 0;
  let disabled = true;

  function handleClick() {
    count++;
  }

  onMount(() => {
    disabled = false;
  });
</script>

<button type="button" {disabled} on:click={handleClick}>count = {count}</button>

This give you an idea about how things like a carousel etc. could be rendered in static HTML and then made interactive once JavaScript is there.
Selection_1094

Edit: More thoughts

  1. When building we can add all Svelte pages as entrypoints in esbuild and with code splitting we get a main bundle plus one for each page. But this requires a change in the terraform pipeline:
  2. There needs to be a way in harp to collect global HTML. Currently when I render a Svelte component inside ejs there is no way for me to inject the styles and script into the ejs head, because at that point inside the ejs it has already been processed. I think this needs need to be split into two passes. The second one is basically free (we can cache each partial result) but it gives us the necessary meta data to properly render the head.
  3. Once that works the first pass also needs to be able to output additional files. Currently it's 1:1. This way we can output css/js files (with proper cache-busting hash-filenames). This is also needed for CSP, because I don't want to use inline script or style in the long run.

Edit2: Scratch that part with the two passes. We can just do replace('</head>', 'injected stuff</head>') and it'll work with whatever you use to render the outer HTML skeleton. So all we need is a way to emit additional css/js files from within the render() call (so in addition to returning the HTML string). And then have a post process that injects it into the head.

@Prinzhorn
Copy link
Author

Another thought: if a Svelte component uses partial (I think we can detect this from meta data provided by the Svelte compiler) we cannot make it work on the client. So if a component is marked as progressive (we could add like a flag and only then compile code for the client) we need to error out and tell the user that progressive components cannot use partial. And if we cannot detect it we can just inject a partial on the client that throws with a useful error.

Also partial would be the place we need to hook in to collect all the entrypoints for esbuild for the code splitting. That means once a harp build has finished we have a list of all Svelte entrypoints that need to be bundled (we can't look in the fs for *.svelte because some of these files are not entrypoint, but imported from a Svelte component). That means you can have a Jade/EJS page and use three Svelte partials in three different places (a carousel, form validation and Google Maps integration) and they will all share one bundle. And if you re-use the carousel on a second page it uses a common bundle, thanks esbuild. Once esbuild supports code splitting for CSS this will also just magically work for CSS. So using Svelte in Harp means you get scoped CSS, a CSS file with all common styles of all pages plus one additional CSS for each page.

BTW this is my vision for Harp and I hope you share it. For me Harp always "made sense". This is entirely subjective but I haven't seen anything that could replace it. The same goes for Svelte, once I started using it it just made sense, while React no longer makes sense to me at all.

So my vision for Harp is to have the same static generator I've always loved, but then I can just sprinkle Svelte components in there and they just work. Nothing to set up. And they are generated in a way that they can even work without JavaScript or at least serve something useful initially. E.g. for a Google Maps integration it's already common to have a "Click here to active the evil Google scripts" overlay. This would be rendered in the static HTML already. And once hydrated the user can click it and the Svelte code takes over. BTW this works already with this PR (having installed @googlemaps/js-api-loader):

maps.jade

doctype html
html
  head
    meta(charset="utf-8")
    meta(name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui")

  body
    h1 Some content
    div!= partial("_map")

_map.svelte

<script>
  import { onMount } from 'svelte';

  let container;

  async function doTheEvil() {
    const { Loader } = await import('@googlemaps/js-api-loader');

    const loader = new Loader({
      apiKey: "",
      version: "weekly",
      libraries: ["places"]
    });

    const mapOptions = {
      center: {
        lat: 0,
        lng: 0
      },
      zoom: 4
    };

    const google = await loader.load();
    const map = new google.maps.Map(container, mapOptions);
  }
</script>

<div bind:this={container}>
  <button on:click={doTheEvil}>Active the evil Google scripts</button>
</div>

<style>
  button {
    color: red;
  }
</style>

esbuild really changes the game

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 22, 2022

Current progress (locally, uncommitted): client side scripts are now only generated when the component is marked as progressive via

<script context="module">
  export const progressive = true;
</script>

This means by default Svelte only behaves like a templating language but you can opt into the magic. For progressive components you also won't be able to use partial. I am still debating if current should be usable, it would then need to be serialized (using devalue) and sent back as part of the hydration script, but that's not an issue unless this could be sensitive in any way. But on the plus side this would only need to be done when you're actually using them (the Svelte compiler tells us which exports a component has, so if it doesn't need current it won't get it on the client).

Man this is fun.

There's also an enhanced prop (hardcoded to false on the server and true on the client) for peak progressive enhancement. Here I'm rendering fallback text (basically <noscript> logic) that is shown until the client script kicks in.

<script context="module">
  export const progressive = true;
</script>

<script>
  export let enhanced;

  import { onMount } from 'svelte';

  let container;

  async function doTheEvil() {
    const { Loader } = await import('@googlemaps/js-api-loader');

    const loader = new Loader({
      apiKey: "",
      version: "weekly",
      libraries: ["places"]
    });

    const mapOptions = {
      center: {
        lat: 0,
        lng: 0
      },
      zoom: 4
    };

    const google = await loader.load();
    const map = new google.maps.Map(container, mapOptions);
  }
</script>

<div bind:this={container}>
  <button type="button" on:click={doTheEvil} disabled={!enhanced}>
    {#if enhanced}
      Active the evil Google scripts
    {:else}
      You need JavaScript to use the evil Google scripts
    {/if}
  </button>
</div>

<style>
  button {
    color: red;
  }
</style>

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 22, 2022

I'm done for today. I'm not sure what's missing exactly. For server rendering you can now use Svelte and also import *.svelte in Svelte as expected. The same files can then be made into client components by adding the progressive flag. Imports work there as will (this was some fiddling with both esbuild for the client and svelte/register on the server). Both server and client can also use current and environment. Using partial or public will throw (only if it's a progressive component, not for regular server templating).

One open problem: the client side bundle will contain the styles again, even though we already rendered them on the server. Need to figure out how to disable that properly.

TODO:

  1. Write tests (I didn't look into how the setup works in Harp)
  2. Add support for emitting css and js files (name + contents) from within render() and inject them into the head.
  3. When doing 3 during build, also run them though esbuild for code splitting (during normal live-serving we just serve the js + css needed for that single page)

@Prinzhorn
Copy link
Author

I also added prettier and eslint because I just couldn't work without it. We can of course remove this from the PR in the future. But they are related to an open question: what about modern js? E.g. I've kept using var and Object.assign in my code to follow the existing style but would otherwise have written different code. I'm aware this comes from years ago and old Node etc. but might be worth updating. Also eslint does produce quite some swiggly red lines across terraform.

@Prinzhorn
Copy link
Author

Prinzhorn commented Jan 22, 2022

I just ported my SvelteKit project over in about 20 minutes. What I learned:

  1. $lib imports obviously don't work, but since this is way less JavaScript heavy (most components generate static HTML) we should get away with regular ../../foo.js imports and loose a little bit of convenience
  2. Mixing CJS and ESM will crash. The harp process runs Node.js and uses regular CJS. But the Svelte client bundling uses ESM. As soon as you leave Svelte and import something else (a JS lib in ESM) it will crash on the server but not the client. So the one helper I had I changed to CJS. Edit: I think I can just run it through esbuild as well before requireFromString
  3. I ported the index.html with the %sapper% placeholders and <Nav> etc. to _layout.ejs with yield and <%- partial('./lib/_Nav.svelte') %>
  4. Swap $page.url.pathname === '/pricing' for current.source === 'pricing'

Other than that it worked perfectly and is so much smoother and simpler than SvelteKit. Less files, no config. Nice. I'm sure there is more, but I'm tired.

If you wonder time curl ... against a page with a progressive component (with the esbuild dance) loads in 200ms. Which is more than reasonable. And in fact identical to Svelte without progressive. An ejs page without any Svelte involved takes about 50ms

scriptBlock = `<script>${clientScript}</script>`;
}

return `<div style="display: contents;">${rendered.html}</div>${scriptBlock}${styleBlock}`;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW this is probably still open to XSS because the devalue above in the inline script will run through esbuild making it kind of pointless. Depending on what esbuild does this might re-enable </script> inside any string. So either we need to replace that here or if we go 100% external scripts then no worries

@Prinzhorn
Copy link
Author

Replace sync-rpc with https://github.com/un-ts/synckit

@Prinzhorn
Copy link
Author

Prinzhorn commented Aug 2, 2022

@sintaxi do you have any plans to get back into Harp? I'd really love to create some static pages again and don't know of anything that comes close to Harp. And with Svelte support I'd be so happy. I think a logical step would be a rewrite of terraform/harp in ESM and also make the processors async, so that I can remove the esbuild hack (using a subprocess or Worker to make something async sync, because esbuild plugins cannot be sync). If I remember correctly an ESM rewrite would also make the Svelte compilation easier.

@sintaxi
Copy link
Owner

sintaxi commented Aug 17, 2022

Hi @Prinzhorn sorry for the delay.

First of all thank you! You have done some really amazing work here. Im sorry for the lack of feedback.

I think Harp is in a great position to take new direction and I really like your ideas. Harp wen't though a huge exorcism this past year and I'm happy with a lot of things regarding its current state. It's stable, small, and simple. I think what exists now could make a good v1. I still would like to add some nice logging before doing that. I think the next direction for harp/terraform will be interesting and fun to build. With the help of esbuild the potential for harp is enormous.

I agree with you terraform likely needs and overhaul and a paradigm shift to ESM. You seem to have have a good grasp on its current limitations and it would be great to see what we can come up with together.

@Prinzhorn
Copy link
Author

First of all thank you! You have done some really amazing work here. Im sorry for the lack of feedback.

Thanks!

I agree with you terraform likely needs and overhaul and a paradigm shift to ESM. You seem to have have a good grasp on its current limitations and it would be great to see what we can come up with together.

Maybe the first step would be to migrate terraform to use eslint/prettier/esm/async/await without changing any functionality (not sure in what shape harp itself is). Then we could clean this Svelte PR up and maybe we're good for a first v1-rc1 and see how it goes? Depending on what else you have planned for esbuild, maybe we also need to find some common API that we can share between the Svelte support and other parts. But I think with SSR it's quite different from regular bundling where you want Harp to serve your entry point as a bundle. Oh that also reminds me that I wanted a way to inject CSS/JS into the head from within a transform. I guess that's something we need for JS bundling too, when using code splitting? Because you would no longer have a 1:1 relationship between the file you include and what you actually get served. Anyway, I'm getting side-tracked. I have other stuff on my mind right now and would need to sort my thoughts and get back into this PR. But feel free to ping me when you need some input.

From the perspective of a Harp user all I really want is the Harp I used for the past >8 years but with Svelte support. I personally don't really need the plain JavaScript bundling support anymore (back then I was running Browserify in parallel to Harp), but I'm sure I will run into situations where I don't want to use Svelte. But it's hard to imagine honestly. Maybe for some really small stuff. It's just too easy to abstract your code in components.

@Prinzhorn
Copy link
Author

Looking at my wall of text, maybe you can formulate a road map? An updated version of sintaxi/harp#664 ? I'd be more than happy to comment on it when you draft something.

@Prinzhorn
Copy link
Author

Prinzhorn commented Oct 10, 2022

I was just thinking if a template like Svelte can emit additional css/js, we need a special path prefix for them. Or else these would then cause another request to the transform pipeline. E.g. let's say Svelte emits an additional "components.106b693f.js" and we include it in the main template like this

<% for(var i=0; i<extra_js.length; i++) {%>
    <script src="<%= extra_js[i] %>"></script>
<% } %>

Then loading this page would cause a request to components.106b693f.js, but it doesn't actually exist. We could mount the endpoint for these emited files at something like /_emitted/components.106b693f.js which does not go through the transform pipeline. Instead it maps to a virtual file system (maybe literally just serve from /tmp/ via static middleware) that contains the emitted files.

But then when compiling (and not serving), these files will actually be copied to the dist folder and served without _emitted (or they're in a folder literally called emitted or extra)

function init() {
return function (options) {
var plugin = sveltePlugin({
// TODO: This will cause CSS to be generated that we don't need (the server already served it).
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Svelte just merged support for css: 'none' sveltejs/svelte#7914

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants