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

Generic Type Constraints with null | undefined Are Stripped During Build #12716

Open
jousi592 opened this issue Jan 15, 2025 · 3 comments
Open
Labels
can't reproduce need more info Further information is requested

Comments

@jousi592
Copy link

jousi592 commented Jan 15, 2025

Vue version

3.4.21

Link to minimal reproduction

https://play.vuejs.org/#eNp9kU1PwzAMhv+KlRNIYzvAaXSTAO0AB0CAdsqltG7JSJ0oH2NS6X/HSbWxw7RTYr+vncdOL+6snW4jirkofOWUDeAxRAu6pHYhRfBSQIuETlUcrgF3Aan24INT1MIvUOw+0eWL1nxEqrFRhLUUS0mqs8YF6B02AzTOdCAFPyfFrSRJlSEfYFvqiLAA9hTr5UXqcympmI083ISDgJ3VZUCOAApFNnLdVWdq1IyVOzDojOVidvCKCfPzG41qpxtviIfsU70Ulems0uhebFDMIMUcspK0Umvz85RzwUWc7PPVF1bfJ/Ibv0s5KV4denRbBjlooXQthlFevT/z8o5Epo+a3WfEN/RGx8Q42u55u4x95Mu0j3nL/B8ffpX+x++HSqDJOWR/Xv3DmdH/ca+nN7lO0iCGP3ruuFQ=

Steps to reproduce

<script setup lang="ts" generic="V extends string | number | null | undefined">
import {ref} from "vue";

const value = ref<V>(null)
</script>

<template>
  <input v-model="value" />
</template>

Then run npm run build.

The built output only includes <V extends string | number>, dropping the null | undefined constraints.

What is expected?

The generic type constraint should preserve null | undefined types
Component should accept null/undefined values when used with appropriate type parameters

What is actually happening?

Built output strips null | undefined from generic constraints
TypeScript errors occur when trying to pass null values to components, even though they're explicitly included in the constraints

System Info

System:
    OS: macOS 14.3.1
    CPU: (8) arm64 Apple M1 Pro
    Memory: 56.75 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.10.0 - ~/.nvm/versions/node/v20.10.0/bin/node
    Yarn: 1.22.21 - ~/.nvm/versions/node/v20.10.0/bin/yarn
    npm: 10.2.3 - ~/.nvm/versions/node/v20.10.0/bin/npm
    pnpm: 9.4.0 - ~/.nvm/versions/node/v20.10.0/bin/pnpm
  Browsers:
    Chrome: 131.0.6778.266
    Safari: 17.3.1
  npmPackages:
    vue: ^3.4.21 => 3.4.27

Any additional comments?

This issue makes it difficult to properly type components that need to handle nullable generic types. Current workarounds involve moving type constraints to prop definitions, but this can lead to other type incompatibility issues when the component is used with more specific types.

@edison1105
Copy link
Member

Please provide a complete, reproducible example that can run npm run build

@edison1105 edison1105 added the need more info Further information is requested label Jan 16, 2025
@jousi592
Copy link
Author

In src, create a component folder and add files below:

Create vue component test.vue file:

<script setup lang="ts" generic="V extends number | string | null">
import { ref } from 'vue';

const value = ref<V>(null as V);
</script>

<template>
  <div
    class="ui-flex ui-p-6 ui-gap-x-4 ui-rounded-lg ui-shadow-button-sm ui-max-w-[344px]"
  >
    {{ value }}
  </div>
</template>

Create export index.js file:

import test from './test.vue';

export { test };`

Then in the src folder, add index.ts file with following contents:

export * from './component';

Then in the root add package.json:

{
  "name": "test",
  "version": "1.0.0",
  "files": [
    "dist"
  ],
  "main": "./dist/ui.umd.js",
  "module": "./dist/ui.mjs",
  "types": "./dist/index.d.ts",
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check build-only",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false"
  },
  "dependencies": {
    "@headlessui/vue": "^1.7.16",
    "@mdi/js": "^7.2.96",
    "@vueuse/core": "^10.1.2",
    "vue": "^3.4.21"
  },
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@chromatic-com/storybook": "^1.5.0",
    "@rushstack/eslint-patch": "^1.10.1",
    "@storybook/addon-actions": "^8.2.9",
    "@storybook/addon-essentials": "^8.1.8",
    "@storybook/addon-interactions": "^8.1.8",
    "@storybook/addon-links": "^8.1.8",
    "@storybook/blocks": "^8.1.8",
    "@storybook/test": "^8.1.8",
    "@storybook/vue3": "^8.1.8",
    "@storybook/vue3-vite": "^8.1.8",
    "@tsconfig/node20": "^20.1.2",
    "@types/cypress": "^1.1.3",
    "@types/jsdom": "^21.1.6",
    "@types/lodash": "^4.14.185",
    "@types/node": "^20.11.28",
    "@types/uuid": "^8.3.4",
    "@vitejs/plugin-vue": "^5.0.4",
    "@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
    "@vue/eslint-config-typescript": "^12.0.0",
    "@vue/test-utils": "^2.4.5",
    "@vue/tsconfig": "^0.5.1",
    "autoprefixer": "^10.4.20",
    "babel-loader": "^8.3.0",
    "cypress": "^13.7.0",
    "eslint": "^8.49.0",
    "eslint-plugin-cypress": "^2.15.1",
    "eslint-plugin-storybook": "^0.8.0",
    "eslint-plugin-vue": "^9.17.0",
    "jsdom": "^24.0.0",
    "npm-run-all": "^4.1.5",
    "npm-run-all2": "^6.1.2",
    "postcss": "^8.4.41",
    "sass": "^1.55.0",
    "start-server-and-test": "^2.0.3",
    "storybook": "^8.1.8",
    "tailwindcss": "^3.1.8",
    "typescript": "~5.4.0",
    "vite": "^5.1.6",
    "vite-plugin-dts": "^3.9.1",
    "vitest": "^1.4.0",
    "vue-component-type-helpers": "^2.0.29",
    "vue-loader": "^16.8.3",
    "vue-tsc": "^2.0.6"
  }
}

Add tsconfig.vitest.json:

{
  "extends": "./tsconfig.app.json",
  "exclude": [],
  "compilerOptions": {
    "composite": true,
    "incremental": true,
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
    "lib": ["ESNext"],
    "types": ["node", "jsdom"]
  }
}

Add vite.config.mts

/* eslint-disable import/no-extraneous-dependencies */
import { fileURLToPath, URL } from 'node:url';

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import dts from 'vite-plugin-dts';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    dts(),
  ],
  base: '/',
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  build: {
    lib: { // tell the build process to treat this project as library
      entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
      name: 'backfeg-ui',
      fileName: 'backfeg-ui',
    },
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue',
        },
      },
    },
  },
});

Once you add these files/folders, just run npm i and npm run build. Then in the /build folder you should see /component/test.vue.d.ts and there the generic type has been changed to <V extends string | number> where null has been stripped.

@edison1105
Copy link
Member

edison1105 commented Jan 17, 2025

Please use the latest version of vue and vue-tsc for testing. If it still doesn't work, please provide a minimal reproducible example that can be directly run. Otherwise, this issue will be automatically closed in three days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
can't reproduce need more info Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants