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

feat(site): improve performance by replacing page content #1917

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions pages/src/localdev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import {ESLDemoSearchPageWrapper} from './search/search';
import {ESLDemoSidebar} from './navigation/navigation';
import {ESLDemoAnchorLink} from './anchor/anchor-link';
import {ESLDemoBanner} from './banner/banner';
import {ESLDemoRouteLink} from './route/route-link';
import {ESLDemoRouteInterceptor} from './route/route-interceptor';
import {ESLDemoSwipeArea} from './esl-swipe-demo/esl-swipe-demo-area';

ESLVSizeCSSProxy.observe();
Expand All @@ -72,6 +74,8 @@ ESLDemoSearchPageWrapper.register();
ESLDemoAnchorLink.register();
ESLDemoBackLink.register();
ESLDemoBanner.register();
ESLDemoRouteLink.register();
ESLDemoRouteInterceptor.register();
ESLDemoSwipeArea.register();

// Register ESL Components
Expand Down
47 changes: 47 additions & 0 deletions pages/src/route/route-interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {memoize, listen, attr, prop} from '@exadel/esl/src/modules/esl-utils/decorators';
import {ESLMixinElement} from '@exadel/esl/src/modules/esl-mixin-element/core';
import {ESLTraversingQuery} from '@exadel/esl/src/modules/esl-traversing-query/core/esl-traversing-query';
import {ESLDemoSidebar} from '../navigation/navigation';
import {ESLDemoRouteLink} from './route-link';

import type {ESLPanel} from '@exadel/esl/src/modules/esl-panel/core/esl-panel';

export class ESLDemoRouteInterceptor extends ESLMixinElement {
static override is = 'esl-d-route-interceptor';

@attr() mask: string;

@prop() activeCls = 'active';

@memoize()
protected get sidebar(): ESLDemoSidebar {
return document.body.querySelector(ESLDemoSidebar.is)!;
}

@listen({target: window, event: 'esl:pushstate'})
protected _onPushState({detail}: CustomEvent): void {
const {oldURL, newURL} = detail;
if (oldURL.pathname?.includes(this.mask)) this.removeNavActive();
if (newURL.pathname?.includes(this.mask)) this.setNavActive();
}

protected setNavActive(): void {
ESLTraversingQuery.first('::child(.sidebar-nav-item-heading)', this.$host)?.classList.add(this.activeCls);
const $panel = (ESLTraversingQuery.first('::child(.sidebar-nav-secondary)', this.$host) as ESLPanel);
$panel?.show();
$panel?.setAttribute('data-open', '');

const $routeLink = ESLTraversingQuery.first(`::find([href="${location.pathname}"])`, this.$host);
if (!$routeLink) return;
ESLTraversingQuery.first(ESLDemoRouteLink.get($routeLink as HTMLElement)?.activeSelector || '', $routeLink)?.classList.add(this.activeCls);
}

protected removeNavActive(): void {
ESLTraversingQuery.first('::child(.sidebar-nav-item-heading)', this.$host)?.classList.remove(this.activeCls);
const $panel = (ESLTraversingQuery.first('::child(.sidebar-nav-secondary)', this.$host) as ESLPanel);
$panel?.hide();
$panel?.removeAttribute('data-open');

ESLTraversingQuery.first(`::find(.sidebar-nav-secondary-item.${this.activeCls})`, this.$host)?.classList.remove(this.activeCls);
}
}
23 changes: 23 additions & 0 deletions pages/src/route/route-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {listen, prop, attr} from '@exadel/esl/src/modules/esl-utils/decorators';
import {ESLMixinElement} from '@exadel/esl/src/modules/esl-mixin-element/core';
import {ESLDemoRouterService} from './router-service';

export class ESLDemoRouteLink extends ESLMixinElement {
static override is = 'esl-d-route-link';

@prop() public activeSelector = '::parent';

@attr() href: string;

public static override register(): void {
ESLDemoRouterService.init();
super.register();
}

@listen('click')
protected async _onClick(e: Event): Promise<void> {
e.preventDefault();
if (!this.href || this.href === location.pathname) return;
fshovchko marked this conversation as resolved.
Show resolved Hide resolved
ESLDemoRouterService.routeContent(this.href);
fshovchko marked this conversation as resolved.
Show resolved Hide resolved
}
}
48 changes: 48 additions & 0 deletions pages/src/route/router-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {memoize} from '@exadel/esl/src/modules/esl-utils/decorators';
import {ESLEventUtils} from '@exadel/esl/src/modules/esl-event-listener/core';

