По стандартному шаблону Asp.Net Core MVC в Visual Studio 2017 создаем новый проект, переводим его на четвертый Bootsrtrap, встраиваем туда модульное приложение Vue.js на TypeScript.
Получаем простую, обозримую и легкую заготовку для создания своих веб-приложений на VS2017 с использованием Vue.js и TypeScript. Привычная среда разработки, в которой можно выполнять большую часть кодинга и отладки, а также быстрая пересборка приложения, делают работу вполне комфортной.
В генерации JavaScript-кода приложения принимает участие только штатный компилятор TypeScript и VS2017, что сильно сужает круг подозреваемых при возникновении глюков. А это, в свою очередь, — тоже большая экономия времени и нервов.
Материал рассчитан на способных управиться с VS2017 и знакомых с прогрессивным JavaScript фреймворком Vue.js.
Содержание
Введение
Проект TryVueMvc
— Создание стартовой болванки
— Установка NPM пакетов
— Настройка бандлинга и минификации
— Настройка компилятора TypeScript
— Создание и сборка AppHello
— Корректировка _Layout.cshtml
— Корректировка Index.cshtml
— Сборка и бандлинг из командной строки
Заключение
Введение
Ожидаемый результат выполнения данного tutorial — компонента Vue.js, внедренная на одну из страниц приложения Asp.Net Core MVC, в котором Bootstrap4 будет обеспечивать адаптивный интерфейс приложения (адаптацию под устройства с разными размерами экрана).
Сборка основного js-бандла приложения будет производится компилятором TypeScript. Сборка остальных бандлов, в том числе для библиотек внешних поставщиков, будет производится Bundler&Minifier. Попутно Bundler&Minifier минифицирует всё необходимое. Генерация html-страницы будет производится на сервере приложением Asp.NetCore MVC с использованием Razor-рендерера представлений. Загрузка и запуск js-скрипта приложения будет производится при помощи SystemJS (легко заменить на RequireJS).
Попутно настроим сборку и бандлинг приложения из командной строки через dotnet bulild
, dotnet bundle
.
Как видите, в чистом виде "сообразить на троих" не получается — каждый участник процесса норовит притащить свою компанию. Самое удачное, что получилось, хотя бы на время, избавится от Webpack, который тащил за собой много чего.
В этом tutorial сделаем порядком надоевшее приложение AppHello. В дальнейшем его можно будет заменить на своё. Для этого будет достаточно заменить ts-файлы, html-шаблоны и css-файлы.
Проект TryVueMvc
Для данной статьи на github размещено решение VS2017 с проектом TryVueMvc. В это решение планируется добавление других проектов — просьба не обращать на них внимания.
Создание стартовой болванки
Для начала создаем стартовую заготовку для приложения. В качестве отправной точки используем проект «Веб-приложение ASP.NET Core» по шаблону MVC (модель-представление-контроллер).
Содержимое wwwroot/ надо очистить, оставить только favicon.ico. На момент написания этой статьи стандартный шаблон приложения Asp.Net Core MVC шел вместе с Bootstrap3, поэтому в каталоге wwwroot/ будут файлы используемых библиотек. Они нам не потребуются, т.к. Bootstrap мы будем устанавливать в node_modules, затем перенесем необходимое в wwwroot/.
Установка NPM пакетов
Определяем конфигурацию NPM (менеджера пакетов Node.js). Для этого добавляем в проект файл конфигурации NPM под именем package.json.
Нам нужны пакеты Vue, SystemJS и Bootstrap4. Последний, в свою очередь, требует jQuery и Popper.js.
{
"version": "1.0.0",
"name": "asp.net",
"private": true,
"dependencies": {
"jquery": "^3.3.1",
"popper.js": "^1.12.9",
"bootstrap": "^4.0.0",
"vue": "^2.5.13",
"systemjs": "^0.21.0"
}
}
Обычно новые NPM-пакеты после изменения в package.json устанавливаются автоматически. В противном случае — вызвать команду восстановления пакетов принудительно.
Настройка бандлинга и минификации
В создаваемом приложении отказываемся от использования CDN (Content Delivery Network или Content Distribution Network), используемые библиотеки внешних поставщиков собираем в бандлы, минифицируем и помещаем в wwwroot/dist/.
Внешние библиотеки разбиваем на 2 части: vendor1 и vendor2 (он же vue). Сборку app-bandle.js делает TypeScript, поэтому здесь его только минифицируем.
Файлы бандлов | Источники | minfy |
---|---|---|
vendor1.js +vendor1.min.js |
node_modules/jquery/dist/jquery.js, node_modules/popper.js/dist/umd/popper.js, node_modules/bootstrap/dist/js/bootstrap.js, node_modules/systemjs/dist/system.src.js |
true |
vendor1.css | node_modules/bootstrap/dist/css/bootstrap.css | false |
vendor1.min.css | node_modules/bootstrap/dist/css/bootstrap.min.css | false |
vendor2.js +vendor2.min.js |
node_modules/vue/dist/vue.js | true |
app-bandle.min.js | wwwroot/dist/app-bandle.js | true |
app-templates.html | ClientApp/**/*.html | false |
main.css +main.min.css |
ClientApp/**/*.css | true |
Создаем пустую папку ClientApp, т.к. она указывается в bundleconfig.json (иначе будет ругань). Файл bundleconfig.json уже должен быть в проекте, остается его только правильно настроить.
[
{
"outputFileName": "wwwroot/dist/vendor1.js",
"inputFiles": [
"node_modules/jquery/dist/jquery.js",
"node_modules/popper.js/dist/umd/popper.js",
"node_modules/bootstrap/dist/js/bootstrap.js",
"node_modules/systemjs/dist/system.src.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": true
},
{
"outputFileName": "wwwroot/dist/vendor1.css",
"inputFiles": [
"node_modules/bootstrap/dist/css/bootstrap.css"
],
"minify": {
"enabled": false
}
},
{
"outputFileName": "wwwroot/dist/vendor1.min.css",
"inputFiles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"minify": {
"enabled": false
}
},
{
"outputFileName": "wwwroot/dist/vendor2.js",
"inputFiles": [
"node_modules/vue/dist/vue.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": true
},
{
"outputFileName": "wwwroot/dist/main.css",
"inputFiles": [
"ClientApp/**/*.css"
],
"minify": {
"enabled": true
}
},
{
"outputFileName": "wwwroot/dist/app-bandle.min.js",
"inputFiles": [
"wwwroot/dist/app-bandle.js"
],
"minify": {
"enabled": true,
"renameLocals": true
}
},
{
"outputFileName": "wwwroot/dist/app-templates.html",
"inputFiles": [
"ClientApp/**/*.html"
],
"minify": {
"enabled": false,
"renameLocals": false
}
}
]
После сохранения изменений bundleconfig.json, в каталоге wwwroot/dist должны появиться бандлы от vendor1 и vendor2. Бандлы нашего приложения появятся после создания необходимых исходных файлов.
Настройка компилятора TypeScript
В свойствах проекта VS2017 есть закладка "Сборка TypeScript", в которой через удобный интерфейс можно задавать большую часть необходимых свойств компилятора. Но часть свойств компилятора, всё равно, придется определять и править в файле TryVueMvc.csproj руками. Желающие могут использовать этот способ настройки компилятора TypeScript.
Если параллельно планируется использовать Webpack или другие системы сборки, то лучше использовать tsconfig.json. Добавляем этот файл и настраиваем по приведенному образцу.
{
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "es5",
"module": "system",
"outFile": "wwwroot/dist/app-bandle.js",
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"./ClientApp/**/*.ts"
]
}
Создание и сборка AppHello
В нашем примере используется вариант организации проекта, когда CSS-файлы не являются собственностью отдельных компонент, а определяются для всего приложения централизованно.
Создаем папку ClientApp/css для общих css-файлов. Создаем папку ClientApp/components для ts-файлов и html-шаблонов компонент. В каталоге ClientApp создаем файл index.ts, используемый как точка входа в приложение. Добавляем остальные файлы клиентского приложения AppHello.
// ClientApp/index.ts
import Vue from "vue";
import AppHelloComponent from "./components/AppHello";
let v = new Vue({
el: "#app-root",
template: '<AppHelloComponent />',
//render: h => h(AppHelloComponent),
components: {
AppHelloComponent
}
});
// ClientApp/components/AppHello.ts
import Vue from "vue";
import HelloComponent from "./Hello";
export default Vue.extend({
template:'#app-hello-template',
data() {
return {
name: "World"
}
},
components: {
HelloComponent
}
});
<!-- ClientApp/components/AppHello.html -->
<template id="app-hello-template">
<div>
Name: <input v-model="name" type="text" />
<hello-component :name="name" :initial-enthusiasm="5" />
</div>
</template>
// ClientApp/components/Hello.ts
import Vue from "vue";
export default Vue.extend({
template:'#hello-template',
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.html -->
<template id="hello-template">
<div>
<div class="greeting">Hello {{name}}{{exclamationMarks}}</div>
<button @click="decrement">-</button>
<button @click="increment">+</button>
</div>
</template>
/* ClientApp/css/site.css */
body {
margin: 5rem;
background-color: honeydew;
}
После сборки проекта и UpdateBundle в каталоге wwwroot/dist должны появиться готовые к использованию файлы: app-bandle.js, app-templates.html, main.css.
Корректировка _Layout.cshtml
Чтобы использовать Boostrap 4 вместо 3, надо маленько подправить содержимое файла Views/Shared/_Layout.cshtml. В качестве образца используем шаблон Bootstrap StarterTemplate с офицального сайта продукта. Результат скрещивания _Layout.cshtml с этим шаблоном приведен под спойлером.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - TryVueMvc</title>
<environment include="Development">
<link rel="stylesheet" href="~/dist/vendor1.css" />
<link rel="stylesheet" href="~/dist/main.css" asp-append-version="true" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/dist/vendor1.min.css" />
<link rel="stylesheet" href="~/dist/main.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Logo</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsDefault"
aria-controls="navbarsDefault" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsDefault">
<ul class="navbar-nav mr-auto">
<li class="nav-item"><a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li class="nav-item"><a class="nav-link" asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li class="nav-item"><a class="nav-link" asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
</div>
</nav>
<main role="main" class="container">
@RenderBody()
<hr />
<footer>
<p>© 2018 - Company</p>
</footer>
</main>
<environment include="Development">
<script src="~/dist/vendor1.js"></script>
</environment>
<environment exclude="Development">
<script src="~/dist/vendor1.min.js"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>
Приложение Asp.Net Core MVC использует движок Razor для генерации html представлений. Обратите внимание, что попутно реализовали возможность переключения на минифицированные бандлы через конфигурацию окружения (свойство ASPNETCORE_ENVIRONMENT). Пример условной генерации ссылок на требуемые бандлы приведен ниже:
...
<environment include="Development">
<link rel="stylesheet" href="~/dist/vendor1.css" />
<link rel="stylesheet" href="~/dist/main.css" asp-append-version="true" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/dist/vendor1.min.css" />
<link rel="stylesheet" href="~/dist/main.min.css" asp-append-version="true" />
</environment>
...
Корректировка Index.cshtml
В нашем примере используется вариант веб-приложения, которое на старте может обойтись без Vue.js. Обратите внимание, что в _Layout.cshtml нет загрузки основного бандла приложения и библиотек Vue.js. Компоненты Vue.js используются только на странице Index.cshtml. Если переместить компоненты Vue.js со стартовой страницы, можно сильно уменьшить время старта приложения.
@* Views/Home/Index.cshtml *@
@using Microsoft.AspNetCore.Hosting
@inject IHostingEnvironment hostingEnv
@{
var vueUrl = hostingEnv.IsDevelopment() ? "dist/vendor2.js" : "dist/vendor2.min.js";
var mainUrl = hostingEnv.IsDevelopment() ? "dist/app-bandle.js" : "dist/app-bandle.min.js";
ViewData["Title"] = "TryVueMvc Sample";
}
<section id="app-templates"></section>
<div id="app-root">loading..</div>
@section Scripts{
<script>
System.config({
map: {
//"vue": "dist/vendor2.js"
"vue": "@vueUrl"
}
});
$.get("dist/app-templates.html").done(function (data) {
$('#app-templates').append(data);
SystemJS.import('@mainUrl').then(function (m) {
SystemJS.import('index');
});
});
</script>
}
}
Текст Views/Home/Index.cshtml должен быть понятен для работающих с Asp.Net Core. На всякий случай объясню поподробнее.
При помощи механизма DI (Dependency Injection) добираемся до свойств окружения и определяем, какие бандлы будем использовать.
@using Microsoft.AspNetCore.Hosting
@inject IHostingEnvironment hostingEnv
@{
var vueUrl = hostingEnv.IsDevelopment() ? "dist/vendor2.js" : "dist/vendor2.min.js";
var mainUrl = hostingEnv.IsDevelopment() ? "dist/app-bandle.js" : "dist/app-bandle.min.js";
ViewData["Title"] = "TryVueMvc Sample";
}
...
Определяем место вставки бандла с vue-шаблонами, а также точку внедрения приложения Vue.js:
<section id="app-templates"></section>
<div id="app-root">loading..</div>
Загружаем бандл с vue-шаблонами и вставляем в секцию #app-templates. Затем загружаем System.js, который, в свою очередь, грузит все необходимое и стартует js-скрипт приложения.
...
<script>
System.config({
map: {
//"vue": "dist/vendor2.js"
"vue": "@vueUrl"
}
});
$.get("dist/app-templates.html").done(function (data) {
$('#app-templates').append(data);
SystemJS.import('@mainUrl').then(function (m) {
SystemJS.import('index');
});
});
</script>
...
Приложение полностью готово, можно запустить стандартным для VS2017 способом.
Сборка и бандлинг из командной строки
При отсутствии потребности в пересборке приложения через командную строку, можно пропустить этот пункт. Возможно, даже стоит его пропустить — бывают глюки с пересборкой в среде VS2017, если не учитывать некоторые особенности VS2017.
Для созданного "с нуля" проекта, команда dotnet build
соберет только DLL, а компилятор TypeScript вызван не будет. Естественно, команда dotnet
совсем ничего не знает про расширение Bundler&Minifier.
Поэтому надо установить пару NuGet пекетов: "Microsoft.TypeScript.MSBuild", "BundlerMinifier.Core". Затем в файл TryVueMvc.csproj внести изменения, которые требует официальная документация на эти продукты.
<!-- фрагмент TryVueMvc.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<ItemGroup>
<DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)MicrosoftVisualStudiov$(VisualStudioVersion)TypeScriptMicrosoft.TypeScript.targets"
Condition="Exists('$(MSBuildExtensionsPath32)MicrosoftVisualStudiov$(VisualStudioVersion)TypeScriptMicrosoft.TypeScript.targets')" />
</Project>
Теперь сборка и запуск приложения может производится не только в среде VS2017, но и через командную строку в каталоге проекта TryVueMvc:
npm install
dotnet build
dotnet bundle
dotnet run
Заключение
Vue.js позиционируется как очень простой для освоения фреймворк. Утверждается, что кривая обучения у конкурентов значительно круче. С моей точки зрения, это действительно так.
Разве что, приходится туго тем, кто использует Asp.Net Core и VS2017 для создания приложений Vue.js на TypeScript. Реально полезных примеров и статей в интернете не много для этого сочетания используемых продуктов и технологий.
Надеюсь, что данный tutorial поможет снизить стартовый барьер при создании приложений Vue.js + Asp.Net Core MVC + TypeScript.
Анонс продолжения
Планирую опубликовать ещё один tutorial, в котором AppHello заменим на AppGrid, созданное на основе примера Grid Component Example с официального сайта Vue.js. Попутно увидим, что надо выкидывать, при замене на своё приложение.
По возможности, постараюсь описать применяемый мной вариант решения проблемы строгой типизации при использовании TypeScript (без декораторов и vue-class-component).
Благодарности
- Идея заглавной картинки и надписи отсюда.
- Фото для заглавной картинки отсюда.
- Частично использовался шаблон Bootstrap StarterTemplate с офицального сайта Bootstrap.
Ссылки
- Подробнее о создании AppHello с нуля: Приложение Vue.js + Asp.NETCore + TypeScript без Webpack.
- Мой пример для данного tutorial на github.
Автор: edward_nsk