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

vite.config.ts can't import untranspiled ts files from other packages in the same monorepo #5370

Open
7 tasks done
zheeeng opened this issue Oct 21, 2021 · 46 comments · May be fixed by #15696
Open
7 tasks done

vite.config.ts can't import untranspiled ts files from other packages in the same monorepo #5370

zheeeng opened this issue Oct 21, 2021 · 46 comments · May be fixed by #15696
Labels
contribution welcome enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority)

Comments

@zheeeng
Copy link
Contributor

zheeeng commented Oct 21, 2021

Describe the bug

If we import something from symlink and the importee is ts file. We counter a such error:

failed to load config from /Users/zheeeng/Workspace/foo/bar/baz/vite.config.ts
error when starting dev server:
TypeError: defaultLoader is not a function

There are two workarounds: compile the ts file to the common js file, or specify the importee path to its real file path rather than symlink.

How could we use it without these two approaches?

Reproduction

https://github.com/zheeeng/test-symlink-vite-config

System Info

Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 1.60 GB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.17.0 - ~/.nvm/versions/node/v14.17.0/bin/node
    Yarn: 1.22.11 - ~/.nvm/versions/node/v14.17.0/bin/yarn
    npm: 6.14.13 - ~/.nvm/versions/node/v14.17.0/bin/npm
  Browsers:
    Chrome: 95.0.4638.54
    Safari: 14.1.2

Used Package Manager

pnpm

Logs

failed to load config from /Users/zheeeng/Workspace/foo/bar/baz/vite.config.ts
error when starting dev server:
TypeError: defaultLoader is not a function
    at Object.require.extensions.<computed> [as .ts] (/Users/zheeeng/Workspace/foo/node_modules/.pnpm/vite@2.6.5_less@4.1.2/node_modules/vite/dist/node/chunks/dep-55830a1a.js:68633:13)
    at Module.load (internal/modules/cjs/loader.js:933:32)
    at Function.Module._load (internal/modules/cjs/loader.js:774:14)
    at Module.require (internal/modules/cjs/loader.js:957:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/Users/zheeeng/Workspace/foo/web/studio/vite.config.ts:37:32)
    at Module._compile (internal/modules/cjs/loader.js:1068:30)
    at Object.require.extensions.<computed> [as .ts] (/Users/zheeeng/Workspace/foo/node_modules/.pnpm/vite@2.6.5_less@4.1.2/node_modules/vite/dist/node/chunks/dep-55830a1a.js:68630:20)
    at Module.load (internal/modules/cjs/loader.js:933:32)
    at Function.Module._load (internal/modules/cjs/loader.js:774:14)

Validations

@sodatea sodatea changed the title Import symlink ts file in vite.config.ts meets resoving problem. vite.config.ts can't import untranspiled ts files from other packages in the same monorepo Oct 21, 2021
@sodatea sodatea added p2-nice-to-have Not breaking anything but nice to have (priority) and removed pending triage labels Oct 25, 2021
@sodatea
Copy link
Member

sodatea commented Oct 25, 2021

Hi, we've discussed this issue at last Friday's team meeting.

Considering that:

  1. I can't think of an efficient way to support this feature.
    • The packages are excluded by esbuild because they're external.
    • Most of the external packages used by vite.config.* don't (and shouldn't) require transpilation
    • It's already a configuration file, we don't want to introduce another option to configure the loading logic of the configuration file.
  2. The use case is quite rare. The logic in the configuration file is usually not very complex. Even if it is extracted to a separate package, I think it's acceptable to be written in plain JS or processed by an additional run of tsc.

So this feature would be a low priority for the team.
But feel free to open a PR if you can find a better and efficient way to handle this use case.

@zheeeng
Copy link
Contributor Author

zheeeng commented Oct 25, 2021

Thx for your discussion, I would try to transpile the ts config file.

@zheeeng zheeeng closed this as completed Oct 25, 2021
@zheeeng
Copy link
Contributor Author

zheeeng commented Oct 25, 2021

@sodatea Can we add an environment variable for configuring this?

@sodatea
Copy link
Member

sodatea commented Oct 25, 2021

Environment variables are also a kind of configuration, so I don't think we should do that.

@github-actions github-actions bot locked and limited conversation to collaborators Nov 9, 2021
@vitejs vitejs unlocked this conversation Nov 24, 2021
@sodatea
Copy link
Member

sodatea commented Nov 24, 2021

I just realized that we could use a loader like esbuild-register for config loading.

