Речь в данной статье пойдет о довольно необычном сочетании технологий: Vue.js + TypeScript + Webpack, в разрезе single-file компонентов. Решение данной задачи отняло у меня приличное количество времени с первого захода, поскольку исчерпывающее объяснение того, как использовать все это вместе, да и еще с рядом ограничений (NPM предоставляет нам runtime-only build Vue.js), найти в цельном виде практически невозможно. Если вас заинтересовала данная тема, то приглашаю к дальнейшему чтению.

Думаю причины, по которым вы можете захотеть использовать данное сочетание с Vue.js, довольно-таки очевидны:

  • Типизацию для JS сейчас хотят почти на всех, крупных и не очень, проектах;
  • Webpack — крайне простое, в использовании, и популярное, средство сборки
    проектов (да еще и import/export может)

Теперь следует перейти от предисловия собственно желаемому результату нашей деятельности: на выходе мы хотим получить заготовку приложения, которую сколь угодно много можно расширять дополнительными компонентами и главное: все это будет собираться Webpack-ом.

Начнем мы, как водится, с демонстрации конфигураций package.json и webpack.config, и дальнейшего разбора последнего.

package.json
{
  "name": "vuejs-webpack-ts",
  "version": "1.0.0",
  "description": "Sample project of Webpack+TS+Vue.js ",
  "main": "webpack.config.js",
  "scripts": {
    "start": "webpack-dev-server --hot --inline --history-api-fallback"
  },
  "repository": "https://github.com/StepanZharychev/vue-ts-webpack.git",
  "author": "Stepan Zharychev",
  "license": "ISC",
  "dependencies": {
    "babel-core": "^6.24.0",
    "babel-loader": "^6.4.1",
    "css-loader": "^0.27.3",
    "style-loader": "^0.16.0",
    "ts-loader": "^2.0.3",
    "typescript": "^2.2.1",
    "webpack": "^2.3.2",
    "vue": "^2.3.3",
    "vue-class-component": "^5.0.1",
    "vue-loader": "^12.1.0",
    "vue-property-decorator": "^5.0.1",
    "vue-template-compiler": "^2.3.3",
    "webpack-dev-server": "^2.4.2"
  }
}

webpack.config.js
module.exports = {
    entry: './app/init.ts',
    output: {
        filename: 'dist/bundle.js',
        path: __dirname,
        publicPath: '/static/'
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                options: {
                    configFileName: 'tsconfig.json',
                    appendTsSuffixTo: [/\.vue$/]
                }
            },
            {
                test: /\.js/,
                loaders: ['babel-loader']
            },
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders: {
                        ts: 'ts-loader'
                    },
                    esModule: true
                }
            },
            {
                test: /\.css/,
                loaders: ['style-loader', 'css-loader']
            }
        ]
    },
    devServer: {
        compress: true,
        port: 8001
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js']
    },
    devtool: 'source-map'
};

Момент, который интересует нас больше остальных, — это использование vue-loader. Как можно заметить в параметрах options, мы указываем еще одну коллекцию лоадеров, и понадобится она нам как раз для того, чтобы при парсинге vue темплейта webpack смог правильно обработать зависимости с отличным от стандартного типом. Теперь я предлагаю вспомнить (или изучить) базовую структуру vue компонента.


Компонент имеет абсолютно типичную для компонентного подхода структуру: темплейтскрипт(ts в нашем случае) — CSS. И как раз скрипт и представляет для нас наибольшую проблему в данном случае: т.к. должен быть обработан предварительно. На стороне Webpack проблему мы решили, теперь ее осталось решить внутри компонента, делается добавлением атрибута lang с соответствующим расширением.

main.vue(рутовый компонент)
<template>
    <hello :message="message"></hello>
</template>
<script src="./main.ts" lang="ts"></script>
<style src="./main.css"></style>

main.ts
import Vue from 'vue'
import Component from 'vue-class-component'
import HelloComponent from '../hello/hello.vue'

@Component({
    components: {
        hello: HelloComponent
    }
})
export default class MainComponent extends Vue {
    public message = 'Hello there, Vue works!'
}

