Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
codediodeio authored Aug 26, 2022
2 parents 87ce167 + 369de02 commit e6e812a
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 32 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ typings/
firebase.json
.firebaserc
/.firebase/
/example/flamethrower.js
/example/flamethrower.js

# vercel build output api
# https://vercel.com/docs/build-output-api/v3
.vercel
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A 2kB zero-config router and prefetcher that makes a static site feel like a bla

**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 an SPA.
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?

Expand Down Expand Up @@ -119,3 +119,13 @@ Make sure all playwright tests pass before submitting new features.
```
npm run test
```

### Deploying

You can deploy Flamethrower to [Vercel](http://vercel.com/) as follows:

```
npm run deploy
```

This uses the [Build Output API](https://vercel.com/docs/build-output-api/v3) and the [Vercel CLI](https://vercel.com/cli) to deploy the `/example` folder.
17 changes: 17 additions & 0 deletions deploy-vercel.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { mkdirSync, cpSync, rmSync, writeFileSync } from 'fs';

// Start fresh by clearing folder
rmSync('.vercel/output', { recursive: true, force: true });

// Create folders
mkdirSync('.vercel/output/static', { recursive: true });

// Copy images and CSS files
console.log('Copying static files from example...');
cpSync('./example', '.vercel/output/static', { recursive: true });

// Define version for Build Output API
// https://vercel.com/docs/build-output-api/v3
writeFileSync('.vercel/output/config.json', `{"version": 3}`);

console.log('✅ Done copying static files');
21 changes: 7 additions & 14 deletions lib/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RouteChangeData } from './interfaces';
* @param {} type
* scroll to top of page
*/
export function scrollToTop(type: string) {
export function scrollToTop(type: string): void {
if (['link', 'go'].includes(type)) {
window.scrollTo({ top: 0 });
}
Expand All @@ -14,7 +14,7 @@ export function scrollToTop(type: string) {
* standard formatting for urls
* url == https://example.com/foo/bar
*/
export function fullURL(url?: string) {
export function fullURL(url?: string): string {
const href = new URL(url || window.location.href).href;
return href.endsWith('/') || href.includes('.') ? href : `${href}/`;
}
Expand All @@ -23,25 +23,23 @@ export function fullURL(url?: string) {
* @param {string} url
* Writes URL to browser history
*/
export function addToPushState(url: string) {
export function addToPushState(url: string): void {
if (!window.history.state || window.history.state.url !== url) {
window.history.pushState({ url }, 'internalLink', url);
}
}

// Smooth stroll to anchor link
export function scrollToAnchor(anchor) {
document
.querySelector(anchor)
.scrollIntoView({ behavior: 'smooth', block: 'start' });
export function scrollToAnchor(anchor): void {
document.querySelector(anchor).scrollIntoView({ behavior: 'smooth', block: 'start' });
}

/**
* @param {PopStateEvent} e
* @returns RouteChangeData
* Handles back button/forward
*/
export function handlePopState(e: PopStateEvent): RouteChangeData {
export function handlePopState(_: PopStateEvent): RouteChangeData {
const next = fullURL();
// addToPushState(next);
return { type: 'popstate', next };
Expand All @@ -60,11 +58,7 @@ export function handleLinkClick(e: MouseEvent): RouteChangeData {
}

// Find element containing href
for (
var n = e.target as HTMLElement;
n.parentNode;
n = n.parentNode as HTMLElement
) {
for (let n = e.target as HTMLElement; n.parentNode; n = n.parentNode as HTMLElement) {
if (n.nodeName === 'A') {
anchor = n as HTMLAnchorElement;
break;
Expand Down Expand Up @@ -101,7 +95,6 @@ export function handleLinkClick(e: MouseEvent): RouteChangeData {

// addToPushState(next);
return { type: 'link', next, prev };

} else {
return { type: 'noop' };
}
Expand Down
3 changes: 2 additions & 1 deletion lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { FlamethrowerOptions, FlameWindow } from './interfaces';
* starts flamethrower router and returns instance
* can be accessed globally with window.flamethrower
*/
export default (opts?: FlamethrowerOptions) => {
export default (opts?: FlamethrowerOptions): Router => {
const router = new Router(opts);
// eslint-disable-next-line no-console
opts.log && console.log('🔥 flamethrower engaged');
if (window) {
const flame = window as FlameWindow;
Expand Down
28 changes: 14 additions & 14 deletions lib/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class Router {
* @param {string} path
* Navigate to a url
*/
go(path: string) {
public go(path: string): Promise<boolean> {
const prev = window.location.href;
const next = new URL(path, location.origin).href;
return this.reconstructDOM({ type: 'go', next, prev });
Expand All @@ -38,21 +38,21 @@ export class Router {
/**
* Navigate back
*/
back() {
public back(): void {
window.history.back();
}

/**
* Navigate forward
*/
forward() {
public forward(): void {
window.history.forward();
}

/**
* Find all links on page
*/
private get allLinks() {
private get allLinks(): (HTMLAnchorElement | HTMLAreaElement)[] {
return Array.from(document.links).filter(
(node) =>
node.href.includes(document.location.origin) && // on origin url
Expand All @@ -62,14 +62,14 @@ export class Router {
);
}

private log(...args: any[]) {
private log(...args: any[]): void {
this.opts.log && console.log(...args);
}

/**
* Check if the route is qualified for prefetching and prefetch it with chosen method
*/
private prefetch() {
private prefetch(): void {
if (this.opts.prefetch === 'visible') {
this.prefetchVisible();
} else if (this.opts.prefetch === 'hover') {
Expand All @@ -82,7 +82,7 @@ export class Router {
/**
* Finds links on page and prefetches them on hover
*/
private prefetchOnHover() {
private prefetchOnHover(): void {
this.allLinks.forEach((node) => {
const url = node.getAttribute('href');
// Using `pointerenter` instead of `mouseenter` to support touch devices hover behavior, PS: `pointerenter` event fires only once
Expand All @@ -93,7 +93,7 @@ export class Router {
/**
* Prefetch all visible links
*/
private prefetchVisible() {
private prefetchVisible(): void {
const intersectionOpts = {
root: null,
rootMargin: '0px',
Expand Down Expand Up @@ -124,14 +124,14 @@ export class Router {
* @param {string} url
* Create a link to prefetch
*/
private createLink(url: string) {
private createLink(url: string): void {
const linkEl = document.createElement('link');
linkEl.rel = `prefetch`;
linkEl.rel = 'prefetch';
linkEl.href = url;
linkEl.as = 'document';

linkEl.onload = () => this.log('🌩️ prefetched', url);
linkEl.onerror = (err) => this.log("🤕 can't prefetch", url, err);
linkEl.onerror = (err) => this.log('🤕 can\'t prefetch', url, err);

document.head.appendChild(linkEl);

Expand All @@ -143,22 +143,22 @@ export class Router {
* @param {MouseEvent} e
* Handle clicks on links
*/
private onClick(e: MouseEvent) {
private onClick(e: MouseEvent): void {
this.reconstructDOM(handleLinkClick(e));
}

/**
* @param {PopStateEvent} e
* Handle popstate events like back/forward
*/
private onPop(e: PopStateEvent) {
private onPop(e: PopStateEvent): void {
this.reconstructDOM(handlePopState(e));
}
/**
* @param {RouteChangeData} routeChangeData
* Main process for reconstructing the DOM
*/
private async reconstructDOM({ type, next, prev }: RouteChangeData) {
private async reconstructDOM({ type, next, prev }: RouteChangeData): Promise<boolean> {
if (!this.enabled) {
this.log('router disabled');
return;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"dev": "vite build --watch",
"build": "vite build && npx tsc --emitDeclarationOnly",
"serve": "serve ./example",
"test": "playwright test"
"test": "playwright test",
"deploy": "node ./deploy-vercel.mjs && vercel deploy --prebuilt"
},
"keywords": [],
"author": "Jeff Delaney",
Expand Down

0 comments on commit e6e812a

Please sign in to comment.