Skip to content

feat(nuxt-img): allow setting placeholder and placeholderClass via presets#2146

Open
DamianGlowala wants to merge 3 commits intomainfrom
feat/placeholder-preset
Open

feat(nuxt-img): allow setting placeholder and placeholderClass via presets#2146
DamianGlowala wants to merge 3 commits intomainfrom
feat/placeholder-preset

Conversation

@DamianGlowala
Copy link
Member

🔗 Linked issue

resolves #1256

📚 Description

This allows setting placeholder and placeholderClass props via presets.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/image@2146

commit: 33b5c07

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

The patch refactors NuxtImg to derive and use placeholderSrc, _placeholder, and _placeholderClass instead of directly using placeholder/placeholderClass; template bindings, src selection, img attributes, and preload/onMounted logic now rely on placeholderSrc. It adds placeholder and placeholderClass to ImageOptions in src/types/image.ts. Tests were updated: one bundle size assertion adjusted, a test suite renamed, and a new test verifying preset inheritance of placeholder and placeholderClass. No exported APIs were removed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: allowing placeholder and placeholderClass to be set via presets, which aligns directly with the changes in NuxtImg.vue and image.ts types.
Description check ✅ Passed The description is related to the changeset by mentioning issue #1256 and explaining that the PR enables setting placeholder and placeholderClass via presets.
Linked Issues check ✅ Passed The code changes fully implement the requirement from issue #1256 to allow placeholder attributes to be configured via presets; placeholder and placeholderClass types were added to ImageOptions, and NuxtImg.vue was updated to derive these from presets and props.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing placeholder/placeholderClass preset support; the bundle test update appears unrelated but is a minor test adjustment and does not constitute a significant scope violation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/placeholder-preset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/runtime/components/NuxtImg.vue (1)

60-66: Consider handling explicit false to allow overriding preset placeholders.

The || operator means that if a user explicitly passes placeholder={false} to override a preset that has a placeholder defined, it will still fall through to the preset's value. While this may be acceptable for most use cases, it prevents explicitly disabling placeholder behavior when using a preset.

Optional: Allow explicit override with `undefined` check
 const _placeholder = computed(
-  () => props.placeholder || (props.preset && $img.options.presets[props.preset]?.placeholder),
+  () => props.placeholder !== undefined
+    ? props.placeholder
+    : (props.preset && $img.options.presets[props.preset]?.placeholder),
 )

 const _placeholderClass = computed(
-  () => props.placeholderClass || (props.preset && $img.options.presets[props.preset]?.placeholderClass),
+  () => props.placeholderClass !== undefined
+    ? props.placeholderClass
+    : (props.preset && $img.options.presets[props.preset]?.placeholderClass),
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/NuxtImg.vue` around lines 60 - 66, The computed values
_placeholder and _placeholderClass currently use the || operator which treats an
explicit props.placeholder = false as falsy and falls back to preset values;
update the logic in computed _placeholder and _placeholderClass to respect an
explicit false by checking for undefined (or using the nullish coalescing
pattern) — i.e., return props.placeholder if it is defined (including false),
otherwise fall back to preset lookup via props.preset and
$img.options.presets[props.preset]?.placeholder (and ?.placeholderClass) so
users can disable placeholders by passing false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/components/NuxtImg.vue`:
- Around line 60-66: The computed values _placeholder and _placeholderClass
currently use the || operator which treats an explicit props.placeholder = false
as falsy and falls back to preset values; update the logic in computed
_placeholder and _placeholderClass to respect an explicit false by checking for
undefined (or using the nullish coalescing pattern) — i.e., return
props.placeholder if it is defined (including false), otherwise fall back to
preset lookup via props.preset and
$img.options.presets[props.preset]?.placeholder (and ?.placeholderClass) so
users can disable placeholders by passing false.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63715fa and 5c290f7.

📒 Files selected for processing (2)
  • src/runtime/components/NuxtImg.vue
  • src/types/image.ts

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 27, 2026

Deploying nuxt-image with  Cloudflare Pages  Cloudflare Pages

Latest commit: c804271
Status: ✅  Deploy successful!
Preview URL: https://1495c642.nuxt-image.pages.dev
Branch Preview URL: https://feat-placeholder-preset.nuxt-image.pages.dev

View logs

@codecov-commenter
Copy link

codecov-commenter commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 81.48148% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.04%. Comparing base (63715fa) to head (c804271).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
src/runtime/components/NuxtImg.vue 81.48% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2146      +/-   ##
==========================================
+ Coverage   32.52%   34.04%   +1.51%     
==========================================
  Files           7        7              
  Lines         372      376       +4     
  Branches      131      132       +1     
==========================================
+ Hits          121      128       +7     
+ Misses        194      191       -3     
  Partials       57       57              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/nuxt/image.test.ts (1)

509-535: Extend this test to verify post-load behavior for preset-inherited placeholder state.

This currently validates only initial inheritance. Please also assert that src switches to the final image and placeholderClass is removed after load in the preset path.

Proposed test update
-  it('preset placeholder and placeholderClass are inherited', () => {
+  it('preset placeholder and placeholderClass are inherited', async () => {
     setImageContext({
       presets: {
         withPlaceholder: {
           placeholder: true,
           placeholderClass: 'placeholder-class',
         },
       },
     })

-    const wrapper = mount(NuxtImg, {
-      propsData: {
-        src,
-        width: 200,
-        height: 200,
-        preset: 'withPlaceholder',
-      },
-    })
-
-    const imgElement = wrapper.find('img').element
-    const domSrc = imgElement.getAttribute('src')
+    let wrapper: VueWrapper<any>
+    const { resolve: resolveImage } = getImageLoad(() => {
+      wrapper = mount(NuxtImg, {
+        propsData: {
+          src,
+          width: 200,
+          height: 200,
+          preset: 'withPlaceholder',
+        },
+      })
+    })
+
+    let imgElement = wrapper.find('img').element
+    let domSrc = imgElement.getAttribute('src')

     expect(domSrc).toMatchInlineSnapshot(
       '"/_ipx/q_50&blur_3&s_10x10/image.png"',
     )
     expect(imgElement.classList).toContain('placeholder-class')
+
+    resolveImage()
+    await nextTick()
+
+    imgElement = wrapper.find('img').element
+    domSrc = imgElement.getAttribute('src')
+    expect(domSrc).toMatchInlineSnapshot('"/_ipx/s_200x200/image.png"')
+    expect(imgElement.classList).not.toContain('placeholder-class')
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/nuxt/image.test.ts` around lines 509 - 535, Extend the "preset
placeholder and placeholderClass are inherited" test to simulate the image load
and assert post-load state: after mounting (using wrapper and the existing
src/preset setup), trigger a load event on the image (e.g.,
wrapper.find('img').trigger('load') or dispatch a new Event('load')), await the
component update (wrapper.vm.$nextTick() or nextTick), then re-read
imgElement.getAttribute('src') and assert it equals the original src variable
(final image) and assert imgElement.classList does not contain
'placeholder-class'; reference the existing wrapper, NuxtImg mount, src
variable, and the 'withPlaceholder' preset in your changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/nuxt/image.test.ts`:
- Around line 509-535: Extend the "preset placeholder and placeholderClass are
inherited" test to simulate the image load and assert post-load state: after
mounting (using wrapper and the existing src/preset setup), trigger a load event
on the image (e.g., wrapper.find('img').trigger('load') or dispatch a new
Event('load')), await the component update (wrapper.vm.$nextTick() or nextTick),
then re-read imgElement.getAttribute('src') and assert it equals the original
src variable (final image) and assert imgElement.classList does not contain
'placeholder-class'; reference the existing wrapper, NuxtImg mount, src
variable, and the 'withPlaceholder' preset in your changes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a0f634 and c804271.

📒 Files selected for processing (1)
  • test/nuxt/image.test.ts

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.

sizes and placeholder attribute do not work in preset from nuxt.config.ts

2 participants