Web系エンジニアのアウトプット練習場

エンジニアリングと書評が中心。たまに全然関係無い話もします。
トップページ/Firebase/Firebase + Vue.jsでなんかサービスを作成する(プロジェクト準備編 その2)/
2018年07月22日

Firebase + Vue.jsでなんかサービスを作成する(プロジェクト準備編 その2)

FirebaseVue.jsTypeScript

https://firebase.google.com/docs/web/setup?hl=jaを参考にfirebaseをjavascriptから使用可能にする。
main.jsに以下のようにFirebaseを使う記述を追加。

import * as firebase from "firebase";

// Initialize Firebase
var config = {
  apiKey: "AIzaSyB5e3yBAyAdMgwEA7Au_ePBT2NK1gOdaeY",
  authDomain: "<プロジェクトID>.firebaseapp.com",
  databaseURL: "https://<プロジェクトID>.firebaseio.com",
  projectId: "<プロジェクトID>",
  storageBucket: "<プロジェクトID>.appspot.com",
  messagingSenderId: "551247899090"
};

firebase.initializeApp(config);

コンソールログを見ると以下のようにいろいろな警告が出ていた。

It looks like you're using the development build of the Firebase JS SDK.
When deploying Firebase apps to production, it is advisable to only import
the individual SDK components you intend to use.

For the module builds, these are available in the following manner
(replace <PACKAGE> with the name of a component - i.e. auth, database, etc):

CommonJS Modules:
const firebase = require('firebase/app');
require('firebase/<PACKAGE>');

ES Modules:
import firebase from 'firebase/app';
import 'firebase/<PACKAGE>';

(anonymous) @ index.cjs.js?9318:125
./node_modules/firebase/dist/index.cjs.js @ app.js:881
__webpack_require__ @ app.js:679
fn @ app.js:89
(anonymous) @ selector.js?type=script&index=0!./src/App.vue:1
./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader/lib/selector.js?type=script&index=0!./src/App.vue @ app.js:837
__webpack_require__ @ app.js:679
fn @ app.js:89
(anonymous) @ App.vue?3172:1
./src/App.vue @ app.js:1155
__webpack_require__ @ app.js:679
fn @ app.js:89
(anonymous) @ main.js?1c90:1
./src/main.js @ app.js:1178
__webpack_require__ @ app.js:679
fn @ app.js:89
0 @ app.js:1195
__webpack_require__ @ app.js:679
(anonymous) @ app.js:725
(anonymous) @ app.js:728
index.js?bed3:147 [WDS] Warnings while compiling.
warnings @ index.js?bed3:147
onmessage @ socket.js?57b2:41
EventTarget.dispatchEvent @ sockjs.js?3600:170
(anonymous) @ sockjs.js?3600:883
SockJS._transportMessage @ sockjs.js?3600:881
EventEmitter.emit @ sockjs.js?3600:86
WebSocketTransport.ws.onmessage @ sockjs.js?3600:2957
index.js?bed3:153 ./src/App.vue
]50;CurrentDir=/Users/<ユーザー名>/workspace/<プロジェクトID>
  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:9:27
  import * as firebase from "firebase";
                             ^

  ✘  http://eslint.org/docs/rules/semi    Extra semicolon               
  src/App.vue:9:37
  import * as firebase from "firebase";
                                       ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:13:11
    apiKey: "AIzaSyB5e3yBAyAdMgwEA7Au_ePBT2NK1gOdaeY",
             ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:14:15
    authDomain: "<プロジェクトID>.firebaseapp.com",
                 ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:15:16
    databaseURL: "https://<プロジェクトID>.firebaseio.com",
                  ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:16:14
    projectId: "<プロジェクトID>",
                ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:17:18
    storageBucket: "<プロジェクトID>.appspot.com",
                    ^

  ✘  http://eslint.org/docs/rules/quotes  Strings must use singlequote  
  src/App.vue:18:22
    messagingSenderId: "551247899090"
                        ^

  ✘  http://eslint.org/docs/rules/semi    Extra semicolon               
  src/App.vue:19:2
  };
    ^

  ✘  http://eslint.org/docs/rules/semi    Extra semicolon               
  src/App.vue:21:31
  firebase.initializeApp(config);
                                 ^


✘ 10 problems (10 errors, 0 warnings)


Errors:
  7  http://eslint.org/docs/rules/quotes
  3  http://eslint.org/docs/rules/semi
 @ ./src/main.js 4:0-24
 @ multi (webpack)-dev-server/client?http://localhost:8080 webpack/hot/dev-server ./src/main.js

問題点抽出

  • For the module builds, these are available in the following manner (replace with the name of a component - i.e. auth, database, etc):