I'm not sure if we can use esbuild-register directly. For now, I can think of a few edge cases:

  1. Each TS module's tsconfig.json isn't correctly respected. We need to reuse https://github.com/dominikg/tsconfck for that.
  2. Plain JS modules that aren't in the project should not be transpiled by esbuild due to performance concerns.

Anyway, this is a low-priority but doable feature.
We can fix it when we are going to refactor the configuration loading logic in the future.

@Niputi Niputi reopened this Nov 24, 2021
@hiukky
Copy link

hiukky commented Mar 10, 2022

Are we still looking at it? because it is very useful when it comes to monorepo and workspaces. Even more so when we can create and use plugins in simple and shared ways within the project.

@github-actions
Copy link

Hello @zheeeng. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!

@julianklumpers
Copy link

We also encounter this problem. Our use case is that we have a monorepo with 5 micro-frontends (vite apps). We want to have 1 base vite config file and extend from that.
It would be really nice to have an option to import and/or extends other config files in .ts format without compiling first.

Anyway, we ended up writing the base config file in plain js with module.exports.

  • vite.apps.base.js
const react = require("@vitejs/plugin-react").default;

module.exports = function ({ root, isDev, plugins }) {
  return {
     ...
      resolve: {
       alias: {
         src: path.resolve(root, "src"),
       },
     },
     plugins: [
         react({
          babel: {
            plugins: [
              [
                "babel-plugin-styled-components",
                {
                  displayName: isDev,
                  fileName: false,
                  ssr: false,
                  minify: !isDev,
                  transpileTemplateLiterals: !isDev,
                  pure: !isDev,
                },
              ],
            ],
          },
        })
       ...more plugins
      ...plugins
     ]
  }
... more options
}

and then we could use like this:

import base from 'config/vite.apps.base';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
  const isDev = mode === 'development';

  return base({
    root: __dirname,
    isDev,
    plugins: [
      partytownVite({
        dest: path.resolve(__dirname, 'build', '~partytown'),
      }),
    ],
  });
});

@airtonix
Copy link

airtonix commented Apr 9, 2022

use preconstruct on your packages... then you can just sidestep all this headache.

https://preconstruct.tools/

it sets up mjs and cjs stubs that behave differently in development than in production. 👍🏻

@julianklumpers
Copy link

@airtonix does that not need to setup babel and stuff to compile everything? The whole reason we use Vite is for the dx and no need to setup a lot of additional babel stuff.

@airtonix
Copy link

🤷🏻 it's not much tbh. compared to the drama llama of your current situation, i'd happily pick preconstruct.

@black7375
Copy link

black7375 commented May 25, 2022

@airtonix Preconstruct seems to be a monorepo management tool that scaffolding can do.
I think this issue is close to tsconfig's extends or prettier's sharing configurations feature.

IMO, I think we need a feature like webpack-merge of webpack production doc


@julianklumpers
I'am using config builder pattern. (Temporary solution)

@bluwy
Copy link
Member

bluwy commented Jun 26, 2022

Wouldn't using something like esbuild-register pollutes the entire nodejs process? e.g. it would affect Vite SSR too which may bring a slight perf penalty, or perhaps inconsistency. Otherwise we'd need to spawn a child process to read the config, but that only works if the config is serializable.

I tried a different solution, by bundling the monorepo package when detected that the resolved path doesn't contain node_modules. It works but if the package defines it's own __filename or _dirname, it would break loading the config. Plus if you're linking Vite locally too, it'll have to re-bundle it entirely (also suffers from __filename issue).

Overall I'm also not sure if this is worth the complexity.

@Hobart2967
Copy link

Hobart2967 commented Jul 2, 2022

Same issue here.

I created 6 package projects within a Lerna workspace.
Additionally, I created a "config" package, which is symlinked to each. Importing the tsconfig as extends option works fine, also all other tools complain.

When it comes to vite, it states the above error message when importing and using the configuration function from that other config package.

I would really like to have and use this, it's very useful to have the same configs for each project ( all of them bundle the exact same way), shared as one config in one Central place.