export class ESLDemoRouterService {
public contentSelector = '#content-routable';

public previousUrl: URL;

@memoize()
public static init(): ESLDemoRouterService {
const router = new ESLDemoRouterService();
router.previousUrl = new URL(location.href);
fshovchko marked this conversation as resolved.
Show resolved Hide resolved
ESLEventUtils.subscribe(router, {target: window, event: 'popstate'}, () => ESLDemoRouterService.routeContent(location.pathname, false));
return router;
}

public static get instance(): ESLDemoRouterService {
return this.init();
}

public static async routeContent(pathname: string, pushState = true): Promise<void> {
const router = ESLDemoRouterService.instance;
try {
const response = await fetch(pathname, {method: 'GET'});
fshovchko marked this conversation as resolved.
Show resolved Hide resolved
const text = await response.text();

pushState && history.pushState(null, '', pathname);
router.replaceContent(document.body.querySelector(router.contentSelector), router.retrieveTextContent(text));
} catch (error) {
throw new Error(`[ESL] Failed to fetch resource: ${pathname}`);
}
}

protected retrieveTextContent(text: string): Element | null {
return new DOMParser().parseFromString(text, 'text/html').querySelector(this.contentSelector);
}

protected replaceContent($currentContent: Element | null, $content: Element | null): void {
if (!$content || !$currentContent) {
location.reload();
return;
}

window.dispatchEvent(new CustomEvent('esl:pushstate', {detail: {oldURL: this.previousUrl, newURL: new URL(location.href)}}));
this.previousUrl = new URL(location.href);
$currentContent.replaceWith($content);
}
}
4 changes: 2 additions & 2 deletions pages/views/_includes/navigation/sidebar-item.njk
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

{% if items.length %}
<div class="sidebar-nav-item-heading {{ 'active' if isPrimaryActive }}">
<a class="sidebar-nav-item-link icon-link" href="{{ ('/' + collection) | url }}"
<a esl-d-route-link class="sidebar-nav-item-link icon-link" href="{{ ('/' + collection + '/') | url }}"
aria-label="{{ title }} home page">
{% include icon %}
</a>
Expand Down Expand Up @@ -38,7 +38,7 @@
{% set isBeta = [].concat(item.data.tags).includes('beta') %}
<li class="sidebar-nav-secondary-item {{ 'active' if isActive }} {{ 'draft' if isDraft }}"
{% if isActive %}aria-selected="true"{% endif %}>
<a class="sidebar-nav-secondary-link"
<a esl-d-route-link class="sidebar-nav-secondary-link"
{% if isActive %}aria-current="page"{% endif %}
href="{{ item.url | url }}">
{% if isNew %}
Expand Down
15 changes: 8 additions & 7 deletions pages/views/_includes/navigation/sidebar.njk
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<esl-scrollbar class="sidebar-scrollbar" target="::next"></esl-scrollbar>

<ul class="sidebar-nav-list esl-scrollable-content">
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="search" class="sidebar-nav-item">
<div class="sidebar-nav-item-heading {{ 'active' if functions.isActivePath(page.url, 'search') }}">
<a class="sidebar-nav-item-heading-inner"
href="{{ '/search.html' | url }}"
Expand All @@ -23,26 +23,27 @@
</a>
</div>
</li>
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="introduction" class="sidebar-nav-item">
{{ navitem ('Introduction', 'introduction', 'static/assets/sidebar/intro.svg') }}
</li>
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="core" class="sidebar-nav-item">
{{ navitem ('Core', 'core', 'static/assets/sidebar/utils.svg') }}
</li>
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="components" class="sidebar-nav-item">
{{ navitem ('Components', 'components', 'static/assets/sidebar/components.svg') }}
</li>
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="examples" class="sidebar-nav-item">
{{ navitem ('Examples', 'examples', 'static/assets/sidebar/examples.svg') }}
</li>

{% set releasedBlogs = collections.blogs | released %}
{% if releasedBlogs.length %}
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="blogs" class="sidebar-nav-item">
{{ navitem ('Blogs', 'blogs', 'static/assets/sidebar/blogs.svg') }}
</li>
{% endif %}
{% if env.isDev %}
<li class="sidebar-nav-item">
<li esl-d-route-interceptor mask="draft" class="sidebar-nav-item">
{{ navitem ('Drafts', 'draft', 'static/assets/common/flask.svg') }}
</li>
{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion pages/views/_layouts/content.njk
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{% include 'navigation/sidebar.njk' %}

<main class="esl-scrollable-content">
<div class="content {{ containerCls or 'container' }} {{ 'container-aside' if aside }}">
<div id="content-routable" class="content {{ containerCls or 'container' }} {{ 'container-aside' if aside }}">
{% if title %}
<h1 class="page-title">
{% if collectionIcon %}{% include "static/assets/" + collectionIcon %}{% endif %}
Expand Down
2 changes: 1 addition & 1 deletion pages/views/_layouts/forward.njk
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{% include 'navigation/sidebar.njk' %}

<main class="esl-scrollable-content">
<div class="content system-page">
<div id="content-routable" class="content system-page">
<div class="m-auto text-center">
<h1>🦆 301 🦆</h1>
<p class="h2 text-warning">This isn't here anymore!</p>
Expand Down