CommonJS Modules:
const firebase = require('firebase/app');
require('firebase/<PACKAGE>');

ES Modules:
import firebase from 'firebase/app';
import 'firebase/<PACKAGE>';
  • Strings must use singlequote
  • Extra semicolon


対応

  • main.jsを書き換え
-import * as firebase from "firebase";
+import firebase from 'firebase/app'
  • "'に置き換え
  • ;を削除

Typescriptを使ってみたい

TypeScriptロゴ

$ yarn add typescript ts-loader --dev
yarn add v1.7.0
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
warning "firebase > @firebase/database@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore@0.6.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions@0.3.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/messaging@0.3.5" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/auth > @firebase/auth-types@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/database > @firebase/database-types@0.3.2" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore > @firebase/firestore-types@0.5.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions > @firebase/messaging-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage > @firebase/storage-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
[5/5] 📃  Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
info Direct dependencies
├─ ts-loader@4.4.2
└─ typescript@2.9.2
info All dependencies
├─ ts-loader@4.4.2
└─ typescript@2.9.2
✨  Done in 14.82s.

以下を参考に設定ファイルを作成
とにかく楽してVue.jsでTypeScriptを使いたい from さくらインターネット株式会社

ts-loaderの設定ファイルtsconfig.jsonを作成

$ ./node_modules/.bin/tsc --init

生成されたtsconfig.jsonを元に一部設定を変更。

