language-tools
language-tools copied to clipboard
Is it possible to isolate custom blocks from the rest of the SFC?
I am currently working on a PR for a nuxt module called server-block-nuxt by @Hebilicious which will get multiple server blocks inside of the SFC to work.
<server lang="ts">
import db from "db";
export const GET = defineEventHandler(() => {
// ^? Cannot redeclare block-scoped variable 'GET'. ts(2451)
return db.query("hello");
});
</server>
<server lang="ts">
export const GET = defineEventHandler(() => {
// ^? Cannot redeclare block-scoped variable 'GET'. ts(2451)
return { message: "world" };
});
</server>
<script lang="ts" setup>
const { data } = await useFetch("/api/about");
const GET = "GET";
// ^? Cannot redeclare block-scoped variable 'GET'. ts(2451)
db.query("hello")
//^? does exist as a type but at runtime it isn't here
</script>
<template>
<div>
<p>{{ GET }}</p>
</div>
</template>
But if you look at the above example I am faced with a typescript error and the error is not just limited to custom blocks because it appears just the same in script setup.
Another problem is if you look at the top server block we import db from "db" but this import is also made available inside of the script setup only as a type as the server blocks are removed by a vite plugins transform option during run/build-time
There is already a VueLanguagePlugin
required to get auto-imports and types to work inside the custom blocks if the desired functionality can be achieved through a plugin it is also a possibility but I couldn't find any documentation on plugin authoring.
This is the current plugin.
import type { VueLanguagePlugin } from "@vue/language-core"
const plugin: VueLanguagePlugin = () => {
return {
name: "sfc-server-volar",
version: 1,
resolveEmbeddedFile(fileName, sfc, embeddedFile) {
if (embeddedFile.fileName.replace(fileName, "").match(/^\.(js|ts|jsx|tsx)$/)) {
for (const block of sfc.customBlocks) {
const content = embeddedFile.content
if (block.type === "server") {
content.push([
block.content, // text to add
block.name, // source
0, // content offset in source
{
// language capabilities to enable in this segment
hover: true,
references: true,
definition: true,
diagnostic: true,
rename: true,
completion: true,
semanticTokens: true
}
])
}
}
}
}
}
}
export default plugin
My current plan is to manipulate the block.content
inside of the plugin before pushing it to embededFile.content
like so:
Given:
import db from "db";
export const GET = defineEventHandler(() => {
return db.query("hello");
});
export const POST = defineEventHandler(() => {
return db.query("goodbye");
});
Modified:
import db from "db"; // 2. hoist the imports out of the block
{ // 1. put the declarations inside of a block to isolate them
const GET = defineEventHandler(() => {
// ^? 3. delete export string
return db.query("hello");
});
const POST = defineEventHandler(() => {
// ^? 3. delete export string
return db.query("goodbye");
});
}
This wouldn't however solve the issue of imports of one block being also avalible inside of the others but just the "Cannot redeclare block-scoped variable 'GET'. ts(2451)". Which I am actually fine with since there is nothing sensitive being leaked, we only lose the type safety to some degree.
Conclusion:
If the above proposed solution is an acceptable one I will go through with it for the time being.
And I would like to request an official way of enabling/disabling this kind of behaviour inside of the plugin api to address the problems that could not be solved by simply manipulating the content. If there is one and I am not aware of it I would like to learn about it.