Vue2系で作っていたTODOリストをVue3系にアップデートしてみる

Vue2系で作っていたTODOリストをVue3系にアップデートしてみる

はじめに

本記事は、Vue2 系で作成していた TODO リストを Vue3 系にアップデートした際の記録です。

※Composition API を使用した記述や書き直しをしている内容ではありません。

作成したアプリケーションは以下のリンクより確認できます。

GitHub のリポジトリも記載しておきます。

開発環境

  • Windows11
  • WSL2
  • Node.js: v16.17.0
  • npm: v8.19.2
  • yarn: v1.22.19

パッケージをアップデートする

リポジトリをクローンする

リポジトリをクローンして旧バージョンのブランチ(feature/old-version-vue2)へ切り替えます。

$ git clone https://github.com/shimanamisan/vue_todo/

$ git checkout -b feature/old-version-vue2 origin/feature/old-version-vue2

package.json に記述されているパッケージををインストールします。

$ yarn install

さっそく大量のエラーが出力されました。

yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
warning " > autoprefixer@10.0.1" has unmet peer dependency "postcss@^8.1.0".
warning " > postcss-loader@4.0.3" has unmet peer dependency "postcss@^7.0.0 || ^8.0.1".
[4/4] Building fresh packages...
[-/2] ⠁ waiting...
error /home/user01/vue_todo-feature-old-version-vue2/node_modules/node-sass: Command failed.
Exit code: 1
Command: node scripts/build.js
Arguments:
Directory: /home/user01/vue_todo-feature-old-version-vue2/node_modules/node-sass
Output:
Building: /home/user01/.nvm/versions/node/v16.17.0/bin/node /home/user01/vue_todo-feature-old-version-vue2/node_modules/node-gyp/bin/node-gyp.js rebuild --verbose --libsass_ext= --libsass_cflags= --libsass_ldflags= --libsass_library=
gyp info it worked if it ends with ok
gyp verb cli [
gyp verb cli   '/home/user01/.nvm/versions/node/v16.17.0/bin/node',
gyp verb cli   '/home/user01/vue_todo-feature-old-version-vue2/node_modules/node-gyp/bin/node-gyp.js',
gyp verb cli   'rebuild',
gyp verb cli   '--verbose',
gyp verb cli   '--libsass_ext=',
gyp verb cli   '--libsass_cflags=',
gyp verb cli   '--libsass_ldflags=',
gyp verb cli   '--libsass_library='
gyp verb cli ]
gyp info using node-gyp@3.8.0
gyp info using node@16.17.0 | linux | x64
gyp verb command rebuild []
gyp verb command clean []
gyp verb clean removing "build" directory
gyp verb command configure []
gyp verb check python checking for Python executable "python2" in the PATH
gyp verb `which` failed Error: not found: python2
gyp verb `which` failed     at getNotFoundError (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:13:12)
gyp verb `which` failed     at F (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:68:19)
gyp verb `which` failed     at E (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:80:29)
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:89:16
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/index.js:42:5
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/mode.js:8:5
gyp verb `which` failed     at FSReqCallback.oncomplete (node:fs:206:21)
gyp verb `which` failed  python2 Error: not found: python2
gyp verb `which` failed     at getNotFoundError (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:13:12)
gyp verb `which` failed     at F (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:68:19)
gyp verb `which` failed     at E (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:80:29)
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:89:16
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/index.js:42:5
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/mode.js:8:5
gyp verb `which` failed     at FSReqCallback.oncomplete (node:fs:206:21) {
gyp verb `which` failed   code: 'ENOENT'
gyp verb `which` failed }
gyp verb check python checking for Python executable "python" in the PATH
gyp verb `which` failed Error: not found: python
gyp verb `which` failed     at getNotFoundError (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:13:12)
gyp verb `which` failed     at F (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:68:19)
gyp verb `which` failed     at E (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:80:29)
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:89:16
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/index.js:42:5
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/mode.js:8:5
gyp verb `which` failed     at FSReqCallback.oncomplete (node:fs:206:21)
gyp verb `which` failed  python Error: not found: python
gyp verb `which` failed     at getNotFoundError (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:13:12)
gyp verb `which` failed     at F (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:68:19)
gyp verb `which` failed     at E (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:80:29)
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:89:16
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/index.js:42:5
gyp verb `which` failed     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/mode.js:8:5
gyp verb `which` failed     at FSReqCallback.oncomplete (node:fs:206:21) {
gyp verb `which` failed   code: 'ENOENT'
gyp verb `which` failed }
gyp ERR! configure error
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
gyp ERR! stack     at PythonFinder.failNoPython (/home/user01/vue_todo-feature-old-version-vue2/node_modules/node-gyp/lib/configure.js:484:19)
gyp ERR! stack     at PythonFinder.<anonymous> (/home/user01/vue_todo-feature-old-version-vue2/node_modules/node-gyp/lib/configure.js:406:16)
gyp ERR! stack     at F (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:68:16)
gyp ERR! stack     at E (/home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:80:29)
gyp ERR! stack     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/which/which.js:89:16
gyp ERR! stack     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/index.js:42:5
gyp ERR! stack     at /home/user01/vue_todo-feature-old-version-vue2/node_modules/isexe/mode.js:8:5
gyp ERR! stack     at FSReqCallback.oncomplete (node:fs:206:21)
gyp ERR! System Linux 5.10.102.1-microsoft-standard-WSL2
gyp ERR! command "/home/user01/.nvm/versions/node/v16.17.0/bin/node" "/home/user01/vue_todo-feature-old-version-vue2/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /home/user01/vue_todo-feature-old-version-vue2/node_modules/node-sass
gyp ERR! node -v v16.17.0
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
Build failed with error code: 1