Пара слов о @Component
Как можно заметить перед классом нашего компонента находится декоратор, который занимается тем, что «переводит» код компонента в более привычный (в форме объекта) для Vue.js формат, использование данного декоратора не является обязательным, но позволяет писать более читаемый код. (так же хорошим дополнением являются «property decorators»)

Компонент такого вида будет без проблем собран Webpack-ом! Дело за малым, осталось добавить инициализацию нашего небольшого приложения аккурат в точку входа. Но именно тут может возникнуть проблема с импортом…

import MainComponent from './components/main/main.vue'

… поскольку TS ничего не знает о наших vue темплейтах, но это вообщем-то не проблема, все требуется сделать в данном случае — это задекларировать в d.ts файл модуль следующего вида:

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

Эта пара строчек кода «объяснит» TS-у как именно обрабатывать импорт *.vue файлов да и работает все по довольно очевидной причине: все наши компоненты наследовались от Vue.

Теперь можно закончить написание нашего index.ts:


import Vue from 'vue'
import MainComponent from './components/main/main.vue'

class AppCore {
    private instance: Vue;

    private init() {
        this.instance = new Vue({
            el: '#appContainer',
            render: h => h(MainComponent),
        })
    }

    constructor() {
        this.init();
    }
}

new AppCore();

Здесь происходит довольно типичный для инициализации Vue-приложения вызов конструктора, но может возникнуть вопрос зачем нужно указывать render, почему нельзя просто указать темплейт и использовать там рутовый компонент? Дело тут вот в чем, дефолтная версия vue.js из npm (она же по совместительству — лучшая по производительности) является runtime-only build версией, что обозначает невозможность парсинга наших темплейтов налету, и из-за этого мы должны указать render функцию с рутовым компонентом, как точку входа.

Немного о билде
Тут может возникнуть еще более резонный вопрос: почему компонент внутри компонента рендерится нормально? Дело тут вот в чем, внутри vue-loader живет специальный компонент, который занимается тем, что переводит наши темплейты в рендер-функции на стадии билда, и в дальнейшем vue.js пользуется уже собранными функциями без необходимости выполнять парсинг самостоятельно (очевидный + к производительности). Резюмируя: все вложенные компоненты будут использованы уже в виде рендер-функций, а вот для инициализации рутового придется прописать ее самостоятельно по понятным причинам.

Надеюсь, что после данного разбора, вам стало более понятно, как нужно собирать vue компоненты c TS и Webpack. За полным примером можете пройти сюда.
Поделиться с друзьями
-->

Комментарии (10)


  1. izzholtik
    07.06.2017 11:26
    +1

    (\


  1. valsaven
    07.06.2017 14:52

    Актуально. Спасибо.


  1. aliev
    07.06.2017 18:56

    Есть примеры использование с typescript methods, computed и пр?
    Vuex как приживается?


    1. Kozack
      07.06.2017 21:43

      Вот пример от Nuxt.js


    1. StepanZharychev
      07.06.2017 22:21

      Что вас конкретно интересует в плане computed и пр.?


  1. paulgray
    08.06.2017 20:50

    Странно, у Вас стоит тэг «webpack 2», а конфиг в статье на первую версию


    1. StepanZharychev
      08.06.2017 20:51

      Если вы мне сможете сказать, чем должен отличать конфиг второго webpack, буду премного благодарен. Но увы документация говорит о том, что такая структура правильная. Да и использован в проекте-примере webpack 2.


      1. paulgray
        08.06.2017 23:30
        +1

        Первое что бросилось в глаза это старый ключ module.loaders вместо нового module.rules


        1. StepanZharychev
          09.06.2017 09:44

          Ну что же дельное замечание, тут стоит отметить, что пока webpack поддерживает и то, и другое, но в будущем loaders может уйти в deprecated, так что замечание в целом корректное.


  1. eshimischi
    12.06.2017 10:45

    Есть готовые решения для дополнительного изучения на вашу тему vue2+ts2, Vueify+ts2, Microsoft offisial template, Boilerplate, Vue-typed и тд