Vue.js: как перенести большой проект с Vue 2 на Vue 3

Spread the love

Перевод: Vue.js: How to Migrate a large project from Vue 2 to Vue 3

Команда Vue.js выпустила Vue 3.0 в сентябре 2020 года. Эта новая версия содержит множество новых функций и оптимизаций, но также имеет несколько критических изменений.

В июне 2021 года команда Vue.js выпустила версию Vue 3.1. Этот новый релиз поставляется с @vue/compat (он же «сборка миграции»). Он позволяет плавно переносить большие проекты, запуская кодовую базу Vue 2 вместе с изменениями Vue 3.

Переход на Vue 3 может быть сложной задачей (в зависимости от размера вашего проекта). В Crisp мы недавно перенесли наше приложение (250 тыс. строк кода) с Vue 2.6 на Vue 3.2 примерно за 2 недели.

Мы подготовились к миграции, прочитав официальное руководство по миграции Vue (Vue Migration Tutorial), но при миграции нашего проекта столкнулись со множеством различных проблем. В этой статье я опишу наш опыт миграции:

  • Основные различия между Vue 2 и Vue 3
  • Проблемы, с которыми мы столкнулись, и как мы с ними справлялись
  • Наша стратегия миграции на Vue.js 3

Немного предыстории Vue 3

Первоначальная философия Vue.js, созданного в 2013 году, заключалась в создании минималистичной альтернативы AngularJS 1 .

В то время Angular представлял собой громоздкий фреймворк с массой новых функций. Первые версии Vue.js были похожи на мини-фреймворк с шаблонами, привязкой данных (Data Binding), фильтрами (Filters) и директивами (Directives).

Затем в 2016 году был выпущен Vue 2 (почти одновременно с Angular 2) и стал отличной альтернативой AngularJS. Фактически, многие разработчики, использующие Angular 1, решили перейти на Vue 2, потому что им не нравился Angular 2. Vue 2 предлагал что-то столь же простое, как AngularJS, но с более высокой производительностью.

Vue 3 так же создавался с расчетом на производительность. Это скорее эволюция, чем революция. Большинство новых функций и критических изменений были выпущены из соображений производительности.

Внутренняя система реактивности была переработана с нуля, и по этой причине поддержка IE 11 была прекращена (что не является большой потерей 😂 ).

Наконец, эта новая версия должна быть более модульной и включает такие функции, как Composition API .

Самые большие изменения в Vue 3

Глобальный Vue API

Глобальный Vue API устарел. Хотя он все еще поддерживается с помощью @vue/compat , потребуется переработать ваше приложение, чтобы полностью поддерживать Vue 3.

Это означает, что невозможно будет использовать API, такие как Vue.set или Vue.delete. Поскольку Vue 3 поставляется с новой системой реактивности, использование этих API становится бесполезным.

Вместо использования Vue.set(object, key, value) вам придется напрямую использовать object[key] = value . То же самое и с Vue.delete(object, key) , который можно заменить на delete object[key] .

Фильтры

Как объяснялось ранее, Vue.js был создан как альтернатива Angular 1. Именно поэтому изначально поддерживались фильтры.

Самая большая проблема с фильтрами – это производительность: функция фильтрации должна выполняться каждый раз, когда данные обновляются. По этой причине поддержка фильтров в Vue 3 была прекращена.

Это означает, что нельзя будет использовать такие вещи, как {{ user.lastName | uppercase }} в шаблонах Vue 3.

Вместо этого вам придется использовать вычисляемые свойства, такие как {{ uppercasedLastName }} , или методы, такие как {{uppercase(lastName)}} .

Создание экземпляров Vue и плагинов

Начиная с Vue 3, ваше приложение и плагины больше не создаются глобально. Это означает, что в одном проекте может быть несколько приложений Vue.

Пример на Vue 2:

import Vue from "vue";

new Vue({
  router,
  render: h => h(App)
}).$mount("#app");

Пример на Vue 3:

import { createApp, h } from "vue";

const app = createApp({
  render: () => h(App)
});

app.use(router);

app.mount("#app");

v-if + v-for

Использование условий v-if со списками v-for было возможно в Vue 2. Из соображений производительности это поведение было отключено во Vue 3.

Начиная с Vue 3, вам нужно будет использовать свойства вычисляемого списка.

v-model

API v-model немного изменилось во Vue 3.

Свойства value  переименовано в modelValue .

<ChildComponent v-model="pageTitle" />

ChildComponent нужно переписать так:

props: {
  modelValue: String // previously was `value: String`
},

emits: ['update:modelValue'],

methods: {
  changePageTitle(title) {
    this.$emit('update:modelValue', title)
  }
}

