Создаем на Visual Studio 2017 модульное приложение Vue.js + Asp.NETCore + TypeScript. В качестве системы сборки вместо Webpack используем компилятор TypeScript + Bundler&Minifier (расширение к VS2017). Загрузку модулей приложения в рантайм обеспечивает SystemJS или RequireJS. Рассматриваем формат модулей AMD (asynchronous module definition), который понимает не только SystemJS, но и RequireJS.
Предупреждаю сразу — Vue.js не совсем поддерживает AMD или содержит баг, поэтому применен почти хакерский прием, он не всем подойдет. Но надеюсь, данная статья позволит вам лучше понимать, как устроен этот мир Vue.js.
Данная статья является дополнением к tutorial: Приложение Vue.js + Asp.NETCore + TypeScript без Webpack. Где в примерах использовался формат модулей SYSTEM. Делать ставку только на загрузчик SystemJS, как то, боязно. На момент написания статьи SystemJS имеет релиз 0.20, что означает вероятнось радикальных изменений в API, опциях и т.д.
Цель применения формата модулей AMD и загрузчика RequireJS – страховка от радикальных изменений в SystemJS, обеспечение возможности использования более популярного загрузчика RequireJS и формата модулей AMD.
Материал рассчитан на способных управиться с VS2017 и знакомых с прогрессивным JavaScript фрэймворком Vue.js.
Введение
Изначально материал, который касается AMD и RequireJS, был в статье: Приложение Vue.js + Asp.NETCore + TypeScript без Webpack. Но она получалась слишком большая, поэтому пришлось отрезать кусочек. Считаю, что для полноты картины, полезно разобраться также с AMD и RequireJS.
Переход на AMD
Компилятор TypeScript собирает все модули в единственный выходной файл, если установлена опция {«module»: «system»} или {«module»: «amd»}. С опцией «system» получилось, надо теперь попробовать «amd». Без этого RequireJS применить не получится, т.к. этот загрузчик понимает только amd-формат модулей.
Стартовое приложение
В качестве отправной точки, можно взять на github проект TryVue из решения Visual Studio 2017, которое было описано в основной статье. Дальнейшие действия можно выполнять в этом проекте, но лучше создать копию под именем TryVueRequire.
После сборки приложения и бандлов в каталоге wwwroot\dist должны появиться файлы: main.js, main.css, app-templates.html. Запускаем приложение любым удобным для вас способом (F5, Ctrl-F5 в среде VS2017, или снаружи: dotnet run).
В браузере должно получиться что-то подобное изображенному на скриншоте.
Напоминаю, что браузер кэширует файлы вашего приложения, поэтому надо обновлять страницу правильно (со сбросом кэша). Если заподозрите что-то неладное при дальнейших эксперименах, сбросьте кэш браузера.
Error: vue_3.default is not a constructor
Теперь в файле tsconfig.json меняем опцию компилятора на {«module»: «amd»}, пересобираем приложение и пытаемся запуститься.
Должны увидеть в браузере текст "loading..", а в консоли — ошибку, если переключиться в режим разработчика (для Chrome — клавиша F12).
Путем удаления некоторых фрагментов кода в wwwroot\dist\main.js, можно быстро выяснить как ругаются и на что именно. Привожу выдержки и файла main.js со строками, на которых происходит сбой, а также тексты ошибок:
define("components/Hello", ..., function (...) {
...
exports.default = vue_1.default.extend({
...
});
define("components/AppHello", ..., function (...) {
...
exports.default = vue_2.default.extend({
...
});
define("index", ..., function (...) {
...
var v = new vue_3.default({
...
});
Uncaught (in promise) Error: Cannot read property 'extend' of undefined
Uncaught (in promise) Error: vue_3.default is not a constructor
Первая ошибка относится к vue_1.default.extend, vue_2.default.extend. Вторая ошибка относится к vue_3.default. Из текста ошибок понятно, что vue.js не определяет конструктор "default". В этом убедиться очень просто — удалите этот default после точки у vue_1, vue_2, vue_3.
Приложение заработает! Остается разобраться, что делать с неопределенным свойством default у экземпляров Vue.js.
Заплатка для SystemJS
Наверняка, удалять "default" из файла wwwroot\dist\main.js руками после каждой сборки проекта — не самый удобный вариант. Поэтому сделаем заплатку в файле wwwroot\index.html, которая выполняет следующее: грузит vue, прописывает у него свойство default, грузит main.js и запускает приложение.
<!--фрагмент wwwroot\index.html-->
<!-- исходный фрагмент: -->
SystemJS.import('dist/main.js').then(function (m) {
SystemJS.import('index');
});
<!-- заменить на следующий: -->
SystemJS.import('vue').then(function (m) {
if (!m.default) {
m.default = m;
console.warn('HACK: vue.default was undefined');
}
SystemJS.import('dist/main.js').then(function (m) {
SystemJS.import('index');
});
});
Приложение должно заработать. После проверки этого варианта, сохраняем index.html на память в index-system.html.
Переход на RequireJS
При amd-формате модулей использование RequireJS отличается от SystemJS только способом конфигурирования и API (aplication program interface).
Для перехода на RequireJS изменения производятся исключительно в файле wwwroot\index.html. Исходные файлы кода TypeScript, а также настройки проекта не трогаем.
Простой вариант заплатки для RequireJS
Перед тем, как предложить ещё один вариант определения default-конструктора Vue.js, сделаем полный аналог index-system.html, используя RequireJS. В файле wwwroot\index.html достаточно поменять загрузку скрипта system.js -> require.js. Затем сконфигурировать require.js, загрузить необходимое и запустить приложение. Также повторяем код прописывания default при отсутствии.
<!--фрагмент wwwroot\index.html-->
...
<script src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.js"></script>
<script>
require.config({
paths: {
"vue": "https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue",
"index": "dist/main"
}
});
require(['vue'], function (m) {
if (m.default === undefined) {
m.default = m;
console.log('HACK: ' + m.name + '.default was undefined');
}
require(['index']);
});
</script>
...
Сохраняем wwwroot\index.html на память в файл index-require.html, чтобы реализовать в index.html более "правильный" вариант решения проблемы неопределенного default-конструктора.
Переопределение обращений к Vue.js
Есть еще один вариант решения проблемы отсутствия свойства default у экземпляров Vue. До определения модулей в main.js, определяем маленький переходник, который все обращения к "vue" замыкает на себя и делает скорректированный экспорт "vue-parent" (переименованный истинный "vue").
Собственно определение переходника:
define("vue", ["require", "exports", "vue-parent"], function (require, exports, vueParent) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = vueParent.default || vueParent;
});
Часть текста wwwroot\index.html, относящаяся к использованию переходника, приведена ниже. Для упрощения данного примера текст переходника включен в index.html. Правильнее держать его как отдельный файл vue-stub.js, который приклеивать в начало main.js конкатенатором.
<!--фрагмент wwwroot\index.html-->
...
<script src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.5/require.js"></script>
<script>
// vue-stub.js
define("vue", ["require", "exports", "vue-parent"], function (require, exports, vueParent) {
"use strict";
//if (!vueParent.default) console.warn('HACK: vue.default was undefined');
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = vueParent.default || vueParent;
});
</script>
<script>
require.config({
paths: {
"vue-parent": "https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue",
"index": "./dist/main"
}
});
require(['index']);
</script>
...
Заключение
Возможно, я чего-то не так понимаю, но библиотека vue.js в экземплярах Vue не предоставляет определение свойства "default", которое от него ожидают в модулях формата AMD (asynchronous module definition). Честно говоря, баг это или фича — не знаю.
Во такие пирожки с котятами.
Чтобы использовать amd-формат модулей и RequireJS пришлось вставлять свой переходник, в котором определять default. Если кто-нибудь знает более правильный способ — поделитесь.
Ссылки:
- Основная статья: Приложение Vue.js + Asp.NETCore + TypeScript без Webpack.
- Мой пример на github.
- При создании КДПВ (картинки для привлечения внимания) использованы логотипы официальных сайтов продуктов: vuejs.org, docs.microsoft.com, typescriptlang.org, webpack.js.org, requirejs.org.
mayorovp
default не имеет отношения к модулям AMD. Эта штука появилась в ES6-модулях и оттуда перекочевала в TypeScirpt-модули.
На самом деле вы просто неправильно подключаете Vue. Вы используете вот такую конструкцию:
Эта конструкция использует default import, который, в свою очередь, выливается в обращение к свойству default.
А надо делать — вот так:
mayorovp
Ну или можно использовать файл vue.esm.js вместо vue.js: там делается default export вместо простого присваивания.
edward_nsk Автор
Спасибо. Попробую поковырять этот вариант.
Хотя «в лоб» не проходит, при выполнении ошибка:
Uncaught (in promise) Error: Unable to dynamically transpile ES module
A loader plugin needs to be configured via `SystemJS.config({ transpiler: 'transpiler-module' })`.
Instantiating cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.esm.js
edward_nsk Автор
RequireJS, гад такой, вообще отказался vue.esm.js переваривать:
vue.esm.js:10809 Uncaught SyntaxError: Unexpected token export
edward_nsk Автор
Попробовал import * as Vue from «vue»;
Компилятор TS выдает ошибки.
Может подскажете, что ещё подкрутить?
TryVue D:\Git\starter-vue\TryVue\ClientApp\index.ts 5
Ошибка TS2351 Build: Невозможно использовать new с выражением, у типа которого нет сигнатуры вызова или конструктора.
TryVue D:\Git\starter-vue\TryVue\ClientApp\components\Hello.ts 4
Ошибка TS2339 Build: Свойство «extend» не существует в типе «typeof „D:/Git/starter-vue/TryVue/node_modules/vue/types/index“».
mayorovp
А, ну там еще тайпинги поменять надо…
edward_nsk Автор
Компилятор, вроде, берет родные тайпинги здесь: «node_modules/vue/types/index». Если надо что-то добавить — я готов попробовать.
Может есть ссылка на работающий пример с TypeScript для случая: import * as Vue from «vue»?
mayorovp
Ох, там там еще и тайпинги родные?.. Значит, я невнимательно читал вашу прошлую статью...
Вот, нашел еще одно решение. Нужна настройка
"esModuleInterop": true
.edward_nsk Автор
Сработало! Спасибо!
Проект с amd-модулями работает без заплатки и через SystemJS, и через RequireJS. В файлах TypeScript ничего менять не надо, достаточно поменять tsconfig.json:
Заплатку можно убирать. Получается зря гланды вырывал не через то место.