GithubHelp home page GithubHelp logo

flamethrower's Introduction

Flamethrower ๐Ÿ”ฅ

Status: Meme

A 2kB zero-config router and prefetcher that makes a static site feel like a blazingly fast SPA.

Why?

Problem: Static sites feel slow and cannot easily share state between pages. This makes it difficult to create a pleasant user experience (UX) with JavaScript libraries because each new page needs to reboot your JS from scratch.

Rather than requiring a frontend framework to take control of the entire DOM, the goal is to make route changes on static sites feel faster, like a SPA.

How?

  1. It tells the browser to prefetch visible links in the current page with IntersectionObserver.
  2. Intercepts click and popstate events, then updates the HTML5 history on route changes.
  3. Uses fetch to get the next page, swaps the <body> out, merges the <head>, but does not re-execute head scripts (unless asked to).

This means you can have long-lived JavaScript behaviors between navigations. It works especially well with native web components.

QuickStart

npm i flamethrower-router
import flamethrower from 'flamethrower-router';
const router = flamethrower();

That's it. Your site now feels blazingly fast.

Advanced Usage

// with opts
const router = flamethrower({ prefetch: 'visible', log: false, pageTransitions: false });

// Navigate manually
router.go('/somewhere');
router.back();
router.forward();

// Listen to events
window.addEventListener('flamethrower:router:fetch', showLoader);
window.addEventListener('flamethrower:router:fetch-progress', updateProgressBar);
window.addEventListener('flamethrower:router:end', hideLoader);

// Disable it
router.enabled = false;

Opt-out of specific links for full page load.

<a href="/somewhere" data-cold></a>

Scripts in <body> will run on every page change, but you can force scripts in the <head> to run:

<script src="..." data-reload></script>

The fetch-progress event is a custom event, so usage will look something like this:

window.addEventListener('flamethrower:router:fetch-progress', ({ detail }) => {
	const progressBar = document.getElementById('progress-bar');
	// progress & length will be 0 if there is no Content-Length header
	const bytesReceived = detail.received; // number
	const length = detail.length; // number
	progressBar.style.width = detail.progress + '%';
});

Prefetching

Prefecthing is disabled by default.

  • visible: prefetch visible links on the page with IntersectionObserver
  • hover: prefetch links on hover
const router = flamethrower({ prefetch: 'visible' });

Misc

Supported in all browsers? Yes. It will fallback to standard navigation if window.history does not exist.

Does it work with Next.js? No, any framework that fully hydrates to an SPA does not need this - you already have a client-side router.

Does it work with Astro? I think so. It can share state between routes, but partially hydrated components may flash between routes.

Other things to know:

  • <head> scripts run only on the first page load. <body> scripts will still run on every page change (by design).
  • It's a good idea to show a global loading bar in case of a slow page load.
  • This library is inspired by Turbo Drive.
  • This project is experimental.

Contributing

Build it:

npm run dev

Serve the example:

npm run serve

Make sure all playwright tests pass before submitting new features.

npm run test

Deploying

You can deploy Flamethrower to Vercel as follows:

npm run deploy

This uses the Build Output API and the Vercel CLI to deploy the /example folder.

flamethrower's People

Contributors

7flash avatar alonhor avatar amirkian007 avatar b0iq avatar badhan-abhishek avatar codediodeio avatar depapp avatar dezren39 avatar dhirajgagrai avatar ekwoka avatar emmacyril avatar ishankbg avatar leerob avatar okyanusoz avatar omar-dulaimi avatar oriyadid avatar oumarbarry avatar prowebat avatar rodrigoteran avatar sb3p avatar sebastianopperman avatar sunneydev avatar tobyleye avatar tsar-boomba avatar vinaykulk621 avatar xeospheric avatar zeepk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flamethrower's Issues

Title is not updating

I am using flamethrower for deserve.deno.dev a documentation site and the title is not updating between page changes, I haven't looked into what's going on but it shows the correct title when I refresh the page

Time in view before prefetching

