pnpm, vue, and vite monorepo: tackling alias path imports within a workspace package

I am in the process of creating a monorepo for UI applications that utilize shared components and styles with pnpm, typescript, vue, and vite.

While attempting to streamline development and deployment using pnpm's workspace feature, I am encountering difficulties when it comes to importing packages into an application using alias paths.

Here 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

The HomeView.vue file within my application is trying to import the Header.vue component from 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 shown above, @/ serves as a path alias for the src folder of the application, functioning properly. The issue arises with 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 assumption is that due to the vite entry point being src/apps/app1/, and having set an alias in vite's configuration from @ to src/, it attempts to resolve the @ path from the package as well, resulting in incorrect imports like those 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": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d4a4baa4b994ecfae2faed">[email protected]</a>",
      "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": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="83f3edf3eec3bbadb5adba">[email protected]</a>",
      "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": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="75051b0518354d5b435b4c">[email protected]</a>",
      "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"
      ]
    }

Answer №2

my experience with implementing nextJS


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

I made changes to:

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

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

This transformation was done to facilitate a vue & vite monorepo setup.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The error you are seeing is a result of your application code and not generated by Cypress

I attempted to test the following simple code snippet: type Website = string; it('loads examples', () => { const website: Website = 'https://www.ebay.com/'; cy.visit(website); cy.get('input[type="text"]').type(& ...

Prevent redirection when submitting and show an error message instead

I implemented a login system where, upon entering the correct username and password, a token is stored in local storage. If there's an error with the credentials, an "Login Unsuccessful" message is displayed. Everything was functioning correctly until ...

"Configuration files for webpack and Vue: webpack.config.js and vue.config

Just recently diving into exploring Vue, I decided to kick off a project from the ground up. My current requirement is to incorporate vuetify. But it dawned on me that I lack both webpack.config.js and vue.config.js. Is it necessary for me to utilize npm ...

Guide to acquiring the webViewLink using Google Drive Api v3?

I'm having trouble locating the webViewLink. The documentation (https://developers.google.com/drive/api/v3/reference/files) states that I should receive this parameter when requesting gapi.client.drive.files.list(). However, I don't even have a c ...

During my attempt to convert my Slice.js file to ts using the redux toolkit, I encountered some Type-errors

After creating a sample Redux toolkit with JavaScript files, I am now attempting to convert them to TypeScript. Some errors have been resolved, but I am facing issues with the following two errors: The error "Property 'name' does not exist on ty ...

Observing nested data changes in Vue.JS?

In my Vue.js components, I have a group of checkboxes that are sending data to the parent element. My goal is to monitor this data for changes and use it to filter information in an API call. When certain checkboxes are selected, the data stored on the pa ...

Is there a way to easily toggle a Material Checkbox in Angular with just one click?

Issue with Checkbox Functionality: In a Material Dialog Component, I have implemented several Material Checkboxes to serve as column filters for a table: <h1 mat-dialog-title>Filter</h1> <div mat-dialog-content> <ng-container *ng ...

Could anyone provide an explanation for the statement "What does '[P in keyof O]: O[P];' signify?"

As a new Typescript user looking to build a passport strategy, I came across a line of code that has me completely baffled. The snippet is as follows: here. The type StrategyCreated<T, O = T & StrategyCreatedStatic> = { [P in keyof O]: O[P]; ...

Optimize your website by reducing the size of CSS, JavaScript, and HTML files through NUXT's

I'm currently working on a project in NUXT utilizing yarn for managing packages. My objective is to compress (and potentially merge) js, css, and html files to enhance load time efficiency. I came across "https://www.npmjs.com/package/nuxt-compress" ...

The Vue.js/Vuex login feature encountered an error: [vuex] The action type "postLogin" is unrecognized

I am relatively new to Vuejs and still learning Vuex. While I have a good understanding of the basics, I am facing difficulties getting my login feature to work. The error message I keep encountering is: [vuex] unknown action type: postLogin. My goal is ...

Tips for typing a destructured object key in TypeScript

Assuming I have a query parameter from the router in my Next.js app const { query: { id }, } = useRouter(); The value of { id } is currently string | string[] | undefined. I want to send it as a parameter to another function, and I am certain that ...

Assigning object properties from a request to every item in an observable array of objects using RxJS

Despite my efforts to search various resources like mergeMap and switchMap, I am unable to solve the problem I'm facing as I am still new to RxJs. While I would like to provide more details about my attempts in this post, I fear it may complicate my q ...

Remove the Prisma self-referencing relationship (one-to-many)

I'm working with this particular prisma schema: model Directory { id String @id @default(cuid()) name String? parentDirectoryId String? userId String parentDirectory Directory? @relation("p ...

Tips for resolving the issue of 'defineExpose' method being undefined in Vue3

Struggling to pass a method from child to parent; unfortunately, the defineExpose() method seems to be malfunctioning. Could anyone provide guidance on what might be going wrong? For additional context, feel free to check out my previous question <scri ...

What is the process for transitioning global reusable types to package types within turborepo?

When creating an app within the apps folder, a global.d.ts file is required with specific types defined like this: interface Window{ analytics: any; } This file should be designed to be reusable and placed in the packages/types directory for easy acce ...

Ways to decrease the size of this item while maintaining its child components?

Here is an object that I am working with: { "name": "A", "children": [ { "name": "B", "open": false, "registry": true, "children": [ { ...

Tips on utilizing the ternary operator when binding in Vue

<label class="switch"> <input type="checkbox" v-on:change="updateRequiredData(list.input_permission_id,selected, selected == 'Applicant' ? list.status : selected =='Guaranter' ? list.guaranter_required : selected ='Refer ...

Determining the appropriate scenarios for using declare module and declare namespace

Recently, I came across a repository where I was exploring the structure of TypeScript projects. One interesting thing I found was their typings file: /** * react-native-extensions.d.ts * * Copyright (c) Microsoft Corporation. All rights reserved. * Li ...

Implementing a conditional chaining function in TypeScript

I'm currently facing an issue while implementing a set of chained functions. interface IAdvancedCalculator { add(value: number): this; subtract(value: number): this; divideBy(value: number): this; multiplyBy(value: number): this; calculate( ...

Accessing parent properties in the setup() function of Vue 3 using the composition API. How can you retrieve the context parent

Currently facing a dilemma with Vue 3 (alpha 4): Within the setup() function, I am attempting to access the parent component. According to the guidance provided on , the parent should be accessible via the context argument, either as a property of context ...