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

Clean up unoptimizations. #89

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .replit
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
run = "npm run build; npm run serve"
hidden = [".config", "package-lock.json", "tsconfig.json"]

[packager]
language = "nodejs"
[packager.features]
enabledForHosting = false
packageSearch = true
guessImports = true

[nix]
channel = "stable-22_11"

[env]
XDG_CONFIG_HOME = "$REPL_HOME/.config"
PATH = "$REPL_HOME/node_modules/.bin:$REPL_HOME/.config/npm/node_global/bin"
npm_config_prefix = "$REPL_HOME/.config/npm/node_global"

[gitHubImport]
requiredFiles = [".replit", "replit.nix", ".config"]

[languages]
[languages.typescript]
pattern = "**/{*.ts,*.js,*.tsx,*.jsx,*.json}"
[languages.typescript.languageServer]
start = "typescript-language-server --stdio"

[deployment]
run = ["sh", "-c", "npm run start"]
deploymentTarget = "cloudrun"
ignorePorts = false
54 changes: 17 additions & 37 deletions lib/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,54 +32,34 @@ export function mergeHead(nextDoc: Document): void {
// Update head
// Head elements that changed on next document
const getValidNodes = (doc: Document): Element[] => Array.from(doc.querySelectorAll('head>:not([rel="prefetch"]'));
const oldNodes = getValidNodes(document);
const nextNodes = getValidNodes(nextDoc);
const { staleNodes, freshNodes } = partitionNodes(oldNodes, nextNodes);
// Spc - Ofc, we simple;
const { staleNodes, freshNodes } = partitionNodes(
getValidNodes(document),
getValidNodes(nextDoc)
);

staleNodes.forEach((node) => node.remove());

document.head.append(...freshNodes);
}

function partitionNodes(oldNodes: Element[], nextNodes: Element[]): PartitionedNodes {
const staleNodes: Element[] = [];
const freshNodes: Element[] = [];
let oldMark = 0;
let nextMark = 0;
while (oldMark < oldNodes.length || nextMark < nextNodes.length) {
const old = oldNodes[oldMark];
const next = nextNodes[nextMark];
if (old?.isEqualNode(next)) {
oldMark++;
nextMark++;
continue;
}
const oldInFresh = old ? freshNodes.findIndex((node) => node.isEqualNode(old)) : -1;
if (oldInFresh !== -1) {
freshNodes.splice(oldInFresh, 1);
oldMark++;
continue;
}
const nextInStale = next ? staleNodes.findIndex((node) => node.isEqualNode(next)) : -1;
if (nextInStale !== -1) {
staleNodes.splice(nextInStale, 1);
nextMark++;
continue;
}
old && staleNodes.push(old);
next && freshNodes.push(next);
oldMark++;
nextMark++;
}

return { staleNodes, freshNodes };
}

// Spc - Move type above only use...
type PartitionedNodes = {
freshNodes: Element[];
staleNodes: Element[];
};

function partitionNodes(oldNodes: Element[], nextNodes: Element[]): PartitionedNodes {
// Spc - Holy Jesus...
const oldNodeSet = new Set(oldNodes);
const nextNodeSet = new Set(nextNodes);

const staleNodes = oldNodes.filter(node => !nextNodeSet.has(node));
const freshNodes = nextNodes.filter(node => !oldNodeSet.has(node));

return { staleNodes, freshNodes };
}

/**
* Runs JS in the fetched document
* head scripts will only run with data-reload attr
Expand Down
10 changes: 6 additions & 4 deletions lib/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export function scrollTo(type: string, id?: string): void {
if (['link', 'go'].includes(type)) {
if (id) {
const el = document.querySelector(id);
el ? el.scrollIntoView({ behavior: 'smooth', block: 'start' }) : window.scrollTo({ top: 0 });
} else {
window.scrollTo({ top: 0 });
}
el && el.scrollIntoView({ behavior: 'smooth', block: 'start' })
return;
}

// Spc - Remove redundant Block
window.scrollTo({ top: 0 });
}
}
/**
Expand Down
6 changes: 2 additions & 4 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ 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;
flame.flamethrower = router;
}

globalThis?.window && ((window as FlameWindow).flamethrower = router);
return router;
};
133 changes: 61 additions & 72 deletions lib/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ export class Router {
document.addEventListener('click', (e) => this.onClick(e));
window.addEventListener('popstate', (e) => this.onPop(e));
this.prefetch();
} else {
console.warn('flamethrower router not supported in this browser or environment');
this.enabled = false;
return;
}

console.warn('Flamethrower router is not supported in this Browser or Environment');
this.enabled = false;
}

/**
Expand Down Expand Up @@ -74,8 +75,6 @@ export class Router {
this.prefetchVisible();
} else if (this.opts.prefetch === 'hover') {
this.prefetchOnHover();
} else {
return;
}
}

Expand Down Expand Up @@ -105,15 +104,14 @@ export class Router {
entries.forEach((entry) => {
const url = entry.target.getAttribute('href');

if (this.prefetched.has(url)) {
if (this.prefetched.has(url) || entry.isIntersecting) {
// Spc - Please nest guys...
entry.isIntersecting && this.createLink(url);

observer.unobserve(entry.target);
return;
}

if (entry.isIntersecting) {
this.createLink(url);
observer.unobserve(entry.target);
}
});
}, intersectionOpts);
this.allLinks.forEach((node) => this.observer.observe(node));
Expand All @@ -125,13 +123,13 @@ export class Router {
* Create a link to prefetch
*/
private createLink(url: string): void {
const linkEl = document.createElement('link');
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);
const linkEl = Object.assign(document.createElement('link'), {
rel: 'prefetch',
href: url,
as: 'document',
onload: () => this.log('🌩️ prefetched', url),
onerror: (err) => this.log('🤕 can\'t prefetch', url, err),
});