One way to solve the many prefetch calls on a page and still have a fast experience would be to add the possibility to pass a time argument to the prefetch attribute. It could look like this prefetch?: 'visible' | 'hover' | 'time'.

  • prefetch 'time' with time 0 would just be prefetch 'visible'
  • prefetch 'time' with time 1000 would wait for the element to be in view for 1 second before prefetching

Also, after testing a page, you can see what links are hotter (are accessed more) than others and set them a lower prefetch time than the links that are not clicked as much.

Good idea yay or nay?

Discussion: what are the differences between it and Navigo?

I've seen the video, I know it's a bit of a meme, but there's a legitimate non-meme project similar to this called Navigo that I've actually, non-ironically been using. This appears to be in early meme status, but Navigo's an actual thing you might wanna look at.

How I use it in my site: it's a simple HTML+CSS static docs site, it doesn't need javascript bloat. However, for people that have JS enabled, I provide a client side router that downloads all the doc pages HTML in the background on first load and from there on out it can work offline and on shitty internet connections. And it feels blazingly fast ๐Ÿ”ฅ ๐Ÿ”ฅ ๐Ÿ”ฅ

iframes cause issues

I have a discord widget on my website, and it creates this error:

document.requestStorageAccess() may not be called in a sandboxed iframe without allow-storage-access-by-user-activation in its sandbox attribute.

Route Announcements

Many client-side routers, including SvelteKit and Next's routers have a visually hidden "announcer" element that declares new routes to assistive technologies when the page is navigated. This is acomplished through the aria-live="assertive" state.

image
image

From looking at existing implementations, the announcer simply populates it's innerHTML with name of any new page title after navigation is completed.

The Next.js route announcer looks for the page name to announce by first inspecting document.title, then the <h1> element, and finally the URL pathname. For the most accessible user experience, ensure that each page in your application has a unique and descriptive title.

SvelteKit's implementation
Next Docs

Flamethrower breaks mermaid.js

Hello @fireship-io,
I like to use a this great project, but I could not get mermaid diagrams working with it.
I tried to embed flamethrower into Zola blog, and managed to get TOC working, but I could not get mermaid diagrams rendered - I had to hit the refresh button in a browser for mermaid diagram to appear, despite flamethrower documentation saying scripts inside body reload, so I had to revert the change, despite adding flamethrower gave really good user experience overall.
Commit history: reference-architecture-ai/reference-architecture.ai@9f79341

Ways to replicate:
Add example below into home and similar into about, switch between tabs. Observe markdown is not rendered.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
  <div class="mermaid">
  graph LR
      A --- B
      B-->C[fa:fa-ban forbidden]
      B-->D(fa:fa-spinner);
  </div>
 <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
 <script>mermaid.initialize({startOnLoad:true});
