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"
]
}