# tsconfig.json
{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "es2015",                       /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    "sourceMap": true,                        /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    "noImplicitThis": true,                   /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true,     /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    /* Source Map Options */
    // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

webpack.base.conf.jsを書き換えます。

  • entryのファイルの拡張子を変更(main.ts)
    • 合わせて実際にmain.jsをmain.tsにリネーム
    • router/index.jsも.tsに変更
$ git diff build/webpack.base.conf.js
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 1f4f47e..a899762 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -22,7 +22,7 @@ const createLintingRule = () => ({
 module.exports = {
   context: path.resolve(__dirname, '../'),
   entry: {
-    app: './src/main.js'
+    app: './src/main.ts'
   },
   output: {
     path: config.build.assetsRoot,
@@ -32,7 +32,7 @@ module.exports = {
       : config.dev.assetsPublicPath
   },
   resolve: {
-    extensions: ['.js', '.vue', '.json'],
+    extensions: ['.ts', '.js', '.vue', '.json'],
     alias: {
       'vue$': 'vue/dist/vue.esm.js',
       '@': resolve('src'),
@@ -46,6 +46,14 @@ module.exports = {
         loader: 'vue-loader',
         options: vueLoaderConfig
       },
+      {
+        test: /\.ts$/,
+        loader: 'ts-loader',
+        options: {
+          appendTsSuffixTo: [/\.vue$/],
+        }
+      },
       {
         test: /\.js$/,
         loader: 'babel-loader',

vueファイルをimportするためにsrc/sfc.d.tsを作成する。

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}

なんでこれでVueファイルをインポートできるようになるのかは・・・調べないとな。
ちなみに、sfcはSingle File Componentの略らしい。
Vueファイル内の<script>タグを<script lang='ts'>に置換。
App.vueにVue.extendを追加。

$ git diff src/App.vue
diff --git a/src/App.vue b/src/App.vue
index d74c648..257c204 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -5,10 +5,11 @@
   </div>
 </template>
 
-<script>
-export default {
+<script lang='ts'>
+import Vue from 'vue'
+export default Vue.extend({
   name: 'App'
-}
+})
 </script>
 
 <style>

tslintをインストール。

$ yarn add tslint tslint-loader --dev
yarn add v1.7.0
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
warning "firebase > @firebase/database@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore@0.6.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions@0.3.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/messaging@0.3.5" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/auth > @firebase/auth-types@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/database > @firebase/database-types@0.3.2" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore > @firebase/firestore-types@0.5.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions > @firebase/messaging-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage > @firebase/storage-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
[5/5] 📃  Building fresh packages...
success Saved lockfile.
success Saved 3 new dependencies.
info Direct dependencies
├─ tslint-loader@3.6.0
└─ tslint@5.11.0
info All dependencies
├─ tslint-loader@3.6.0
├─ tslint@5.11.0
└─ tsutils@2.28.0
✨  Done in 12.31s.

http://itexplorer.hateblo.jp/entry/20170807-vuejs-typescript-dev-envを参考に設定ファイルを編集していく。
tslint.jsonを作成。

$ ./node_modules/.bin/tslint --init
diff --git a/build/vue-loader.conf.js b/build/vue-loader.conf.js
index 33ed58b..d94f130 100644
--- a/build/vue-loader.conf.js
+++ b/build/vue-loader.conf.js
@@ -7,10 +7,13 @@ const sourceMapEnabled = isProduction
   : config.dev.cssSourceMap
 
 module.exports = {
-  loaders: utils.cssLoaders({
-    sourceMap: sourceMapEnabled,
-    extract: isProduction
-  }),
+  loaders: Object.assign({},
+    utils.cssLoaders({
+      sourceMap: sourceMapEnabled,
+      extract: isProduction,
+    }),
+    { ts: 'ts-loader!tslint-loader' }
+  ),
   cssSourceMap: sourceMapEnabled,
   cacheBusting: config.dev.cacheBusting,
   transformToRequire: {
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
index 0bd742f..a2b83d7 100644
--- a/build/webpack.base.conf.js
+++ b/build/webpack.base.conf.js
@@ -8,16 +8,25 @@ function resolve (dir) {
   return path.join(__dirname, '..', dir)
 }
 
-const createLintingRule = () => ({
-  test: /\.(js|vue)$/,
-  loader: 'eslint-loader',
-  enforce: 'pre',
-  include: [resolve('src'), resolve('test')],
-  options: {
-    formatter: require('eslint-friendly-formatter'),
-    emitWarning: !config.dev.showEslintErrorsInOverlay
+const createLintingRule = () => (
+  {
+    test: /\.(js|vue)$/,
+    loader: 'eslint-loader',
+    enforce: 'pre',
+    include: [resolve('src'), resolve('test')],
+    options: {
+      formatter: require('eslint-friendly-formatter'),
+      emitWarning: !config.dev.showEslintErrorsInOverlay
+    }
+  },
+  {
+    test: /\.ts$/,
+    loader: 'tslint-loader',
+    enforce: 'pre',
+    include: [resolve('src'), resolve('test')],
+    exclude: /(node_modules)/
   }
-})
+)

tslint.jsonのルールを自分好みに変更。

  • セミコロンはつけない
  • quoteは'よりも"
  • import順はアルファベット順である必要はない
  • 末尾のコンマはいらない
  • 連続して空行が2行続いても良い
    • Vueと相性が悪いのか、すべてのVueファイルで警告を出されたため
  • 関数名と()の間に空白が必要
diff --git a/tslint.json b/tslint.json
index 32fa6e5..8e4a6e8 100644
--- a/tslint.json
+++ b/tslint.json
@@ -4,6 +4,14 @@
         "tslint:recommended"
     ],
     "jsRules": {},
-    "rules": {},
+    "rules": {
+        "semicolon": [true, "never"],
+        "quotemark": [true, "double"],
+        "ordered-imports": [false],
+        "object-literal-sort-keys": [false],
+        "trailing-comma": [true, "never"],
+        "no-consecutive-blank-lines": [false],
+        "space-before-function-paren": [true, "always"]
+    },
     "rulesDirectory": []
 }
\ No newline at end of file

Visual Studio CodeにてTSLintTSLint Vueをインストール。
TSlint

発生した問題

Cannot set property 'tsLoaderFileVersion' of undefined

ts-loaderのバージョンを4.4.2から3.5.0に変えたら治った!

$ yarn add ts-loader@^3.5.0 --dev

どうやらWebpackのバージョンとの関連もあるらしい。
https://github.com/zeit/next-plugins/issues/84

Unknown custom element: <router-link>

$ yarn add @vue/test-utils --dev
yarn add v1.7.0
[1/5] 🔍  Validating package.json...
[2/5] 🔍  Resolving packages...
[3/5] 🚚  Fetching packages...
[4/5] 🔗  Linking dependencies...
warning "firebase > @firebase/database@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore@0.6.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions@0.3.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/messaging@0.3.5" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/auth > @firebase/auth-types@0.3.4" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/database > @firebase/database-types@0.3.2" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/firestore > @firebase/firestore-types@0.5.0" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/functions > @firebase/messaging-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
warning "firebase > @firebase/storage > @firebase/storage-types@0.2.3" has unmet peer dependency "@firebase/app-types@0.x".
[5/5] 📃  Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ @vue/test-utils@1.0.0-beta.20
info All dependencies
└─ @vue/test-utils@1.0.0-beta.20
✨  Done in 18.07s.

Error retrieving a new session from the selenium server. Connection refused! Is selenium server started?

https://github.com/nightwatchjs/nightwatch/issues/913
結論から言うと、CircleCIではnightwatchでSeleniumを立ち上げると上記のエラーが出る。
https://circleci.com/docs/2.0/project-walkthrough/#install-and-run-selenium-to-automate-browser-testing
公式に従ってstandaloneで立ち上げた後、yarn testすると良い。

https://gist.github.com/h-sakano/7d1ef65035f43c8f26f76002d06b0df1