Skip to content

Commit bf0c98b

Browse files
authored
Merge pull request #79 from swup/feat/optimize-get-fragment-visit
2 parents 740a3ce + 6b6d10d commit bf0c98b

7 files changed

+2079
-1017
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [1.1.1] - 2024-06-01
4+
5+
- Optimize logic of persisting fragment elements
6+
- Optimize API usage of `getFragmentVisit`
7+
38
## [1.1.0] - 2024-05-29
49

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

8287
- Initial Release
8388

89+
[1.1.1]: https://github.com/swup/fragment-plugin/releases/tag/1.1.1
8490
[1.1.0]: https://github.com/swup/fragment-plugin/releases/tag/1.1.0
8591
[1.0.2]: https://github.com/swup/fragment-plugin/releases/tag/1.0.2
8692
[1.0.1]: https://github.com/swup/fragment-plugin/releases/tag/1.0.1

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ Type: `boolean`. Set to `true` for debug information in the console. Defaults to
262262

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

267267
## Advanced use cases
268268

package-lock.json

+1,969-981
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@swup/fragment-plugin",
33
"amdName": "SwupFragmentPlugin",
4-
"version": "1.1.0",
4+
"version": "1.1.1",
55
"description": "A swup plugin for dynamically replacing containers based on rules",
66
"type": "module",
77
"source": "src/index.ts",

