Skip to content
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

Vue 3.4 alpha 4 compiler break Nuxt fixtures tests #9853

Closed
huang-julien opened this issue Dec 16, 2023 · 2 comments
Closed

Vue 3.4 alpha 4 compiler break Nuxt fixtures tests #9853

huang-julien opened this issue Dec 16, 2023 · 2 comments
Labels

Comments

@huang-julien
Copy link
Contributor

huang-julien commented Dec 16, 2023

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

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

@edison1105
Copy link
Member

edison1105 commented Dec 18, 2023

The root cause is that the scope variable name in the scopeIds on the node have not been removed, while it is removed from the knownIds. Due to the reuse the AST, failing to add the scope variable to the knownIds in markScopeIdentifier.

const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
return
}

@yyx990803
Copy link
Member

closed via #9867

@github-actions github-actions bot locked and limited conversation to collaborators Jan 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants