Vue-loader: cant work with typescript

Created on 22 Dec 2015  ·  37Comments  ·  Source: vuejs/vue-loader

structure of the project

image

webpack.config.js

module.exports = {
    entry: './src/js/app.js',
    output: {
        path: 'dist/',
        filename: './js/bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader'
            },
            {
                test: /\.vue$/,
                loader: 'vue'
            }
        ]
    }
}

app.ts

declare var require;
declare var Vue;

new Vue({
    el: 'body',
    components: {
        'nani': require('../components/nani.vue')
    }
})

nani.vue

<template><p>纳尼?你再说一次.</p></template>

<script lang="ts">
    export = {
        methods: {
            sayHello: function () {
                alert('hello')
            }
    }
</script>

each time I run webpack, it always report this error,

$ webpack                                                                                                                     ‹ruby-2.1.4›
ts-loader: Using [email protected]
Hash: 54f28de48a69455dfb02
Version: webpack 1.12.9
Time: 1239ms
         Asset     Size  Chunks             Chunk Names
./js/bundle.js  2.74 kB       0  [emitted]  main
   [0] ./src/js/app.js 178 bytes {0} [built]
    + 4 hidden modules

ERROR in ./~/ts-loader!./~/vue-loader/lib/selector.js?type=script&index=0!./src/components/nani.vue
Module build failed: Error: Could not find file: '/Users/tcstory/WebstormProjects/test/src/components/nani.vue'.
    at getValidSourceFile (/Users/tcstory/WebstormProjects/test/node_modules/typescript/lib/typescript.js:43586:23)
    at Object.getEmitOutput (/Users/tcstory/WebstormProjects/test/node_modules/typescript/lib/typescript.js:46680:30)
    at Object.loader (/Users/tcstory/WebstormProjects/test/node_modules/ts-loader/index.js:410:34)
 @ ./src/components/nani.vue 2:17-111

how to solve this problem?

Most helpful comment

Hey all, here's a sample repo of TypeScript working with .vue files. It's still a bit of a work in progress, but it'll get the job done for you. https://github.com/DanielRosenwasser/typescript-vue-todomvc

It currently uses some augmented .d.ts files that I've been working on, and right now type checking requires that you write

export default Vue.extend({
  // ... component options here ...
});

instead of just

export default {
  // ... component options here ...
};

You'll also always need an export default from a .vue file so that TypeScript knows that it's a module.

If you use it, try it out with the Vetur VS Code plugin, which can actually give you some of the same experience TypeScript does in .ts files.


vue-loader specific things:

  • Of note: you'll need to write <script lang="ts"> for your script tags
  • You'll need to use the appendTsSuffixTo option
  • To import from .vue files, you'll need a declare module "*.vue"-type thing. Specifically this:

    declare module '*.vue' {
    import Vue from 'vue';
    declare const component: typeof Vue;
    export default component;
    }
    

    which I realize could probably have just been written as

    declare module '*.vue' {
    import Vue from 'vue';
    export default Vue;
    }
    

@ARomancev maybe your code doesn't work because you're default-exporting the type of Vue, not Vue itself.

All 37 comments

visit the link to see the project
https://github.com/tcstory/vue-loader-test

Interestingly this is because ts-loader is a big hack and is not a in-memory transform (i.e. it relies on actual files on disk), so it can't work with *.vue files... I don't think there's an easy way to make this work, unless someone who knows more about TypeScript can figure out why ts-loader is implemented that way.

On another look, you may need to set the transpileOnly flag to true: https://github.com/TypeStrong/ts-loader#transpileonly-boolean-defaultfalse

:) thanks

I too, have tried to setup a TS environment using vue components (I love the idea that it is based on, its small site, and the goal to keep it that way), and while using vue in a plain ts file setup is not a big problem (you can even user nice decorators) , the vue files is a problem as stated above.

Further more, the editor support for the vue files is a problem too, as some kind of .d.ts file are missing too.

It would be nice if the vue-loader could optionally write a ts (or a d.ts) file in the same path as the vue component. This would fix the above problem, but more importantly it would make better IDE support too, as the tsc now knows the components types too.

Do you think this problem could be solved by the shorthand ambient module declarations proposal ?

Hey @yyx990803 can you elaborate a little on the problems? I'm not that familiar with Webpack's internals, but it looks like TypeScript is being fed the .vue file itself. I don't necessarily know why that would occur, since we don't try to resolve plain calls to require AFAIK.

just to clarify, will ts hot-reload with non-*.vue files?
i.e: with vue-typescript ?
in this case, vue-loader wouldn't be used at all.

I have managed to get vuex to hot-reload with typescript, but the UI does'nt reload

thanks

You can make vue-loader work with TS, if you put ts code in separate file.

