Skip to content

feat(browser): scope waitFor rules by page type instead of a duplicated path regex (v1.17.0) - #71

Open
harper-joseph wants to merge 1 commit into
mainfrom
feat/page-types-browser
Open

feat(browser): scope waitFor rules by page type instead of a duplicated path regex (v1.17.0)#71
harper-joseph wants to merge 1 commit into
mainfrom
feat/page-types-browser

Conversation

@harper-joseph

Copy link
Copy Markdown
Contributor

Consumes the page types introduced in #70. Independent of that PR — nothing here requires it to merge first; a job without pageType simply carries none.

The problem

waitFor[].pathPattern says which pages have a widget as a regex over the URL path:

{ selector: '#reviews', pathPattern: '^/product/', devices: ['mobile'] }

That is a second description of a routing fact the plugin's route list already owns, maintained in a different repository. The two drift silently — add a route to that template on the plugin side and it renders without the rule, with nothing reporting the gap. It also can't express cheaply what a template reached by several unrelated URL shapes needs: an alternation kept in step by hand.

The change

The plugin now sends the matched template name on each queue job. This consumes it:

{ selector: '#reviews', pageTypes: ['pdp'], devices: ['mobile'] }
file change
RenderJob carries pageType from the job payload (absent on plugins < prerender-v0.34.0)
config.ts WaitForRule.pageTypes?: string[], validated; ANDs with devices
renderer.ts the scoping check
renderOnce a pageType option, so the harness reproduces a scoped rule
audit/renderAudit pageType now reaches the render, not just the report
audit/suggest emits pageTypes: [name] over pathPattern when given a type

Two decisions worth review

A job with no declared page type matches no pageTypes rule. Skipping costs the content that one rule would have waited for. Applying it blindly would restore the poll-to-timeout on every page lacking the widget — the exact failure scoping exists to prevent, and measured at ~+15s per non-PDP render. Skipping is the safe direction.

renderAudit.pageType was a report label that never reached the render. Harmless while nothing in a render consulted it. Left alone here, a pageTypes-scoped rule would be skipped on every audit render, so state B would settle differently from the fleet it is supposed to reproduce — and the audit would report content as missing that production actually captures. It now scopes the render too (empty string → undefined, so an unlabelled audit is "no page type", not a type named '').

Compatibility

pathPattern still works and is not deprecated — the two coexist on the same rule and the migration is rule-by-rule. Nothing changes for a deployment that sets no pageTypes.

Testing

112 tests pass (3 new), build clean, lint and format clean. New coverage: pageTypes validation, the scoping gate (matches its type / skipped for another / matches any listed / a typeless job matches none), and the job carrying the field through.

Sequence

This is PR 2 of 3. #70 (plugin) is PR 1. PR 3 swaps render-service's pathPattern: '^/product/' for pageTypes: ['pdp'] and is blocked on this merging and a v1.17.0 release tarball.

🤖 Generated with Claude Code

…ed path regex; v1.17.0

`waitFor[].pathPattern` describes which pages have a widget as a regex over the
URL path — a SECOND description of a routing fact the plugin's route list already
owns, maintained in a different repository. The two drift silently: add a route to
a template on the plugin side and it renders without the rule, with nothing
reporting the gap.

The plugin (prerender-v0.34.0) now declares named page types and sends the matched
name on each queue job. This consumes it:

  RenderJob      carries `pageType` from the job payload (absent on older plugins).
  WaitForRule    gains `pageTypes?: string[]`, ANDed with `devices`. A job with no
                 declared type matches no such rule — skipping costs that rule's
                 content, while applying it blindly would restore the poll-to-
                 timeout on every page lacking the widget that scoping exists to
                 prevent.
  renderOnce     gains a `pageType` option, so the harness reproduces a scoped rule
                 instead of silently rendering what the fleet does not.
  renderAudit    its `pageType` was a REPORT LABEL that never reached the render.
                 Left alone, a pageTypes-scoped rule would be skipped on every
                 audit render, so state B would settle differently from the fleet
                 it reproduces and the audit would report content as missing that
                 production captures. It now scopes the render too.
  suggest        emits `pageTypes: [name]` over `pathPattern` when the audit was
                 given a type — a suggested regex is the same second copy of the
                 route list, drifting from the moment a route is added.

`pathPattern` still works and is not deprecated; migrate rule by rule.
@harper-joseph

Copy link
Copy Markdown
Contributor Author

⚠️ Deploy-order hazard for PR 3 (render-service)

Recording this now so the follow-up doesn't get sequenced wrong.

The scopes AND together (renderer.ts):

if (rule.devices && !rule.devices.includes(deviceType)) continue;
if (rule.pageTypes && !(job.pageType && rule.pageTypes.includes(job.pageType))) continue;
if (rule.pathPattern && !new RegExp(rule.pathPattern).test(path)) continue;

So if render-service swaps pathPattern: '^/product/'pageTypes: ['pdp'] before the plugin is deployed with a pageType: pdp on the product route, then job.pageType is null, the rule is skipped, and mobile/tablet PDP renders lose the Bazaarvoice review list — the exact regression that rule was added to fix.

Setting both fields is not a safe transition either: they AND, so the rule would need the page type and the path to match, which is strictly more restrictive.

Required order

  1. feat(plugin): named page types as the unit for per-template config + metrics (v0.34.0) #70 merges → prerender-v0.34.0 released → deployed to the component with pageTypes declared and pageType: pdp on the product route.
  2. Verify the plugin actually sends it — GET /prerender_admin explain on a product URL should show the page type as declared.
  3. This PR merges → v1.17.0 released.
  4. render-service bumps the tarball and swaps the rule, in that order, only after step 2 confirms.

Steps 1 and 3 are independent; it is step 4 that must come after both.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces pageTypes scoping for waitFor rules in @harperfast/prerender-browser, allowing rules to be scoped by template names (such as pdp or category) instead of relying solely on regex-based pathPatterns. This change includes updates to configuration validation, the renderer, audit tools, and documentation. The review feedback recommends enhancing configuration validation to reject empty pageTypes arrays, adding corresponding test coverage, and simplifying a logical expression in the renderer to improve readability.

Comment on lines +368 to +373
if (
rule.pageTypes !== undefined &&
(!Array.isArray(rule.pageTypes) || rule.pageTypes.some((t) => typeof t !== 'string' || t.trim() === ''))
) {
throw new Error(`prerender config: waitFor[${i}].pageTypes must be an array of non-empty type names`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The validation check for rule.pageTypes does not prevent an empty array ([]) from being configured. If pageTypes is configured as an empty array, the rule will always be skipped silently because rule.pageTypes.includes(job.pageType) will always evaluate to false. To prevent this silent misconfiguration, we should explicitly check that rule.pageTypes.length === 0 is also treated as an invalid configuration.

Suggested change
if (
rule.pageTypes !== undefined &&
(!Array.isArray(rule.pageTypes) || rule.pageTypes.some((t) => typeof t !== 'string' || t.trim() === ''))
) {
throw new Error(`prerender config: waitFor[${i}].pageTypes must be an array of non-empty type names`);
}
if (
rule.pageTypes !== undefined &&
(!Array.isArray(rule.pageTypes) || rule.pageTypes.length === 0 || rule.pageTypes.some((t) => typeof t !== 'string' || t.trim() === ''))
) {
throw new Error("prerender config: waitFor[" + i + "].pageTypes must be an array of non-empty type names");
}

// predate the field, or the route may name no template. Skipping costs the content this
// rule would have waited for; applying it blindly would cost a poll to the timeout on
// every page that lacks the widget, which is the failure this scoping exists to prevent.
if (rule.pageTypes && !(job.pageType && rule.pageTypes.includes(job.pageType))) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logical expression !(job.pageType && rule.pageTypes.includes(job.pageType)) is a bit hard to read and can evaluate to a non-boolean value (e.g., undefined or null) before the logical NOT operator ! coerces it. It is cleaner and more idiomatic to explicitly check both conditions under which the rule should be skipped: either the job has no page type, or the job's page type is not included in the allowed list.

Suggested change
if (rule.pageTypes && !(job.pageType && rule.pageTypes.includes(job.pageType))) continue;
if (rule.pageTypes && (!job.pageType || !rule.pageTypes.includes(job.pageType))) continue;

Comment on lines +145 to +148
assert.throws(
() => mergeConfig({ waitFor: [{ selector: '#r', pageTypes: ['pdp', ''] }] }),
/waitFor\[0\]\.pageTypes must be an array/
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Let's add a test case to verify that configuring pageTypes as an empty array ([]) is correctly rejected by the validation logic.

	assert.throws(
		() => mergeConfig({ waitFor: [{ selector: '#r', pageTypes: ['pdp', ''] }] }),
		/waitFor\\[0\\]\\.pageTypes must be an array/
	);
	assert.throws(
		() => mergeConfig({ waitFor: [{ selector: '#r', pageTypes: [] }] }),
		/waitFor\\[0\\]\\.pageTypes must be an array/
	);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant