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: optimize get fragment visit #79

Merged
merged 15 commits into from
Jun 4, 2024
Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [1.1.1] - 2024-06-01

- Optimize logic of persisting fragment elements
- Optimize API usage of `getFragmentVisit`

## [1.1.0] - 2024-05-29

- Match rules conditionally: [`rule.if`](https://swup.js.org/plugins/fragment-plugin/#rule-if)
Expand Down Expand Up @@ -81,6 +86,7 @@

- Initial Release

[1.1.1]: https://github.com/swup/fragment-plugin/releases/tag/1.1.1
[1.1.0]: https://github.com/swup/fragment-plugin/releases/tag/1.1.0
[1.0.2]: https://github.com/swup/fragment-plugin/releases/tag/1.0.2
[1.0.1]: https://github.com/swup/fragment-plugin/releases/tag/1.0.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ Type: `boolean`. Set to `true` for debug information in the console. Defaults to

- The `containers` of the matching rule **need to be shared between the current and the incoming document**
- For each selector in the `containers` array, the **first** matching element in the DOM will be selected
- The plugin will check if an element already matches the new URL before replacing it
- If a visit isn't be considered a reload of the current page, fragment elements that already match the new URL will be ignored

## Advanced use cases

Expand Down
2,950 changes: 1,969 additions & 981 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@swup/fragment-plugin",
"amdName": "SwupFragmentPlugin",
"version": "1.1.0",
"version": "1.1.1",
"description": "A swup plugin for dynamically replacing containers based on rules",
"type": "module",
"source": "src/index.ts",
Expand Down
14 changes: 11 additions & 3 deletions src/SwupFragmentPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,12 @@ export default class SwupFragmentPlugin extends PluginBase {
* @access public
*/
getFragmentVisit(route: Route, visit?: Visit): FragmentVisit | undefined {
const rule = getFirstMatchingRule(route, this.parsedRules, visit || this.swup.visit);
const rule = getFirstMatchingRule(
route,
this.parsedRules,
// @ts-expect-error createVisit is protected
visit || this.swup.createVisit(route)
);

// Bail early if no rule matched
if (!rule) return;
Expand All @@ -154,8 +159,11 @@ export default class SwupFragmentPlugin extends PluginBase {
this.swup,
this.logger
);
// Bail early if there are no containers to be replaced for this visit
if (!containers.length) return;

/** Bail early if there are no fragment elements found for this visit */
if (!containers.length) {
return;
}

// Pick properties from the current rule that should be projected into the fragmentVisit object
const { name, scroll, focus } = rule;
Expand Down
77 changes: 53 additions & 24 deletions src/inc/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,41 +116,70 @@ function prepareFragmentElements({ parsedRules, swup, logger }: FragmentPlugin):
}

/**
* Get all containers that should be replaced for a given visit's route
* Get all containers that should be replaced for a given visit's route.
* Ignores containers that already match the current URL, if the visit can't be considered a reload.
*
* A visit is being considered a reload, if one of these conditions apply:
* - `route.from` equal to `route.to`
* - all containers match the current url and swup is set to navigate on `linkToSelf`
*/
export const getFragmentVisitContainers = (
route: Route,
selectors: string[],
swup: Swup,
logger?: Logger
) => {
const isReload = isEqualUrl(route.from, route.to);

return selectors.filter((selector) => {
const el = document.querySelector<FragmentElement>(selector);
): string[] => {
let fragments: { selector: string; el: FragmentElement }[] = selectors
.map((selector) => {
const el = document.querySelector<FragmentElement>(selector);

if (!el) {
if (__DEV__) logger?.log(`${highlight(selector)} missing in current document`);
return false;
}

if (!el) {
if (__DEV__) logger?.log(`${highlight(selector)} missing in current document`);
return false;
}
const fragmentElement = queryFragmentElement(selector, swup);

if (!queryFragmentElement(selector, swup)) {
if (__DEV__) {
// prettier-ignore
logger?.error(`${highlight(selector)} is outside of swup's default containers`);
if (!fragmentElement) {
if (__DEV__) {
// prettier-ignore
logger?.error(`${highlight(selector)} is outside of swup's default containers`);
}
return false;
}
return false;
}

if (!isReload && elementMatchesFragmentUrl(el, route.to)) {
if (__DEV__)
// prettier-ignore
logger?.log(`ignoring fragment ${highlight(selector)} as it already matches the current URL`);
return false;
}
return {
selector,
el
};
})
.filter((record): record is { selector: string; el: FragmentElement } => !!record);

return true;
});
const isLinkToSelf = fragments.every((fragment) =>
elementMatchesFragmentUrl(fragment.el, route.to)
);

const isReload =
isEqualUrl(route.from, route.to) ||
(isLinkToSelf && swup.options.linkToSelf === 'navigate');

/**
* If this is NOT a reload, ignore fragments that already match `route.to`
*/
if (!isReload) {
fragments = fragments.filter((fragment) => {
if (elementMatchesFragmentUrl(fragment.el, route.to)) {
if (__DEV__) {
// prettier-ignore
logger?.log(`ignoring fragment ${highlight(fragment.selector)} as it already matches the current URL`);
}
return false;
}
return true;
});
}

return fragments.map((fragment) => fragment.selector);
};

/**
Expand Down
45 changes: 38 additions & 7 deletions tests/vitest/getFragmentVisitContainers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,44 @@ describe('getFragmentVisitContainers()', () => {
});

it('should get the correct containers when navigating to the same URL', () => {
const fragmentContainers = getFragmentVisitContainers(
{ from: '/page-1/', to: '/page-1/' },
['#fragment-1', '#fragment-2', '#fragment-3', '#fragment-outside', '#fragment-missing'],
fragmentPlugin.swup,
fragmentPlugin.logger
);
// route.from is equal to route.to
expect(
getFragmentVisitContainers(
{ from: '/page-1/', to: '/page-1/' },
[
'#fragment-1',
'#fragment-2',
'#fragment-3',
'#fragment-outside',
'#fragment-missing'
],
fragmentPlugin.swup,
fragmentPlugin.logger
)
).toEqual(['#fragment-1', '#fragment-2', '#fragment-3']);
});

it("should reload fragments if swup.options.linkToSelf equals 'navigate'", () => {
fragmentPlugin.swup.options.linkToSelf = 'navigate';
expect(
getFragmentVisitContainers(
{ from: '/page-1/', to: '/page-2/' },
['#fragment-3'],
fragmentPlugin.swup,
fragmentPlugin.logger
)
).toEqual(['#fragment-3']);
});

expect(fragmentContainers).toEqual(['#fragment-1', '#fragment-2', '#fragment-3']);
it("should ignore fragments if swup.options.linkToSelf equals 'scroll'", () => {
fragmentPlugin.swup.options.linkToSelf = 'scroll';
expect(
getFragmentVisitContainers(
{ from: '/page-1/', to: '/page-2/' },
['#fragment-3'],
fragmentPlugin.swup,
fragmentPlugin.logger
)
).toEqual([]);
});
});
Loading