myComponent.vue:

<template>
    ...
</template>
<style>
    ...
</style>
<script lang="ts" src="./myComponent.vue.ts"></script>

myComponent.vue.ts:

TS code goes here

You can use both 'ts' and 'awesome-typescript' loaders, just override ts lang option in vue-loader settings.

Sample usage here:

https://github.com/yegor-sytnyk/contoso-express/ branch alt/vue

@DanielRosenwasser It seems vue-loader will transform .vue file into JavaScript, where content <script> tag will be transformed into seome require call like
require("!!ts!./../../node_modules/vue-loader/lib/selector.js?type=script&index=0!./nani.vue")

Mind the selector.js in the middle. It comes from vue-loader and extract content in the script tag of a vue file. Then selector.js feed the extracted content to callback, which calls ts-loader defined here.

Here comes the problem. ts-loader do compile file by using filePath, not content. vue-loader feeds to ts-loader file path like *.vue rather than *.ts. TSC rejects vue file and ignores extracted content.

From what I understand of Webpack, sounds like if vue-loader was able to know to create a .ts intermediate file, things would "just work", but I could be wrong.

I found as long as the filename ends with .ts, typescript will compile. ts-loader has already used a languageService so no intermediate file is needed. I could be wrong because I haven't tested multiple files but from reading the source I guess it would work.

If anyone is still interested in using TypeScript in *.vue, vue-ts-loader is a choice to have a look.

@HerringtonDarkholme what changes are there between vue-ts-loader and ts-loader? It looks like there are only baseline changes.

The key change is just a suffix hack. https://github.com/HerringtonDarkholme/vue-ts-loader/commit/d581d4635776429ccf8090c11a1163710ad0a398

And all *.vue files are served to tsc by getScriptSnapshot call rather than readFile on disk. So when a vue file is feed to tsc, it has already been processed by vue-loader.

This is the easiest and fastest way I can come up.

ts-loader needs much maintenance. most test baselines are outdated

I see - would it not be better to have vue-loader generate a different extension for output files when the language="typescript"?

Oh - I think I see. Loaders don't have control over "generating" files. That file exists on its own and no new distinct files are created (even virtually). Is that correct?

Exactly.

I think a good approach is to support non-ts file by loading extension in tsc. And that should be done by vue-ts-loader so that vue-loader has a uniform interface.

So ts-loader should just accept arbitrary file extensions? I think that'd be the right call.

Maybe @mhegazy and @jbrantly can weigh in here on any risks of (or previous issues with) that. Also CCing @TheLarkInn.

Closing, but here's a guide if anyone is trying to get latest vue-loader working with TS2: http://herringtondarkholme.github.io/2016/10/03/vue2-ts2/

add ts-loader with options: AppendTsSuffixTo works. @DanielRosenwasser

@doun How did you do that? Please tell me the details.

@doun It's my fault. I gone well when I remove { modules: false } options in babel.
Here is my webpack2 config.

module: {
        rules: [
            {
                test: /\.js/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    // presets: ['es2015', { modules: false }] <- fail
                    presets: ['es2015']
                }
            },
            {
                test: /\.ts$/,
                exclude: /node_modules|vue\/src/,
                use: [
                    {
                        loader: 'ts-loader',
                        options: {
                            appendTsSuffixTo: [/\.vue$/]
                        }
                    },
                ]
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    esModule: true,
                }
            },
        ]
    }

Has anyone gotten vue2 + typescript2 working with hot reloading using .vue component files?

Yeps, it works nicely. I think the trick it that webpack uses websockets, and there are a few scripts out there that mix this in a not always functional way.

Has anyone gotten vue2 + typescript2 working with hot reloading using .vue component files?

@druppy Yes, I just got it working when I was converting a project over from JavaScript to TypeScript.

See my comment on how I fixed this Error: Could not find file error with ts-loader and appendTsSuffixTo option.

Still won't work for me. Using ts-loader v2.0.3.
references.d.ts:

declare module "*.vue" {
  import Vue from 'vue'
  export default typeof Vue
}

tsconfig.json:

{
  "compilerOptions": {
    "lib": [
      "dom",
      "es5",
      "es2015",
      "es2015.promise"
    ],
    "module": "es2015",
    "moduleResolution": "node",
    "target": "es5",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true
  },
  "exclude": [
    "node_modules"
  ]
}

webpack.conf.js:

...
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/]
        }
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          esModule: true
        }
      },
...

Getting this output:

