nuxtjsを使ってみる

# インストール

[create-nuxt-app](https://ja.nuxtjs.org/guide/installation/)を使用する。

```sh
$ npx create-nuxt-app nuxt-app
```

いろいろ聞かれるけど、
Programming language: `JavaScript`
Package manager: `Npm`
Nuxt.js modules: `Axios`
Linting tools: `ESLint`と`Prettier`
Rendering mode: `Universal (SSR / SSG)`
Deployment target: `Server (Node.js hosting)`
を選択した。

nuxt-appディレクトリが出来るので移動して`npm run dev`コマンドを実行する。
ブラウザで`http://localhost:3000/`を開く。

# 構成

github
[h23k/nuxt-app](https://github.com/h23k/nuxt-app)

## layouts

[レイアウト](https://ja.nuxtjs.org/guide/views#%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88)
`<Nuxt />`にpagesの`<template></template>`内容が読み込まれる。

```vue:default.vue
<template>
  <div>
    <Nuxt />
  </div>
</template>
<style>
html {
  font-family: 'Hiragino Sans', ヒラギノ角ゴシック,
    YuGothic, 游ゴシック, Roboto, sans-serif;
  letter-spacing: 0.07em;
  line-height: 1.5;
}
</style>
```

## pages

`<Button />`でcomponents/Button.vueを読み込む。

```vue:index.vue
<template>
  <div class="container">
    <div class="box">
      <h1 class="title">
        {{ title }}
      </h1>
      <p class="message">
        あいうえお、かきくけこ、さしすせそ、たちつてと、なにぬねの<br />
        はひふへほ、まみむめも、やゆよ、らりるれろ、わをん<br />
        かぎぐげご、ざじずぜぞ、だぢづでど<br />
        ばびぶべぼ、ぱぴぷぺぽ
      </p>
      <Button :label="titles[newIndex]" @on-click="convert" />
    </div>
  </div>
</template>

<script>
import Button from '../components/Button.vue'

export default {
  components: {
    Button,
  },
  data: () => ({
    titles: ['ひらがな', 'カタカナ'],
    titleIndex: 0,
  }),
  computed: {
    title() {
      return this.titles[this.titleIndex]
    },
    newIndex() {
      return this.titleIndex === 0 ? 1 : 0
    },
  },
  methods: {
    convert(e) {
      e.preventDefault()
      const message = document.querySelector('.message')
      const src = message.innerHTML
      let dest
      if (this.titleIndex === 0) {
        dest = src.replace(/[\u3041-\u3096]/g, (c) => {
          return String.fromCharCode(c.charCodeAt(0) + 0x60)
        })
      } else {
        dest = src.replace(/[\u30A1-\u30F6]/g, (c) => {
          return String.fromCharCode(c.charCodeAt(0) - 0x60)
        })
      }
      message.innerHTML = dest
      this.titleIndex = this.newIndex
    },
  },
  head() {
    return {
      title: this.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: 'ひらがなとカタカナ',
        },
      ],
    }
  },
}
</script>

<style>
.container {
  margin: 0 auto;
  min-height: 100vh;
  background: linear-gradient(#3b57f2 0%, #3b57f2 50%, #fff 50%);
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.box {
  min-height: 62.962962963vh;
  width: 81.5625%;
  background: #fff;
  margin: 0 auto;
  padding: 70px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.16);
  border-radius: 10px;
  text-align: left;
  position: relative;
  z-index: 1;
  overflow: hidden;
}

.title {
  display: block;
  font-weight: normal;
  font-size: 60px;
  color: #35495e;
}
.message {
  max-width: 41.371681416%;
  margin-top: 1.25em;
  margin-bottom: 1.875em;
  font-size: 16px;
  line-height: 1.6875;
}
</style>
```

## components

コンポーネント呼び出し時に`label`を受け取る。

```vue:Button.vue
<template>
  <a class="button" href="#" @click="click">{{ label }}</a>
</template>

<script>
export default {
  props: {
    label: {
      type: String,
      required: true,
    },
  },
  methods: {
    click(e) {
      this.$emit('on-click', e)
    },
  },
}
</script>

<style>
.button {
  display: block;
  height: 50px;
  width: 175px;
  border: 1px solid #c4c4c4;
  font-size: 16px;
  color: inherit;
  cursor: pointer;
  line-height: 50px;
  text-align: center;
  text-decoration: none;
  position: relative;
  overflow: hidden;
  transition: color 0.3s ease, border-color 0.3s ease 0.3s,
    box-shadow 0.3s ease 0.3s;
}
.button::before {
  content: '';
  border-right: 50px solid transparent;
  border-bottom: 50px solid #3b57f2;
  transform-origin: left center;
  position: absolute;
  top: 0;
  right: -50px;
  bottom: 0;
  left: 0;
  z-index: -1;
  transform: translate(-100%, 0);
  transition: transform 0.3s ease;
}
.button:hover {
  border-color: #3b57f2;
  box-shadow: 0 2px 4px -2px rgba(0, 0, 0, 0.4);
  color: #fff;
}
.button:hover::before {
  transform: translate(0, 0);
}
</style>
```

# scssを使う

[cssプロパティ](https://ja.nuxtjs.org/api/configuration-css/)

```sh
$ npm install --save-dev sass sass-loader
```

vueファイルの`<style>`に`lang="scss"`を付ける。

```vue:pages/index.vue
<style lang="scss">
.container {
  margin: 0 auto;
...
</style>
```

[Scoped CSS](https://vue-loader.vuejs.org/guide/scoped-css.html#mixing-local-and-global-styles)
`<style>`に`scoped`を付けると他のファイルに影響しない。

```vue
<style lang="scss" scoped>
.button {
  display: block;
...
</style>
```

# その他

## lsp server

```sh
$ npm install vls -g
```

## emacs

`package-list-packages`で`vue-mode`と`lsp-mode`と`lsp-ui`と`company-lsp`をインストール
※ *`company`と`flycheck`はインストール済み*

```conf:init.el
;; vue-mode
(when (require 'vue-mode nil t)
  (add-to-list 'auto-mode-alist '("\\.vue\\'" . vue-mode))
)

;; mmm-mode
(when (require 'mmm-auto nil t)
  (setq mmm-global-mode 'maybe)
  (mmm-add-classes
   '((vue-embedded-scss-mode
      :submode scss-mode
      :front "<style *lang=\"scss\" [^>]*?>\n"
      :back "</style>")))
  (mmm-add-mode-ext-class 'vue-mode nil 'vue-embedded-scss-mode)
)

;; lsp-mode
(when (require 'lsp-mode nil t)
  (add-hook 'vue-mode-hook #'lsp)
)
```

### 編集中にモードが変になったら

`M-x mmm-parse-buffer`で再描画

### lsp

lsp-modeのキーバインド`s-l`は`C-x @ s l`