-
Notifications
You must be signed in to change notification settings - Fork 33
fix(vue): transpile vue template #289
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
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #289 +/- ##
==========================================
+ Coverage 82.86% 84.67% +1.81%
==========================================
Files 12 13 +1
Lines 852 1103 +251
Branches 133 235 +102
==========================================
+ Hits 706 934 +228
- Misses 144 167 +23
Partials 2 2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
i wonder if this will also fix a bug i haven't yet had time to report here (can be reproduced in nuxt/image repo) it was compiling nuxtimg component to reference properties in the template that were not exposed from setup |
I built it with The pr just remove ts syntax from vue template so it should has no effect to this.
Vue expose all props and variables defined in |
|
you can replicate by running |
I replicated it, minimal reproduction vue playground <script>
import { defineComponent } from "vue";
export default defineComponent({
setup() {
const isServer = true;
const __returned__ = { isServer };
Object.defineProperty(__returned__, "__isScriptSetup", { enumerable: false, value: true });
return __returned__;
}
});
</script>
<template>
{{ isServer || 'nothing here' }}
</template>once I removed Then I found the description about __isScriptSetup: compileScript.ts
This is weird, according to the comments it should expose more stuff. But later I found this: const hasSetupBinding = (state: Data, key: string) =>
state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key)It looks like we also need to compile the template and get these variables in |
|
Now it seems a bit of a dilemma:
|
|
now for files with no As for point 2 in #289 (comment), I think make our own transpiler requires thousands lines of code to handling edge cases, it would be better to make it in upstream if we really want. Tested with nuxt/image, now it passed all test. build log |
|
Thanks so much for your great work! I've also stumbled upon this issue today and was really delighted to see it already being taken care of. This seems to be breaking when using a prop with no value, which will normally imply The
|
|
You are right, the project didn't enable |
|
We also ran into the problem Daniel described, for now this blocks us from updating mkdist past 1.6.0. |
|
Ideally it’d be great to disable compilation entirely and simply copy the file as is. Not sure if this is already possible, but we definitely have a use case for it. |
|
@robinscholz This pr also fixed Daniel's problem, see #289 (comment) As for disabling compilation, you can set only the loader you need to transform in options, for example: mkdist({ loaders: ['js', 'postcss', 'sass'] })it will copy |
|
@Teages Omitting vue from the loader array also disables |
|
@robinscholz I can't think of any reason why you would want to build it this way. But you can try this if you really want to. mkdist({
loaders: [
'js',
'sass',
'postcss',
(input, { loadFile }) => {
// push a fake script block to call vue-tsc
if (input.extension === '.vue') {
loadFile({
getContents: () => `export default {}`,
path: `${input.path}.ts`,
srcPath: `${input.srcPath}.ts`,
extension: '.ts',
})
}
return undefined
},
],
})I sincerely recommend you try building with this PR. @danielroe can we have something like pkg.pr.new for mkdist? |
|
@Teages building with your fix does indeed fix the problems we ran into. Wasn’t sure, since I needed to fix some other things as well 😅. We ran mkdist v1.6 before and manually invoked vue-tsc, which I wanted to replicate (thus my question above). Happy to move forward with transpiling files for our build. The repository/build I’m referring too is somewhat complicated in its structure: https://github.com/magicasaservice/vue-equipment/blob/main/packages/plugins/scripts/build.ts @danielroe Would highly appreciate if this PR could get merged and published soon, since that would enable us to build directly with the npm mkdist package instead of the fork as a dependency. 🙂 |
|
I’m running into a warning in our library about a missing template or rendering function. This is the input: <template>
<primitive
ref="elRef"
:data-id="`${viewId}-trigger`"
:data-active="view?.active"
:data-disabled="mappedDisabled"
:tabindex="mappedTabindex"
:as-child="asChild"
class="magic-menu-trigger"
@click="onClick"
@contextmenu="onClick"
@mouseenter="onMouseenter"
>
<slot :view-active="view?.active" :trigger-disabled="mappedDisabled" />
</primitive>
</template>
<script lang="ts" setup>
import { computed, inject, ref, toValue, watch } from 'vue'
import { Primitive } from '@maas/vue-primitive'
import { useMenuState } from '../composables/private/useMenuState'
import { useMenuView } from '../composables/private/useMenuView'
import { useMenuItem } from '../composables/private/useMenuItem'
import { useMenuTrigger } from '../composables/private/useMenuTrigger'
import {
MagicMenuInstanceId,
MagicMenuViewId,
MagicMenuItemId,
} from '../symbols'
import type { Interaction } from '../types'
import { onKeyStroke } from '@vueuse/core'
interface MagicMenuTriggerProps {
disabled?: boolean
trigger?: Interaction[]
asChild?: boolean
}
const { disabled, trigger } = defineProps<MagicMenuTriggerProps>()
const elRef = ref<InstanceType<typeof Primitive> | undefined>(undefined)
const instanceId = inject(MagicMenuInstanceId, undefined)
const viewId = inject(MagicMenuViewId, undefined)
const itemId = inject(MagicMenuItemId, undefined)
if (!instanceId) {
throw new Error('MagicMenuTrigger must be nested inside MagicMenuProvider')
}
if (!viewId) {
throw new Error('MagicMenuTrigger must be nested inside MagicMenuView')
}
const { getView, getRelativeViewIndex } = useMenuView(instanceId)
const view = getView(viewId)
const viewIndex = getRelativeViewIndex(viewId)
const { initializeState } = useMenuState(instanceId)
const state = initializeState()
const { getItem } = useMenuItem({ instanceId, viewId })
const item = getItem(itemId ?? '')
const mappedDisabled = computed(() => disabled ?? item?.disabled ?? false)
const mappedTrigger = computed<Interaction[]>(() => {
if (trigger?.length) {
return trigger
}
switch (state.options.mode) {
case 'menubar':
return view?.parent.item
? ['mouseenter', 'click']
: state.active
? ['mouseenter', 'click']
: ['click']
case 'dropdown':
return view?.parent.item ? ['mouseenter', 'click'] : ['click']
case 'context':
return view?.parent.item ? ['mouseenter', 'click'] : ['right-click']
case 'navigation':
return ['mouseenter']
default:
return []
}
})
const mappedTabindex = computed(() => {
if (viewIndex === 0 && state.options.mode !== 'context' && !itemId) {
return 0
} else {
return undefined
}
})
const { onMouseenter, onClick, onEnter } = useMenuTrigger({
instanceId,
viewId,
itemId,
mappedDisabled,
mappedTrigger,
elRef,
})
watch(
() => view?.active,
async (value) => {
if (value) {
await new Promise((resolve) => requestAnimationFrame(resolve))
toValue(elRef)?.$el?.blur()
}
}
)
onKeyStroke('Enter', onEnter)
</script>
<style>
.magic-menu-trigger {
cursor: var(--magic-menu-trigger-cursor, pointer);
}
.magic-menu-trigger.-disabled {
pointer-events: none;
}
</style>This is the compiled output: <script>
import { defineComponent as _defineComponent } from "vue";
import { unref as _unref, renderSlot as _renderSlot, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue";
import { computed, inject, ref, toValue, watch } from "vue";
import { Primitive } from "@maas/vue-primitive";
import { useMenuState } from "../composables/private/useMenuState";
import { useMenuView } from "../composables/private/useMenuView";
import { useMenuItem } from "../composables/private/useMenuItem";
import { useMenuTrigger } from "../composables/private/useMenuTrigger";
import {
MagicMenuInstanceId,
MagicMenuViewId,
MagicMenuItemId
} from "../symbols";
import { onKeyStroke } from "@vueuse/core";
export default /* @__PURE__ */ _defineComponent({
__name: "MagicMenuTrigger",
props: {
disabled: { type: Boolean, required: false },
trigger: { type: Array, required: false },
asChild: { type: Boolean, required: false }
},
setup(__props) {
const elRef = ref(void 0);
const instanceId = inject(MagicMenuInstanceId, void 0);
const viewId = inject(MagicMenuViewId, void 0);
const itemId = inject(MagicMenuItemId, void 0);
if (!instanceId) {
throw new Error("MagicMenuTrigger must be nested inside MagicMenuProvider");
}
if (!viewId) {
throw new Error("MagicMenuTrigger must be nested inside MagicMenuView");
}
const { getView, getRelativeViewIndex } = useMenuView(instanceId);
const view = getView(viewId);
const viewIndex = getRelativeViewIndex(viewId);
const { initializeState } = useMenuState(instanceId);
const state = initializeState();
const { getItem } = useMenuItem({ instanceId, viewId });
const item = getItem(itemId ?? "");
const mappedDisabled = computed(() => __props.disabled ?? item?.disabled ?? false);
const mappedTrigger = computed(() => {
if (__props.trigger?.length) {
return __props.trigger;
}
switch (state.options.mode) {
case "menubar":
return view?.parent.item ? ["mouseenter", "click"] : state.active ? ["mouseenter", "click"] : ["click"];
case "dropdown":
return view?.parent.item ? ["mouseenter", "click"] : ["click"];
case "context":
return view?.parent.item ? ["mouseenter", "click"] : ["right-click"];
case "navigation":
return ["mouseenter"];
default:
return [];
}
});
const mappedTabindex = computed(() => {
if (viewIndex === 0 && state.options.mode !== "context" && !itemId) {
return 0;
} else {
return void 0;
}
});
const { onMouseenter, onClick, onEnter } = useMenuTrigger({
instanceId,
viewId,
itemId,
mappedDisabled,
mappedTrigger,
elRef
});
watch(
() => view?.active,
async (value) => {
if (value) {
await new Promise((resolve) => requestAnimationFrame(resolve));
toValue(elRef)?.$el?.blur();
}
}
);
onKeyStroke("Enter", onEnter);
return (_ctx, _cache) => {
return _openBlock(), _createBlock(_unref(Primitive), {
ref_key: "elRef",
ref: elRef,
"data-id": `${_unref(viewId)}-trigger`,
"data-active": _unref(view)?.active,
"data-disabled": mappedDisabled.value,
tabindex: mappedTabindex.value,
"as-child": _ctx.asChild,
class: "magic-menu-trigger",
onClick: _unref(onClick),
onContextmenu: _unref(onClick),
onMouseenter: _unref(onMouseenter)
}, {
default: _withCtx(() => [
_renderSlot(_ctx.$slots, "default", {
viewActive: _unref(view)?.active,
triggerDisabled: mappedDisabled.value
})
]),
_: 3
/* FORWARDED */
}, 8, ["data-id", "data-active", "data-disabled", "tabindex", "as-child", "onClick", "onContextmenu", "onMouseenter"]);
};
}
});
</script>
<style>
.magic-menu-trigger {
cursor: var(--magic-menu-trigger-cursor, pointer);
}
.magic-menu-trigger.-disabled {
pointer-events: none;
}
</style>The component is still working, just maybe something to take a look at? |
|
@robinscholz I think it is not a mkdist issue, see Usage with Render Functions, it already have a render function. |
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
|
@danielroe I'd like to get your opinion on render functions: should we keep render functions, or work on transpiling
|
|
I would like to work to keep although this PR looks great I'm worried about the fragility of this approach in general. I would prefer an approach where we transpile only the TS features we have to do (such as the type-syntax for defineProps, defineEmits, etc.) and leave the rest for the vue compiler in the final project |
|
I did some research, reactive props destructure make it really complex. for example: <script setup lang="ts">
const a = defineModel<number>()
const { b = 'hi' } = defineProps<{ b: string }>()
watch(() => b, { /* ... */ })
</script>We need to process it into <script setup>
const props = defineProps(['modelValue', 'b']) // no runtime types because I am lazy
const emits = defineEmits(['update:modelValue'])
const a = useModel(props, 'modelValue')
watch(() => props.b, { /* ... */ })
</script>vue have already expose I don't have much experience with AST, but it seems like a feasible approach. I think I found a way to transpile without merge the two script block, I'll open a new PR later, but I'll keep this in case it was needed until I finish the new one. |
|
continue on #300 |
resolves #281
Transpile vue template if the file have a typescript script block