調べてみると、"node-sass": "^4.14.1"のバージョンが現在の Node.js のバージョンをサポートしていないことが原因でした。

supported-node-sass-version

また、node-sass の Github リポジトリの README に、node-sass は非推奨のパッケージになっているので、Dart Sass を使用するように書かれていました。

packege.json を更新します。node-sass-glob-importerも不要になったので削除します。

# node-sassを削除
$ yarn remove node-sass node-sass-glob-importer

# DartSassをインストール
$ yarn add -D sass

もう一度パッケージをインストールします。

# 一度失敗しているので、node_modulesを削除しておく
$ rm -rf node_modules/

$ yarn install

今度は成功しました!!

インストールされているパッケージの更新

# 最新の状態に更新
$ yarn upgrade --latest

package.json も更新されます。

アップデートコマンドに関する詳細はドキュメントを参照してください。

今回のアップデートで webpack や babel のバージョンも上がりました。理由は後述しますが、以下のパッケージは不要になったため削除します。

$ yarn remove @babel/polyfill url-loader file-loader uglifyjs-webpack-plugin

特に利用しないパッケージも削除します。

$ yarn remove webpack-livereload-plugin
"package.json"
- "@babel/polyfill": "^7.12.1",
- "url-loader": "^4.1.0",
- "file-loader": "^6.1.0",
- "uglifyjs-webpack-plugin": "^2.2.0",
- "webpack-livereload-plugin": "^2.3.0"

アップデート後の package.json の差分

"package.json"
"devDependencies": {
- "@babel/core": "^7.11.6",
+ "@babel/core": "^7.19.3",
- "@babel/preset-env": "^7.11.5",
+ "@babel/preset-env": "^7.19.4",
- "autoprefixer": "^10.0.1",
+ "autoprefixer": "^10.4.12",
- "babel-loader": "^8.1.0",
+ "babel-loader": "^8.2.5",
- "clean-webpack-plugin": "^3.0.0",
+ "clean-webpack-plugin": "^4.0.0",
- "css-loader": "^4.3.0",
+ "css-loader": "^6.7.1",
  "import-glob-loader": "^1.1.0",
- "lodash": "^4.17.20",
+ "lodash": "^4.17.21",
- "mini-css-extract-plugin": "^0.11.2",
- "optimize-css-assets-webpack-plugin": "^5.0.4",
+ "optimize-css-assets-webpack-plugin": "^6.0.1",
- "postcss-loader": "^4.0.3",
+ "postcss-loader": "^7.0.1",
- "prettier": "^2.1.2",
+ "prettier": "^2.7.1",
  "sass": "^1.55.0",
- "sass-loader": "^10.0.2",
+ "sass-loader": "^13.1.0",
- "style-loader": "^1.2.1",
+ "style-loader": "^3.3.1",
- "terser-webpack-plugin": "^4.2.2",
+ "terser-webpack-plugin": "^5.3.6",
+ "mini-css-extract-plugin": "^2.6.1",
- "vue-loader": "^15.9.3",
+ "vue-loader": "^17.0.0",
  "vue-svg-loader": "^0.16.0",
- "vue-template-compiler": "^2.6.12",
+ "vue-template-compiler": "^2.7.12",
- "vuedraggable": "^2.24.1",
+ "vuedraggable": "^2.24.3",
- "webpack": "^4.44.2",
+ "webpack": "^5.74.0",
- "webpack-build-notifier": "^2.1.0",
+ "webpack-build-notifier": "^2.3.0",
- "webpack-cli": "^3.3.12",
+ "webpack-cli": "^4.10.0",
- "webpack-dev-server": "^3.11.0",
+ "webpack-dev-server": "^4.11.1",
},
"dependencies": {
  "particles.js": "^2.0.0",
- "vue": "^2.6.12"
+ "vue": "^3.2.40"
  }

