Skip to content

Enforce appended script ordering#18559

Open
timkelty wants to merge 6 commits into4.18from
bugfix/enforce-sync-js
Open

Enforce appended script ordering#18559
timkelty wants to merge 6 commits into4.18from
bugfix/enforce-sync-js

Conversation

@timkelty
Copy link
Copy Markdown
Contributor

@timkelty timkelty commented Mar 13, 2026

Summary

  • Load appended scripts in order via native script insertion.
  • Queue appendHeadHtml() / appendBodyHtml() so existing non-awaited call sites still complete in order.
  • Stop relying on app/resource-js for this path, while leaving the controller in place as a deprecated.

Reproduce

On any CP page, open DevTools and enable network throttling to make the race easier to reproduce, then run:

delete window.__appendTest;

(async () => {
  await Craft.appendBodyHtml(
    '<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"><\/script>'
  );

  await Craft.appendBodyHtml(
    '<script>window.__appendTest = typeof dayjs; console.log("append test:", window.__appendTest);<\/script>'
  );
})();

Before this fix, this can still log undefined, because the returned promise does not wait for appended external scripts to finish loading. With this fix, it should log function.

To verify the shared queue for existing non-awaited call sites:

delete window.__headBodyTest;

Craft.appendHeadHtml(
  '<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"><\/script>'
);

Craft.appendBodyHtml(
  '<script>window.__headBodyTest = typeof dayjs; console.log("head/body test:", window.__headBodyTest);<\/script>'
);

With this fix, it should log function.

Background

The earlier app/resource-js fallback was introduced in 1a958a7. It could appear to help because it converted cross-domain CP resource URLs into same-origin requests, keeping jQuery on its same-origin loading path. But the append flow still relied on jQuery's script handling, so dependent appended scripts were not guaranteed to execute in order.

Relevant jQuery issues:

@timkelty timkelty marked this pull request as ready for review April 3, 2026 03:51
@timkelty timkelty requested a review from brandonkelly April 3, 2026 03:51
@timkelty timkelty changed the title Enforce sync JS Enforce appended script ordering Apr 3, 2026
@timkelty timkelty requested a review from brianjhanson April 3, 2026 03:57
@brandonkelly
Copy link
Copy Markdown
Member

Craft.appendHeadHtml() is an async function. If you add await to those calls, it works as expected.

delete window.__appendTest;

(async () => {
    await Craft.appendBodyHtml(
      '<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"><\/script>'
    );

    await Craft.appendBodyHtml(
      '<script>window.__appendTest = typeof dayjs; console.log("append test:", window.__appendTest);<\/script>'
    );
})();

@timkelty
Copy link
Copy Markdown
Contributor Author

Craft.appendHeadHtml() is an async function. If you add await to those calls, it works as expected.

It is declared as async, but it doesn't await anything, so its promise doesn’t wait for appended external scripts to finish loading.

app/resource-js technically fixed this by forcing CP resources to be same-origin, which keeps jQuery on its same-origin loading path. But that relies on jQuery internals rather than explicitly enforcing execution order, and it forces requests that should go to the CDN back through PHP.

@brandonkelly brandonkelly reopened this Apr 14, 2026
@timkelty timkelty changed the base branch from 4.x to 4.18 April 14, 2026 14:12
@timkelty timkelty force-pushed the bugfix/enforce-sync-js branch from 737d66e to 0b5ccc1 Compare April 14, 2026 14:28
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.

2 participants