- PVSM.RU - https://www.pvsm.ru -

Типизированные компоненты в Vue.js, или как подружить Vue, TypeScript и Webpack

Типизированные компоненты в Vue.js, или как подружить Vue, TypeScript и Webpack - 1

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

Думаю причины, по которым вы можете захотеть использовать данное сочетание с 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: {
        loaders: [
            {
                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 компонента.

Типизированные компоненты в Vue.js, или как подружить Vue, TypeScript и Webpack - 2

Компонент имеет абсолютно типичную для компонентного подхода структуру: темплейтскрипт(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

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

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

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

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

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 живет специальный компонент [6], который занимается тем, что переводит наши темплейты в рендер-функции [7] на стадии билда, и в дальнейшем vue.js пользуется уже собранными функциями без необходимости выполнять парсинг самостоятельно (очевидный + к производительности). Резюмируя: все вложенные компоненты будут использованы уже в виде рендер-функций, а вот для инициализации рутового придется прописать ее самостоятельно по понятным причинам.

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

Автор: StepanZharychev

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/257297

Ссылки в тексте:

[1] single-file компонентов: https://vuejs.org/v2/guide/single-file-components.html

[2] runtime-only build Vue.js: https://vuejs.org/v2/guide/installation.html#Explanation-of-Different-Builds

[3] декоратор: https://www.typescriptlang.org/docs/handbook/decorators.html

[4] «property decorators»: https://www.npmjs.com/package/vue-property-decorator

[5] d.ts: https://basarat.gitbooks.io/typescript/docs/types/ambient/d.ts.html

[6] специальный компонент: https://www.npmjs.com/package/vue-template-compiler

[7] рендер-функции: https://vuejs.org/v2/guide/render-function.html

[8] сюда: https://github.com/StepanZharychev/vue-ts-webpack

[9] Источник: https://habrahabr.ru/post/330400/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox