Skip to content

Commit

Permalink
Move registering lifecycle callbacks and their execution from the Ren…
Browse files Browse the repository at this point in the history
…derer to Navigator. Fix ESLint errors.
  • Loading branch information
Kapelianovych committed Jan 11, 2024
1 parent 9d5a16a commit a6e0967
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 243 deletions.
10 changes: 5 additions & 5 deletions client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
*
* - `onRender` - register **after page is rendered** callback.
* ```ts
* function onRender(callback: VoidFunction, options?: CallbackOptions): void;
* function onRender(callback: (currentUrl: URL) => void, options?: CallbackOptions): void;
* ```
* - `onLeave` - register **before page is rendered** callback.
* ```ts
* function onLeave(callback: VoidFunction, options?: CallbackOptions): void;
* function onLeave(callback: (currentUrl: URL, nextUrl: URL) => void, options?: CallbackOptions): void;
* ```
*
* The `CallbackOptions` allows configuring where, when and how a callback is executed.
Expand Down Expand Up @@ -77,13 +77,13 @@ import Session from './session.js';
globalThis.PostDoc ??= new Session();

export function onLeave(callback, options) {
globalThis.PostDoc.onLeave(callback, options);
globalThis.PostDoc.navigator.registerOnLeaveCallback(callback, options);
}

export function onRender(callback, options) {
globalThis.PostDoc.onEnter(callback, options);
globalThis.PostDoc.navigator.registerOnRenderCallback(callback, options);
}

export async function go(url, replace) {
await globalThis.PostDoc.navigateTo(url, replace);
await globalThis.PostDoc.navigator.navigateTo(url, replace);
}
61 changes: 60 additions & 1 deletion client/navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Snapshot from './snapshot.js';
export default class Navigator {
#session;
#pageLoaded;
#beforeLeavingSnapshotCallbacks = [];
#afterRenderingSnapshotCallbacks = [];

constructor(session) {
this.#session = session;
Expand All @@ -14,9 +16,34 @@ export default class Navigator {
return new URL(location.href);
}

registerOnLeaveCallback(callback, options) {
const registeredOn = this.currentUrl;

this.#beforeLeavingSnapshotCallbacks.push({
test: this.#createURLMatchFunction(registeredOn, options?.forPage),
fn: callback
});
}

registerOnRenderCallback(callback, options) {
const registeredOn = this.currentUrl;

const test = this.#createURLMatchFunction(registeredOn, options?.forPage);

this.#afterRenderingSnapshotCallbacks.push({ test, fn: callback });

if (test(registeredOn)) {
callback(registeredOn);
}
}

async navigateTo(url, replace = false) {
url = url instanceof URL ? url : new URL(url, this.currentUrl.origin);

const nextSnapshot = await this.#prepareSnapshotFor(url);

await this.#callLeaveCallbacks(url);

await this.#session.renderer.render(nextSnapshot);

history[replace ? 'replaceState' : 'pushState'](
Expand All @@ -25,7 +52,7 @@ export default class Navigator {
url
);

await this.#session.renderer.callRenderCallbacks();
await this.#callRenderCallbacks();

if (url.hash.length) {
this.#session.renderer.currentSnapshot.body
Expand All @@ -40,6 +67,38 @@ export default class Navigator {
location.reload();
}

async #callRenderCallbacks() {
const url = this.currentUrl;

const maybePromises = this.#afterRenderingSnapshotCallbacks
.filter(({ test }) => test(url))
.map(({ fn }) => fn(url));

await Promise.all(maybePromises);
}

async #callLeaveCallbacks(nextUrl) {
const url = this.currentUrl;

const maybePromises = this.#beforeLeavingSnapshotCallbacks
.filter(({ test }) => test(url))
.map(({ fn }) => fn(url, nextUrl));

await Promise.all(maybePromises);
}

#createURLMatchFunction(registeredOn, forPage) {
if (!forPage) {
return (url) => registeredOn.pathname === url.pathname;
}

if (forPage instanceof RegExp) {
return (url) => forPage.test(url.href);
}

return forPage;
}

#setup() {
addEventListener('load', async () => {
// Span execution until the next microtask, so the very first popstate event will be skipped.
Expand Down
53 changes: 0 additions & 53 deletions client/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import Snapshot from './snapshot.js';
export default class Renderer {
#session;
#currentSnapshot = new Snapshot(document);
#cleanupCallbacks = [];
#afterRenderCallbacks = [];

constructor(session) {
this.#session = session;
Expand All @@ -17,62 +15,11 @@ export default class Renderer {
}

async render(nextSnapshot) {
await this.#performCleanups();

await nextSnapshot.into(this.#currentSnapshot);

this.#setupAnchorTraps();
}

registerCleanup(callback, options, registeredOn) {
this.#cleanupCallbacks.push({
test: this.#createURLMatchFunction(registeredOn, options?.forPage),
fn: callback
});
}

registerRender(callback, options, registeredOn) {
const test = this.#createURLMatchFunction(registeredOn, options?.forPage);

this.#afterRenderCallbacks.push({ test, fn: callback });

if (test(registeredOn)) {
callback(registeredOn);
}
}

async callRenderCallbacks() {
const url = this.#session.navigator.currentUrl;

const maybePromises = this.#afterRenderCallbacks
.filter(({ test }) => test(url))
.map(({ fn }) => fn(url));

await Promise.all(maybePromises);
}

async #performCleanups() {
const url = this.#session.navigator.currentUrl;

const maybePromises = this.#cleanupCallbacks
.filter(({ test }) => test(url))
.map(({ fn }) => fn(url));

await Promise.all(maybePromises);
}

#createURLMatchFunction(registeredOn, forPage) {
if (!forPage) {
return (url) => registeredOn.pathname === url.pathname;
}

if (forPage instanceof RegExp) {
return (url) => forPage.test(url.href);
}

return forPage;
}

#setupAnchorTraps() {
Array.from(this.#currentSnapshot.body.querySelectorAll('a[href]'))
.filter((element) => element.host === location.host)
Expand Down
19 changes: 0 additions & 19 deletions client/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,6 @@ export default class Session {
this.#setupViteConnection();
}

onLeave(callback, options) {
this.renderer.registerCleanup(callback, options, this.navigator.currentUrl);
}

onEnter(callback, options) {
this.renderer.registerRender(callback, options, this.navigator.currentUrl);
}

async navigateTo(url, replace) {
if (
typeof url === 'string' &&
!url.startsWith(this.navigator.currentUrl.origin)
) {
url = new URL(url, this.navigator.currentUrl.origin);
}

await this.navigator.navigateTo(url, replace);
}

#setupViteConnection() {
if (import.meta.hot) {
import.meta.hot.on('postdoc:reload-page', () => this.navigator.reload());
Expand Down
10 changes: 5 additions & 5 deletions lib/commands/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ export const runPreview = async ({ host, open } = {}) => {
Server is listening on:
- ${server.resolvedUrls.local.join(' | ')}
${
server.resolvedUrls.network.length
? '- ' + server.resolvedUrls.network.join(' | ')
: ''
server.resolvedUrls.network.length
? '- ' + server.resolvedUrls.network.join(' | ')
: ''
}
`,
Logger.SuccessLevel
);

return server;
}
};

export default function createPreviewCommand() {
return new PostDocCommand('preview')
Expand All @@ -75,5 +75,5 @@ export default function createPreviewCommand() {
'The proxy option for the Vite\'s "host" CLI argument. It exposes the dev and preview server to the LAN and public addresses.'
)
.action(({ host, open }) =>
runAndMeasureAction(() => runPreview({host, open})));
runAndMeasureAction(() => runPreview({ host, open })));
}
24 changes: 12 additions & 12 deletions nightwatch.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,32 @@
// |___/
//

const path = require("path");
const path = require('path');

module.exports = {
src_folders: ["test/src", "test/e2e"],
globals_path: path.resolve(__dirname, "test", "lib", "globals.cjs"),
src_folders: ['test/src', 'test/e2e'],
globals_path: path.resolve(__dirname, 'test', 'lib', 'globals.cjs'),

webdriver: {
start_process: true,
start_process: true
},

test_settings: {
default: {
launch_url: "http://localhost:5173",
launch_url: 'http://localhost:5173',
desiredCapabilities: {
browserName: "chrome",
browserName: 'chrome'
},
exclude: "test/src/**",
exclude: 'test/src/**'
},
unittests: {
unit_tests_mode: true,
filter: "test/src/**",
exclude: "",
},
filter: 'test/src/**',
exclude: ''
}
},

test_workers: {
enabled: false,
},
enabled: false
}
};
Loading

0 comments on commit a6e0967

Please sign in to comment.