Vue.js 2.0 の Single File Components を試してみる

http://vuejs.org/guide/single-file-components.html
https://github.com/vuejs-templates/webpack-simple

今回はVue.js 2.0のコンポーネントを１つのファイルに纏めて定義する機能を見ていきます。
npmやらWebpackやらBrowserifyが必要なようなので、cloud9上でやります。
なおVue.jsはテンプレート(HTML)、ロジック(javascript)、スタイル(CSS)は本来１つであり、１つのファイルにまとめたほうが開発効率が上がるという思想のため、すべて.vueファイル上に記述するようです。
https://jp.vuejs.org/2015/10/28/why-no-template-url/

#Getting Started
はい。やることはコマンドラインツール（CLI)をインストールしてサンプルプロジェクト立ち上げるだけです。

CLIのインストール：`npm install -g vue-cli`

```
~/workspace/vue $ npm install -g vue-cli
npm WARN deprecated minimatch@0.3.0: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
/home/ubuntu/.nvm/versions/node/v4.2.4/bin/vue -> /home/ubuntu/.nvm/versions/node/v4.2.4/lib/node_modules/vue-cli/bin/vue
vue-cli@2.4.0 /home/ubuntu/.nvm/versions/node/v4.2.4/lib/node_modules/vue-cli
├── semver@5.3.0
├── uid@0.0.2
├── commander@2.9.0 (graceful-readlink@1.0.1)
├── validate-npm-package-name@2.2.2 (builtins@0.0.7)
├── chalk@1.1.3 (supports-color@2.0.0, escape-string-regexp@1.0.5, ansi-styles@2.2.1, has-ansi@2.0.0, strip-ansi@3.0.1)
├── minimatch@3.0.3 (brace-expansion@1.1.6)
├── ora@0.2.3 (object-assign@4.1.0, cli-spinners@0.1.2, cli-cursor@1.0.2)
├── rimraf@2.5.4 (glob@7.1.1)
├── consolidate@0.14.1 (bluebird@3.4.6)
├── read-metadata@1.0.0 (yaml-js@0.0.8)
├── request@2.75.0 (aws-sign2@0.6.0, forever-agent@0.6.1, is-typedarray@1.0.0, caseless@0.11.0, tunnel-agent@0.4.3, oauth-sign@0.8.2, stringstream@0.0.5, isstream@0.1.2, json-stringify-safe@5.0.1, aws4@1.4.1, extend@3.0.0, tough-cookie@2.3.1, node-uuid@1.4.7, qs@6.2.1, combined-stream@1.0.5, mime-types@2.1.12, form-data@2.0.0, hawk@3.1.3, bl@1.1.2, http-signature@1.1.1, har-validator@2.0.6)
├── metalsmith@2.2.0 (is-utf8@0.2.1, absolute@0.0.1, has-generators@1.0.1, win-fork@1.1.1, thunkify@2.1.2, clone@0.1.19, stat-mode@0.2.2, async@0.9.2, is@2.2.1, unyield@0.0.1, chalk@0.5.1, ware@1.3.0, recursive-readdir@1.3.0, fs-extra@0.10.0, co-fs-extra@0.0.2, gray-matter@2.0.2)
├── download-git-repo@0.2.1 (git-clone@0.1.0, download@4.4.3)
├── handlebars@4.0.5 (async@1.5.2, source-map@0.4.4, optimist@0.6.1, uglify-js@2.7.3)
├── inquirer@0.12.0 (ansi-regex@2.0.0, strip-ansi@3.0.1, ansi-escapes@1.4.0, rx-lite@3.1.2, through@2.3.8, cli-width@2.1.0, figures@1.7.0, cli-cursor@1.0.2, run-async@0.1.0, readline2@1.0.1, string-width@1.0.2, lodash@4.16.4)
└── async@2.0.1 (lodash@4.16.4)
~/workspace/vue $ 
```
１つワーニングでましたけど、まあいいでしょう。

つづいてサンプルプロジェクトを作成： `vue init webpack-simple my-project`

```
~/workspace/vue $ vue init webpack-simple my-project

  This will install Vue 2.x version of template.

  For Vue 1.x use: vue init webpack-simple#1.0 my-project

? Project name my-project
? Project description A Vue.js project
? Author ****** <********@gmail.com>

   vue-cli · Generated "my-project".

~/workspace/vue $ 
```
設定を聞かれるので全部エンターで初期値のままいきます。

つぎにプロジェクトに移動: `cd my-project` 
初期化：`npm install`

```
~/workspace/vue $ cd my-project
~/workspace/vue/my-project $ npm install
npm WARN optional dep failed, continuing fsevents@1.0.14
file-loader@0.9.0 node_modules/file-loader
└── loader-utils@0.2.16 (object-assign@4.1.0, big.js@3.1.3, emojis-list@2.1.0, json5@0.5.0)

babel-loader@6.2.5 node_modules/babel-loader
├── object-assign@4.1.0
├── loader-utils@0.2.16 (big.js@3.1.3, emojis-list@2.1.0, json5@0.5.0)
└── mkdirp@0.5.1 (minimist@0.0.8)

cross-env@3.1.2 node_modules/cross-env
└── cross-spawn@3.0.1 (lru-cache@4.0.1, which@1.2.11)

vue@2.0.1 node_modules/vue

vue-loader@9.5.1 node_modules/vue-loader
├── object-assign@4.1.0
├── vue-hot-reload-api@2.0.6
├── vue-template-es2015-compiler@1.0.0
├── vue-style-loader@1.0.0
├── hash-sum@1.0.2
├── loader-utils@0.2.16 (big.js@3.1.3, emojis-list@2.1.0, json5@0.5.0)
├── lru-cache@4.0.1 (pseudomap@1.0.2, yallist@2.0.0)
├── source-map@0.5.6
├── vue-template-compiler@2.0.1 (de-indent@1.0.2, entities@1.1.1)
├── postcss-selector-parser@2.2.1 (indexes-of@1.0.1, flatten@1.0.2, uniq@1.0.1)
├── consolidate@0.14.1 (bluebird@3.4.6)
├── postcss@5.2.4 (js-base64@2.1.9, supports-color@3.1.2, chalk@1.1.3)
└── js-beautify@1.6.4 (nopt@3.0.6, config-chain@1.1.11, mkdirp@0.5.1, editorconfig@0.13.2)

css-loader@0.25.0 node_modules/css-loader
├── object-assign@4.1.0
├── postcss-modules-extract-imports@1.0.1
├── postcss-modules-scope@1.0.2
├── postcss-modules-local-by-default@1.1.1
├── loader-utils@0.2.16 (big.js@3.1.3, emojis-list@2.1.0, json5@0.5.0)
├── postcss-modules-values@1.2.2 (icss-replace-symbols@1.0.2)
├── source-list-map@0.1.6
├── babel-code-frame@6.16.0 (js-tokens@2.0.0, esutils@2.0.2, chalk@1.1.3)
├── lodash.camelcase@3.0.1 (lodash._createcompounder@3.0.0)
├── css-selector-tokenizer@0.6.0 (fastparse@1.1.1, cssesc@0.1.0, regexpu-core@1.0.0)
├── postcss@5.2.4 (js-base64@2.1.9, supports-color@3.1.2, source-map@0.5.6, chalk@1.1.3)
└── cssnano@3.7.7 (decamelize@1.2.0, postcss-normalize-charset@1.1.0, postcss-minify-gradients@1.0.3, postcss-discard-empty@2.1.0, postcss-discard-duplicates@2.0.1, postcss-merge-idents@2.1.7, postcss-reduce-transforms@1.0.3, postcss-reduce-initial@1.0.0, defined@1.0.0, postcss-reduce-idents@2.3.0, postcss-convert-values@2.4.1, postcss-discard-comments@2.0.4, postcss-value-parser@3.3.0, postcss-minify-font-values@1.0.5, postcss-discard-overridden@0.1.1, postcss-ordered-values@2.2.2, postcss-merge-longhand@2.0.1, postcss-zindex@2.1.1, postcss-discard-unused@2.2.1, postcss-unique-selectors@2.0.2, postcss-minify-params@1.0.5, postcss-merge-rules@2.0.10, has@1.0.1, postcss-filter-plugins@2.0.2, postcss-minify-selectors@2.0.5, postcss-normalize-url@3.0.7, postcss-calc@5.3.1, postcss-colormin@2.2.1, postcss-svgo@2.1.5, autoprefixer@6.5.0)

webpack-dev-server@2.1.0-beta.8 node_modules/webpack-dev-server
├── connect-history-api-fallback@1.3.0
├── strip-ansi@3.0.1 (ansi-regex@2.0.0)
├── supports-color@3.1.2 (has-flag@1.0.0)
├── opn@4.0.2 (object-assign@4.1.0, pinkie-promise@2.0.1)
├── compression@1.6.2 (on-headers@1.0.1, vary@1.1.0, bytes@2.3.0, compressible@2.0.8, debug@2.2.0, accepts@1.3.3)
├── express@4.14.0 (escape-html@1.0.3, methods@1.1.2, range-parser@1.2.0, parseurl@1.3.1, etag@1.7.0, vary@1.1.0, array-flatten@1.1.1, utils-merge@1.0.0, cookie-signature@1.0.6, content-type@1.0.2, encodeurl@1.0.1, merge-descriptors@1.0.1, fresh@0.3.0, content-disposition@0.5.1, cookie@0.3.1, path-to-regexp@0.1.7, serve-static@1.11.1, depd@1.1.0, qs@6.2.0, on-finished@2.3.0, debug@2.2.0, finalhandler@0.5.0, proxy-addr@1.1.2, accepts@1.3.3, type-is@1.6.13, send@0.14.1)
├── webpack-dev-middleware@1.8.4 (path-is-absolute@1.0.1, range-parser@1.2.0, mime@1.3.4, memory-fs@0.3.0)
├── spdy@3.4.3 (http-deceiver@1.2.7, handle-thing@1.2.5, select-hose@2.0.0, debug@2.2.0, spdy-transport@2.0.15)
├── serve-index@1.8.0 (escape-html@1.0.3, parseurl@1.3.1, batch@0.5.3, http-errors@1.5.0, mime-types@2.1.12, accepts@1.3.3, debug@2.2.0)
├── sockjs@0.3.18 (uuid@2.0.3, faye-websocket@0.10.0)
├── sockjs-client@1.1.1 (inherits@2.0.3, json3@3.3.2, debug@2.2.0, url-parse@1.1.4, faye-websocket@0.11.0, eventsource@0.1.6)
├── yargs@4.8.1 (decamelize@1.2.0, get-caller-file@1.0.2, lodash.assign@4.2.0, y18n@3.2.1, which-module@1.0.0, set-blocking@2.0.0, window-size@0.2.0, require-main-filename@1.0.1, require-directory@2.1.1, cliui@3.2.0, yargs-parser@2.4.1, string-width@1.0.2, os-locale@1.4.0, read-pkg-up@1.0.1)
└── http-proxy-middleware@0.17.2 (is-glob@3.0.0, http-proxy@1.15.1, micromatch@2.3.11, lodash@4.16.4)

webpack@2.1.0-beta.22 node_modules/webpack
├── object-assign@4.1.0
├── interpret@1.0.1
├── clone@1.0.2
├── async@1.5.2
├── tapable@0.2.4
├── supports-color@3.1.2 (has-flag@1.0.0)
├── loader-utils@0.2.16 (big.js@3.1.3, emojis-list@2.1.0, json5@0.5.0)
├── loader-runner@2.2.0
├── source-map@0.5.6
├── mkdirp@0.5.1 (minimist@0.0.8)
├── acorn@3.3.0
├── enhanced-resolve@2.3.0 (graceful-fs@4.1.9)
├── memory-fs@0.3.0 (errno@0.1.4, readable-stream@2.1.5)
├── webpack-sources@0.1.2 (source-list-map@0.1.6)
├── uglify-js@2.6.4 (async@0.2.10, uglify-to-browserify@1.0.2, yargs@3.10.0)
├── yargs@4.8.1 (get-caller-file@1.0.2, lodash.assign@4.2.0, decamelize@1.2.0, y18n@3.2.1, which-module@1.0.0, set-blocking@2.0.0, window-size@0.2.0, require-main-filename@1.0.1, require-directory@2.1.1, yargs-parser@2.4.1, cliui@3.2.0, os-locale@1.4.0, string-width@1.0.2, read-pkg-up@1.0.1)
├── node-libs-browser@1.0.0 (string_decoder@0.10.31, https-browserify@0.0.1, tty-browserify@0.0.0, path-browserify@0.0.0, punycode@1.4.1, constants-browserify@1.0.0, os-browserify@0.2.1, process@0.11.9, assert@1.4.1, domain-browser@1.1.7, querystring-es3@0.2.1, timers-browserify@1.4.2, stream-browserify@2.0.1, events@1.1.1, vm-browserify@0.0.4, util@0.10.3, readable-stream@2.1.5, console-browserify@1.1.0, url@0.11.0, http-browserify@1.7.0, buffer@4.9.1, browserify-zlib@0.1.4, crypto-browserify@3.11.0)
└── watchpack@1.1.0 (graceful-fs@4.1.9, chokidar@1.6.0, async@2.0.0-rc.4)

babel-core@6.17.0 node_modules/babel-core
├── slash@1.0.0
├── path-exists@1.0.0
├── path-is-absolute@1.0.1
├── shebang-regex@1.0.0
├── babel-messages@6.8.0
├── babel-template@6.16.0
├── private@0.1.6
├── babel-helpers@6.16.0
├── convert-source-map@1.3.0
├── source-map@0.5.6
├── babylon@6.11.4
├── debug@2.2.0 (ms@0.7.1)
├── minimatch@3.0.3 (brace-expansion@1.1.6)
├── babel-types@6.16.0 (to-fast-properties@1.0.2, esutils@2.0.2)
├── babel-code-frame@6.16.0 (js-tokens@2.0.0, esutils@2.0.2, chalk@1.1.3)
├── babel-generator@6.17.0 (jsesc@1.3.0, detect-indent@3.0.1)
├── babel-traverse@6.16.0 (globals@8.18.0, invariant@2.2.1)
├── json5@0.4.0
├── lodash@4.16.4
├── babel-register@6.16.3 (home-or-tmp@1.0.0, source-map-support@0.4.3, mkdirp@0.5.1, core-js@2.4.1)
└── babel-runtime@6.11.6 (regenerator-runtime@0.9.5, core-js@2.4.1)

babel-preset-es2015@6.16.0 node_modules/babel-preset-es2015
├── babel-plugin-transform-es2015-function-name@6.9.0 (babel-types@6.16.0, babel-helper-function-name@6.8.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-parameters@6.17.0 (babel-helper-get-function-arity@6.8.0, babel-helper-call-delegate@6.8.0, babel-template@6.16.0, babel-types@6.16.0, babel-traverse@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-modules-commonjs@6.16.0 (babel-plugin-transform-strict-mode@6.11.3, babel-template@6.16.0, babel-types@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-shorthand-properties@6.8.0 (babel-types@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-modules-umd@6.12.0 (babel-template@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-modules-systemjs@6.14.0 (babel-template@6.16.0, babel-helper-hoist-variables@6.8.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-arrow-functions@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-block-scoping@6.15.0 (babel-types@6.16.0, babel-template@6.16.0, babel-traverse@6.16.0, lodash@4.16.4, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-typeof-symbol@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-for-of@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-unicode-regex@6.11.0 (regexpu-core@2.0.0, babel-helper-regex@6.9.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-spread@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-duplicate-keys@6.8.0 (babel-types@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-object-super@6.8.0 (babel-helper-replace-supers@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-modules-amd@6.8.0 (babel-template@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-computed-properties@6.8.0 (babel-template@6.16.0, babel-helper-define-map@6.9.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-literals@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-template-literals@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-block-scoped-functions@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-check-es2015-constants@6.8.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-destructuring@6.16.0 (babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-sticky-regex@6.8.0 (babel-helper-regex@6.9.0, babel-types@6.16.0, babel-runtime@6.11.6)
├── babel-plugin-transform-es2015-classes@6.14.0 (babel-helper-replace-supers@6.16.0, babel-messages@6.8.0, babel-helper-optimise-call-expression@6.8.0, babel-helper-function-name@6.8.0, babel-helper-define-map@6.9.0, babel-template@6.16.0, babel-types@6.16.0, babel-traverse@6.16.0, babel-runtime@6.11.6)
└── babel-plugin-transform-regenerator@6.16.1 (private@0.1.6, babel-types@6.16.0, babel-runtime@6.11.6)
~/workspace/vue/my-project $ 
```
すこし時間かかりましたが入りました。

