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

в 7:39, , рубрики: components, javascript, TypeScript, vue.js, webpack 2, Программирование, системы сборки, метки: ,
Типизированные компоненты в Vue.js, или как подружить Vue, TypeScript и Webpack - 1

Речь в данной статье пойдет о довольно необычном сочетании технологий: 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: {
        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

Как можно заметить перед классом нашего компонента находится декоратор, который занимается тем, что «переводит» код компонента в более привычный (в форме объекта) для 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. За полным примером можете пройти сюда.

Автор: StepanZharychev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js