diff --git a/.gitignore b/.gitignore index 76e3c6e..775e611 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,8 @@ typings/ firebase.json .firebaserc /.firebase/ -/example/flamethrower.js \ No newline at end of file +/example/flamethrower.js + +# vercel build output api +# https://vercel.com/docs/build-output-api/v3 +.vercel \ No newline at end of file diff --git a/README.md b/README.md index cda83e3..8259974 100644 --- a/README.md +++ b/README.md @@ -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? @@ -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. diff --git a/deploy-vercel.mjs b/deploy-vercel.mjs new file mode 100644 index 0000000..0ced86a --- /dev/null +++ b/deploy-vercel.mjs @@ -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'); diff --git a/lib/handlers.ts b/lib/handlers.ts index 5008b16..931173d 100644 --- a/lib/handlers.ts +++ b/lib/handlers.ts @@ -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 }); } @@ -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}/`; } @@ -23,17 +23,15 @@ 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' }); } /** @@ -41,7 +39,7 @@ export function scrollToAnchor(anchor) { * @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 }; @@ -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; @@ -101,7 +95,6 @@ export function handleLinkClick(e: MouseEvent): RouteChangeData { // addToPushState(next); return { type: 'link', next, prev }; - } else { return { type: 'noop' }; } diff --git a/lib/main.ts b/lib/main.ts index 91c34ff..6a7b5ac 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -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; diff --git a/lib/router.ts b/lib/router.ts index d90ba82..0eeea9b 100644 --- a/lib/router.ts +++ b/lib/router.ts @@ -29,7 +29,7 @@ export class Router { * @param {string} path * Navigate to a url */ - go(path: string) { + public go(path: string): Promise { const prev = window.location.href; const next = new URL(path, location.origin).href; return this.reconstructDOM({ type: 'go', next, prev }); @@ -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 @@ -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') { @@ -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 @@ -93,7 +93,7 @@ export class Router { /** * Prefetch all visible links */ - private prefetchVisible() { + private prefetchVisible(): void { const intersectionOpts = { root: null, rootMargin: '0px', @@ -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); @@ -143,7 +143,7 @@ export class Router { * @param {MouseEvent} e * Handle clicks on links */ - private onClick(e: MouseEvent) { + private onClick(e: MouseEvent): void { this.reconstructDOM(handleLinkClick(e)); } @@ -151,14 +151,14 @@ export class Router { * @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 { if (!this.enabled) { this.log('router disabled'); return; diff --git a/package.json b/package.json index 707c379..b7bc8ef 100644 --- a/package.json +++ b/package.json @@ -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",