ERROR in ./~/ts-loader!./~/vue-loader/lib/selector.js?type=script&index=0!./src/app.vue
Module build failed: Error: Could not find file: '/home/andrew/Documents/projects/mine-vue/src/app.vue'.
    at getValidSourceFile (/home/andrew/Documents/projects/mine-vue/node_modules/typescript/lib/typescript.js:85887:23)
    at Object.getEmitOutput (/home/andrew/Documents/projects/mine-vue/node_modules/typescript/lib/typescript.js:86252:30)
    at getEmit (/home/andrew/Documents/projects/mine-vue/node_modules/ts-loader/dist/index.js:99:43)
    at Object.loader (/home/andrew/Documents/projects/mine-vue/node_modules/ts-loader/dist/index.js:27:11)
 @ ./src/app.vue 7:2-94
 @ ./src/main.ts

But if i just replace ts-loader with vue-ts-loader and change script attr to lang="vue-ts" in .vue it works fine:

      {
        test: /\.tsx?$/,
        loader: 'vue-ts-loader'
      },

Any ideas?

Hey all, here's a sample repo of TypeScript working with .vue files. It's still a bit of a work in progress, but it'll get the job done for you. https://github.com/DanielRosenwasser/typescript-vue-todomvc

It currently uses some augmented .d.ts files that I've been working on, and right now type checking requires that you write

export default Vue.extend({
  // ... component options here ...
});

instead of just

export default {
  // ... component options here ...
};

You'll also always need an export default from a .vue file so that TypeScript knows that it's a module.

If you use it, try it out with the Vetur VS Code plugin, which can actually give you some of the same experience TypeScript does in .ts files.


vue-loader specific things:

  • Of note: you'll need to write <script lang="ts"> for your script tags
  • You'll need to use the appendTsSuffixTo option
  • To import from .vue files, you'll need a declare module "*.vue"-type thing. Specifically this:

    declare module '*.vue' {
    import Vue from 'vue';
    declare const component: typeof Vue;
    export default component;
    }
    

    which I realize could probably have just been written as

    declare module '*.vue' {
    import Vue from 'vue';
    export default Vue;
    }
    

@ARomancev maybe your code doesn't work because you're default-exporting the type of Vue, not Vue itself.

@DanielRosenwasser yes, that was the case. Thanks!

Any idea how to use babel loader in conjunction with ts-loader, because this wont work:

{ // ts
    test: /\.tsx?$/,
    use: [{
        loader: 'babel-loader',
    }, {
        loader: 'ts-loader',
        options: {
            appendTsSuffixTo: [/\.vue$/]
        }
    }],
    exclude: /node_modules/,
},

...

{ // vue
    test: /\.vue$/,
    loader: 'vue-loader',
    options: {
        loaders: {
            scss: ExtractTextPlugin.extract(cssExtract(false))
        },
        options: {
            esModule: true
        }
    }
}

@StevenTheEVILZ why would you need babel if you have TS?

@DanielRosenwasser I've noticed your example repo is using custom vue / vue-router dependencies, does it still require them? if so do you know when they'll be able to work with the official vue/vue-router packages?

@blocka because babel allow you to use es6 api features (non-syntax features) such as iterators, generators, async functions, promises, etc. It automatically polyfills it in for you. That's why lots of people use babel with typescript.

@StevenTheEVILZ babel doesn't automatically pollyfill any run-time behavior for you.

Something about babel-loader polyfills because I can use iterator. But now I realize that the above actually works, and it's not babel causing the problem, it's typescript. Sorry about that.

Does this compile the ts in components twice?

{
    test: /\.vue$/,
    loader: 'vue-loader', // doesn't want to be use for whatever reason
    options: {
        loaders: {
            scss: cssExtract(false),
            ts:  [{
                loader: 'babel-loader',
            }, {
                loader: 'ts-loader',
                options: {
                    appendTsSuffixTo: [/\.vue$/]
                }
            }]
        },
        options: {
            esModule: true
        }
    }
},
{ // ts
    test: /\.tsx?$/,
    use: [{
        loader: 'babel-loader',
    }, {
        loader: 'ts-loader',
        options: {
            appendTsSuffixTo: [/\.vue$/]
        }
    }],
    exclude: /node_modules/, // not sure if needed
}, 

I'm with @mythz in wondering if there is a way to get this working on the vue master branch. @DmacMcgreg asks a good question, and I'm wondering if there is an answer to it using the existing "production APIs" . @DanielRosenwasser 's todoMCV is cool because it's a working example. I just wish Vue truly had first class support for TypeScript in it's best features, like https://vuejs.org/v2/guide/single-file-components.html .

Was this page helpful?
0 / 5 - 0 ratings

Related issues

birdgg picture birdgg  ·  3Comments

C0deZLee picture C0deZLee  ·  3Comments

sdvcrx picture sdvcrx  ·  3Comments

TheVexatious picture TheVexatious  ·  3Comments

matt-sanders picture matt-sanders  ·  4Comments