Looks like I really need to convert the shared config to a JS file, loosing TS Support :(

@jrson83
Copy link

jrson83 commented Jul 3, 2022

If you want to use a monorepo/workspace with typescript, you should set it up correctly using project references with compilerOptions composite: true, since you can't import an untranspiled .ts file in a transpiled module. Every module (like a plugin) you import into vite.config.ts is already transpiled to js.
Even without vite, the projects need to be referenced. While developing you should use ts-node or tsx, so you don't need to rebuild the files all the time.

There are many different ways to set up a working typescript workspace. I created an example vite-typescript-monorepo.

@Hobart2967
Copy link

If you want to use a monorepo/workspace with typescript, you should set it up correctly using project references with compilerOptions composite: true, since you can't import an untranspiled .ts file in a transpiled module. Every module (like a plugin) you import into vite.config.ts is already transpiled to js. Even without vite, the projects need to be referenced. While developing you should use ts-node or tsx, so you don't need to rebuild the files all the time.

There are many different ways to set up a working typescript workspace. I created an example vite-typescript-monorepo.

May be true, but the expectation from a user's point of view is a little different. If typescript support for configs is coming out of the box, it should be supported fully. In my point of view there's only two ways of solving this:

  1. Remove TypeScript support to avoid "misusage"
  2. Make it work ;)

@airtonix
Copy link

airtonix commented Jul 13, 2022

If you want to use a monorepo/workspace with typescript, you should set it up correctly using project references with compilerOptions composite: true, since you can't import an untranspiled .ts file in a transpiled module. Every module (like a plugin) you import into vite.config.ts is already transpiled to js. Even without vite, the projects need to be referenced. While developing you should use ts-node or tsx, so you don't need to rebuild the files all the time.

There are many different ways to set up a working typescript workspace. I created an example vite-typescript-monorepo.

This is precisely the point of preconstruct.

Feels like everyone is trying to reinvent nodejs to avoid using preconstruct?

because there are several problems here you can keep using your project references with preconstruct.

  1. how do you speed up vscode intellisense resolving and compiling? tsconfig references
  2. how do you resolve packages? either:
    a. a root tsconfig with all the monorepo packages listed in paths, or
    b. just use normal nodejs way where each package has it's own package.json and then you use preconstruct to provide easy no-compile access to ts and non ts tooling.

If you go for 2.a, then you cant dogfood your own typescript tooling packages with any tooling you use that doesn't know how to typescript.

because of that, i choose to work with preconstruct

@btakita
Copy link

btakita commented Jul 24, 2022

Now that vite 3 is using esm, I'm getting an issue with importing *.ts files in monorepo packages (TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"). The "right" answer is to compile the *.ts files. I would love to avoid the extra build step, as it takes development maintenance, time, complexity, & makes the DX considerably worse. Bun will handle this but it's is 6+ months away. At this point, what would be the best way to import *.ts files without having to compile those files? I get that it's a low priority & the "right" answer is to compile the *.ts files, but it sure would be great if it were not necessary.

TSX has been working great for running from the cli. Importing *.ts from monorepo packages worked in vite v2. Now how do we get the automatic transpilation of *.ts back in vite3 + nodejs?

There seems to be a confluence of underlying approaches which cause this problem. The need to automatically transpile ts to js. The need to use a monorepo with separate npm packages. The need to have a quick build...etc

May be true, but the expectation from a user's point of view is a little different. If typescript support for configs is coming out of the box, it should be supported fully. In my point of view there's only two ways of solving this:

  1. Remove TypeScript support to avoid "misusage"
  2. Make it work ;)

Agreed. The problem with partial support for TypeScript is that the support becomes a moving target...such as the ERR_UNKNOWN_FILE_EXTENSION regression between v2 & v3...which makes for some nasty surprises when upgrading.

@evilsamaritan
Copy link

This solution has worked for me:

https://github.com/privatenumber/tsx

"NODE_OPTIONS='--loader tsx/esm' vite ..." // node 18
"NODE_OPTIONS='--import tsx' vite ..." // node 20

@ayonli
Copy link

ayonli commented Dec 13, 2023

@antitoxic The types of package import-single-ts is broken, you specified the type declaration file path to be src/import-single.d.ts, however, the src directory is not included in the published package.

@antitoxic
Copy link

@ayonli you are right, i've just released a new version (1.0.2) which fixes that. Thanks for pointing out!

barankyle added a commit to EtherealEngine/etherealengine that referenced this issue Jan 15, 2024
Path that was being used for fs.existsSync was not correct, missing .ts on
the end, so no extensions were being found or applied.

Import of extensions from correct staticPath triggers a TypeError:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

This is a known issue with vite importing from files in other packages
in a monorepo: vitejs/vite#5370

For now, using the workaround of importing via the relative path. Switching
to bun will apparently solve this.
barankyle added a commit to EtherealEngine/etherealengine that referenced this issue Jan 15, 2024
Path that was being used for fs.existsSync was not correct, missing .ts on
the end, so no extensions were being found or applied.

Import of extensions from correct staticPath triggers a TypeError:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

This is a known issue with vite importing from files in other packages
in a monorepo: vitejs/vite#5370