Замечательно то, что теперь можно иметь несколько пользовательских значений v-model , например v-model:valueAv-model:valueB и т. д.

$emit

В Vue 2 можно было использовать экземпляр Vue для создания глобальной EventBus с vm.$on и vm.$off . Начиная с Vue 3, это больше невозможно, поскольку vm.$on и vm.$off были удалены из экземпляра Vue.

В качестве замены предлагаем использовать библиотеку Mitt:

mounted() {
  this.eventbus = mitt();

  eventbus.on("ready", () = {
    console.log("Event received");
  });

  eventbus.emit("ready");
}

тот же код, использующий Vue 2, будет:

mounted() {
  this.$on("ready", () = {
    console.log("Event received");
  });

  this.$emit("ready");
}

По-прежнему можно отправлять события от компонентов к их родителям, однако все события должны быть объявлены с помощью новой опции emit (она очень похожа на существующую опцию props ).

Например, если у вашего компонента есть свойство @click , генерируемое с помощью this.$emit("click") , вам нужно будет объявить событие ” click ” в вашем компоненте:

props: {
  name: {
    type: String,
    default: ""
  },
},

emits: ["click"], // events have to be declared here

data() {
  return {
    value: ""
  }
}

Стратегия миграции

Наше приложение называется Crisp. Это приложение для обмена сообщениями для крупного бизнеса, которым ежедневно пользуются 300 тысяч компаний по всему миру. Компании используют Crisp, чтобы отвечать своим клиентам, используя командную папку «Входящие», которая централизует чат, электронную почту и многие другие каналы.

Поскольку наше текущее приложение используется многими разными пользователями, для нас, очевидно, важно ничего не сломать. Таким образом, нам также нужно было ежедневно улучшать наше программное обеспечение, чтобы мы могли исправлять ошибки и новые функции.

Итак, нам нужно было иметь две разные ветки:

  • наша основная ветвь для Vue 2.6 с нашим текущим release lifecycle
  • ветка vue3 для работы над миграцией кодовой базы на Vue 3

Кроме того, мы каждый день выпускали бета-версию нашей кодовой базы Vue 3 и вносили почти ежедневные изменения.

Наконец, мы решили не полагаться на новые API Vue, такие как Composition API , и перенести только то, что необходимо. Причина этого заключалась в том, чтобы снизить риск введения регрессий.

Обновление инструментов сборки Vue

Наше приложение использует Vue Cli ( webpack ). Опять же, мы решили пока не переходить на Vite , чтобы не допустить появления новых проблем, поскольку наша система сборки довольно сложна.

Перенести Vue Cli на поддержку Vue 3 довольно просто.

Сначала вам нужно отредактировать файл package.json, чтобы обновить Vue.js и его зависимости.

Поэтому мы заменяем "vue": "^2.6.12" на "vue": "^3.2.6" .

Кроме того, нам придется использовать "@vue/compat": "^3.2.6" , что позволит плавно перенести кодовую базу с Vue 2 на Vue 3.

Нам также придется обновить "vue-template-compiler": "^2.6.12" до "@vue/compiler-sfc": "^3.2.6".

Теперь нам нужно обновить наш файл vue.config.js и отредактировать функцию chainWebpack. Это заставит все ваши существующие библиотеки использовать пакет @vue/compat .

  // Vue 2 > Vue 3 compatibility mode
  config.resolve.alias.set("vue", "@vue/compat");

  config.module
    .rule("vue")
    .use("vue-loader")
    .loader("vue-loader")
    .tap(options => {
      // Vue 2 > Vue 3 compatibility mode
      return {
        ...options,
        compilerOptions: {
          compatConfig: {
            // default everything to Vue 2 behavior
            MODE: 2
          }
        }
      };
    });

Обновление нашего файла main.js

Теперь нам нужно создать экземпляр Vue следующим образом:

import { createApp, h, configureCompat } from "vue";

const app = createApp({
  render: () => h(App)
});

app.use(router);
app.use(store);
// Initiate other plugins here

configureCompat({
  // default everything to Vue 2 behavior
  MODE: 2
});


app.mount("#app");

Обновление Vue Router

Нам нужно будет использовать последнюю версию Vue Router "vue-router": "4.0.11"

Использование последнего Vue.js Router не сильно отличается. Основное отличие состоит в том, что вам придется вручную включить режим истории, используя:

import { createWebHistory, createRouter } from "vue-router";

var router = createRouter({
  history: createWebHistory(),
  routes: [
    // all your routes
  ]
});

Перенос фильтров

Наше существующее приложение во многом зависит от фильтров (около 200 использований). Первым делом нужно было выяснить, сколько фильтров мы использовали. Поскольку современные IDE (VSCode, SublimeText) поддерживают поиск Regex, мы создали регулярное выражение, чтобы мы могли узнать все использования фильтров Vue в наших шаблонах: {{ (.*?)\|(.*?) }}

Regex Search Using Sublime Text

Так как Vue 3 полностью отказывается от поддержки фильтров, мы попытались найти элегантный способ по-прежнему использовать фильтры. Решение заключалось в переносе фильтров Vue на настраиваемые Singletons Helpers. .

Например, на Vue 2:

Vue.filter("uppercase", function(string) {
  return string.toUpperCase();
});

Становится на Vue 3:

uppercase(string) {
  return string.toUpperCase();
}

export { uppercase };

Затем мы глобально создали экземпляр класса StringsFilter:

import { uppercase } from "@/filters/strings";

app.config.globalProperties.$filters = {
  uppercase: uppercase
};

Наконец, мы можем использовать наш фильтр:

{{ firstName | uppercase }} становиться {{ $filters.uppercase(firstName) }}

Правка ошибок

По мере продвижения процесса миграции вы будете замечать ошибки в консоли браузера. В режиме совместимости с Vue 3 предусмотрены различные журналы, которые помогут вам перенести приложение на Vue 3.

Vue 3 compat mode comes with explicit warnings

Вместо того, чтобы пытаться перенести функцию за функции или шаблон за шаблоном, мы рекомендуем начать перенос с API.

Например, если вы видите в журналах уведомление об устаревании WATCH_ARRAY, мы рекомендуем ознакомиться с руководством по миграции (migration guide), указанным в логах.

Затем выполните поэтапную миграцию каждого массива watch.

После завершения миграции всех watch вы можете отключить режим совместимости для этой функции:

import { createApp, h, configureCompat } from "vue";

const app = createApp({
  render: () => h(App)
});

// Initiate all plugins here

configureCompat({
  // default everything to Vue 2 behavior
  MODE: 2,

  // opt-in to Vue 3 behavior for non-compiler features
  WATCH_ARRAY: false
});


app.mount("#app");

Обновление библиотек

Vue 3 поставляется с пакетом @vue/compat , поддерживающим библиотеки Vue 2 и Vue 3. Однако режим @vue/compat также снижает производительность.

Использование режима совместимости возможно только при преобразовании вашего приложения в Vue 3 и все его библиотеки. После того, как весь проект и библиотеки будут мигрированы, вы можете избавиться от режима совместимости.

Чтобы этого добиться, нам придется перенести все библиотеки на совместимые с Vue 3.

Все официальные библиотеки Vue (Vuex, Vue Router) были перенесены на Vue 3, в то время как большинство популярных пакетов уже имеют версии, совместимые с Vue 3.

Около 20% используемых нами библиотек не имеют версии Vue 3, поэтому мы решили форкнуть все библиотеки, которые еще не были перенесены на Vue 3, например vue-router-prefetch .

Перенос библиотеки написанной на Vue 3 обычно довольно прост и занимает от 30 минут до полдня, в зависимости от сложности библиотеки.

После того, как мы закончили миграцию библиотеки для поддержки Vue 3, мы создаем « pull request » в исходную библиотеку, чтобы другие могли извлечь выгоду из нашей работы.

О производительности

Vue 3 имеет множество различных улучшений производительности благодаря новой внутренней системе реактивности. Размер Javascript heap был уменьшен с 20-30% в большинстве случаев и с 50% для некоторых сложных списков. В нашем случае использования обмена сообщениями мы заметили значительные улучшения ЦП.

Наше новое приложение Crisp стало работать намного быстрее.

Заключение

Пока мы писали эту статью, мы занимались развертываем нового приложение Crisp в производственной среде. На перенос нашего приложения у нас ушло около 2 недель.

Для сравнения, переход с Angular 1 на Vue 2 занял у нас около 9 месяцев.

Переход с Vue 2 на Vue 3 – важная, но не невыполнимая задача. Эта новая версия Vue предлагает несколько отличных улучшений производительности.

Чтобы поблагодарить сообщество Vue.js, мы начали вносить свой вклад в проект в качестве золотого спонсора. Как компания, мы чувствуем, что должны расширить возможности проектов с открытым исходным кодом, чтобы построить лучший мир, в котором программное обеспечение и среда могут взаимодействовать друг с другом.

Была ли вам полезна эта статья?
[6 / 5]

Spread the love
Подписаться
Уведомление о
guest
1 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Виталий
Виталий
2 лет назад

Перенос библиотеки Vue 3 обычно довольно прост и занимает от 30 минут до полдня, в зависимости от сложности библиотеки.
что это значит? вы переписывали код сторонней библиотеки?