
Речь в данной статье пойдет о довольно необычном сочетании технологий: 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!' }
Компонент такого вида будет без проблем собран 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 компоненты c TS и Webpack. За полным примером можете пройти сюда.