src/SwupFragmentPlugin.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ export default class SwupFragmentPlugin extends PluginBase {
142142
* @access public
143143
*/
144144
getFragmentVisit(route: Route, visit?: Visit): FragmentVisit | undefined {
145-
const rule = getFirstMatchingRule(route, this.parsedRules, visit || this.swup.visit);
145+
const rule = getFirstMatchingRule(
146+
route,
147+
this.parsedRules,
148+
// @ts-expect-error createVisit is protected
149+
visit || this.swup.createVisit(route)
150+
);
146151

147152
// Bail early if no rule matched
148153
if (!rule) return;
@@ -154,8 +159,11 @@ export default class SwupFragmentPlugin extends PluginBase {
154159
this.swup,
155160
this.logger
156161
);
157-
// Bail early if there are no containers to be replaced for this visit
158-
if (!containers.length) return;
162+
163+
/** Bail early if there are no fragment elements found for this visit */
164+
if (!containers.length) {
165+
return;
166+
}
159167

160168
// Pick properties from the current rule that should be projected into the fragmentVisit object
161169
const { name, scroll, focus } = rule;

src/inc/functions.ts

+53-24
Original file line numberDiff line numberDiff line change
@@ -116,41 +116,70 @@ function prepareFragmentElements({ parsedRules, swup, logger }: FragmentPlugin):
116116
}
117117

118118
/**
119-
* Get all containers that should be replaced for a given visit's route
119+
* Get all containers that should be replaced for a given visit's route.
120+
* Ignores containers that already match the current URL, if the visit can't be considered a reload.
121+
*
122+
* A visit is being considered a reload, if one of these conditions apply:
123+
* - `route.from` equal to `route.to`
124+
* - all containers match the current url and swup is set to navigate on `linkToSelf`
120125
*/
121126
export const getFragmentVisitContainers = (
122127
route: Route,
123128
selectors: string[],
124129
swup: Swup,
125130
logger?: Logger
126-
) => {
127-
const isReload = isEqualUrl(route.from, route.to);
128-
129-
return selectors.filter((selector) => {
130-
const el = document.querySelector<FragmentElement>(selector);
131+
): string[] => {
132+
let fragments: { selector: string; el: FragmentElement }[] = selectors
133+
.map((selector) => {
134+
const el = document.querySelector<FragmentElement>(selector);
135+
136+
if (!el) {
137+
if (__DEV__) logger?.log(`${highlight(selector)} missing in current document`);
138+
return false;
139+
}
131140

132-
if (!el) {
133-
if (__DEV__) logger?.log(`${highlight(selector)} missing in current document`);
134-
return false;
135-
}
141+
const fragmentElement = queryFragmentElement(selector, swup);
136142

137-
if (!queryFragmentElement(selector, swup)) {
138-
if (__DEV__) {
139-
// prettier-ignore
140-
logger?.error(`${highlight(selector)} is outside of swup's default containers`);
143+
if (!fragmentElement) {
144+
if (__DEV__) {
145+
// prettier-ignore
146+
logger?.error(`${highlight(selector)} is outside of swup's default containers`);
147+
}
148+
return false;
141149
}
142-
return false;
143-
}
144150

145-
if (!isReload && elementMatchesFragmentUrl(el, route.to)) {
146-
if (__DEV__)
147-
// prettier-ignore
148-
logger?.log(`ignoring fragment ${highlight(selector)} as it already matches the current URL`);
149-
return false;
150-
}
151+
return {
152+
selector,
153+
el
154+
};
155+
})
156+
.filter((record): record is { selector: string; el: FragmentElement } => !!record);
151157

152-
return true;
153-
});
158+
const isLinkToSelf = fragments.every((fragment) =>
159+
elementMatchesFragmentUrl(fragment.el, route.to)
160+
);
161+
162+
const isReload =
163+
isEqualUrl(route.from, route.to) ||
164+
(isLinkToSelf && swup.options.linkToSelf === 'navigate');
165+
166+
/**
167+
* If this is NOT a reload, ignore fragments that already match `route.to`
168+
*/
169+
if (!isReload) {
170+
fragments = fragments.filter((fragment) => {
171+
if (elementMatchesFragmentUrl(fragment.el, route.to)) {
172+
if (__DEV__) {
173+
// prettier-ignore
174+
logger?.log(`ignoring fragment ${highlight(fragment.selector)} as it already matches the current URL`);
175+
}
176+
return false;
177+
}
178+
return true;
179+
});
180+
}
181+
182+
return fragments.map((fragment) => fragment.selector);
154183
};
155184

156185
/**

tests/vitest/getFragmentVisitContainers.test.ts

+38-7
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,44 @@ describe('getFragmentVisitContainers()', () => {
4848
});
4949

5050
it('should get the correct containers when navigating to the same URL', () => {
51-
const fragmentContainers = getFragmentVisitContainers(
52-
{ from: '/page-1/', to: '/page-1/' },
53-
['#fragment-1', '#fragment-2', '#fragment-3', '#fragment-outside', '#fragment-missing'],
54-
fragmentPlugin.swup,
55-
fragmentPlugin.logger
56-
);
51+
// route.from is equal to route.to
52+
expect(
53+
getFragmentVisitContainers(
54+
{ from: '/page-1/', to: '/page-1/' },
55+
[
56+
'#fragment-1',
57+
'#fragment-2',
58+
'#fragment-3',
59+
'#fragment-outside',
60+
'#fragment-missing'
61+
],
62+
fragmentPlugin.swup,
63+
fragmentPlugin.logger
64+
)
65+
).toEqual(['#fragment-1', '#fragment-2', '#fragment-3']);
66+
});
67+
68+
it("should reload fragments if swup.options.linkToSelf equals 'navigate'", () => {
69+
fragmentPlugin.swup.options.linkToSelf = 'navigate';
70+
expect(
71+
getFragmentVisitContainers(
72+
{ from: '/page-1/', to: '/page-2/' },
73+
['#fragment-3'],
74+
fragmentPlugin.swup,
75+
fragmentPlugin.logger
76+
)
77+
).toEqual(['#fragment-3']);
78+
});
5779

58-
expect(fragmentContainers).toEqual(['#fragment-1', '#fragment-2', '#fragment-3']);
80+
it("should ignore fragments if swup.options.linkToSelf equals 'scroll'", () => {
81+
fragmentPlugin.swup.options.linkToSelf = 'scroll';
82+
expect(
83+
getFragmentVisitContainers(
84+
{ from: '/page-1/', to: '/page-2/' },
85+
['#fragment-3'],
86+
fragmentPlugin.swup,
87+
fragmentPlugin.logger
88+
)
89+
).toEqual([]);
5990
});
6091
});

0 commit comments

Comments
 (0)