--- title: C++/Vue.js/WebpackでSPAサンプルを作った話 tags: Vue.js webpack SPA C++ author: S_H_ slide: false --- # はじめに 「C++のWebフレームワークでWeb開発がやりたいなぁ……」と思い、アレコレ調べていたら、[Luna](https://github.com/DEGoodmanWilson/luna)というフレームワークを見つけたのが事の発端。 で、いろいろ弄ってるうちに「これ、Vue.js使ってSPAにできそう?」となり作ってみた。 ![lwv.gif](https://qiita-image-store.s3.amazonaws.com/0/191521/fcf8fa12-d932-59d7-6699-a2547b8a66f4.gif) なお、ソースコードは下記 [S-H-GAMELINKS/LunaWithVue](https://github.com/S-H-GAMELINKS/LunaWithVue) # やったこと ## Luna編 [Luna](https://github.com/DEGoodmanWilson/luna)はGCC/ClangとCMake、それと[Conan](https://conan.io/)というC++のパッケージマネージャを使うことで導入できる。 その為、まずやったことはConanのインストール。 ### Conanのインストール [https://conan.io/](https://conan.io/)からバイナリをダウンロードして実行するだけ。 ### Conanのパッケージ依存をよしなにする ``` conan remote add vthiery https://api.bintray.com/conan/vthiery/conan-packages conan remote add degoodmanwilson https://api.bintray.com/conan/degoodmanwilson/opensource conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan ``` インストール後に、上記のコマンドを実行して依存関係をよしなにする。 ### Lunaの導入 [DEGoodmanWilson/luna](https://github.com/DEGoodmanWilson/luna)からソースコードを`clone`する ``` git clone https://github.com/DEGoodmanWilson/luna.git ``` あとは、`examples`ディレクトリ内にある`project_template`を適当な場所にコピー。 それと、`Luna`ディレクトリをコピー先の`project_template`に貼り付けること。 これで、`Luna`の導入は完了。 ## Vue.js編 このままだとシンプルなものは作れるけどもフロント周りが寂しい。 当初はCDN経由で使っていたんだけど、`Bootstrap`などを導入したり、単一コンポーネントなどを使って楽がしたかった。 そうして次に取り掛かったのが、`Vue.js`をフロントエンドで使えるようにすることだったね。 ### Vue.js&Webpackの導入 以下のような`package.json`を`assets`ディレクトリ以下に作成した。 ```json:package.json { "dependencies": { } } ``` この後、`yarn`を使って、`Vue.js`を導入した ``` yarn add vue ``` 先ほどの`package.json`が以下のようになっていればOK ```json:package.json { "dependencies": { "vue": "^2.5.17", } } ``` とりあえず、これで`Vue.js`をyarn経由で導入できた。 とはいえ、このままだと他なライブラリとかを組み合わせて使うのはちと厳しいかった。 というわけで、[Webpack](https://github.com/webpack/webpack)を使うことにした。 ```js:webpack.config.js module.exports = { entry: './index.js', // entry pointを起点にバンドルしていきます output: { // 出力に関して filename: 'index.js', // 出力するファイル名 path: `${__dirname}/webpack/` // 出力するディレクトリ階層 // pathは絶対パスで指定、そのため __dirname でディレクトリ階層を取得しています }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } } }; ``` ```js:index.js import Vue from 'vue/dist/vue.esm'; const hello = new Vue({ el: '#app', message: "Hello!" }) ``` ```html:assets/index.html
{{message}
``` これらを作成後、`assets`ディレクトリ内で ``` webpack ``` を実行し、`index.html`の`{{message}}`が`Hello!` となっていればWebpackでのビルドは成功。 ### Bootstrapの導入 まずは、yarnで`Bootstrap`を追加 ``` yarn add bootstrap ``` その後、`assets/inedx.js`を下記のように書き換える ```js:index.js import Vue from 'vue/dist/vue.esm'; import * as Bootstrap from 'bootstrap'; import 'bootstrap/dist/css/bootstrap.css'; Vue.use(Bootstrap) const hello = new Vue({ el: '#app' }) ``` このままでは、`CSS`などが読み込まれないので、`style-loader`と`css-loader`をyarnで追加し、 ``` yarn add style-loader yarn add css-loader ``` `webpack.config.js`で`CSS`について設定する。 ```js:webpack.config.js module.exports = { entry: './index.js', // entry pointを起点にバンドルしていきます output: { // 出力に関して filename: 'index.js', // 出力するファイル名 path: `${__dirname}/webpack/` // 出力するディレクトリ階層 // pathは絶対パスで指定、そのため __dirname でディレクトリ階層を取得しています }, module: { rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } }, }; ``` これで`Bootstrap`が適用されていればOK。 ### 単一コンポーネントの導入 `Vue.js`の単一コンポーネントを使用するには、`vue-loader`が必要となる。 なので、yarnで追加。 ``` yarn add vue-loader ``` そして、`webpack.config.js`へ`vue-loader`の設定を追加。 ```js:webpack.config.js const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { entry: './index.js', // entry pointを起点にバンドルしていきます output: { // 出力に関して filename: 'index.js', // 出力するファイル名 path: `${__dirname}/webpack/` // 出力するディレクトリ階層 // pathは絶対パスで指定、そのため __dirname でディレクトリ階層を取得しています }, module: { rules: [{ test: /\.vue$/, use: 'vue-loader' }, { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } }, plugins: [ // ... new VueLoaderPlugin() ] }; ``` これで`Vue.js`の単一コンポーネントを使用できるようになった。 あとは、`assets/components`ディレクトリ以下に各ページのコンポーネントを作成する。 ```vue:header.vue ``` ```vue:index.vue ``` ```vue:about.vue ``` ```vue:contact.vue ``` これで単一コンポーネントを使える。 ### vue-routerの導入 SPAな感じで動作させたいので、`vue-router`を導入。 ``` yarn add vue-router ``` 次に、`router/router.js`を作成し、以下のようにルーティングを設定する。 ```js:router.js import Vue from 'vue/dist/vue.esm.js' import VueRouter from 'vue-router' import Index from '../components/index.vue' import About from '../components/about.vue' import Contact from '../components/contact.vue' Vue.use(VueRouter) export default new VueRouter({ mode: 'history', routes: [ { path: '/', component: Index }, { path: '/about', component: About }, { path: '/contact', component: Contact }, ], }) ``` `main.cpp`のルーティングも追加。 ```cpp:main.cpp #include #include #include #include "logger.h" #include #include using namespace luna; int main() { // set up the loggers set_access_logger(access_logger); set_error_logger(error_logger); // determine which port to run on, default to 8080 auto port = 8080; if (auto port_str = std::getenv("PORT")) { try { port = std::atoi(port_str); } catch (const std::invalid_argument &e) { error_logger(log_level::FATAL, "Invalid port specified in env $PORT."); return 1; } catch (const std::out_of_range &e) { error_logger(log_level::FATAL, "Port specified in env $PORT is too large."); return 1; } } // create a server server server; // add endpoints // File serving example; serve files from the assets folder on / // index pages auto index = server.create_router("/"); index->serve_files("/", "assets"); // about pages auto about = server.create_router("/about"); about->serve_files("/", "assets"); // contact pages auto contact = server.create_router("/contact"); contact->serve_files("/", "assets"); server.start(port); return 0; } ``` それと`assets/index.js`と`index.html`を下記のように変更。 ```html:index.html
``` ```js:index.js import Vue from 'vue/dist/vue.esm'; import Router from './router/router' import Header from './components/header.vue'; import * as Bootstrap from 'bootstrap'; import 'bootstrap/dist/css/bootstrap.css'; Vue.use(Bootstrap) const hello = new Vue({ router: Router, el: '#app', components: { 'nav-bar': Header } }) ``` 最後に、`assets/components/header.vue`のリンクを変更。 ```vue:header.vue ``` これで、メニューの各項目をクリックするとSPA風に動作するようになる。 ### axiosでのAPI使用 `axios`でAPIを使ってテキストデータの送受信を行えるようにする。 まず、`yarn`で`axios`を追加 ``` yarn add axios ``` 次に、`assets/components/index.vue`を以下のように変更。 ```vue:index.vue ``` これでフロントエンドからAPI経由でテキストデータの登録はできるようになった。 次に、`main.cpp`を編集し、テキストデータを受け取るAPIなどを作成する。 ```cpp:main.cpp #include #include #include #include "logger.h" #include #include using namespace luna; int main() { // set up the loggers set_access_logger(access_logger); set_error_logger(error_logger); // determine which port to run on, default to 8080 auto port = 8080; if (auto port_str = std::getenv("PORT")) { try { port = std::atoi(port_str); } catch (const std::invalid_argument &e) { error_logger(log_level::FATAL, "Invalid port specified in env $PORT."); return 1; } catch (const std::out_of_range &e) { error_logger(log_level::FATAL, "Port specified in env $PORT is too large."); return 1; } } std::vector container; // create a server server server; // add endpoints // API example, served from /api auto api = server.create_router("/api"); api->handle_request(request_method::GET, "/endpoint", [&](auto request) -> response { nlohmann::json retval; for(int i = 0; i < container.size(); i++) retval[std::to_string(i)] = container[i]; return retval.dump(); }); auto post = server.create_router("/api/"); post->handle_request(request_method::POST, "/post", [&](auto request) -> response { nlohmann::json retval; container.emplace_back(std::move(request.params.at("text"))); retval[std::to_string(container.size())] = request.params.at("text"); return retval.dump(); }); // File serving example; serve files from the assets folder on / // index pages auto index = server.create_router("/"); index->serve_files("/", "assets"); // about pages auto about = server.create_router("/about"); about->serve_files("/", "assets"); // contact pages auto contact = server.create_router("/contact"); contact->serve_files("/", "assets"); server.start(port); return 0; } ``` これで、APIを使ってのデータの送受信ができるようになった。 # サンプルのビルド方法 [README](https://github.com/S-H-GAMELINKS/LunaWithVue/blob/master/README.md)を参照。 # おわりに とりあえず、こんな感じでC++/Vue.jsでWeb開発ができそう。 今後は実際にローンチしやすいPaaSなどがないか調べてみて、本番環境で動かしてみようかと思う。 # 参考資料など [Luna](https://github.com/DEGoodmanWilson/luna) [Getting started with Luna](https://luna.goodman-wilson.com/using.html) [【5分でなんとなく理解!】Webpack入門](https://qiita.com/Shagamii/items/698a67bab0cd5eefcb4f) [webpack 4 入門](https://qiita.com/soarflat/items/28bf799f7e0335b68186) [Vue.js + webpack で Bootstrap を使う](https://kagasu.hatenablog.com/entry/2017/07/24/083551)