RonZ
RonZ

Reputation: 853

pnpm & vue & vite monorepo - resolve import of alias path inside a workspace package

I'm building a monorepo of UI applications using shared components and style using pnpm, typescript, vue, and vite.

While trying to leverage pnpm's workspace ecosystem to ease the development experience and deployment, I'm struggling with using alias paths when importing a package into an app.

This is my folder structure:


    src/
    |
    |- apps/
    |   |- app1/
    |   |   |- env/
    |   |   |- node_modules/
    |   |   |- src/
    |   |   |   |- plugins/
    |   |   |   |   |- some-logic.ts
    |   |   |   |- styles/
    |   |   |   |   |- app.scss
    |   |   |   |- views/
    |   |   |   |   |- HomeView.vue
    |   |   |   ...
    |   |   |   |- App.vue
    |   |   |   |- main.ts
    |   |   |
    |   |   |- index.html
    |   |   |- package.json
    |   |   |- tsconfig.json
    |   |   |- vite.config.ts
    |
    |- packages/
    |   |- shared-ui/
    |   |   |- node_modules/
    |   |   |- src/
    |   |   |   |- components/
    |   |   |   |   |- Header.vue
    |   |   |   |- plugins/
    |   |   |   |   |- another-logic.ts
    |   |   |   |- styles/
    |   |   |   |   |- header.scss
    |   |   |- package.json
    |   |   |- tsconfig.json
    |
    |- node_modules/
    ...
    |- package.json
    |- pnpm-lock.yaml
    |- pnpm-workspace.yaml
    |- tsconfig.base.json
    ...
    |- package.json
    |- pnpm-lock.yaml
    |- pnpm-workspace.yaml
    |- tsconfig.base.json

My HomeView.vue in my application is importing the Header.vue component of my shared-ui package:


    <script setup lang="ts">
    import stuff from '@/plugins/some-logic.ts'
    import Header from '@namespace/shared-ui/src/components/Header.vue';
    
    stuff();
    </script>
    
    <template>
        <div class="container">
            <Header />
        </div>
    </template>
    
    <style lang="scss">
    @import '@/styles/app.scss';
    </style>

As you can see above, @/ acts as a path alias for the src folder of the application. This works as expected. The problem starts under the Header component:


    <script setup lang="ts">
    import moreStuff from '@/plugins/another-logic.ts' // doesn't work
    
    
    moreStuff();
    </script>
    
    <template>
        <div class="header">
            ...
        </div>
    </template>
    
    <style lang="scss">
    // @import '@/styles/header.scss'; // doesn't work
    @import '../styles/header.scss'; // works
    </style>

My guess is since the entry point of vite is src/apps/app1/, and in the vite's config, I've created an alias of @ to src/, it's trying to resolve the @ of the package as well, and leads to wrong import as described below:


    import/no-unresolved    Unable to resolve path to module '@/plugins/another-logic.ts'
    import/no-unresolved    [vite] Internal server error: [sass] ENOENT: no such file or directory, open '/namespace/apps/app1/src/styles/header.scss'


root package.json


    {
      "name": "namespace",
      "private": true,
      "type": "module",
      "packageManager": "[email protected]",
      "browserslist": [
        "> 1%",
        "last 2 versions",
        "not dead",
        "not ie <= 11"
      ],
      "devDependencies": {
        "@types/node": "~20.3.3",
        "@typescript-eslint/eslint-plugin": "~5.61.0",
        "@typescript-eslint/parser": "~5.61.0",
        "eslint": "~8.44.0",
        "eslint-config-prettier": "~8.8.0",
        "eslint-import-resolver-typescript": "~3.5.5",
        "eslint-plugin-import": "~2.27.5",
        "eslint-plugin-prettier": "~4.2.1",
        "eslint-plugin-vue": "~9.15.1",
        "prettier": "~2.8.8",
        "ts-node": "~10.9.1",
        "typescript": "~5.1.6",
        "vite": "~4.3.9",
        "vite-plugin-eslint": "~1.8.1",
        "vue-eslint-parser": "~9.3.1"
      }
    }

pnpm-workspace.yaml


    packages:
      - 'apps/*'
      - 'packages/*'

tsconfig.base.json

