-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
fix: evaluate dynamic image sources once #16900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: version-3
Are you sure you want to change the base?
Changes from all commits
1d1bd60
de386d4
8d7a34d
c2f8c32
f708087
a042da5
9e477c4
fdc921d
57589e5
3f708c9
cb2a591
4059e21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@sveltejs/enhanced-img': patch | ||
| --- | ||
|
|
||
| fix: evaluate dynamic image source expressions once |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,10 @@ | ||
| /** @import { Expression, Super } from 'estree' */ | ||
| /** @import { AST } from 'svelte/compiler' */ | ||
| import { existsSync } from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import MagicString from 'magic-string'; | ||
| import sharp from 'sharp'; | ||
| import { parse } from 'svelte-parse-markup'; | ||
| import { parse } from 'svelte/compiler'; | ||
| import { walk } from 'zimmerframe'; | ||
|
|
||
| // TODO: expose this in vite-imagetools rather than duplicating it | ||
|
|
@@ -58,6 +59,23 @@ export function image_plugin(imagetools_plugin) { | |
| * @type {Map<string, string>} | ||
| */ | ||
| const imports = new Map(); | ||
| const identifiers = new Set(); | ||
| let generated_name_index = 0; | ||
|
|
||
| walk(/** @type {any} */ (ast), null, { | ||
| _(node, { next }) { | ||
| if (node.type === 'Identifier') identifiers.add(node.name); | ||
| next(); | ||
| } | ||
| }); | ||
|
|
||
| function generate_name() { | ||
| while (true) { | ||
| const index = generated_name_index++; | ||
| const name = `__img${index ? `_${index}` : ''}`; | ||
| if (!identifiers.has(name)) return name; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param {import('svelte/compiler').AST.RegularElement} node | ||
|
|
@@ -67,20 +85,31 @@ export function image_plugin(imagetools_plugin) { | |
| async function update_element(node, src_attribute) { | ||
| if (src_attribute.type === 'ExpressionTag') { | ||
| const start = | ||
| 'end' in src_attribute.expression | ||
| ? src_attribute.expression.end | ||
| : src_attribute.expression.range?.[0]; | ||
| const end = | ||
| 'start' in src_attribute.expression | ||
| ? src_attribute.expression.start | ||
| : src_attribute.expression.range?.[0]; | ||
| const end = | ||
| 'end' in src_attribute.expression | ||
| ? src_attribute.expression.end | ||
| : src_attribute.expression.range?.[1]; | ||
|
|
||
| if (typeof start !== 'number' || typeof end !== 'number') { | ||
| throw new Error('ExpressionTag has no range'); | ||
| } | ||
| const src_var_name = content.substring(start, end).trim(); | ||
|
|
||
| s.update(node.start, node.end, dynamic_img_to_picture(content, node, src_var_name)); | ||
| const src_expression = content.substring(start, end).trim(); | ||
| const should_declare = !is_reference(src_attribute.expression); | ||
| const src_var_name = should_declare ? generate_name() : src_expression; | ||
|
|
||
| s.update( | ||
| node.start, | ||
| node.end, | ||
| dynamic_img_to_picture( | ||
| content, | ||
| node, | ||
| should_declare ? src_expression : undefined, | ||
| src_var_name | ||
| ) | ||
| ); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -311,6 +340,15 @@ function stringToNumber(param) { | |
| return typeof param === 'string' ? parseInt(param) : param; | ||
| } | ||
|
|
||
| /** | ||
| * @param {Expression | Super} expression | ||
| */ | ||
| function is_reference(expression) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this needs to account for more expression types such as https://github.com/sveltejs/dts-buddy/blob/c1c53c6b93e5db189243540dcb193bf8cd2b0bd1/src/utils.js#L550 |
||
| if (expression.type === 'Identifier') return true; | ||
| if (expression.type !== 'MemberExpression' || expression.computed) return false; | ||
| return is_reference(expression.object); | ||
| } | ||
|
|
||
| /** | ||
| * @param {string} content | ||
| * @param {import('svelte/compiler').AST.RegularElement} node | ||
|
|
@@ -355,9 +393,10 @@ function to_value(src) { | |
| * For images like `<img src={manually_imported} />` | ||
| * @param {string} content | ||
| * @param {import('svelte/compiler').AST.RegularElement} node | ||
| * @param {string | undefined} src_expression | ||
| * @param {string} src_var_name | ||
| */ | ||
| function dynamic_img_to_picture(content, node, src_var_name) { | ||
| function dynamic_img_to_picture(content, node, src_expression, src_var_name) { | ||
| const attributes = node.attributes; | ||
| /** | ||
| * @param attribute_name {string} | ||
|
|
@@ -377,7 +416,7 @@ function dynamic_img_to_picture(content, node, src_var_name) { | |
| attributes.splice(size_index, 1); | ||
| } | ||
|
|
||
| return `{#if typeof ${src_var_name} === 'string'} | ||
| const picture = `{#if typeof ${src_var_name} === 'string'} | ||
| {#if import.meta.env.DEV && ${!width_index && !height_index}} | ||
| {${src_var_name}} was not enhanced. Cannot determine dimensions. | ||
| {:else} | ||
|
|
@@ -397,4 +436,17 @@ function dynamic_img_to_picture(content, node, src_var_name) { | |
| })} /> | ||
| </picture> | ||
| {/if}`; | ||
|
|
||
| // When the source is a computed expression we cache it in a variable to avoid evaluating it | ||
| // multiple times (e.g. calling a function once per template position). We use a reactive | ||
| // `{@const}` — wrapped in an `{#if true}` block so it's valid at this position — rather than a | ||
| // plain `{const}` declaration tag. Declaration tags cannot be used in legacy-mode components | ||
| // (they throw a compile error) and are only evaluated once, breaking reactivity when the | ||
| // expression depends on reactive state. `{@const}` is reactive, memoized, and works in both | ||
| // legacy and runes mode since Svelte 5.0. | ||
| if (src_expression) { | ||
| return `{#if true}{@const ${src_var_name} = ${src_expression}}\n${picture}\n{/if}`; | ||
| } | ||
|
|
||
| return picture; | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.