For now, using the workaround of importing via the relative path. Switching
to bun will apparently solve this.
HexaField pushed a commit to EtherealEngine/etherealengine that referenced this issue Jan 17, 2024
Path that was being used for fs.existsSync was not correct, missing .ts on
the end, so no extensions were being found or applied.

Import of extensions from correct staticPath triggers a TypeError:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

This is a known issue with vite importing from files in other packages
in a monorepo: vitejs/vite#5370

For now, using the workaround of importing via the relative path. Switching
to bun will apparently solve this.
@tazyong tazyong linked a pull request Jan 23, 2024 that will close this issue
9 tasks
@petergaal91
Copy link

I resolved this issue in my case with the following:

We have a monorepo repository with 2 packages: @something/app, @something/common

When I start @something/app vite app - which contains a vue file import from @something/common - I got this error:

Uncaught SyntaxError: Invalid or unexpected token (at ...)

Vite cannot transpile the decorator in my case whether I enable experimentalDecorators and emitDecoratorMetadata or not in the @something/app tsconfig.json file.

I also need to create a tsconfig.json file in the @something/common package folder and enable experimentalDecorators and emitDecoratorMetadata.

@tolu
Copy link
Contributor

tolu commented Feb 17, 2024

I also had this issue (a shared config that I want to import in several other workspaces), but just realized today that the simplest solution for us was to write the shared file as *.mjs and swap Typescript for JSDoc to keep type safety and intellisense.

I realize this might be more work with shared plugins but for colocating a shared config, it was a breeze ✌️

// vite.config.shared.mjs

/** @type {(rootDir: string) => import('vite').UserConfig} */
export const sharedViteConfig = (dirname, pkgName, pkgVersion) => ({
  /* shared config here */
});

@maximan3000
Copy link

This solution has worked for me:

https://github.com/privatenumber/tsx

"NODE_OPTIONS='--loader tsx/esm' vite ..." // node 18 "NODE_OPTIONS='--import tsx' vite ..." // node 20

works for me, but requres node 18+, dependency tsx and permament setting NODE_OPTIONS env

As an alternative, relative path will work and requires 0 setup. In our team we'll wait until out-of-box solution and then replace relative path to package name

// vite.config.ts
// @see https://stackblitz.com/edit/vitejs-vite-lpsoay?file=main.js

import { defineConfig } from 'vite';
// won't work
// import { defaultConfig } from '@foo/tools';
// If use relative import instead of local workspace library, it'll work
import { defaultConfig } from './tools';

export default defineConfig(() => {
  return defaultConfig;
});

PS: Using Bun instead of Node.js also good alternative

@denno020
Copy link

Important update on using Bun - it will work great, until you want to enable a HTTPS server.. Issue is being tracked here: oven-sh/bun#8823

This has prevented my team going for the Bun solution, and instead we've renamed out base vite.config to a .mjs file

@skf-funzt
Copy link

This solution has worked for me:
https://github.com/privatenumber/tsx
"NODE_OPTIONS='--loader tsx/esm' vite ..." // node 18 "NODE_OPTIONS='--import tsx' vite ..." // node 20

works for me, but requres node 18+, dependency tsx and permament setting NODE_OPTIONS env

As an alternative, relative path will work and requires 0 setup. In our team we'll wait until out-of-box solution and then replace relative path to package name

// vite.config.ts
// @see https://stackblitz.com/edit/vitejs-vite-lpsoay?file=main.js

import { defineConfig } from 'vite';
// won't work
// import { defaultConfig } from '@foo/tools';
// If use relative import instead of local workspace library, it'll work
import { defaultConfig } from './tools';

export default defineConfig(() => {
  return defaultConfig;
});

PS: Using Bun instead of Node.js also good alternative

Thanks for sharing @maximan3000 ❤️
Solved my issue importing a typescript remark plugin into my astro.config.ts

❌ Not Working: absolute path

import { remarkDateToday } from 'src/plugins/remark/date-today'

✔️ Working: relative path

import { remarkDateToday } from './src/plugins/remark/date-today'

@MQYForverT
Copy link

Do we have a planned schedule for this issue?

@starsolaris
Copy link

starsolaris commented May 12, 2024

I use next workaround:
Call all necessary functions from typescript, and then run with ts-node (or tsx):
vite.config.ts

import { defineConfig } from "vite";
// import any ts code

export default defineConfig(() => {
  return {...};
});

vite.ts

import { build as viteBuild, createServer, preview as vitePreview } from "vite";
// import any ts code