cloud9で実行する場合、サーバの設定が必要ですのでします。
package.jsonのscripts の devにIPとポートの設定`--host 0.0.0.0 --port 8080`を追加します。

``` 変更後_package.json
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --inline --hot --host 0.0.0.0 --port 8080",

```

最後にサーバーを起動：`npm run dev`

```
~/workspace/vue/my-project $ npm run dev

> my-project@ dev /home/ubuntu/workspace/vue/my-project
> cross-env NODE_ENV=development webpack-dev-server --open --inline --hot --host 0.0.0.0 --port 8080

 http://0.0.0.0:8080/
webpack result is served from /dist/
content is served from /home/ubuntu/workspace/vue/my-project
404s will fallback to /index.html

```

では確認してみましょう。各自の環境に合わせてページにアクセスします。
https://<プロジェクト名>-<ユーザ名>.c9users.io/index

画面：
![image](https://qiita-image-store.s3.amazonaws.com/0/71988/963fa1a2-5890-349c-2b50-5eca2ab69838.png)

こんなんでました。
ソースはこんなんでした。最初にいったようにテンプレート・ロジック・スタイルがすべて１つのファイルに入ってます。
cloud9で開くときはエディタタブ右下にあるTextをクリックしてhtmlに変えるとハイライトがいい感じに出ます。

``` App.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello Vue!'
    }
  }
}
</script>

<style>
body {
  font-family: Helvetica, sans-serif;
}
</style>
```

App.vueの呼び出し側のindex.htmlはこんなの。中で使ってる/dist/build.jsはdevモードだとはアクセス時にビルドして、そのまま返しているようです。プロジェクト内に生成されてませんでした。

``` index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>my-project</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>

```

でこっちが実際のブラウザが表示してるDOM

``` 実行時.html
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title>my-project</title>
  <style type="text/css">
    body {
      font-family: Helvetica, sans-serif;
    }
  </style>
 </head>
 <body>
    <div id="app">
　　　<img src="/dist/logo.png?82b9c7a5a3f405032b1db71a25f67021"> 
　　　<h1>Hello Vue!</h1>
　　</div>
   <script src="/dist/build.js"></script>
 </body>

</html>
```
div内がテンプレートで置き換わってる他、画像ソースにキャッシュクリアがついてたり、ヘッダにスタイルが追加されてたり、いろいろやってるみたいですね。


以上になります。個人的にはコンポーネントはテンプレート・ロジック・スタイルを並べて編集したいので、ファイルを分けてフォルダ単位で管理したほうが、変更楽だと思うんですけど、どうなんでしょうね。まぁ１つのファイルを３つのエディタで開けば似たようなことはできるんですけど。１エディタで編集しづらいくらいにでかいのは、コンポーネントを小分けにして設計見直せってことかもしれませんね。