Pull to refresh

Vue.js + Asp.Net Core MVC + TypeScript и ещё Bootstrap4

Reading time 11 min
Views 32K

image


По стандартному шаблону 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-файлы.


скрытый скриншот AppHello

image AppHello


Проект 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.


скрытый текст package.json
{
  "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 уже должен быть в проекте, остается его только правильно настроить.


скрытый текст 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. Добавляем этот файл и настраиваем по приведенному образцу.


скрытый текст 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
// 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/AppHello.*
// 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/Hello.*
// 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
/* 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 с этим шаблоном приведен под спойлером.


скрытый текст Views/Shared/_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>&copy; 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
@* 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
<!-- фрагмент TryVueMvc.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  ...
  <ItemGroup>
    <DotNetCliToolReference Include="BundlerMinifier.Core" Version="2.6.362" />
  </ItemGroup>  

  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets"
          Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.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. Попутно увидим, что надо выкидывать, при замене на своё приложение.


скрытый скриншот AppGrid

image AppGrid


По возможности, постараюсь описать применяемый мной вариант решения проблемы строгой типизации при использовании TypeScript (без декораторов и vue-class-component).


Благодарности


  • Идея заглавной картинки и надписи отсюда.
  • Фото для заглавной картинки отсюда.
  • Частично использовался шаблон Bootstrap StarterTemplate с офицального сайта Bootstrap.

Ссылки



Update 05.06.2019:


На данный момент исходный код примеров на на github немного отличается от приведенного в статье. Изменения вызваны обновлением версий используемых компонент (переход на Asp.NETCore 2.2 и т.д.).

Tags:
Hubs:
+11
Comments 2
Comments Comments 2

Articles