Создаем на Visual Studio 2017 модульное приложение Vue.js + Asp.NETCore + TypeScript без использования Webpack или Broserify. Причем сначала делаем проект с использованием Webpack, а потом без него. Чтобы прочувствовать, от какого счастья мы отказываемся.
Материал рассчитан на способных управиться с VS2017 и знакомых с прогрессивным JavaScript фрэймворком Vue.js.
Цель замены системы сборки – снижение стартового барьера для освоения Vue.js за счет уменьшения количества применяемых инструментов при создании современных веб-приложений.
Кроме того, существенно упрощается процесс отладки приложений, сильно сокращается время на пересборку приложения, а для части сценариев работы — пересборка не требуется совсем.
Содержание
Введение
Проект TryVueWebpack
— Создание стартовой болванки
— Добавление программного кода
— Добавление файлов конфигурации
— Сборка и запуск приложения
Проект TryVue
— Создание стартовой болванки
— Копирование программного кода
— Добавление файлов конфигурации и компиляция
— Сборка шаблонов и CSS
— Создание index.html и запуск приложения
Заключение
Введение
Webpack – очень мощный и полезный инструмент, который позволяет делать практически всё. Вам, рано или поздно, придется его освоить, если собираетесь заниматься веб-разработкой серьезно.
Но когда изучаешь Vue.js одновременно с TypeScript, очень хочется убрать лишних посредников. Хотя бы при освоении новых технологий и на простых задачах. Сложно разбираться c TypeScript, когда получаемый на выходе код является результатом переваривания кучей фильтров и конверторов Webpack.
«Прежде, чем объединяться, и для того, чтобы объединиться, мы должны сначала решительно и определенно размежеваться». В.И. ЛенинВ нашем проекте TryVue без Webpack: большую часть работы по сборке и установлению взаимосвязей между модулями будет делать штатный компилятор TypeScript, загрузку модулей во время исполнения – System.js, конкатенацию шаблонов и файлов CSS – Bundler&Minifier (почти штатное расширение к VS2017).
Функции каждого участника процедуры сборки приложения четко определены, а артефакты легко посмотреть и убедиться, что полученное соотвествует вашим ожиданиям.
Сначала собираем модульное приложение с использованием Webpack. Затем переводим полученное приложение с Webpack на сборку штатным компилятором TypeScript + Bundler&Minifier.
Для иллюстрации способа избавления от Webpack подготовлено решение Asp.Net Core с двумя проектами: TryVueWebpack, TryVue (исходники на gihub). Структура проекта TryVueWebpack приведена на скриншоте, что-то подобное вы получите после выполнения описанных здесь действий.
Проект TryVueWebpack
При написании этого руководства использовался пример TypeScript Vue Starter от разработчиков TypeScript из компании Microsoft. Исходный код примера реструктурирован и превращен в проект Asp.Net Core на Visual Studio 2017.
Создание стартовой болванки
Для начала создаем стартовую заготовку для приложения. В качестве отправной точки используем проект «Веб-приложение ASP.NET Core» по шаблону «Пустой»
В новом проекте создаем стартовую страницу wwwroot\index.html, в которой определяем место внедрения приложения Vue.js, а также загружаемый js-файл.
<!-- wwwroot/index.html-->
...
<body>
<div id="app-root">loading..</div>
<script src="./dist/build.js"></script>
</body>
Затем обеспечиваем открытие index.html страницы при запуске приложения Asp.Net Core, изменив текст класса в файле Startup.cs.
// Startup.cs
...
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDefaultFiles();
app.UseStaticFiles();
}
}
Сейчас можно проверить, что приложение почти запускается (VS2017 запустит IIS Express, в браузере отобразится «loading...»).
Добавление программного кода
Теперь добавляем в приложение vue-компоненты и другой необходимый код для компиляции и работы приложения.
В каталоге ClientApp\components создаем 6 файлов для двух vue-компонент. Для каждой компоненты стили CSS и код TypeScript выносим в отдельные файлы, а в vue-файле оставляем только шаблон (плюс ссылки на css, ts). Однофайловые Vue-компоненты в чистом виде применять не будем (чтобы не ставить плагин для подсветки синтаксиса и т.д.).
<!-- ClientApp/components/Hello.vue -->
<template>
<div>
<div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
<script src="./Hello.ts" lang="ts"></script>
<style src="./Hello.css"></style>
// ClientApp/components/Hello.ts
import Vue from "vue";
export default Vue.extend({
props: ['name', 'initialEnthusiasm'],
data() {
return {
enthusiasm: this.initialEnthusiasm
}
},
methods: {
increment() { this.enthusiasm++; },
decrement() {
if (this.enthusiasm > 1) {
this.enthusiasm--;
}
},
},
computed: {
exclamationMarks(): string {
return Array(this.enthusiasm + 1).join('!');
}
}
});
/* ClientApp/components/Hello.css */
.greeting {
font-size: 20px;
}
<!-- ClientApp/components/AppHello.vue -->
<template>
<div>
Name: <input v-model="name" type="text" />
<HelloComponent :name="name" :initialEnthusiasm="5" />
</div>
</template>
<script src="./AppHello.ts" lang="ts"></script>
<style src="./AppHello.css"></style>
// ClientApp/components/AppHello.ts
import Vue from "vue";
import HelloComponent from "./Hello.vue";
export default Vue.extend({
data() {
return {
name: "World"
}
},
components: {
HelloComponent
}
});
/* ClientApp/components/AppHello.css */
body {
background-color: aliceblue;
}
В каталоге ClientApp создаем файл index.ts, используемый как точка входа в приложение, а также заглушку vue-stub.ts, которая позволит компилятору TypeScript понимать, что делать с vue-модулями.
// ClientApp/index.ts
import Vue from "vue";
import AppHelloComponent from "./components/AppHello.vue";
let v = new Vue({
el: "#app-root",
template: '<AppHelloComponent />',
//render: h => h(AppHelloComponent),
components: {
AppHelloComponent
}
});
// vue-stub.ts
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
Добавление файлов конфигурации
Определяем конфигурацию NPM (менеджера пакетов Node.js), компилятора TypeScript, а также Webpack.
Добавляем в проект файл конфигурации NPM под именем package.json. Если у вас включено автоматическое обновление NPM-пакетов, учтите, что обновление может идти долго. Кроме того, возможен сбой при обновлении. В случае сбоя придется повторить восстановление пакетов, а лучше закрыть VS2017 и установить NPM-пакеты из командной строки.
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"scripts": {
"build": "webpack"
},
"dependencies": {
"vue": "^2.5.13"
},
"devDependencies": {
"css-loader": "^0.28.9",
"ts-loader": "^3.5.0",
"typescript": "^2.7.2",
"vue-loader": "^14.1.1",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.11.0"
}
}
Добавляем в проект файл конфигурации TypeScript под именем tsconfig.json, в котором определяются опции компилятора (compilerOptions) и каталог, в котором компилятор будет искать «свои» файлы (include).
{
"compilerOptions": {
"outDir": "./built/",
"sourceMap": true,
"strict": true,
"module": "es2015",
"moduleResolution": "node",
"target": "es5"
},
"include": [
"./ClientApp/**/*"
]
}
Добавляем в проект файл JavaScript под именем webpack.config.js, в котором определяются входные/выходные файлы и способы их обработки.
// webpack.config.js
var path = require('path')
var webpack = require('webpack')
module.exports = {
entry: './ClientApp/index.ts',
output: {
path: path.resolve(__dirname, 'wwwroot/dist'),
publicPath: 'wwwroot/dist/',
filename: 'build.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
// the "scss" and "sass" values for the lang attribute to the right configs here.
// other preprocessors should work out of the box, no loader config like this necessary.
'scss': 'vue-style-loader!css-loader!sass-loader',
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
}
// other vue-loader options go here
}
},
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
options: {
appendTsSuffixTo: [/\.vue$/]
}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
devServer: {
historyApiFallback: true,
noInfo: true
},
performance: {
hints: false
},
devtool: '#eval-source-map'
}
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map'
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
])
}
Сборка и запуск приложения
Остается проверить работоспособность сделанного. Закрываем Visual Studio и через командную строку в каталоге проекта выполняем следующее:
npm install
npm run build
dotnet run
В браузере идем по указанному адресу, например, localhost:52643. В вашем случае порт наверняка будет другим. Должны получить что-то подобное изображенному на скриншоте.
Этот способ сборки и запуска приложения далеко не самый удобный, но его было проще описать. Запускать полученное приложение можно прямо из VS2017 (Ctrl+F5) или просто открывать в браузере файл wwwroot\index.html.
Со сборкой чуть посложнее. Если потребуется часто запускать скрипты сборки из package.json, попробуйте NPM Task Runner – достаточно популярное расширение к VS2017.
Желающие могут посмотреть внимательно на файл wwwroot\dist\build.js. Это файл у меня имеет размер 893kB, там код нашего приложения собран вместе с кодом vue.esm.js. Причем сборщик туда закопал не только наш код JavaScript, но также и CSS. Если в этом файле поставить в нужном месте точку остановки и походить отладчиком, то можно увидеть, что Webpack выполняет массу полезной работы. Обеспечивает инициализацию компонент с учетом зависимостей, кэширует вызовы и т.д.
Встроенный отладчик VS2017 на ts-файлах работать не будет, если оставить webpack.config.js без изменений. После некоторых изысканий нашел, что отладчик начинает работать если установить опцию {devtool:'#source-map'} вместо {devtool:'#eval-source-map'}. Так как этот конфиг я взял у большого гуру, а сам не хочу разбираться на что ещё влияет эта опция, то оставил исходную версию.
Проект TryVue
Теперь приступаем к созданию проекта Vue.js + Asp.NETCore + TypeScript без Webpack.
Создание стартовой болванки
Процедура создания стартовой заготовки для проекта TryVue идентична ранее описанной процедуре для проекта TryVueWebpack. Стартовую страницу wwwroot\index.html поправим позже.
Копирование программного кода
Полностью копируем папку ClientApp из сделанного в предыдущем разделе проекта. Удаляем файл ClientApp/vue-stub.ts.
Меняем расширение имени файлов *.vue -> *.html. Затем в этих файлах удаляем тэги <script /> и <style />, прописываем значения id для <template /> (внутренности шаблонов оставляем без изменений). В результате получим следующее:
<!-- ClientApp/components/AppHello.html -->
<template id="app-hello-template">
.. внутренности шаблона ..
</template>
<!-- ClientApp/components/Hello.html -->
<template id="hello-template">
.. внутренности шаблона ..
</template>
Теперь у нас «мухи — отдельно, котлеты — отдельно», в смысле: шаблоны лежат отдельно от ts-кода компонент. Поэтому в ts-коде самих компонент надо в свойство «template» прописать идентификторы своих шаблонов.
// ClientApp/components/AppHello.ts
...
export default Vue.extend({
template: "#app-hello-template",
...
});
// ClientApp/Hello.ts
...
export default Vue.extend({
template:"#hello-template",
...
});
Окончательно убираем следы однофайловых vue-компонент, исправив ссылки в директивах импорта файлов ClientApp/AppHello.ts, ClientApp/index.ts.
// ClientApp/components/AppHello.ts
import Vue from "vue";
import HelloComponent from "./Hello"; // было "./Hello.vue"
...
// ClientApp/index.ts
import Vue from "vue";
import AppHelloComponent from "./components/AppHello"; // было "./AppHello.vue"
...
Добавление файлов конфигурации и компиляция
Добавляем в проект файл конфигурации NPM под именем package.json. На этот раз нам достаточно указать только пакет Vue. Обычно новые NPM-пакеты после изменения в package.json устанавливаются автоматически. В противном случае — вызвать команду восстановления пакетов принудительно.
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"devDependencies": {
"vue": "^2.5.13"
}
}
Добавляем в проект файл конфигурации TypeScript под именем tsconfig.json и прописываем необходимые параметры.
{
"compilerOptions": {
"sourceMap": true,
"target": "es5",
"strict": true,
"module": "system",
"outFile": "wwwroot/dist/main.js",
"moduleResolution": "node"
},
"include": [
"./ClientApp/**/*.ts"
]
}
Обратите внимание на опции компилятора TypeScript {«module»: «system», «outFile»: «wwwroot/dist/main.js»}. При таких настройках, компилятор сам соберет полученный на выходе js-код всех модулей в единый файл. Причем специальные обертки этих модулей и библиотека System.js обеспечивают инициализацию модулей в нужном порядке, с учетом взаимных зависимостей.
Именно опция «module» компилятора TypeScript (при значении «amd» или «system») позволяют нам отказаться от Webpack. Посмотрите на файл wwwroot/dist/main.js после компиляции проекта со значениями {«module»: «amd»}, затем {«module»: «system»}.
Файл с результатами работы TypeScript компилятора содержит последовательность вызовов функции define() или System.register(). В параметрах вызова функций можно увидеть определение зависимости модулей друг от друга.
// wwwroot/dist/main.js при компиляции с опцией {"module": "amd"}
define("components/Hello", ["require", "exports", "vue"], function (require, exports, vue_1) {
...
});
define("components/AppHello", ["require", "exports", "vue", "components/Hello"], function (require, exports, vue_2, Hello_1) {
...
});
define("index", ["require", "exports", "vue", "components/AppHello"], function (require, exports, vue_3, AppHello_1) {
...
});
// wwwroot/dist/main.js при компиляции с опцией {"module": "system"}
System.register("components/Hello", ["vue"], function (exports_1, context_1) {
...
});
System.register("components/AppHello", ["vue", "components/Hello"], function (exports_2, context_2) {
...
});
System.register("index", ["vue", "components/AppHello"], function (exports_3, context_3) {
...
});
Если компилятор TypeScript сам способен выполнить большую часть необходимой работы, тогда «нафига козе баян»? После анализа результатов компиляции должно быть ясно, в какую сторону копать, чтобы избавиться от сторонних тяжелых сборщиков.
Сборка шаблонов и CSS
Со сборкой JavaScript в bandle разобрались. Теперь надо сделать бандлы vue-шаблонов и CSS. Для этого можно действовать совсем тупо — использовать команду copy (конкатенация — она и в Африке конкатенация):
copy ClientApp\components\*.css wwwroot\dist\main.css
copy ClientApp\components\*.html wwwroot\dist\app-templates.html
Но, боюсь, меня тут неправильно поймут. Обычно принято такие вещи делать при помощи Gulp или Grunt. Также применяется специальное расширение для Visual Studio — Bundler&Minifier. Надеюсь, с установкой и применением этого расширения справитесь. Привожу bundleconfig.json для нашего случая:
[
{
"outputFileName": "wwwroot/dist/main.css",
"inputFiles": [
"ClientApp/**/*.css"
]
},
{
"outputFileName": "wwwroot/dist/app-templates.html",
"inputFiles": [
"ClientApp/**/*.html"
],
"minify": {
"enabled": false,
"renameLocals": false
}
}
]
Создание index.html и запуск приложения
После выполнения описанного выше, в каталоге wwwroot\dist должны получить три бандла: main.js, main.css, app-templates.html. Остается только сделать index.html, который обеспечит их использование.
Подключаем стили, добавив в <head /> ссылку на main.css:
<link rel="stylesheet" href="dist/main.css" type="text/css" />
Загружаем файл с vue-шаблонами с самом начале <body />, затем определяем точку внедрения приложения Vue.js:
<section id="app-templates"></section>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script>
$.get("dist/app-templates.html").done(function (data) {
$('#app-templates').append(data);
});
</script>
<div id="app-root">loading..</div>
Загружаем System.js, который, в свою очередь, грузит все необходимое и стартует приложение.
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.20.19/system.src.js"></script>
<script>
System.config({
map: {
"vue": "https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"
}
});
SystemJS.import('dist/main.js').then(function (m) {
SystemJS.import('index');
});
</script>
Запуск полученного приложения в среде Visaul Studio — обычный (например: F5). Нормально работает встроенный отладчик VS2017, причем точки остановки можно ставить и в коде ts-модулей.
Заключение
Отказ от Webpack при разработке Vue.js-приложений, позволил вести разработку и отладку исключительно в привычной среде VS2017. В получении бандлов участвует только TypeScript и конкатенатор.
Для сравнения — в случае использования Webpack, в процедуре сборки участвуют, как минимум: webpack, typescript, ts-loader, css-loader, sass-loader, vue-loader, vue-style-loader, vue-template-compiler, vue-template-es2015-compiler, vue-hot-reload-api. Всего в каталоге node_modules: более 400 пакетов объемом около 80 Мб.
Правда, для production-версии полезно vue-шаблоны компилировать в рендер-функции, поэтому посредники для нашего случая тоже потребуется. Но, всё равно, без Webpack их будет значительно меньше.
Когда собираешь бандлы (main.js, main.css, app-temlates.html) без Webpack, есть понимание, из чего и как они получены. Поэтому незначительные правки можно вносить в бандлы напрямую (потом не забывать переносить в исходники). Экономия времени и нервов очень большая, особенно, при прототипировании отдельных компонент, для экспериментов и проверки идей.
Надеюсь, и вам, описанное здесь, принесет пользу.
Ссылки:
- При написании статьи частично использовался: TypeScript Vue Starter.
- При создании КДПВ (картинки для привлечения внимания) использованы логотипы официальных сайтов продуктов: vuejs.org, docs.microsoft.com, typescriptlang.org, webpack.js.org.
- Мой пример на github.
UPD 20.02.2018:
- Опубликовано дополнение к данной статье: RequireJS для приложений Vue.js + Asp.NETCore + TypeScript.
- В решение VS2017 на github добавлен проект TryVueRequire.
Комментарии (15)
k12th
25.02.2018 17:20Чтобы уменьшить количество инструментов, отказываемся от webpack… в пользу SystemJS! Браво!
edward_nsk Автор
25.02.2018 18:36У SystemJS настроек на порядок меньше, чем у Webpack.
Поэтому, в ряде случаев, замена на SystemJS выглядит предпочтительнее.
Для осваивающих экосистему веб-программирования с нуля, грустно начинать с изучения Webpack, чтобы написать «Hello World!»
Ещё отладчик VS2017 меньше глючит на сборке штатным компилятором TypeScript.k12th
25.02.2018 19:47+1Согласен, настраивать с нуля webpack очень грустно. Но есть же vue-cli.
Ну а кодить под веб в vs2017 — это уж, простите, ССЗБ:)
edward_nsk Автор
25.02.2018 20:07Куда ж крестьянину податься, который, в основном, именно на VS2017 работает. У нас приложение Asp.Net Core + WPF, а веб-интерфейс дополнительный.
apapacy
25.02.2018 21:36Обычно делают так: оставить на ASP только api фронтенд делать на технологиях Vue/React/Angular/Elm… Вот например интересный проект в котором одну не самую примитивную задачу решают разными технологиями в т.ч. и ASP. github.com/gothinkster/realworld. В смысле задача конечно простая но не совсем примитивная. Не ToDoMVC.
Dreyk
25.02.2018 23:10да че там настраивать. кроме того, есть parcel, с его zero-configuration, да и webpack 4 уже тоже без конфига искаропки работает
mikhailt
26.02.2018 02:05А как это в продакшн деплоить? Скажем на IIS.
Какие папки с собой тащить? Что делать с толстым node_modules?k12th
26.02.2018 03:57Точно так же, как всё остальное — в собранном для продакшена виде это обычная статика. Собирать, наверное, лучше перед деплоем, и заливать только артефакты сборки.
edward_nsk Автор
26.02.2018 07:42Толстый node_modules для деплоя не нужен, в приведенном примере используется только для сборки. Всё необходимое в рантайм тащится по ссылкам CDN: vue.js, jquery.js, system.js. Деплой можно делать визардом vs2017.
pawlo16
28.02.2018 11:42-1Писать что-то серьезное на нетипизированном языке нельзя. Следовательно все фреймворки наподобие этого Vue со своими шаблонами (DSL, в котором строгой типизации нет, и как ее приделать — непонятно) сразу выкидываются в помойку, без попыток в них разобраться.
apapacy
Интересный способ. Но есть ещё вопросы как на счёт code splitting? Скомпоновать все в один бандл это не вариант уже сейчас. Иначе первая страница грузит все приложение и это долго на слабых мобильных девайсах. Webpack реализовал фичу которая ещё не попала в стандарт динамический import() который делит код на несколько бандлов автоматически или без него можно это сделать вручную
edward_nsk Автор
Так для production никто не запрещает резать бандл «правильно». У меня, например, их 4 штуки получается. Более того, на стартовой странице приложения я, вообще, хочу оставить только Bootstrap + jQuery. А грузить основную часть приложения только для желающих пойти дальше.
Пока собираю эти бандлы при помощи Webpack, но собираюсь попробовать обойтись Gulp (vue-template-compiler + самописный конкатенатор).
apapacy
А как правильно? webpack делает это сам на основании динамических импортов (хотя есть возможность и вручную разрулить все). Какая альтернатива в Вашем решении? Есть ли возмодност сделать это автоматилированно?
edward_nsk Автор
Наверно ваш вариант с Webpack правильнее, особенно, для production. Вариант без Webpack полезен при прототипировании, отладке, изучении потрохов библиотек и т.п.