-
-
Notifications
You must be signed in to change notification settings - Fork 8.8k
Closed
Labels
Description
Vue version
3.4-alpha.4
Link to minimal reproduction
https://github.com/nuxt/nuxt/actions/runs/7208581804/job/19637892933?pr=23998
Steps to reproduce
- Start the fixtures from Nuxt with
pnpm nuxi dev ./test/fixtures/basic
- go to
localhost:3000/islands
- see
Property "t" was accessed during render but is not defined on instance.
in logs - nuxt fixtures test don't pass with
pnpm test
What is expected?
- not having any error or warn in the console
- fixtures tests should also pass
What is actually happening?
The compiled result of the SFC has a
JSON.stringify($setup.__vforToArray(["fall", "back"]).map((t, index) => ({ t: _ctx.t }))
(source: JSON.stringify(__vforToArray(['fall', 'back']).map((t, index) => ({ t: t })))
)
t
shouldn't be prefixed with _ctx
Investigation
- doing a nuxt hot reload seems to fix it
- triggering 5 to 6 time HMR seems to fix it (some cache ?)
- We tried to reproduce it on the SFC playground, unsuccessfully
debugging the SFC file transformation
- the issue seems to be coming from
@vue/compiler-sfc
through@vitejs/plugin-vue
- code passed to
transformMain
is always the same - the descriptor for the SFC file is not identical at first loading (
_ctx
prefix) and at nuxt hot reload or hmr.
looking for the vue core commit
- feat(compiler-sfc): analyze import usage in template via AST #9729 seems to be the source of this bug
Additionnal Infos
SFC source:
<template>
<div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:2:3">
<div v-if="count > 2" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:3:5">
count is above 2
</div>
<div style="display: contents;" nuxt-ssr-slot-name="default" />
{{ data }}
<div id="long-async-component-count" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:8:5">
{{ count }}
</div>
{{ headers['custom-head'] }}
<div style="display: contents;" nuxt-ssr-slot-name="test" :nuxt-ssr-slot-data="JSON.stringify([{ count: count }])"/>
<p data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:16:5">hello world !!!</p>
<div style="display: contents;" nuxt-ssr-slot-name="hello" :nuxt-ssr-slot-data="JSON.stringify(__vforToArray(3).map((t, index) => ({ t: t })))"><div nuxt-slot-fallback-start="hello"/><div v-for="(t, index) in 3" style="display: contents;">
<div :key="t" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:22:7">
fallback slot -- index: {{ index }}
</div>
</div><div nuxt-slot-fallback-end/></div>
<div style="display: contents;" nuxt-ssr-slot-name="fallback" :nuxt-ssr-slot-data="JSON.stringify(__vforToArray(['fall', 'back']).map((t, index) => ({ t: t })))"><div nuxt-slot-fallback-start="fallback"/><div v-for="(t, index) in ['fall', 'back']" style="display: contents;">
<div :key="t" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:32:7">
{{ t }} slot -- index: {{ index }}
</div>
<div
:key="t"
class="fallback-slot-content" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:35:7"
>
wonderful fallback
</div>
</div><div nuxt-slot-fallback-end/></div>
</div>
</template>
<script setup lang="ts">
import { vforToArray as __vforToArray } from '#app/components/utils'
import { getResponseHeaders } from 'h3'
defineProps<{
count: number
}>()
const evt = useRequestEvent()
const headers = getResponseHeaders(evt)
const { data } = await useFetch('/api/very-long-request')
</script>
vite plugin vue result:
import { withAsyncContext as _withAsyncContext, defineComponent as _defineComponent } from "vue";
import { vforToArray as __vforToArray } from "#app/components/utils";
import { getResponseHeaders } from "h3";
const _sfc_main = /* @__PURE__ */ _defineComponent({
__name: "LongAsyncComponent",
props: {
count: { type: Number, required: true }
},
async setup(__props, { expose: __expose }) {
__expose();
let __temp, __restore;
const evt = useRequestEvent();
const headers = getResponseHeaders(evt);
const { data } = ([__temp, __restore] = _withAsyncContext(() => useFetch("/api/very-long-request")), __temp = await __temp, __restore(), __temp);
const __returned__ = { evt, headers, data, get __vforToArray() {
return __vforToArray;
} };
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
return __returned__;
}
});
import { mergeProps as _mergeProps } from "vue";
import { ssrRenderStyle as _ssrRenderStyle, ssrRenderAttr as _ssrRenderAttr, ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from "vue/server-renderer";
function _sfc_ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
_push(`<div${_ssrRenderAttrs(_mergeProps({ "data-v-inspector": "test/fixtures/basic/components/islands/LongAsyncComponent.vue:2:3" }, _attrs))}>`);
if ($props.count > 2) {
_push(`<div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:3:5"> count is above 2 </div>`);
} else {
_push(`<!---->`);
}
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="default"></div> ${_ssrInterpolate($setup.data)} <div id="long-async-component-count" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:8:5">${_ssrInterpolate($props.count)}</div> ${_ssrInterpolate($setup.headers["custom-head"])} <div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="test"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify([{ count: $props.count }]))}></div><p data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:16:5">hello world !!!</p><div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="hello"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify($setup.__vforToArray(3).map((t, index) => ({ t: _ctx.t }))))}><div nuxt-slot-fallback-start="hello"></div><!--[-->`);
_ssrRenderList(3, (t, index) => {
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}"><div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:22:7"> fallback slot -- index: ${_ssrInterpolate(index)}</div></div>`);
});
_push(`<!--]--><div nuxt-slot-fallback-end></div></div><div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="fallback"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify($setup.__vforToArray(["fall", "back"]).map((t, index) => ({ t: _ctx.t }))))}><div nuxt-slot-fallback-start="fallback"></div><!--[-->`);
_ssrRenderList(["fall", "back"], (t, index) => {
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}"><div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:32:7">${_ssrInterpolate(t)} slot -- index: ${_ssrInterpolate(index)}</div><div class="fallback-slot-content" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:35:7"> wonderful fallback </div></div>`);
});
_push(`<!--]--><div nuxt-slot-fallback-end></div></div></div>`);
}
import { useSSRContext as __vite_useSSRContext } from "vue";
const _sfc_setup = _sfc_main.setup;
_sfc_main.setup = (props, ctx) => {
const ssrContext = __vite_useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/islands/LongAsyncComponent.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
import _export_sfc from "\0plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender], ["__file", "G:/repo/nuxt/test/fixtures/basic/components/islands/LongAsyncComponent.vue"]]);
What it should be:
import { withAsyncContext as _withAsyncContext, defineComponent as _defineComponent } from "vue";
import { vforToArray as __vforToArray } from "#app/components/utils";
import { getResponseHeaders } from "h3";
const _sfc_main = /* @__PURE__ */ _defineComponent({
__name: "LongAsyncComponent",
props: {
count: { type: Number, required: true }
},
async setup(__props, { expose: __expose }) {
__expose();
let __temp, __restore;
const evt = useRequestEvent();
const headers = getResponseHeaders(evt);
const { data } = ([__temp, __restore] = _withAsyncContext(() => useFetch("/api/very-long-request")), __temp = await __temp, __restore(), __temp);
const __returned__ = { evt, headers, data, get __vforToArray() {
return __vforToArray;
} };
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
return __returned__;
}
});
import { mergeProps as _mergeProps } from "vue";
import { ssrRenderStyle as _ssrRenderStyle, ssrRenderAttr as _ssrRenderAttr, ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate, ssrRenderList as _ssrRenderList } from "vue/server-renderer";
function _sfc_ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
_push(`<div${_ssrRenderAttrs(_mergeProps({ "data-v-inspector": "test/fixtures/basic/components/islands/LongAsyncComponent.vue:2:3" }, _attrs))}>`);
if ($props.count > 2) {
_push(`<div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:3:5"> count is above 2 </div>`);
} else {
_push(`<!---->`);
}
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="default"></div> ${_ssrInterpolate($setup.data)} <div id="long-async-component-count" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:8:5">${_ssrInterpolate($props.count)}</div> ${_ssrInterpolate($setup.headers["custom-head"])} <div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="test"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify([{ count: $props.count }]))}></div><p data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:16:5">hello world !!!</p><div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="hello"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify($setup.__vforToArray(3).map((t, index) => ({ t }))))}><div nuxt-slot-fallback-start="hello"></div><!--[-->`);
_ssrRenderList(3, (t, index) => {
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}"><div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:22:7"> fallback slot -- index: ${_ssrInterpolate(index)}</div></div>`);
});
_push(`<!--]--><div nuxt-slot-fallback-end></div></div><div style="${_ssrRenderStyle({ "display": "contents" })}" nuxt-ssr-slot-name="fallback"${_ssrRenderAttr("nuxt-ssr-slot-data", JSON.stringify($setup.__vforToArray(["fall", "back"]).map((t, index) => ({ t }))))}><div nuxt-slot-fallback-start="fallback"></div><!--[-->`);
_ssrRenderList(["fall", "back"], (t, index) => {
_push(`<div style="${_ssrRenderStyle({ "display": "contents" })}"><div data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:32:7">${_ssrInterpolate(t)} slot -- index: ${_ssrInterpolate(index)}</div><div class="fallback-slot-content" data-v-inspector="test/fixtures/basic/components/islands/LongAsyncComponent.vue:35:7"> wonderful fallback </div></div>`);
});
_push(`<!--]--><div nuxt-slot-fallback-end></div></div></div>`);
}
import { useSSRContext as __vite_useSSRContext } from "vue";
const _sfc_setup = _sfc_main.setup;
_sfc_main.setup = (props, ctx) => {
const ssrContext = __vite_useSSRContext();
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/islands/LongAsyncComponent.vue");
return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
};
import _export_sfc from "\0plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["ssrRender", _sfc_ssrRender], ["__file", "G:/repo/nuxt/test/fixtures/basic/components/islands/LongAsyncComponent.vue"]]);
System Info
No response
Any additional comments?
- It is really strange that triggering HMR multiple times (around 5 times) seems to correctly compile the sfc template. A reload of nuxt also fix it.
- also, not every
.map((t, index) => ())
are prefixed with_ctx
cc @danielroe
danielroedanielroe and baiwusanyu-c