とりあえず一回ビルドしてみます。

Invalid dependencies have been reported by plugins or loaders for this module. All reported dependencies need to be absolute paths.
Invalid dependencies may lead to broken watching and caching.
As best effort we try to convert all invalid values to absolute paths and converting globs into context dependencies, but this is deprecated behavior.
Loaders: Pass absolute paths to this.addDependency (existing files), this.addMissingDependency (not existing files), and this.addContextDependency (directories).
Plugins: Pass absolute paths to fileDependencies (existing files), missingDependencies (not existing files), and contextDependencies (directories).
Globs: They are not supported. Pass absolute path to the directory as context dependencies.
The following invalid values have been reported:

* "layout/"
* "object/"
* "object/component/"
* and more ...
@ ./src/scss/style.scss
@ ./src/js/index.js 8:0-28

sassに関するとことでエラーが出ました。webpac5 にアップデートしてからimport-glob-loaderを使用したファイルの読み込みは動作しませんでした。

そのため、import-glob-loaderを削除し、以下のようにファイルの読み込みを絶対パスに指定したところエラーは解消しました。

"style.scss

- @import "layout/**";
+ @import "layout/main";

// コンポーネント
- @import "object/**";
+ @import "object/component/btn";
+ @import "object/component/form";
+ @import "object/component/modal";
+ @import "object/component/valid";

// プロジェクト
+ @import "object/project/logo";
+ @import "object/project/task";
+ @import "object/project/title";

// ユーティリティ
+ @import "object/utility/utility";
$ yarn remove import-glob-loader

再度ビルドを実行します。

ERROR in ./src/js/App.vue?vue&type=template&id=3ea74058 (./node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/js/App.vue?vue&type=template&id=3ea74058) 1:0-300
Module not found: Error: Can't resolve 'vue' in '/home/user01/vue_todo-feature-old-version-vue2/vue_todo/src/js'
 @ ./src/js/App.vue?vue&type=template&id=3ea74058 1:0-263 1:0-263
 @ ./src/js/App.vue 1:0-64 6:68-74
 @ ./src/js/index.js 7:0-28 12:13-16

Vue ローダーに関するエラーが発生しました。公式の移行ビルドを参考にローダーやコンパイラを変更していきます。

Vue 本体のアップデート

コンパイラを変更

移行ビルドを参考に Vue2 と互換性のあるコンパイラをインストールします。vue/compat は、Vue2 互換の動作を設定可能な Vue3 のビルドです。

$ yarn remove vue-template-compiler

$ yarn add -D @vue/compiler-sfc

$ yarn add -S @vue/compat
"packege.json
"devDependencies": {
- "vue-template-compiler": "^2.7.12"
+ "@vue/compiler-sfc": "^3.2.40"
},
"dependencies": {
  "vue": "^3.2.40",
+ "@vue/compat": "^3.2.40"
   ...
},

@vue/compat@vue/compiler-sfcのバージョンが同じであることを確認してください。