{
  "compilerOptions": {
    "target": "es6",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "isolatedModules": true,
    "strict": true,
    "jsx": "preserve",
    "experimentalDecorators": true,
    "noEmit": false,
    "skipLibCheck": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "useDefineForClassFields": true,
    "sourceMap": true,
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "references": [
    {
      "path": "./packages/shared-ui"
    }
  ],
  "exclude": [
    "**/node_modules",
    "packages/**/dist"
  ]
}

apps/app1/package.json


    {
      "name": "@namespace/app1",
      "private": true,
      "type": "module",
      "packageManager": "[email protected]",
      "scripts": {
        "serve": "vite"
      },
      "dependencies": {
        "@namespace/shared-ui": "workspace:*",
        "@vee-validate/zod": "~4.10.8",
        "axios": "~1.4.0",
        "pinia": "~2.1.4",
        "vee-validate": "~4.10.8",
        "vite-plugin-vuetify": "~1.0.2",
        "vue": "~3.3.4",
        "vue-router": "~4.2.4",
        "vuetify": "~3.3.6",
        "zod": "~3.21.4"
      },
      "devDependencies": {
        "@vitejs/plugin-vue": "~4.2.3",
        "sass": "~1.64.1",
        "vite-tsconfig-paths": "~4.2.0"
      }
    }

apps/app1/tsconfig.json


    {
      "extends": "../../tsconfig.base.json",
      "compilerOptions": {
        "baseUrl": "./",
        "outDir": "./dist/",
        "paths": {
          "@/*": [
            "src/*"
          ]
        },
        "typeRoots": [
          "./node_modules/@types",
          "./src/types"
        ]
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.d.ts",
        "src/**/*.tsx",
        "src/**/*.vue",
        "vite.config.ts"
      ]
    }

apps/app1/vite.config.ts


    import { type UserConfigExport, defineConfig } from 'vite';
    import eslint from 'vite-plugin-eslint';
    import vuetify from 'vite-plugin-vuetify';
    import tsconfigPaths from 'vite-tsconfig-paths';
    
    import vue from '@vitejs/plugin-vue';
    
    export default defineConfig(({ mode }) => {
        const isDevelopment = mode === 'development';
        const config: UserConfigExport = {
            root: `${process.cwd()}/`,
            envDir: `${process.cwd()}/env/`,
            plugins: [tsconfigPaths(), eslint(), vue(), vuetify()],
            resolve: {
                alias: {
                    '@/': `${process.cwd()}/src/`,
                    vue: 'vue/dist/vue.esm-bundler.js'
                }
            }
        };
    
        if (isDevelopment) {
            config.server = {
                host: true,
                port: Number(process.env.PORT)
            };
        }
    
        return config;
    });

packages/shared-ui/package.json


    {
      "name": "@namespace/shared-ui",
      "private": true,
      "type": "module",
      "packageManager": "[email protected]",
      "dependencies": {
        "axios": "~1.4.0"
      },
      "devDependencies": {
        "@vee-validate/zod": "~4.10.8",
        "vee-validate": "~4.10.8",
        "vue": "~3.3.4",
        "vuetify": "~3.3.6",
        "zod": "~3.21.4"
      }
    }

packages/shared-ui/tsconfig.json


    {
      "extends": "../../tsconfig.base.json",
      "compilerOptions": {
        "baseUrl": "./",
        "outDir": "./dist/",
        "paths": {
          "@/*": [
            "src/*"
          ]
        },
        "typeRoots": [
          "./node_modules/@types",
          "./src/types"
        ]
      },
      "include": [
        "src/**/*.ts",
        "src/**/*.d.ts",
        "src/**/*.tsx",
        "src/**/*.vue"
      ]
    }

Upvotes: 9

Views: 4475

Answers (2)

renderVis
renderVis

Reputation: 21

my case with nextJS where

```
# pnpm-workspace.yaml
packages:
  - app
  - packages/*
```

I replaced:

"@/*": ["src/*"] -> "@<package_name>/*": ["src/*"]

 and in /app/tsconfig.json added:
"@<package_name>/*": ["../packages/<package_name>/src/*"]

It should translate to a vue & vite monorepo.

Upvotes: 0

RonZ
RonZ

Reputation: 853

As described in the following URL, vite (and rollup) apparently do not support resolving aliases with an array but only a string. The solution, as of this moment, is to pick different aliases per app/package.

https://github.com/vercel/turbo/discussions/620#discussioncomment-2136195

Upvotes: 0

Related Questions