Skip to content

fix(utils): replace randomIdString() with deterministic createElementId()#936

Draft
caugner wants to merge 6 commits intomainfrom
913-replace-randomIdString
Draft

fix(utils): replace randomIdString() with deterministic createElementId()#936
caugner wants to merge 6 commits intomainfrom
913-replace-randomIdString

Conversation

@caugner
Copy link
Copy Markdown
Contributor

@caugner caugner commented Oct 17, 2025

Description

Remove randomIdString() in favor of a deterministic createElementId() function.

Motivation

Increase chances of HTML files being identical on subsequent builds, speeding up the sync part of the deployment.

Additional details

Tested locally with the following script:

# 1. Build rari content (once)
BUILD_OUT_ROOT=./out node --env-file=.env --run rari -- build

# 2. Build Fred JS/CSS bundles (once)
BUILD_OUT_ROOT=./out npm run build

# 3. First SSR run → save HTML to build1/
BUILD_OUT_ROOT=./out npm run ssr
rsync -a --include="*/" --include="*.html" --exclude="*" ./out/ ./build1/
find ./out -name "*.html" -not -path "*/static/*" -delete

# 4. Second SSR run → save HTML to build2/
BUILD_OUT_ROOT=./out npm run ssr
rsync -a --include="*/" --include="*.html" --exclude="*" ./out/ ./build2/

# 5. Compare
diff -rq ./build1/ ./build2/

Only the homepage and the Playground had differences:

$ diff -rq ./build1/ ./build2/ 2>&1

Files ./build1/en-us/index.html and ./build2/en-us/index.html differ
Files ./build1/en-us/play/index.html and ./build2/en-us/play/index.html differ
Files ./build1/es/index.html and ./build2/es/index.html differ
Files ./build1/es/play/index.html and ./build2/es/play/index.html differ
Files ./build1/fr/index.html and ./build2/fr/index.html differ
Files ./build1/fr/play/index.html and ./build2/fr/play/index.html differ
Files ./build1/ja/index.html and ./build2/ja/index.html differ
Files ./build1/ja/play/index.html and ./build2/ja/play/index.html differ
Files ./build1/ko/index.html and ./build2/ko/index.html differ
Files ./build1/ko/play/index.html and ./build2/ko/play/index.html differ
Files ./build1/pt-br/index.html and ./build2/pt-br/index.html differ
Files ./build1/pt-br/play/index.html and ./build2/pt-br/play/index.html differ
Files ./build1/ru/index.html and ./build2/ru/index.html differ
Files ./build1/ru/play/index.html and ./build2/ru/play/index.html differ
Files ./build1/zh-cn/index.html and ./build2/zh-cn/index.html differ
Files ./build1/zh-cn/play/index.html and ./build2/zh-cn/play/index.html differ
Files ./build1/zh-tw/index.html and ./build2/zh-tw/index.html differ
Files ./build1/zh-tw/play/index.html and ./build2/zh-tw/play/index.html differ

Homepage differences:

$ diff ./build1/en-us/index.html ./build2/en-us/index.html

1933c1933
<               4 hours ago
---
>               5 hours ago
1953c1953
<               4 hours ago
---
>               5 hours ago
1973c1973
<               4 hours ago
---
>               5 hours ago
1993c1993
<               1 hour ago
---
>               2 hours ago
2013c2013
<               20 minutes ago
---
>               2 hours ago
`

Playground differences:

```console
$ diff ./build1/en-us/play/index.html ./build2/en-us/play/index.html

