Речь в данной статье пойдет о довольно необычном сочетании технологий: 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 компонента.
Компонент имеет абсолютно типичную для компонентного подхода структуру: темплейт — скрипт(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. За полным примером можете пройти сюда.
Автор: StepanZharychev