document.head.appendChild(linkEl);

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

/**
* @param {PopStateEvent} e
* Handle popstate events like back/forward
*/
private onPop(e: PopStateEvent): void {
onPop(e: PopStateEvent): void {
console.log(e, this, 'asd');
this.reconstructDOM(handlePopState(e));
}
/**
Expand All @@ -164,9 +164,10 @@ export class Router {
return;
}

try {
this.log('⚡', type);
// Does not need to be tried; should be type string, otherwise no log?
this.log('⚡', type);

try {
// Check type && window href destination
// Disqualify if fetching same URL
if (['popstate', 'link', 'go'].includes(type) && next !== prev) {
Expand All @@ -180,48 +181,37 @@ export class Router {
}

// Fetch next document
const res = await fetch(next, { headers: { 'X-Flamethrower': '1' } })
.then((res) => {
const reader = res.body.getReader();
const length = parseInt(res.headers.get('Content-Length'));
let bytesReceived = 0;

// take each received chunk and emit an event, pass through to new stream which will be read as text
return new ReadableStream({
start(controller) {
// The following function handles each data chunk
function push() {
// "done" is a Boolean and value a "Uint8Array"
reader.read().then(({ done, value }) => {
// If there is no more data to read
if (done) {
controller.close();
return;
}

bytesReceived += value.length;
window.dispatchEvent(
new CustomEvent<FetchProgressEvent>('flamethrower:router:fetch-progress', {
detail: {
// length may be NaN if no Content-Length header was found
progress: Number.isNaN(length) ? 0 : (bytesReceived / length) * 100,
received: bytesReceived,
length: length || 0,
},
}),
);
// Get the data and send it to the browser via the controller
controller.enqueue(value);
// Check chunks by logging to the console
push();
});
}

push();
},
});
})
.then((stream) => new Response(stream, { headers: { 'Content-Type': 'text/html' } }));
const response = await fetch(next, { headers: { 'X-Flamethrower': '1' } });
const reader = response.body.getReader();
const length = parseInt(response.headers.get('Content-Length') || '0', 10);

let bytesReceived = 0;
// take each received chunk and emit an event, pass through to new stream which will be read as text
const stream = new ReadableStream({
start(controller) {
void async function push() {
const { done, value } = await reader.read();
if (done) {
controller.close();
return;
}
bytesReceived += value.length;
window.dispatchEvent(
new CustomEvent<FetchProgressEvent>('flamethrower:router:fetch-progress', {
detail: {
progress: Number.isNaN(length) ? 0 : (bytesReceived / length) * 100,
received: bytesReceived,
length: length || 0,
},
}),
);
controller.enqueue(value);
await push();
}();
},
});

const res = new Response(stream, { headers: { 'Content-Type': 'text/html' } });

const html = await res.text();
const nextDoc = formatNextDocument(html);
Expand All @@ -231,19 +221,18 @@ export class Router {

// Merge BODY
// with optional native browser page transitions
if (this.opts.pageTransitions && (document as any).createDocumentTransition) {
const transition = (document as any).createDocumentTransition();
transition.start(() => {
replaceBody(nextDoc);
runScripts();
scrollTo(type, scrollId);
});
} else {
const handleMerge = () => {
replaceBody(nextDoc);
runScripts();
scrollTo(type, scrollId);
}

if (this.opts.pageTransitions && (document as any).createDocumentTransition) {
const transition = (document as any).createDocumentTransition();
transition.start(handleMerge);
} else {
handleMerge();
}

window.dispatchEvent(new CustomEvent('flamethrower:router:end'));

Expand All @@ -257,7 +246,7 @@ export class Router {
} catch (err) {
window.dispatchEvent(new CustomEvent('flamethrower:router:error', err));
this.opts.log && console.timeEnd('⏱️');
console.error('💥 router fetch failed', err);
console.error('💥 Router fetch failed', err);
return false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ const config: PlaywrightTestConfig = {
// outputDir: 'test-results/',

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

export default config;
10 changes: 10 additions & 0 deletions replit.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{ pkgs }: {
deps = [
pkgs.yarn
pkgs.esbuild
pkgs.nodejs-18_x

pkgs.nodePackages.typescript
pkgs.nodePackages.typescript-language-server
];
}