1420c1420
<          lit$391985138$0 run-on-start run-on-change defer-hydration><template shadowroot="open" shadowrootmode="open"><style>
---
>          lit$070035930$0 run-on-start run-on-change defer-hydration><template shadowroot="open" shadowrootmode="open"><style>
1562c1562
<       <!--lit-node 32--><mdn-modal   lit$391985138$20 class="share" defer-hydration><template shadowroot="open" shadowrootmode="open"><style>dialog{border:1px solid var(--color-border-primary);border-radius:.25rem;padding:0}header{display:flex;padding:.5rem}header h2{margin:0}header mdn-button{margin-left:auto}slot{display:block;padding:0 1rem 1rem}</style><!--lit-part 89wSBtjvN/w=-->
---
>       <!--lit-node 32--><mdn-modal   lit$070035930$20 class="share" defer-hydration><template shadowroot="open" shadowrootmode="open"><style>dialog{border:1px solid var(--color-border-primary);border-radius:.25rem;padding:0}header{display:flex;padding:.5rem}header h2{margin:0}header mdn-button{margin-left:auto}slot{display:block;padding:0 1rem 1rem}</style><!--lit-part 89wSBtjvN/w=-->
1647c1647
<       <!--lit-node 47--><mdn-modal   lit$391985138$29 class="report" defer-hydration><template shadowroot="open" shadowrootmode="open"><style>dialog{border:1px solid var(--color-border-primary);border-radius:.25rem;padding:0}header{display:flex;padding:.5rem}header h2{margin:0}header mdn-button{margin-left:auto}slot{display:block;padding:0 1rem 1rem}</style><!--lit-part 89wSBtjvN/w=-->
---
>       <!--lit-node 47--><mdn-modal   lit$070035930$29 class="report" defer-hydration><template shadowroot="open" shadowrootmode="open"><style>dialog{border:1px solid var(--color-border-primary);border-radius:.25rem;padding:0}header{display:flex;padding:.5rem}header h2{margin:0}header mdn-button{margin-left:auto}slot{display:block;padding:0 1rem 1rem}</style><!--lit-part 89wSBtjvN/w=-->

Related issues and pull requests

Fixes #913.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Oct 17, 2025

c2d9952 was deployed to: https://fred-pr936.review.mdn.allizom.net/

@caugner caugner changed the title fix(utils): replace randomIdString() with deterministicIdString() fix(utils): replace randomIdString() with deterministic createElementId() Apr 13, 2026
@caugner caugner marked this pull request as ready for review April 13, 2026 15:03
@caugner caugner requested a review from a team as a code owner April 13, 2026 15:03
@caugner caugner requested a review from LeoMcA April 13, 2026 15:03
Copy link
Copy Markdown
Member

@LeoMcA LeoMcA left a comment

Choose a reason for hiding this comment

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

I think this fixes everything in the wrong place:

We're sacrificing the guarantee of ID-uniqueness in a number of places, which would result in invalid HTML, and a confused accessibility tree, for an issue which IIRC we already have a fix for: just upload everything on each build, no rsync/diff - that's the slow bit.

Regardless, having deterministic output is a good goal, but I don't think this is at all the correct approach: the new function does very little that manually adding ids would do, and the addition of name in dropdowns isn't enforced, nor is necessary - we can just add ids to the slotted elements directly in the DOM.

I'd suggest we keep the random ID fallback where we can't be sure that manual IDs have been set, and enforce this deterministic requirement in CI so we catch future fallbacks to the random ID and fix.

let id = this._dropdownSlotElements.find((element) => element.id)?.id;
if (!id) {
id = randomIdString("uid_");
id = createElementId("dropdown", this.name);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems unnecessary, why not construct the string directly?

Comment thread components/utils/index.js
* @param {string} str - String to hash
* @returns {string} - Hexadecimal hash string
*/
function hashString(str) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure what hashing gives us here: if the input isn't unique, the output won't be either - this seems unnecessary

Comment thread components/utils/index.js
*/
export function randomIdString(prefix = "id-") {
return Math.random().toString(36).replace("0.", prefix);
export function createElementId(prefix, content) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This function itself seems unnecessary: where we can set a static, unique ID in call sites, let's do that instead of routing it through here.

static styles = styles;

static properties = {
name: { type: String, required: true },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

required: true doesn't do anything

Comment thread components/button/pure.js
const labelId = randomIdString("label-");
const labelId = createElementId(
"label",
`${typeof label === "string" ? label : "btn"}-${href || ""}`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is likely to cause collisions: we can realistically have two buttons with the same href on the same page.

We only need this ID to set aria-labelledby for icon-only buttons. Perhaps we set aria-label for icon-only buttons instead, and enforce that label is a string with types when iconOnly is true?

@caugner caugner marked this pull request as draft April 23, 2026 14:06
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.

Use of randomIdString() impacts deployment time

3 participants