vue/compat の特徴

  • デフォルトで Vue2 モードで動作
  • ほとんどの公開 API は Vue2 と全く同じように動く
  • Vue3 で変更された機能または非推奨の機能を使用すると、実行時に警告が表示される
  • 機能の互換性はコンポーネント単位で有効・無効化が出来る

webpack の設定を変更

webpack のエイリアスで指定しているコンパイラとオプションの記述を変更します

"webpack.config.common.js"
 module: {
    rules: [
      {
        // Vueファイルに対する設定
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              compilerOptions: {
                compatConfig: {
                  MODE: 2,
                },
              },
            },
          },
        ],
      },

      // ~ 中略 ~
    ],
  },
  // 各種プラグインを読み込む
  plugins: [
    // ~ 中略 ~
  ],
  resolve: {
    // エイリアスを指定
    alias: {
      vue: "@vue/compat", // 変更
      "@": `${src}/js`,
      "@img": `${src}/images`,
      "@scss": `${src}/scss`,
    },
  },

Vue インスタンスを生成している書き方を変更

createAppを使用した書き方に変更します。

"index.js"
// Vue2でのインスタンス化
new Vue({
  render: h => h(App),
}).$mount("#app")

// Vue3でのインスタンス化
Vue.createApp(App).mount("#app")

Webpack 関連のパッケージや設定を変更

webpack 5 から url-loader, file-loader, raw-loader が要らなくなった

これまでは画像ファイルの出力にurl-loaderを使用していましたが、Webpack5 にアップデートしたことにより必要なくなりました。

Webpack5 からAsset Modulesという機能が追加されたので、そちらを使うように修正します。

"webpack.config.common.js"
// ***********************
// * 画像ファイルに関する設定
// ***********************
{
  // webpack 5からurl-loader/file-loader/raw-loaderが要らなくなった
  // 拡張子の大文字も許容するように最後尾に i を加える
  // jpegとjpgの様にeがあるかないかを許容するのに、jpe?gという形式にする
  test: /\.(jpe?g|png|svg|gif|ico)$/i,
  type: 'asset/resource',
  generator: {
    filename: 'images/[name][ext]'
  }
},

url-loaderを使用していた箇所をasset/resourceに変更します。

またファイルの出力先の指定にgenerator内で指定します。

設定ファイルを書き換えたら、使用していたurl-loaderを削除します。

@babel/polyfill を削除する

Babel 7.4.0 から polyfill を使用することが非推奨になり、代わりにcore-jsregenerator-runtimeを使用する用になりました。

$ yarn remove @babel/polyfill

$ yarn add core-js regenerator-runtime

追加したパッケージを index.js でインポートします。

"index.js"

+ import "core-js/stable";
+ import "regenerator-runtime/runtime";
import 'particles.js/particles'
const particlesJS = window.particlesJS
particlesJS.load('particles-js', './particles.json', function () {
  console.log('callback - particles.js config loaded')
})

import '@scss/style.scss'

import Vue, { configureCompat } from 'vue'
import App from '@/App.vue'

Vue.createApp(App).mount('#app')

babel ローダーの設定を別ファイルに切り出す

babel ローダーのオプションで設定していた@babel/preset-envは、babel.config.jsファイルに切り出して以下のように記述します。

module.exports = {
  // インストールしたpreset-envを指定
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage', // 必要なポリフィルのみを取り込む設定
        corejs: 3, // corejsのバージョンを指定
        debug: true, // 取り込まれたポリフィルを確認する。不要であれば削除する
      },
    ],
  ],
};

vue-draggable-next を動作させる

今回のアプリケーションではvuedraggableという指定した要素をドラッグ&ドロップで並び替えが出来るライブラリを使用しており、Vue3 のアップデートしたことによってこのライブラリもアップデートしました。

$ yarn remove vuedraggable

$ yarn add vuedraggable@next

ここで一度ビルドして簡易サーバを起動してみます。

$ yarn run start

背景の関係でちょっと見えづらいですが、draggable 要素は item slot が必須と言うようなメッセージが出力されています。

not-vue3-vuedraggable.png

ここからは新しいバージョンでの書き方に変更してい行きます。

コンポーネントを修正

TaskList.vueコンポーネントを以下のように修正します。以前のvuedraggableの書き方ではv-forを使用して更に子コンポーネントのTaskItem.vueでデータを受け取っていましたが、今回からDraggableコンポーネント内で描画されるのでTaskItem.vueは削除します。

draggable-component

"TaskList.vue"
<template>
  <div class="p-task">
    <ul class="p-task__list">
      <draggable :list="taskList" item-key="id" @start="drag = true" @end="drag = false">
        <template #item="{ element }">
          <li class="p-task__item" :class="{ 'p-task__item--isDone': element.isDone }">
            <i class="c-form__check"
              :class="{ 'far fa-circle': !element.isDone, 'fas fa-check-circle': element.isDone }"
              @click="changeIsDone(element.id)"></i>
            {{ element.value }}
            <span class="u-handle"></span>
            <i class="fas fa-trash-alt p-task__trash" @click="removeItem(element.id)"></i>
          </li>
        </template>
      </draggable>
    </ul>
  </div>
</template>

<script>
import draggable from "vuedraggable";
export default {
  data() {
    return {
      drag: false,
      isDone: this.list,
    }
  },
  props: {
    taskList: {
      // 受け取るデータの型は配列を指定
      type: Array,
      // propsが渡されていない場合はコンソールに警告を表示する
      required: true,
      // デフォルト値として空の配列を指定する場合は関数経由で指定する(requiredと併用はしない)
      // default: () => [],
    },
  },
  components: {
    draggable,
  },
  methods: {
    changeIsDone(id) {
      this.$emit("isdone-event", id);
    },
    removeItem(id) {
      this.$emit("remove-event", id);
    },
    taskEdit(id) {
      this.$emit("edit-event", id);
    },
    coloseEdit(todo) {
      this.$emit("editclose-event", todo);
    },
  },
};
</script>

<style>
.fadeOut-enter-active,
.fadeOut-leave-active {
  transition: all 1s;
}

.fadeOut-enter,
.fadeOut-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

.ghost {
  opacity: 0.5;
  background: #c8ebfb;
}
</style>

再度簡易サーバを立ち上げて見ましょう。

$ yarn run start

次は違うエラーが発生しました。

Cannot read properties of undefined (reading 'updated')

cannot-read-properties-of-undefined

またコンソールに以下の警告がありました。

[Vue warn]: (deprecation RENDER_FUNCTION) Vue 3's render function API has changed. You can opt-in to the new API with:

  configureCompat({ RENDER_FUNCTION: false })

  (This can also be done per-component via the "compatConfig" option.)
  Details: https://v3-migration.vuejs.org/breaking-changes/render-function-api.html
  at <Draggable list= []length: 0[[Prototype]]: Array(0) item-key="id" onStart=fn  ... >
  at <TaskList taskList= []length: 0[[Prototype]]: Array(0) onIsdoneEvent=fn<bound changeIsDone> onRemoveEvent=fn<bound removeItem>  ... >
  at <App>

compat の設定で、機能を個別に無効化できるので警告にある通りRENDER_FUNCTION: falseを設定します。

"src/index.js"
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import "particles.js/particles";
const particlesJS = window.particlesJS;
particlesJS.load("particles-js", "../particles.json", function () {
  console.log("callback - particles.js config loaded");
});

// configureCompatを追加
import Vue, { configureCompat } from "vue";
import App from "@/App.vue";

// 追加
configureCompat({
  RENDER_FUNCTION: false,
})

import "@scss/style.scss";

Vue.createApp(App).mount("#app")

正しく描画されました!

draggable-next-true

子コンポーネントを削除

Draggable コンポーネント内でデータが管理されているので、TaskItem.vue は削除します

vue3-draggable-props

Vue のコンポーネントの書き換えは以上です。

最終的なコードは以下のようになりました。

"TaskList.vue"
<template>
  <div class="p-task">
    <ul class="p-task__list">
      <draggable class="list-group" :list="taskList" tag="transition-group" :component-data="{ name: 'fadeOut' }"
        ghost-class="ghost" item-key="order" @start="drag = true" @end="drag = false">
        <template #item="{ element }">
          <li class="p-task__item" :class="{ 'p-task__item--isDone': element.isDone }">
            <i class="c-form__check"
              :class="{ 'far fa-circle': !element.isDone, 'fas fa-check-circle': element.isDone }"
              @click="changeIsDone(element.id)"></i>
            <span class="u-handle"></span>
            <i class="fas fa-trash-alt p-task__trash" @click="removeItem(element.id)"></i>
            <template v-if="element.edit">
              <input class="c-form c-form__edit" type="text" v-model="element.value"
                @keypress.enter="coloseEdit(element)" @blur="coloseEdit(element)" />
            </template>
            <template v-else>
              <div class="p-task__item__wrapp" @dblclick="taskEdit(element.id)">
                <span class="p-task__item__text" :class="{ 'p-task__item--isDoneText': element.isDone }">{{
                element.value
                }}</span>
              </div>
            </template>
          </li>
        </template>
      </draggable>
    </ul>
  </div>
</template>

<script>
import draggable from "vuedraggable";
export default {
  data() {
    return {
      drag: false,
      isDone: this.list,
    }
  },
  props: {
    taskList: {
      // 受け取るデータの型は配列を指定
      type: Array,
      // propsが渡されていない場合はコンソールに警告を表示する
      required: true,
      // デフォルト値として空の配列を指定する場合は関数経由で指定する(requiredと併用はしない)
      // default: () => [],
    },
  },
  components: {
    draggable,
  },
  methods: {
    changeIsDone(id) {
      this.$emit("isdone-event", id);
    },
    removeItem(id) {
      this.$emit("remove-event", id);
    },
    taskEdit(id) {
      this.$emit("edit-event", id);
    },
    coloseEdit(todo) {
      this.$emit("editclose-event", todo);
    },
  },
};
</script>

<style>
.fadeOut-enter-active,
.fadeOut-leave-active {
  transition: all 1s;
}

.fadeOut-enter,
.fadeOut-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}

.ghost {
  opacity: 0.5;
  background: #c8ebfb;
}
</style>

【余談】子スロットは一つでなければならない

以下の書き方はエラーになります。

Error: Item slot must have only one child

"TaskList.vue"
<template>
  <div class="p-task">
    <ul class="p-task__list">
      <draggable :list="list" item-key="id" @start="drag = true" @end="drag = false">
        <template #item="{ element }">
          <li class="p-task__item" :class="{ 'p-task__item--isDone': element.isDone }">
            <i
              class="c-form__check"
              :class="{ 'far fa-circle': !element.isDone, 'fas fa-check-circle': element.isDone }"
              @click="changeIsDone(element.id)"
            ></i>
            {{ element.value }}
            <span class="u-handle"></span>
            <i class="fas fa-trash-alt p-task__trash" @click="removeItem(element.id)"></i>
          </li>
          <div></div> <!-- ここに要素は追加できない -->
          <!-- <div></div> ←コメントアウトしててもエラーになる -->
        </template>
      </draggable>
    </ul>
  </div>
</template>

Vue3 のアップデート後に発生した警告を対応する

Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined.
You are running the esm-bundler build of Vue, which expects these compile-time feature flags to be globally injected via the bundler config in order to get better tree-shaking in the production bundle.

/*
Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ は明示的に定義されていません。
Vue の esm-bundler ビルドを実行している場合、これらのコンパイル時機能フラグが bundler config を介してグローバルに注入され、
本番バンドルでより良いツリーシェイクが得られると期待されます。
*/

Tree Shaking とは、webpack などでファイルをバンドルする際に、デッドコード(利用されていない不要なコード)を除去してファイルを出力することです。Bundler Build Feature Flagsでは Tree Shaking を利用することが推奨されていました。

webpack では DefinePlugin という機能を使ってコンパイル時にグローバル定数を作成することができます。

""
const webpack = require('webpack');

  // ...中略...

  // 各種プラグインを読み込む
  plugins: [

    // ...中略...

    // ビルド時に特定のファイルをdistディレクトリにコピーする
    new CopyFilePlugin({
      patterns: [
        {
          context: src,
          from: `${src}/modules/particles.json`,
          to: dist,
        },
      ],
    }),
    // ↓追加
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
  ],

最後に

今回は主に webpack や Vue.js のアップデート作業時に発生したエラーの解決などをまとめました。

次は新しい書き方に変えてみたり、vuex や vue-router その他のライブラリを使用したときにどのような注意点が必要かまとめて見ようと思います。

少しでも参考になれば幸いです。