# VueCli::Rails [![Build Status](https://travis-ci.com/eGust/vue_cli-rails.svg?branch=master)](https://travis-ci.com/eGust/vue_cli-rails) Let's make cool boy Vue even cooler on Rails! ## Installation Add this line to your Rails application's `Gemfile`: ```ruby gem 'vue_cli-rails' ``` And then execute: $ bundle install ## Requirements - Ruby >= 2.3 - Rails >= 4.2 - Node >= [8.9+](https://cli.vuejs.org/guide/installation.html) - Optional: `yarn` ## Features - Feel free to use `yarn` or `npm`. - Single `vue_entry` rather than confusing `stylesheet_pack_tag`, `javascript_packs_tag` and `javascript_packs_with_chunks_tag`. - Get all benefits of [@vue/cli](https://cli.vuejs.org/). - Powered by `webpack` 4 - DRY: all-in-one configuration file rather than repeating for `webpack`, `eslint` and etc. - Out-of-box tooling: Babel, TypeScript, PWA, `vue-router`, `vuex`, CSS pre-processors, linter and testing tools. - Enhanced alias support in `jest.config.js`. - Run `webpack-dev-server` together with Rails server with development mode. - Just single `RAILS_ENV`, no more `NODE_ENV`. - Rails way configurations in `config/vue.yml`. ## Getting started Out-of-box workflow: 1. `bundle exec rake vue:create` and follow the steps. > Don NOT select `In package.json` for "Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?". Some functionalities like alias of jest may not work. 2. Put your JavaScript files under `app/assets/vue/entry_points`. 3. Insert your entry point by `vue_entry 'entry_point'` in views or `render vue: 'entry_point'` in controllers. 4. `webpack-dev-server` auto starts alongside `rails server` in dev mode. 5. Invoke `env RAILS_ENV=production bundle exec rake vue:compile` to compile assets (you still must manually set `RAILS_ENV` to `production`). > More settings are available in `config/vue.yml` ## Usage ### Core #### Concept: Entry Point and File structure The root path of your Vue assets is `app/assets/vue`. This gem will generate several folders. However, `app/assets/vue/entry_points` is the only one matters. Webpack sees one JavaScript file as the center of a web page rather than HTML. Thus all styles, images, fonts and other assets are related to a JS files by `import 'css/png/svg/woff2/json'`. Any `.js` file under `app/assets/vue/entry_points` will be a entry-point. Please ONLY puts your entry-point files under `app/assets/vue/entry_points` folder with `.js` extension name. > Be aware, `.js.erb` and `.vue.erb` are NOT supported. I will explain the reason in [Q&A section](#difference-from-webpacker). If you are new to modern front-end development, or more specifically with `webpack` in this case, please read [Q&A section](#qa) for more information. #### Helper `vue_entry` `vue_entry` is like `javascript_include_tag` and `stylesheet_link_tag` which generates relative assets links for your entry point. (It's like `javascript_packs_with_chunks_tag` in Webpacker 4. I will explain why it's different in [Q&A](#qa).) > You may have interest of path alias in `config/vue.yml`. #### Use `render vue: ` in controllers Usually you only need `
` and `vue_entry 'entry/point'` to render a Vue page. You can use `render vue: 'entry/point'` inside your controller. This method is simply a wrap of `render html: vue_entry('entry_point'), layout: true`. So you can pass any arguments supported by `render` including `layout`.
For example ```ruby # app/controllers/my_vue_controller class MyVueController < ApplicationController layout 'vue_base' def foo render vue: 'foo/bar' end end ``` ```html My Vue
<%= yield %> ``` ```js // app/assets/vue/entry_points/foo/bar.js import Vue from 'vue'; import Bar from '../views/Bar.vue'; Vue.config.productionTip = false; new Vue({ render: h => h(Bar), }).$mount('#app'); ```
#### Public Output Path If the default setting `vue_assets` does not bother you at all, you can ignore this section. Actually `public_output_path` in `config/vue.yml` is very simple - just a sub path under `public` directory. You might suffer some problems by changing it without understanding how it works: - My regular assets no longer work in dev mode server. - I lost all my files in `public` folder. (Using a VCS would get your ass saved.) - Where are my compiled assets for prod under `public/assets` directory?
TL, DR - DO NOT name it as any path used by anything else - It's being used in: - Rails dev server will forward all request under `/#{public_output_path}` to `webpack-dev-server`; - Webpack will put all compiled assets under `public/#{public_output_path}`. Unfortunately, it always remove the entire folder before compiling. - Alternative ways - For dev proxy problem: to set different values for `development` and `production` mode in `config/vue.yml`. - For deleting folder when set it to `assets` for prod: run `rake vue:compile[with_rails_assets]` to invoke `rake compile:assets` as well.
#### Summary If you still feel confusing, please create a new project and select copy demo code. I will explain what happens in [Explanation by Demo](#explanation-by-demo). ### Available Settings #### General settings file is `config/vue.yml` - `manifest_output` Where to put `manifest.json` which required by Rails production mode. You can set it in development mode for inspection. All entry-points will be compiled into assets files. Rails needs `manifest.json` to know what are the files and will serve all its JS/CSS tags. - `package_manager` Pretty straightforward, which package manager will be used. Valid value: `npm` or `yarn`. It does NOT support `pnpm` or other package managers. You can find the reason in [Q&A](#qa). - `public_output_path` Because it is very important I put it in core [section](#public-output-path). - `launch_dev_service` (NOT available for `production` mode) `rails server` will launch it when starting by default `vue-cli-service serve`. It will be invoked by `npx vue-cli-service serve` or `yarn exec vue-cli-service serve` depends on your `package_manager`. - `camelCase` settings will be used in `vue.config.js` Please see [available options](#valid-vue-cli-config-options). - `alias` It's basically `resolve/alias` for Webpack. However, you don't have to config this settings in `.eslintrc.js` and `jest.config.js` again and again. `@vue/cli` will pass the settings to eslint via its plugin. The configuration for jest will be generated and passed to `jest.config.js` through `vue.rails.js`. #### Customize your webpack configurations in `vue.config.js` Feel free to update `vue.config.js` by yourself. There are some lines of boiler-plate code to adapt `compression-webpack-plugin` and `webpack-bundle-analyzer`. ### Rake Tasks - `vue:create` Install required packages and configurations. You should run this task to get `@vue/cli` initializing your project.
What it does for you 1. Select package manager: Y=`yarn`, N=`npm` - Directly use npm if yarn has not been installed. - Prefer yarn by default unless detect `package-lock.json` 2. Auto install `@vue/cli` globally with your package manager. 3. Invoke `vue create` to initialize Vue project. When detected existing `package.json` - `Y` - Yes: Fully overwrite - `N` - No: Skip - `A` - Auto: You won't loss anything (`old_config.merge(new_config)`) - `K` - Keep: Keep all your settings already have (`new_config.merge(old_config)`) 4. Install `js-yaml` and `webpack-assets-manifest` 5. Deleting Vue demo code under `src` folder 6. Copy demo code to under `app` folder and update `config/routes.rb` 7. Copy `vue.rails.js` and `vue.config.js` - Do not change `vue.rails.js`! This rake task will always restore `vue.rails.js` back. - Yes you can update `vue.config.js`. Just make sure you know what are you won't break the configuration. You can chance `config/vue.yml` instead. 8. Generate `config/vue.yml` - The convention is: `camelCase` for regular `vue.config.js`, `snake_case` for special usage. - You can find a full list of [Vue CLI config options below](#valid-vue-cli-config-options). - All available options [here](#available-options) > BE AWARE: the default option for `config/vue.yml` is `Y` (to replace existing file), otherwise your package manager change won't be saved. All your files won't be overwritten silently except `vue.rails.js`.
- `vue:compile` Compile Vue assets. Please specify `RAILS_ENV=production` to compile assets for production. Optional argument: `[with_rails_assets]` to invoke `rake compile:assets` after it finished. However, you can invoke `vue-cli-service build` (if `vue-cli-service` installed globally, or you can use `npx vue-cli-service build` or `yarn exec vue-cli-service build`) with `RAILS_ENV=production` to build assets. > A good practice is to use [`cross-env`](https://www.npmjs.com/package/cross-env) to pass `RAILS_ENV=production`. So `cross-env RAILS_ENV=production vue-cli-service build` will work on any platform and shell. - `vue:json_config` Converts `config/vue.yml` to JSON to be used by `vue.rails.js`. `vue.rails.js` prefers parsing `config/vue.yml` with `js-yaml`. So this is just in case. You may suffer performance issue when your Rails app grow big. - `vue:support[formats]` Adds template or style language support. Vue ships with supporting `pug`, `slm`, `sass`, `less` and `stylus` out-of-box. How ever, you still have to install some loaders manually if you did not select that language with `vue:create`. You can add multiple languages at the same time: `rake vue:support[pug,stylus]` - `vue:node_env` Adds `cross-env` and `npm-run-all` to your `devDependencies` in `package.json`, and adds `dev`, `prod`, `serve` and `rails-s` to `scripts` as well. It enables you to start rails dev server alongside `webpack-dev-server` without pain, and compile production assets. ```bash # to start `rails s` and `webpack-dev-server` together npm run dev # or yarn dev # same as `/usr/bin/env RAILS_ENV=production bundle exec vue:compile` npm run prod # or yarn prod ``` You can update `scripts/rails-s` and/or `scripts/prod` if you need to more stuff: ```json { "scripts": { "rails-s": "cross-env NO_WEBPACK_DEV_SERVER=1 rails s -b 0.0.0.0", "prod": "cross-env RAILS_ENV=production bundle exec rake vue:compile[with_rails_assets]" } } ``` > You may need to invoke `rake` with `bundle exec`. Rails 5 and above supports new `rails rake:task` flavor. ## Valid Vue CLI config Options You can check the full list on [Vue CLI official website](https://cli.vuejs.org/config/). - Special - publicPath - see [`public_output_path`](#public-output-path) - outputDir - see [`public_output_path`](#public-output-path) - configureWebpack - `vue.rails.js` will generate it. `entry`, `output` and `resolve/alias` are heavily required by this gem. So you must manually update it in `vue.config.js` very carefully.
Demo Changes to `vue.config.js` ```diff const { manifest, pickUpSettings, // isProd, - // getSettings, + getSettings, } = railsConfig; + const { configureWebpack: { entry, output, resolve, module: cwModule } } = getSettings('configureWebpack'); module.exports = { ...pickUpSettings` outputDir publicPath devServer - configureWebpack filenameHashing lintOnSave runtimeCompiler transpileDependencies productionSourceMap crossorigin css parallel pwa pluginOptions `, + configureWebpack: { + entry, + output, + resolve, + module: cwModule, + }, chainWebpack: (config) => { ```
- Supported - [x] filenameHashing - [x] lintOnSave - [x] runtimeCompiler - [x] transpileDependencies - [x] productionSourceMap - [x] crossorigin - [x] css - [x] devServer - [x] parallel - [x] pwa - [x] pluginOptions - Unsupported - [ ] baseUrl - Deprecated - [ ] assetsDir - ignored - [ ] indexPath - N/A - [ ] pages - N/A - [ ] integrity - N/A - [ ] chainWebpack - directly edit `vue.config.js` ## Trouble Shooting & Known Issues - My dev server can't find assets Sometimes your `webpack-dev-server` might still be running while Rails dev server had terminated. For example, you had executed `exit!` in `pry`. Usually `webpack-dev-server` should be killed when next time you start `rails server`. However, for some reason it could fail and the new `webpack-dev-server` will listen to another port. Then you must manually kill them: ```bash lsof -i:3080 -sTCP:LISTEN -Pn kill -9 ``` Alternatively, you can run `rake vue:node_dev` and always start your dev server with: ```bash npm run dev # or yarn dev ``` > I know it is not Rails-way at all. I don't want to waste time to just get it worked properly in Rails way - you are already using Node, why it bothers you? - My API does not work with CSRF token Because Vue does not have opinion of Ajax (or JSON API) preference, you must implement what `jquery-ujs` does by yourself. There is an example code in vanilla JS with [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) whish should work for IE8+: ```JS // fetch API async (url, data) => { const $csrfParam = document.querySelector('meta[name="csrf-param"]'); const $csrfToken = document.querySelector('meta[name="csrf-token"]'); const csrfParam = ($csrfParam && $csrfParam.getAttribute('content')) || undefined; const csrfToken = ($csrfToken && $csrfToken.getAttribute('content')) || undefined; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-type': 'application/json', 'X-CSRF-Token': csrfToken, }, body: JSON.stringify({ ...data, [csrfParam]: csrfToken, }), }); return response.json(); } catch (e) { // handle failed case } } ``` Alternatively you can turn off CSRF token and set [SameSite cookie](https://gist.github.com/will/05cb64dc343296dec4d58b1abbab7aaf) if all your clients no longer use IE. [Modern browsers](https://caniuse.com/#feat=same-site-cookie-attribute) can handle `SameSite` flag to [prevent CSRF attacks](http://www.sjoerdlangkemper.nl/2016/04/14/preventing-csrf-with-samesite-cookie-attribute/). - Mocha tests not working This is an known issue. - TypeScript can not find my aliases This is an known issue. TS is still using `tsconfig.json` rather than a `.js` or `.ts` file. You must manually update it for now. I will try to find a way out. - My `yarn test:...`/`npm run test:...` not working properly The test requires setting `RAILS_ENV=test`. You can invoke `rake vue:test[unit]` `rake vue:test[e2e]` instead. - Got errors like `command "..." does not exist` for `rake vue:lint/test` This rake task simply invokes `vue-cli-service test:...`. Those commands will be generated by some vue plugins. It won't be available unless you got correct plugin installed.
Q & A ## Q&A ### Can I get rid of `js-yaml` and `webpack-assets-manifest` Only `webpack-assets-manifest` is a required dependency. It will be used to generate `manifest.json` which required for both dev and prod. `vue.rails.js` uses `js-yaml` for parsing `config/vue.yml`. It will fallback to `rake vue:json_config` if `js-yaml` not been installed. However, when your Rails app grow bigger, you will very likely find rake tasks start slower and slower. ### Can I use YAML for template inside .vue Short answer I don't know and I don't recommend. There are several HAML packages but all are too old. JS world suggests [pug](https://pugjs.org). You can also use [slm](https://github.com/slm-lang/slm) if you prefer [Slim](http://slim-lang.com/). Both are quite similar to CSS selector syntax, which means you don't really need to spend time to learn. Just `rake vue:support[pug,slm]` and try them out: `