While switching from rollup-plugin-typescript
to rollup-plugin-typescript2
in order to produce declaration files, the *.vue
files are not recognized anymore.
[!] (rpt2 plugin) Error: someFolder/index.ts(2,53): semantic error TS2307 Cannot find module './component.vue'.
src\index.ts
Error: someFolder/index.ts(2,53): semantic error TS2307 Cannot find module './component.vue'.
Trying by just importing rollup-plugin-typescript
instead of rollup-plugin-typescript2
bundles without a problem.
This could be bound to this issue though I have the last version (of every plugin today indeed).
Could you post your tsconfig and rollup config?
Or a small repo with reproduction :)
I unfortunately don't have a "small" repo. I am working here, trying to migrate from webpack
to rollup
.
The import in rollup.config.js
can be changed to rollup-plugin-typescript2
to see the difference.
Hello.
First of all thank you very much for working on this plugin. It does indeed make much more sense than rollup-plugin-typescript
.
I can confirm that this issue exists and setup a small demo repository:
https://github.com/danimoh/rollup-plugin-typescript2-vue-demo
If you commment out the line import AnotherComponent from './AnotherComponent.vue';
it does compile, but unfortunately not with this line enabled.
It's funny that we encountered this issue around the same time. Maybe it was caused by a recent change?
A guess from my side with very limited knowledge about rollup, rollup plugins and typescript would be:
Is it possible that typescript itself is trying to import AnotherComponent
instead of rollup or rollup-plugin-vue
handling that import first?
That would explain why rollup-plugin-typescript
does not have this issue as it compiles on a per file basis with transpileModule
.
In this case, the following might be interesting: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#customizing-module-resolution
Any work on this issue is very much appreciated.
Reproduced on both repos, not sure if it is the same problem yet, but it is likely.
To fix second case we do indeed need to send module resolution back to rollup so that vue plugin can do it's thing.
Problem with hooking up rollup's module resolution and typescript is that rollup in later versions returns a Promise from context.resolveId(...)
. So the call chain looks like this:
LanguageService.getEmitOutput
LanguageHost.resolveModuleNames
and expects resolved paths to be returned in that functionPluginContext.resolveId
LanguageService
seems to be strictly synchronous: https://github.com/Microsoft/TypeScript/issues/1857
Plugin.transform
can itself return a Promise, but the mechanics of chaining multiple promises made deep inside callbacks on an observer object elude me at the moment.
Somewhat related: https://github.com/rollup/rollup/issues/2631
Hello ezolenko.
Nowadays asynchronous methods in Javascript mostly means methods returning Promises. The new async
/await
syntax is essentially just syntactic sugar for asynchronous methods that enables the developer to write his code similar to synchronous code, async
methods still return promises. await
can only be used in async
methods though. As you noticed, the LanguageHost.resolveModuleNames
method is not asynchronous and therefore it is not possible to return from that method only after waiting for a promise in plain Javascript.
However, in NodeJs, stuff like this is actually possible by yielding the synchronous method on the current thread, then jumping to the async method and jumping back to the synchronous method when the asynchronoues method resolves. See Fibers or wrappers around it like synchronize.js.
Thus, the thing with the async method invocation is not much of a problem actually. There might be another problem though. While the plugin context offers a method resolveId
, that's not gonna be enough. We need to call the transform
of rollup-plugin-vue
to extract the typescript code from the vue single file components. The plugin context does not seem to offer that functionality though unfortunately.
One approach to solve this might be to add rollup-plugin-vue
as a dependency to your project and trigger the the transform
on the vue plugin directly. That's for sure not beautiful at all and not the intended way to work with rollup plugins.
Another approach might be to run only transpileModule
in transform
on a per-file basis in a first run to let rollup collect all the imports, let the vue plugin transform
the single file components and cache the extracted typescript code. Then before rollup finishes, discard the transpiled code and do a proper typescript compilation on the code we cached in a renderChunk
or generateBundle
plugin hook. This might interfere with other plugins though that apply additional transformations to the code that we would discard.
For now I don't see a more beautiful solution yet.
Edit: On second thought, the second approach is maybe not _that_ ugly. Instead of renderChunk
or generateBundle
hooks, the plugin could detect itself when the last import is being imported and at that point start the compilation from scratch and add the compiled file to the rollup queue such that it can actually be processed by all the other plugins also. The previously generated files still need to be discarded though to avoid that they end up in the final bundle.
Still, this approach wastes some processing time as it let's the other plugins also on files we'd discard anyways.
@danimoh @eddow Workaround for both example repos are disabling error checking with // @ts-ignore
just above offending imports.
The error is basically typescript complaining that it doesn't know what type *.vue stuff is (Cannot find module
means module type). Once that is silenced, everything seems to compile correctly. Downside is that things imported from vue files have any
type and don't help with error checking.
(in the minimal repo, first component needs a reference to the second one, otherwise rollup treeshakes it away from the bundle)
@danimoh yep, no way to get module source from rollup via context. Most of it can be done on vue plugin side (I opened a case there), but there are still potential pitfalls, like rpt2 will need to have transformed extracted script before transforming a script that imports it.
I think the approach you describe in the vue issue thread requires typescript to process the files one at a time as it essentially ignores the vue file imports and waits for rollup to feed them back to typescript. Therefore you'd loose type checking across files.
As an alternative to letting the vue plugin handle ts, the following might be a valid approach and a sort of better iteration of the ugly hacks I proposed earlier:
options
hook?transform
method on the vue plugin.CompilerHost
and LanguageServiceHost
can be fed custom fileExists
, readFile
, getScriptSnapshot
and the like..vue
files. For all vue files, we cache the extracted typescript code and overwrite methods like readFile
to return that cached ts code for a vue
import.Edit: Actually, if we can overwrite fileExists
and readFile
, we'd not need to collect imports ourselves by traversing the AST as typescript is just gonna call these methods for all imports it wants to import anyways. We then just need to call the vue plugin on demand.
Vue plugin instance is probably exposed, I don't know if rollup expects plugins to call each other and if something will break (immediately or in the future) in that case.
Your second point will work, that's exactly what LanguageServiceHost
is for.
This approach might work, the main downside being potentially fragile coupling with vue plugin and extra cycles for throwaway work.
I wish rollup had a way for plugins to abort transform and declare a dependency to be transformed before current file being retried, then this could be implemented cleanly...
I think there is actually no throwaway work. Typescript will only compile the code once and also the vue plugin will need to process every file only once if we cache the extracted ts code. This approach does not discard any of it's own results or results of other plugins other than my previous suggestion.
Yeah, some architectural change would be required in rollup. Maybe one could open an issue there to implement something like this but it would probably take ages.
Also I'm not sure whether it makes things really much better. We still have to ensure that typescript compiles the whole thing at once to do type checking across all files. Otherwise we might also run into this.
It works for me with this setup, to compile a single vue component:
import VuePlugin from 'rollup-plugin-vue'
import typescript from 'rollup-plugin-typescript2'
export default {
plugins: [
typescript({
typescript: require('typescript'),
objectHashIgnoreUnknownHack: true,
}),
VuePlugin(/* VuePluginOptions */),
],
input: 'src/components/HelloWorld.vue',
output: [
{ file: 'dist/HelloWorld.cjs.js', format: 'cjs' },
{ file: 'dist/HelloWorld.esm.js', format: 'esm' },
],
}
I'm not sure if this is the best way to create a module out of a Vue SFC with lang="ts".
It works for me with this setup, to compile a single vue component:
Right, but that's not a real use case. That's hello world. For anyone having trouble understanding the problem, here's what I've gleaned.
why? here's an example:
<script lang="ts">
import Bar from './Bar.vue';
...
</script>
1) vue plugin passes script over to typescript plugin
2) typescript plugin encounters a .vue
file, but has no way of knowing what to do with it because rollup doesn't provide a mechanism for plugins to defer to other plugins on imports like webpack. Regular JS can defer to plugins, but code already being processed by a plugin cannot.
3) I actually don't understand why this is different than lang=scss
or lang=ts
, but it is I guess.
Well, what can I do?
Not much.
But vuetify! buefy!
Vuetify is typescript, but doesn't use SFCs. It's pure render functions.
Buefy uses SFCs and rollup, but no typescript.
Really though, there's nothing I can do?
You aren't going to like it. For every Vue file you need to import from a typescript file, you'll have to create a regular javascript file intermediary.
import Bar from './Bar.vue';
export default Bar;
Then and only then will you be able to build your typescript SFC component lib with rollup.
geez, that sucks
If you've come up with a better solution, I'd love to hear it.
Most helpful comment
Right, but that's not a real use case. That's hello world. For anyone having trouble understanding the problem, here's what I've gleaned.
rollup literally can't do this
why? here's an example:
1) vue plugin passes script over to typescript plugin
2) typescript plugin encounters a
.vue
file, but has no way of knowing what to do with it because rollup doesn't provide a mechanism for plugins to defer to other plugins on imports like webpack. Regular JS can defer to plugins, but code already being processed by a plugin cannot.3) I actually don't understand why this is different than
lang=scss
orlang=ts
, but it is I guess.Not much.
Vuetify is typescript, but doesn't use SFCs. It's pure render functions.
Buefy uses SFCs and rollup, but no typescript.
You aren't going to like it. For every Vue file you need to import from a typescript file, you'll have to create a regular javascript file intermediary.
Then and only then will you be able to build your typescript SFC component lib with rollup.
If you've come up with a better solution, I'd love to hear it.