Skip to content

fix: correctly escape backticks when precomputing CSS to inline#15593

Merged
teemingc merged 7 commits into
mainfrom
fix-css-inlining-escaping-backticks
Mar 25, 2026
Merged

fix: correctly escape backticks when precomputing CSS to inline#15593
teemingc merged 7 commits into
mainfrom
fix-css-inlining-escaping-backticks

Conversation

@teemingc
Copy link
Copy Markdown
Member

@teemingc teemingc commented Mar 23, 2026

closes #15588

This PR cleans up the escaping logic before we create a function that returns template literal string. Firstly, we have to perform escaping after the CSS processing to avoid interfering with it. Then, only after escaping, can we replace the placeholders with our interpolation placeholders such as${assets}. This helps avoid those dollar signs from being escaped when they eventually end up in the template literal


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 23, 2026

🦋 Changeset detected

Latest commit: 2d61d66

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

assert.equal(escape_html('\ud800\ud800\udc00\udc00', true), '�\ud800\udc00�');
});

test('escape_for_interpolation escapes both backticks and dollar signs', () => {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We don't need this. The tests in utils.spec.js are more useful since they test that it works when the generated module is evaluated

Comment on lines +71 to +72
const assets_placeholder = '__SVELTEKIT_ASSETS__';
const base_placeholder = '__SVELTEKIT_BASE__';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would there be any value in creating a quick util function like generate_placeholder(str, base) that you could call like generate_placeholder(css, '__SVELTEKIT_ASSETS__ ') that checks to see if the placeholder is in the string already, then adds some random stuff to the end of it in a loop if it is?

Super edge case but it's not impossible a CSS file includes these strings 😆 Might be more trouble than it's worth, though

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Probably! When I was chatting with Sonnet, it suggested using things like null-byte strings to make it more unique but that causes issues with the Svelte CSS parser. Let me see try one where we just suffix an incremental number or something

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.

Something that https://github.com/Rich-Harris/code-red did for Svelte 3/4-era compiler output was to just randomly choose at runtime a special placeholder string. That always felt safe enough to me. It prevented people from trying to screw up the compiler because there would be no way for them to guess or access what the special placeholder was going to be.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah this ^ was what I originally started at. But if you check the string for the placeholder ahead of time you also end up with the same result

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Here's an attempt 😆 2cc47c1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay one thing that just hit me -- we might want to do this in such a way that the result is stable, i.e. not random -- like starting from 1, then incrementing upwards. Mainly because if it's random, we can't cache an otherwise-identical asset between builds.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

better, but maybe we should move the key initialization into the function itself? Otherwise if the function just so happens to be called in a different order it'll produce different results

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Mm makes sense. I've moved it in here 2d61d66 (this PR)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good, just one thought

@teemingc teemingc merged commit bdbbe07 into main Mar 25, 2026
25 of 26 checks passed
@teemingc teemingc deleted the fix-css-inlining-escaping-backticks branch March 25, 2026 22:56
This was referenced Mar 25, 2026
elliott-with-the-longest-name-on-github pushed a commit that referenced this pull request Apr 3, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @sveltejs/kit@2.56.0

### Minor Changes

- breaking: rework client-driven refreshes
([#15562](#15562))


- breaking: stabilize remote function caching by sorting object keys
([#15570](#15570))


- breaking: add `run()` method to queries, disallow awaiting queries
outside render ([#15533](#15533))


- feat: support TypeScript 6.0
([#15595](#15595))


- breaking: isolate command-triggered query refresh failures per-query
([#15562](#15562))


- feat: use `hydratable` for remote function transport
([#15533](#15533))


- feat: allow `form` fields to specify a default value (`field.as(type,
value)`) ([#15577](#15577))

### Patch Changes

- fix: don't request new data when `.refresh` is called on a query with
no cache entry ([#15533](#15533))


- fix: allow using multiple remote functions within one async derived
([#15561](#15561))


- fix: avoid false-positive overridden Vite `base` setting warning when
setting a `paths.base` in `svelte.config.js`
([#15623](#15623))


- fix: manage queries in their own `$effect.root`
([#15533](#15533))


- fix: avoid `inlineDynamicImports` deprecation warning when building
the service worker with Vite 8
([#15550](#15550))


- fix: correctly escape backticks when precomputing CSS
([#15593](#15593))


- fix: discard obsolete forks before finishing navigation
([#15634](#15634))


- chore: tighten up override implementation
([#15562](#15562))


- fix: ensure the default Svelte 5 `error.svelte` file uses runes mode
([#15609](#15609))


- fix: deduplicate same-cache-key `batch` calls during SSR
([#15533](#15533))


- fix: decrement pending_count when form callback doesn't call submit()
([#15520](#15520))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
ampelectrecuted pushed a commit to amp-mod/aw3 that referenced this pull request Apr 4, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@sveltejs/kit](https://svelte.dev) ([source](https://github.com/sveltejs/kit/tree/HEAD/packages/kit)) | [`2.55.0` → `2.56.1`](https://renovatebot.com/diffs/npm/@sveltejs%2fkit/2.55.0/2.56.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@sveltejs%2fkit/2.56.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@sveltejs%2fkit/2.55.0/2.56.1?slim=true) |

---

### Release Notes

<details>
<summary>sveltejs/kit (@&#8203;sveltejs/kit)</summary>

### [`v2.56.1`](https://github.com/sveltejs/kit/blob/HEAD/packages/kit/CHANGELOG.md#2561)

[Compare Source](https://github.com/sveltejs/kit/compare/@sveltejs/kit@2.56.0...@sveltejs/kit@2.56.1)

##### Patch Changes

- chore: update JSDoc ([#&#8203;15640](sveltejs/kit#15640))

### [`v2.56.0`](https://github.com/sveltejs/kit/blob/HEAD/packages/kit/CHANGELOG.md#2560)

[Compare Source](https://github.com/sveltejs/kit/compare/@sveltejs/kit@2.55.0...@sveltejs/kit@2.56.0)

##### Minor Changes

- breaking: rework client-driven refreshes ([#&#8203;15562](sveltejs/kit#15562))

- breaking: stabilize remote function caching by sorting object keys ([#&#8203;15570](sveltejs/kit#15570))

- breaking: add `run()` method to queries, disallow awaiting queries outside render ([#&#8203;15533](sveltejs/kit#15533))

- feat: support TypeScript 6.0 ([#&#8203;15595](sveltejs/kit#15595))

- breaking: isolate command-triggered query refresh failures per-query ([#&#8203;15562](sveltejs/kit#15562))

- feat: use `hydratable` for remote function transport ([#&#8203;15533](sveltejs/kit#15533))

- feat: allow `form` fields to specify a default value (`field.as(type, value)`) ([#&#8203;15577](sveltejs/kit#15577))

##### Patch Changes

- fix: don't request new data when `.refresh` is called on a query with no cache entry ([#&#8203;15533](sveltejs/kit#15533))

- fix: allow using multiple remote functions within one async derived ([#&#8203;15561](sveltejs/kit#15561))

- fix: avoid false-positive overridden Vite `base` setting warning when setting a `paths.base` in `svelte.config.js` ([#&#8203;15623](sveltejs/kit#15623))

- fix: manage queries in their own `$effect.root` ([#&#8203;15533](sveltejs/kit#15533))

- fix: avoid `inlineDynamicImports` deprecation warning when building the service worker with Vite 8 ([#&#8203;15550](sveltejs/kit#15550))

- fix: correctly escape backticks when precomputing CSS ([#&#8203;15593](sveltejs/kit#15593))

- fix: discard obsolete forks before finishing navigation ([#&#8203;15634](sveltejs/kit#15634))

- chore: tighten up override implementation ([#&#8203;15562](sveltejs/kit#15562))

- fix: ensure the default Svelte 5 `error.svelte` file uses runes mode ([#&#8203;15609](sveltejs/kit#15609))

- fix: deduplicate same-cache-key `batch` calls during SSR ([#&#8203;15533](sveltejs/kit#15533))

- fix: decrement pending\_count when form callback doesn't call submit() ([#&#8203;15520](sveltejs/kit#15520))

</details>

---

### Configuration

📅 **Schedule**: (in timezone UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDQuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwNC4xIiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6WyJLaW5kL0RlcGVuZGVuY2llcyJdfQ==-->

Reviewed-on: https://codeberg.org/ampmod/aw3/pulls/57
Co-authored-by: AmpMod Bot <ampmod-bot@noreply.codeberg.org>
Co-committed-by: AmpMod Bot <ampmod-bot@noreply.codeberg.org>
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.

create_function_as_string doesn't escape backticks/template expressions, breaking SSR with CSS containing literal backticks

3 participants