</script>
</body>
</html>```

# Scrolling not working

a element

<li><a href="http://localhost:5501/#contact">Contact</a></li>

Creates url http://localhost:5501/#contact/ => thinks its link => wont scroll

local debugging
flamethrower.js:32 creates const o with value o = http://localhost:5501/#contact/
which causes next if to false which would otherwise process the scroll
flamethrower.js:33 if (e.preventDefault(), o != null && o.startsWith("#")) => false

local dirty workaround
starting line flamethrower.js:31

if (t != null && t.hasAttribute("href")) {
  const o = t.getAttribute("href"), s = new URL(o, location.href);
- if (e.preventDefault(), o != null && o.startsWith("#"))
+ if (e.preventDefault(), o != null && o.includes("#"))
-   return w(o), { type: "scrolled" };
+   return w("#" + o.split("#")[1]), { type: "scrolled" };
  {
....

this will always scroll to the first # in the url.

No time myself creating an PR maybe someone else can fix it.

Documentation?

I know that the project is in meme status, but what about documentation? I can help write it! It would be helpful for people who want to use it.

playwright local dev server

since playwright provide

  /* Run your local dev server before starting the tests */
  webServer: {
    command: 'npm run start',
    port: 3000,
  },

should we try to implement it to this project @codediodeio ?

Build as a single file for use in a <script>?

Hi, I'm working on 2m2d whose webserver is written in Rust, so I can't use npm for the frontend (It has to be static sites only). Is there a way to build this library as a single file so I can load it in a <script> tag?

<script type="module"> is not re-run when replacing the body

This library is actually very useful to me because it makes my static site smoother and allows me to use page transitions.
However, I usually write the small logic islands with JavaScript using ES modules.

  <header>
    <h2>User Portal</h2>
  </header>

  <script type="module" src="/js/portal.js"></script>

This snippet will run the code in /js/portal.js (console.log("portal")) once when the page is loaded for the first time, but not when navigating out and into it again.

  <header>
    <h2>User Portal</h2>
  </header>

  <script src="/js/portal.js"></script>

This snippet, on the other hand, doesn't have this issue and shows the "portal" output in the console each time I navigate out and into it again.

I hope this is a simple fix and I'll be waiting for a response!
Love your videos,
Ainara.

Keep existing target in <a>

Flamethrower currently replaces existing link target:

<a href="https://astro.build" target="astro">

is replaced by

<a href="https://astro.build" target="_blank">

Existing target should be kept untouched.

anchor.target = '_blank';

should be replaced by

anchor.target = anchor.target || '_blank';

[help] cannot find imported module

hello everyone,
I've tried to implement page object model for the playwright test, but I have an issue related to import module.

  • here's my code
    image

  • folder structure
    image

  • error message Error: Cannot find module '/Users/user/open-source/flamethrower/test/home.page' imported from /Users/user/open-source/flamethrower/test/main.spec.ts
    image

is there any missing configuration?
thanks in advance for any help ๐Ÿ™๐Ÿป

Integrating a ci pipeline into the project

What about integrating a ci pipeline into the project?
In this way, we could automate the process of checking that the new changes are breaking the codebase or not.
I think that a Github Actions pipeline would fit well here.

Why is flamethrower handling scrolls to ids?

There is a bug causing errors right now because of it, and I really just don't see the point. I can elaborate on the bug of course, but the simple solution is really just to... Not

Add hook for load progress amount

Based on the Content-Length header, calculate the progress via the bytes transferred thus far. This allows you to get more accurate loading indicators.

Maybe even add in the option for a debounce threshold to throttle the frequency of the progress update hook being called.

In the on progress callback, you could return an object that contains the bytes transferred, total bytes, percentage and timestamp.

Add linkSelector option

Setting [data-cold] might not always be possible. It would be nice to have the option to opt-(out/in) using a selector.

Something like that:

const linkSelector = ':not(.some-class)'

if (!anchor.matches(linkSelector)) {
  return { type: 'disqualified' };
}

Any thoughts?

Where's the demo!

I want to click around and see it working - is there a hosted demo or stackblitz? ... do i realllyy have to spend the 2 mins to clone / build it ? ;P

Counter Example

It would be great if to add the counter example. Obviously because flamethrower replaces the whole html body, the counter still gets reinitialized when navigating. So you need to save the state in the browser storage?
i'm also thinking about a way to not recreate the counter instance in the dom, but rather replace everything around it.

flamefthrower-preserve

dear @codediodeio

thank you for merging the flamethrower-preserve option from
#54 (comment)_

, but your code changes break a part of my original intended usecase, i try to explain:
the preserve option should preserve the node when navigating to other pages, so it does not get reinitiated.
this is useful for eg. a react widget (see your counter example), which loads a blog feed. and won't need to reload it on each page navigation.
-> i think your changes will achieve this also, but i haven't tested it yet.

the additional usecase of my original pull request was to also update the attributes and children of the flamethrower-preserve web component. the web component can then listen for any attribute changes, or child changes and react accordingly.
eg. an <menu selectedNode="aaa"> element can liste for changes of the selectedNode attribute, which gets updated when navigation to the next page, the menu element itself won't get reinitialized.

regards, daniel.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.