const start = async () => {
  const server = await createServer({
    configFile: "./vite.config.ts"
  });

  await server.listen();

  server.printUrls();
};

const build = async () => {
  await viteBuild({
    configFile: "./vite.config.ts"
  });
};

const preview = async () => {
  const server = await vitePreview({
    configFile: "./vite.config.ts"
  });

  server.printUrls();
  server.bindCLIShortcuts({ print: true });
};

await start();
tsx vite.ts

I think, this or similar example should be in documentation for running vite app with ts.

@i7eo
Copy link

i7eo commented May 15, 2024

NODE_OPTIONS=\"--import tsx/esm\" pnpm exec vitepress dev

NODE_OPTIONS=\"--import tsx/esm\" pnpm exec vite

Save my time !

@smaven
Copy link

smaven commented May 15, 2024

This solution has worked for me:

https://github.com/privatenumber/tsx

"NODE_OPTIONS='--loader tsx/esm' vite ..." // node 18 "NODE_OPTIONS='--import tsx' vite ..." // node 20

Using "NODE_OPTIONS='--import tsx' works but vite does not watch for changes in the imported package of the monorepo.

// vite.config.ts

import { defineConfig } from 'vite';

// will work but vite won't HMR if @foo/tools change
import { defaultConfig } from '@foo/tools';
// HMR works with relative import
// import { defaultConfig } from './tools';

export default defineConfig(() => {
  return defaultConfig;
});

@MQYForverT
Copy link

This solution has worked for me:
https://github.com/privatenumber/tsx
"NODE_OPTIONS='--loader tsx/esm' vite ..." // node 18 "NODE_OPTIONS='--import tsx' vite ..." // node 20

Using "NODE_OPTIONS='--import tsx' works but vite does not watch for changes in the imported package of the monorepo.

// vite.config.ts

import { defineConfig } from 'vite';

// will work but vite won't HMR if @foo/tools change
import { defaultConfig } from '@foo/tools';
// HMR works with relative import
// import { defaultConfig } from './tools';

export default defineConfig(() => {
  return defaultConfig;
});

My alias configuration is as follows:
resolve.ts

import { resolve } from 'path'
import type { AliasOptions, ResolveOptions } from 'vite'
type IResolve = ResolveOptions & {
	alias?: AliasOptions
}
export function setupViteResolve(): IResolve {
	return {
		alias: {
			'@': resolve(__dirname, './src'),
		},
	}
}

vite.config.ts

import { ConfigEnv, defineConfig } from 'vite'
import { setupViteResolve } from '@mqy/vite-config/resolve'

// https://vitejs.dev/config/
export default defineConfig((configEnv: ConfigEnv) => {
	return {
		resolve: {
			...setupViteResolve(),
		},
	}
})

package.json

	"scripts": {
		"start": "NODE_OPTIONS=\"--import tsx/esm\" pnpm exec vite",
	},

but report a error:ReferenceError: __dirname is not defined
at setupViteResolve (/xxx/resolve.ts:9:17)

@smaven
Copy link

smaven commented May 16, 2024

@MQYForverT I think the error is because __dirname is not defined in ESM. You can use import.meta.dirname instead (you might need need node > 20).

@MQYForverT
Copy link

MQYForverT commented May 17, 2024

@MQYForverT I think the error is because __dirname is not defined in ESM. You can use import.meta.dirname instead (you might need need node > 20).

thanks,But I also get errors when building, and I try to add NODE-OPTIONS="-- import tsx/sm" pnpm exec before it, but there are limitations.
For example, the default:

"Build": "Vue tsc && vite build",

I found this to be feasible

"Build": "NODE-OPTIONS=\" -- import tsx/em \ "pnpm exec vite build",

But adding Vue TSC is not enough, what should I do

"Build": "NODE-OPTIONS=\" -- import tsx/sm \ "pnpm exec vue tsc && vite build",

@garydex
Copy link

garydex commented May 17, 2024

I just realized that we could use a loader like esbuild-register for config loading.

I'm not sure if we can use esbuild-register directly. For now, I can think of a few edge cases:

  1. Each TS module's tsconfig.json isn't correctly respected. We need to reuse https://github.com/dominikg/tsconfck for that.
  2. Plain JS modules that aren't in the project should not be transpiled by esbuild due to performance concerns.

Anyway, this is a low-priority but doable feature. We can fix it when we are going to refactor the configuration loading logic in the future.

@sodatea I'd love to get involved in this. I'm comfortable with using tsconfck and esbuild-register to register tsconfig paths in resolveConfig for the first point, but how would you solve for the second point?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution welcome enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.