├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .node-version ├── .nvmrc ├── LICENSE ├── README.md ├── build ├── index.mjs ├── lib │ ├── builder.mjs │ └── file.mjs └── projects │ ├── computed-filter.mjs │ ├── form.mjs │ ├── i18n.mjs │ ├── index.mjs │ ├── sample-vuex-modal.mjs │ └── sample-vuex-transition-probrem.mjs ├── computed_filter ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .postcssrc ├── README.md ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── ComputedExample.vue │ │ ├── ComputedForm.vue │ │ └── FilterExample.vue │ └── main.js └── yarn.lock ├── docs ├── .vuepress │ ├── axios.js │ ├── config.js │ ├── enhanceApp.js │ ├── routes.js │ ├── store.js │ └── views │ │ ├── DemoForm.vue │ │ ├── DemoI18n.vue │ │ ├── DemoModal.vue │ │ ├── DemoVuexTransition.vue │ │ ├── LayoutDemo.vue │ │ └── index.js ├── README.md ├── demo │ ├── README.md │ ├── computed_filter │ │ └── README.md │ └── modal_store │ │ └── README.md └── guide │ └── README.md ├── form ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .postcssrc ├── README.md ├── jest.config.js ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── components │ │ ├── FormInput.vue │ │ ├── FormSelect.vue │ │ ├── FormTextarea.vue │ │ └── index.js │ ├── forms │ │ ├── ContactForm.js │ │ ├── PasswordUpdateForm.js │ │ ├── SampleForm.js │ │ ├── index.js │ │ └── items │ │ │ ├── BodyFormItem.js │ │ │ ├── CategoryFormItem.js │ │ │ ├── EmailFormItem.js │ │ │ ├── NameFormItem.js │ │ │ ├── PasswordFormItem.js │ │ │ ├── TitleFormItem.js │ │ │ └── index.js │ ├── helpers │ │ ├── store.js │ │ └── validators.js │ ├── lib │ │ ├── BaseForm.js │ │ ├── BaseFormItem.js │ │ ├── BaseSelectFormItem.js │ │ └── index.js │ ├── main.js │ ├── mixins │ │ ├── form-item.js │ │ └── index.js │ ├── router.js │ ├── store.js │ ├── store │ │ └── form.js │ └── views │ │ ├── Complete.vue │ │ ├── Confirm.vue │ │ ├── Form.vue │ │ ├── PasswordUpdate.vue │ │ └── SampleValidationAttribute.vue ├── tests │ ├── resources │ │ └── components │ │ │ └── FormItem.vue │ └── unit │ │ ├── .eslintrc │ │ ├── App.spec.js │ │ ├── components │ │ ├── FormInput.spec.js │ │ ├── FormSelect.spec.js │ │ └── FormTextarea.spec.js │ │ ├── forms │ │ ├── ContactForm.spec.js │ │ └── SampleForm.spec.js │ │ ├── helpers │ │ ├── store.spec.js │ │ └── validators.spec.js │ │ ├── lib │ │ ├── BaseForm.spec.js │ │ ├── BaseFormItem.spec.js │ │ └── BaseSelectFormItem.spec.js │ │ ├── mixins │ │ └── form-item.spec.js │ │ ├── store │ │ └── form.spec.js │ │ └── views │ │ ├── Complete.spec.js │ │ ├── Confirm.spec.js │ │ └── Form.spec.js ├── vue.config.js └── yarn.lock ├── i18n ├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .postcssrc ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── locales │ │ ├── about │ │ ├── en.json │ │ └── ja.json │ │ ├── common │ │ ├── en.json │ │ └── ja.json │ │ └── home │ │ ├── en.json │ │ └── ja.json ├── src │ ├── App.vue │ ├── i18n.js │ ├── main.js │ ├── router.js │ └── views │ │ ├── About.vue │ │ └── Home.vue └── yarn.lock ├── package.json ├── samples ├── README.md ├── modal_store │ ├── .babelrc │ ├── .eslintrc.js │ ├── .gitignore │ ├── .postcssrc │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── scss │ │ │ │ └── styles.scss │ │ ├── components │ │ │ ├── BaseButton.vue │ │ │ ├── TheModal.vue │ │ │ └── index.js │ │ ├── helpers │ │ │ └── store.js │ │ ├── main.js │ │ ├── router.js │ │ ├── store.js │ │ ├── store │ │ │ └── modal.js │ │ └── views │ │ │ ├── About.vue │ │ │ └── Home.vue │ └── yarn.lock └── vuex_transition_problem │ ├── .babelrc │ ├── .eslintrc.js │ ├── .gitignore │ ├── .postcssrc │ ├── README.md │ ├── package.json │ ├── public │ ├── api │ │ └── sections │ │ │ ├── asc.json │ │ │ └── desc.json │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.vue │ ├── axios.js │ ├── constants │ │ └── api.js │ ├── helpers │ │ └── store.js │ ├── layouts │ │ ├── Expect.vue │ │ └── Problem.vue │ ├── main.js │ ├── router.js │ ├── store.js │ ├── store │ │ └── section.js │ └── views │ │ ├── ExpectAsc.vue │ │ ├── ExpectDesc.vue │ │ ├── Home.vue │ │ ├── ProblemAsc.vue │ │ └── ProblemDesc.vue │ └── yarn.lock └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | computed_filter 2 | form 3 | i18n 4 | samples 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/prettier' 9 | ], 10 | rules: { 11 | 'prettier/prettier': [ 12 | 'error', 13 | { 14 | 'singleQuote': true, 15 | 'trailingComma': 'all', 16 | } 17 | ], 18 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 20 | 'vue/attribute-hyphenation': [ 21 | 'error', 22 | 'never' 23 | ], 24 | 'vue/html-end-tags': 'error', 25 | 'vue/html-indent': [ 26 | 'error', 27 | 2 28 | ], 29 | 'vue/html-self-closing': 'error', 30 | 'vue/require-default-prop': 'error', 31 | 'vue/require-prop-types': 'error', 32 | 'vue/attributes-order': 'error', 33 | 'vue/html-quotes': [ 34 | 'error', 35 | 'double' 36 | ], 37 | 'vue/order-in-components': 'error' 38 | }, 39 | parserOptions: { 40 | parser: 'babel-eslint' 41 | }, 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | 22 | # doc 23 | docs/.vuepress/components 24 | docs/.vuepress/store 25 | docs/.vuepress/public/api 26 | docs/.vuepress/public/locales 27 | docs/.vuepress/i18n.js 28 | docs/.vuepress/mixins 29 | docs/.vuepress/lib 30 | docs/.vuepress/forms 31 | docs/.vuepress/helpers 32 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v10.8.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.8.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mya-ake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-tips-samples 2 | 3 | [現場で使える Vue.js tips 集](https://nextpublishing.jp/book/10057.html)のサンプルコード 4 | 5 | ## このリポジトリについて 6 | 7 | 現場で使える Vue.js tips 集は「商業版」と「同人版」の 2 種類が存在します。 8 | master ブランチは**商業版**に対応したコードとなっています。 9 | 同人版のコードを確認されたい方は[doujinshi ブランチ](https://github.com/mya-ake/vue-tips-samples/tree/doujinshi)をご覧ください。 10 | 11 | ## サポートページ 12 | 13 | 本書のサポートページを用意しました。 14 | サポートページでは書籍内で書ききれなかった内容やサンプルコードのデモを置いています。合わせてご参照ください。 15 | 16 | https://vue-tips-support-page.mya-ake.org 17 | 18 | #### デモサイト 19 | 20 | https://vue-tips-support-page.mya-ake.org/demo/ 21 | 22 | ## 章とフォルダー 23 | 24 | ### 1 章 computed と filter 25 | 26 | - [computed_filter](https://github.com/mya-ake/vue-tips-samples/tree/master/computed_filter) 27 | 28 | ### 2 章 お問い合わせフォームと戦う 29 | 30 | - [form](https://github.com/mya-ake/vue-tips-samples/tree/master/form) 31 | 32 | ### 3 章 フォームのライブラリー実装編 33 | 34 | - [form](https://github.com/mya-ake/vue-tips-samples/tree/master/form) 35 | 36 | ### 4 章 Vuex の tips 37 | 38 | - [form](https://github.com/mya-ake/vue-tips-samples/tree/master/form) 39 | - [samples/modal_store](https://github.com/mya-ake/vue-tips-samples/tree/master/samples/modal_store) 40 | - UI の操作にストアを使う例 41 | - [samples/vuex_transition_problem](https://github.com/mya-ake/vue-tips-samples/tree/master/samples/vuex_transition_problem) 42 | - 遷移前に表示が変わる問題の例 43 | 44 | ### 5 章 vue-test-utils でなにをテストするか 45 | 46 | - [form](https://github.com/mya-ake/vue-tips-samples/tree/master/form) 47 | 48 | ### 6 章 vue-i18n の Lazy loading と vue-router 49 | 50 | - [i18n](https://github.com/mya-ake/vue-tips-samples/tree/master/i18n) 51 | 52 | ## 注意 53 | 54 | - `npm`でなく`yarn`を使っています 55 | - `npm`でも動くとは思います 56 | - [vue-cli v3](https://github.com/vuejs/vue-cli)を利用しています 57 | - ESLint は`ESLint + Prettier`です 58 | - 追加で`'singleQuote': true`, `'trailingComma': 'all'`を付けています 59 | - Chrome で作業していたので他のブラウザの動作までは確認できていません 60 | - モダンブラウザなら動くはずです 61 | - モダンブラウザで動作していなさそうなブラウザがあった場合は[@mya_ake](https://twitter.com/mya_ake)までご連絡ください 62 | - または[issues](https://github.com/mya-ake/vue-tips-samples/issues)にて起票お願いします 63 | 64 | ## 更新予定 65 | 66 | 更新予定などはこのリポジトリの[issues](https://github.com/mya-ake/vue-tips-samples/issues)をご覧ください 67 | -------------------------------------------------------------------------------- /build/index.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import build from './projects'; 4 | 5 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 6 | const ROOT = path.resolve(__dirname, '..'); 7 | const VUEPRESS_PATH = path.join(ROOT, 'docs', '.vuepress'); 8 | const VUEPRESS_COMPONENTS_PATH = path.join(VUEPRESS_PATH, 'components'); 9 | const VUEPRESS_STORE_PATH = path.join(VUEPRESS_PATH, 'store'); 10 | 11 | /** execute */ 12 | const paths = { 13 | root: ROOT, 14 | vuepressPath: VUEPRESS_PATH, 15 | distComponentPathname: VUEPRESS_COMPONENTS_PATH, 16 | distStorePathname: VUEPRESS_STORE_PATH, 17 | }; 18 | 19 | build(paths); 20 | -------------------------------------------------------------------------------- /build/lib/builder.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import { 4 | getFilePathList, 5 | extractFileName, 6 | readFile, 7 | writeFile, 8 | copyFile, 9 | copyFileWithReplacer, 10 | } from './../lib/file'; 11 | 12 | const buildImportRegExp = from => 13 | new RegExp(`import {([^{^}]*)} from ['"]${from}['"]`); 14 | 15 | const extractTargetPathname = (pathname, srcPattname) => { 16 | return pathname.replace(srcPattname, ''); 17 | }; 18 | 19 | const camelToHyphen = str => { 20 | return str.replace(/[A-Z]/g, (match, offset) => { 21 | return offset === 0 ? match.toLowerCase() : `-${match.toLowerCase()}`; 22 | }); 23 | }; 24 | 25 | export const copyFiles = ( 26 | srcPathname, 27 | distDirPathname, 28 | { exclude = [], replacer = null } = {}, 29 | ) => { 30 | const pathList = getFilePathList(srcPathname); 31 | for (const pathname of pathList) { 32 | const fileName = extractFileName(pathname); 33 | if (exclude.includes(fileName)) { 34 | continue; 35 | } 36 | 37 | const targetPathname = extractTargetPathname(pathname, srcPathname); 38 | const distPathname = path.join(distDirPathname, targetPathname); 39 | if (typeof replacer === 'function') { 40 | copyFileWithReplacer(pathname, distPathname, replacer); 41 | } else { 42 | copyFile(pathname, distPathname); 43 | } 44 | } 45 | }; 46 | 47 | export const buildStore = (projectPathname, distPathname) => { 48 | const storePathname = path.join(projectPathname, 'store'); 49 | const pathList = getFilePathList(storePathname); 50 | for (const pathname of pathList) { 51 | const fileName = extractFileName(pathname); 52 | if (fileName === 'index.js') { 53 | continue; 54 | } 55 | 56 | const storeCode = readFile(pathname).replace(/@\//g, `${projectPathname}/`); 57 | const distFilePathname = path.join(distPathname, fileName); 58 | writeFile(distFilePathname, storeCode); 59 | } 60 | }; 61 | 62 | export const splitImport = (code, from) => { 63 | const importCode = code.match(buildImportRegExp(from)); 64 | return importCode === null 65 | ? null 66 | : importCode[1] 67 | .split(/[\s,\n]+/) 68 | .filter(moduleName => moduleName.length > 0); 69 | }; 70 | 71 | export const replaceImport = (code = '', from, replaceValue) => { 72 | return code.replace(buildImportRegExp(from), replaceValue); 73 | }; 74 | 75 | export const buildComponents = ( 76 | projectPathname, 77 | distPathname, 78 | { 79 | prefix = '', 80 | exclude = [], 81 | componentsDirname = 'components', 82 | replacer = null, 83 | } = {}, 84 | ) => { 85 | const componentsPathname = path.join(projectPathname, componentsDirname); 86 | const pathList = getFilePathList(componentsPathname); 87 | for (const pathname of pathList) { 88 | const fileName = extractFileName(pathname); 89 | if (fileName === 'index.js') { 90 | continue; 91 | } 92 | if (exclude.includes(fileName)) { 93 | continue; 94 | } 95 | 96 | let componentsCode = readFile(pathname); 97 | 98 | if (typeof replacer === 'function') { 99 | componentsCode = replacer({ 100 | code: componentsCode, 101 | pathname, 102 | fileName, 103 | }); 104 | } 105 | 106 | // import components 107 | const importComponents = splitImport(componentsCode, '@/components'); 108 | if (importComponents !== null) { 109 | const importString = importComponents 110 | .map(compoent => `${prefix}${compoent}`) 111 | .map(compoent => `import ${compoent} from './${compoent}'`) 112 | .join('\n'); 113 | componentsCode = replaceImport( 114 | componentsCode, 115 | '@/components', 116 | importString, 117 | ); 118 | 119 | // components property 120 | for (const importComponent of importComponents) { 121 | const prefixComponentName = `${prefix}${importComponent}`; 122 | componentsCode = componentsCode.replace( 123 | new RegExp(`${importComponent},`), 124 | `${prefixComponentName},`, 125 | ); 126 | 127 | const customTag = camelToHyphen(importComponent); 128 | const prefixCustomTag = camelToHyphen(prefixComponentName); 129 | componentsCode = componentsCode.replace( 130 | new RegExp(customTag, 'g'), 131 | prefixCustomTag, 132 | ); 133 | } 134 | } 135 | 136 | // replace import 137 | componentsCode = componentsCode 138 | .replace('@/store/', './../store/') 139 | .replace('@/store', './../store') 140 | .replace('@/', `${projectPathname}/`); 141 | 142 | const distFilename = path.join(distPathname, `${prefix}${fileName}`); 143 | writeFile(distFilename, componentsCode); 144 | } 145 | }; 146 | -------------------------------------------------------------------------------- /build/lib/file.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import fs from 'fs'; 4 | import { join, dirname } from 'path'; 5 | 6 | export const existPathname = pathname => { 7 | try { 8 | fs.statSync(pathname); 9 | return true; 10 | } catch (err) { 11 | return false; 12 | } 13 | }; 14 | 15 | export const createDir = pathname => { 16 | fs.mkdirSync(pathname); 17 | }; 18 | 19 | const ensureWriteProcess = pathname => { 20 | const fileDirname = dirname(pathname); 21 | if (existPathname(fileDirname)) { 22 | return; 23 | } 24 | ensureWriteProcess(fileDirname); 25 | createDir(fileDirname); 26 | }; 27 | 28 | export const getFilePathList = folderPath => { 29 | const paths = fs.readdirSync(folderPath); 30 | return paths.reduce((newPaths, filePath) => { 31 | const absoluteFilePath = join(folderPath, filePath); 32 | if (fs.statSync(absoluteFilePath).isDirectory()) { 33 | return newPaths.concat(getFilePathList(absoluteFilePath)); 34 | } else { 35 | return newPaths.concat(absoluteFilePath); 36 | } 37 | }, []); 38 | }; 39 | 40 | export const readFile = pathname => { 41 | return fs.readFileSync(pathname, { encoding: 'utf8' }); 42 | }; 43 | 44 | export const extractFileName = pathname => { 45 | return pathname.split('/').pop(); 46 | }; 47 | 48 | export const writeFile = (pathname, data) => { 49 | ensureWriteProcess(pathname); 50 | return fs.writeFileSync(pathname, data, { encoding: 'utf8' }); 51 | }; 52 | 53 | export const copyFile = (pathname, distPathname) => { 54 | ensureWriteProcess(distPathname); 55 | fs.copyFileSync(pathname, distPathname); 56 | }; 57 | 58 | export const copyFileWithReplacer = ( 59 | pathname, 60 | distPathname, 61 | replacer = null, 62 | ) => { 63 | let data = readFile(pathname); 64 | 65 | if (typeof replacer === 'function') { 66 | data = replacer({ 67 | code: data, 68 | pathname, 69 | fileName: extractFileName(pathname), 70 | }); 71 | } 72 | 73 | writeFile(distPathname, data); 74 | }; 75 | -------------------------------------------------------------------------------- /build/projects/computed-filter.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { buildComponents } from './../lib/builder'; 3 | 4 | export default ({ root, distComponentPathname }) => { 5 | const PROJECT_SRC = path.join(root, 'computed_filter', 'src'); 6 | buildComponents(PROJECT_SRC, distComponentPathname); 7 | }; 8 | -------------------------------------------------------------------------------- /build/projects/form.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { copyFile } from './../lib/file'; 3 | import { 4 | splitImport, 5 | replaceImport, 6 | copyFiles, 7 | buildStore, 8 | buildComponents, 9 | } from './../lib/builder'; 10 | 11 | const mixinReplacer = ({ code }) => { 12 | return code.replace(`from '@/lib'`, `from './../lib/BaseFormItem'`); 13 | }; 14 | 15 | const formsReplacer = ({ code, fileName }) => { 16 | const items = splitImport(code, './items'); 17 | if (items !== null) { 18 | const importString = items 19 | .map(item => `import { ${item} } from './items/${item}'`) 20 | .join('\n'); 21 | code = replaceImport(code, './items', importString); 22 | } 23 | if (fileName === 'ContactForm.js') { 24 | code = code.replace( 25 | `from '@/helpers/validators'`, 26 | `from './../helpers/validators'`, 27 | ); 28 | } 29 | if (/Form\.js$/.test(fileName)) { 30 | code = code.replace( 31 | `import { BaseForm } from '@/lib'`, 32 | `import { BaseForm } from './../lib/BaseForm'`, 33 | ); 34 | } 35 | if (/FormItem\.js$/.test(fileName)) { 36 | code = code 37 | .replace( 38 | `from '@/helpers/validators'`, 39 | `from './../../helpers/validators'`, 40 | ) 41 | .replace( 42 | `import { BaseFormItem } from '@/lib'`, 43 | `import { BaseFormItem } from './../../lib/BaseFormItem'`, 44 | ); 45 | } 46 | if (fileName === 'CategoryFormItem.js') { 47 | code = code.replace( 48 | `import { BaseSelectFormItem } from '@/lib'`, 49 | `import { BaseSelectFormItem } from './../../lib/BaseSelectFormItem'`, 50 | ); 51 | } 52 | 53 | return code; 54 | }; 55 | 56 | const componentReplacer = ({ code }) => { 57 | return code 58 | .replace( 59 | `import { formItemMixin } from '@/mixins';`, 60 | `import { formItemMixin } from './../mixins/form-item';`, 61 | ) 62 | .replace( 63 | `import { BaseSelectFormItem } from '@/lib';`, 64 | `import { BaseSelectFormItem } from './../lib/BaseSelectFormItem';`, 65 | ); 66 | }; 67 | 68 | const viewComponentReplacer = ({ code, fileName }) => { 69 | if (fileName === 'Form.vue') { 70 | code = code.replace( 71 | `this.$router.push('/confirm')`, 72 | `this.$router.push('/demo/form/confirm/')`, 73 | ); 74 | } 75 | if (fileName === 'Confirm.vue') { 76 | code = code.replace( 77 | `this.$router.push('/complete')`, 78 | `this.$router.push('/demo/form/complete/')`, 79 | ); 80 | } 81 | if (fileName === 'Complete.vue') { 82 | code = code.replace( 83 | `if (from.path !== '/confirm')`, 84 | `if (from.path !== '/demo/form/confirm/')`, 85 | ); 86 | } 87 | return code 88 | .replace( 89 | `import { ContactForm } from '@/forms'`, 90 | `import { ContactForm } from './../forms/ContactForm'`, 91 | ) 92 | .replace(`next('/form')`, `next('/demo/form/')`) 93 | .replace(`to="/form"`, `to="/demo/form/"`); 94 | }; 95 | 96 | export default ({ 97 | root, 98 | vuepressPath, 99 | distComponentPathname, 100 | distStorePathname, 101 | }) => { 102 | const PROJECT_SRC = path.join(root, 'form', 'src'); 103 | const MIXINS_SRC = path.join(PROJECT_SRC, 'mixins'); 104 | const MIXINS_DIST = path.join(vuepressPath, 'mixins'); 105 | const LIB_SRC = path.join(PROJECT_SRC, 'lib'); 106 | const LIB_DIST = path.join(vuepressPath, 'lib'); 107 | const FORMS_SRC = path.join(PROJECT_SRC, 'forms'); 108 | const FORMS_DIST = path.join(vuepressPath, 'forms'); 109 | const VALIDATORS_SRC = path.join(PROJECT_SRC, 'helpers', 'validators.js'); 110 | const VALIDATORS_DIST = path.join(vuepressPath, 'helpers', 'validators.js'); 111 | 112 | const componentPrefix = 'Contact'; 113 | buildComponents(PROJECT_SRC, distComponentPathname, { 114 | prefix: componentPrefix, 115 | replacer: componentReplacer, 116 | }); 117 | buildComponents(PROJECT_SRC, distComponentPathname, { 118 | prefix: componentPrefix, 119 | componentsDirname: 'views', 120 | exclude: ['PasswordUpdate.vue', 'SampleValidationAttribute.vue'], 121 | replacer: viewComponentReplacer, 122 | }); 123 | buildStore(PROJECT_SRC, distStorePathname); 124 | 125 | copyFiles(MIXINS_SRC, MIXINS_DIST, { 126 | exclude: ['index.js'], 127 | replacer: mixinReplacer, 128 | }); 129 | 130 | copyFiles(LIB_SRC, LIB_DIST, { 131 | exclude: ['index.js'], 132 | }); 133 | 134 | copyFiles(FORMS_SRC, FORMS_DIST, { 135 | exclude: ['index.js'], 136 | replacer: formsReplacer, 137 | }); 138 | 139 | copyFile(VALIDATORS_SRC, VALIDATORS_DIST); 140 | }; 141 | -------------------------------------------------------------------------------- /build/projects/i18n.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { copyFile } from './../lib/file'; 3 | import { copyFiles, buildComponents } from './../lib/builder'; 4 | 5 | export default ({ root, vuepressPath, distComponentPathname }) => { 6 | const PROJECT_SRC = path.join(root, 'i18n', 'src'); 7 | const LOCALES_SRC_PATH = path.resolve(PROJECT_SRC, '..', 'public', 'locales'); 8 | const LOCALES_DIST_PATH = path.join(vuepressPath, 'public', 'locales'); 9 | const I18N_SRC_PATH = path.join(PROJECT_SRC, 'i18n.js'); 10 | const I18n_DIST_PATH = path.join(vuepressPath, 'i18n.js'); 11 | 12 | copyFile(I18N_SRC_PATH, I18n_DIST_PATH); 13 | copyFiles(LOCALES_SRC_PATH, LOCALES_DIST_PATH); 14 | 15 | const componentPrefix = 'I18n'; 16 | buildComponents(PROJECT_SRC, distComponentPathname, { 17 | prefix: componentPrefix, 18 | componentsDirname: 'views', 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /build/projects/index.mjs: -------------------------------------------------------------------------------- 1 | import computedFilter from './computed-filter'; 2 | import form from './form'; 3 | import i18n from './i18n'; 4 | import sampleVuexModal from './sample-vuex-modal'; 5 | import sampleVuexTransitionProbrem from './sample-vuex-transition-probrem'; 6 | 7 | export default paths => { 8 | computedFilter(paths); 9 | form(paths); 10 | i18n(paths); 11 | sampleVuexModal(paths); 12 | sampleVuexTransitionProbrem(paths); 13 | }; 14 | -------------------------------------------------------------------------------- /build/projects/sample-vuex-modal.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { buildStore, buildComponents } from './../lib/builder'; 3 | 4 | export default ({ root, distComponentPathname, distStorePathname }) => { 5 | const PROJECT_SRC = path.join(root, 'samples', 'modal_store', 'src'); 6 | buildComponents(PROJECT_SRC, distComponentPathname); 7 | buildStore(PROJECT_SRC, distStorePathname); 8 | }; 9 | -------------------------------------------------------------------------------- /build/projects/sample-vuex-transition-probrem.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { copyFiles, buildStore, buildComponents } from './../lib/builder'; 3 | 4 | const replaceRouterLinkTo = ({ code }) => { 5 | return code.replace( 6 | / { 17 | const PROJECT_SRC = path.join( 18 | root, 19 | 'samples', 20 | 'vuex_transition_problem', 21 | 'src', 22 | ); 23 | const API_SRC_PATH = path.resolve(PROJECT_SRC, '..', 'public', 'api'); 24 | const API_DIST_PATH = path.join(vuepressPath, 'public', 'api'); 25 | 26 | copyFiles(API_SRC_PATH, API_DIST_PATH); 27 | buildStore(PROJECT_SRC, distStorePathname); 28 | 29 | const componentPrefix = 'VuexTransition'; 30 | buildComponents(PROJECT_SRC, distComponentPathname, { 31 | prefix: componentPrefix, 32 | componentsDirname: 'views', 33 | replacer: replaceRouterLinkTo, 34 | }); 35 | buildComponents(PROJECT_SRC, distComponentPathname, { 36 | prefix: `${componentPrefix}Layout`, 37 | componentsDirname: 'layouts', 38 | replacer: replaceRouterLinkTo, 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /computed_filter/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ] 5 | } -------------------------------------------------------------------------------- /computed_filter/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/prettier' 9 | ], 10 | rules: { 11 | 'prettier/prettier': [ 12 | 'error', 13 | { 14 | 'singleQuote': true, 15 | 'trailingComma': 'all', 16 | } 17 | ], 18 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 20 | 'vue/attribute-hyphenation': [ 21 | 'error', 22 | 'never' 23 | ], 24 | 'vue/html-end-tags': 'error', 25 | 'vue/html-indent': [ 26 | 'error', 27 | 2 28 | ], 29 | 'vue/html-self-closing': 'error', 30 | 'vue/require-default-prop': 'error', 31 | 'vue/require-prop-types': 'error', 32 | 'vue/attributes-order': 'error', 33 | 'vue/html-quotes': [ 34 | 'error', 35 | 'double' 36 | ], 37 | 'vue/order-in-components': 'error' 38 | }, 39 | parserOptions: { 40 | parser: 'babel-eslint' 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /computed_filter/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /computed_filter/.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "autoprefixer": {} 4 | } 5 | } -------------------------------------------------------------------------------- /computed_filter/README.md: -------------------------------------------------------------------------------- 1 | # 1 章 computed と filter 2 | 3 | computed と filter の使い分けに関する章のサンプルコードです。 4 | 5 | ## デモ 6 | 7 | https://vue-tips-support-page.mya-ake.org/demo/computed_filter/ 8 | 9 | ## commands 10 | 11 | ### yarn 12 | 13 | #### インストール 14 | 15 | ``` 16 | $ yarn 17 | ``` 18 | 19 | #### 動作確認 20 | 21 | ``` 22 | $ yarn serve 23 | ``` 24 | 25 | ### npm 26 | 27 | #### インストール 28 | 29 | ``` 30 | $ npm install 31 | ``` 32 | 33 | #### 動作確認 34 | 35 | ``` 36 | $ npm run serve 37 | ``` 38 | -------------------------------------------------------------------------------- /computed_filter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "computed_filter", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --open", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "vue": "^2.5.17" 12 | }, 13 | "devDependencies": { 14 | "@vue/cli-plugin-babel": "^3.0.0", 15 | "@vue/cli-plugin-eslint": "^3.0.0", 16 | "@vue/cli-service": "^3.0.0", 17 | "@vue/eslint-config-prettier": "^3.0.0", 18 | "babel-core": "^7.0.0-0", 19 | "node-sass": "^4.9.3", 20 | "sass-loader": "^6.0.6", 21 | "vue-template-compiler": "^2.5.17" 22 | }, 23 | "browserslist": [ 24 | "> 1%", 25 | "last 2 versions", 26 | "not ie <= 8" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /computed_filter/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vue-tips-samples/4f1de429dfafb646e9379ef4f557f8147a06964b/computed_filter/public/favicon.ico -------------------------------------------------------------------------------- /computed_filter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | computed_filter 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /computed_filter/src/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | -------------------------------------------------------------------------------- /computed_filter/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vue-tips-samples/4f1de429dfafb646e9379ef4f557f8147a06964b/computed_filter/src/assets/logo.png -------------------------------------------------------------------------------- /computed_filter/src/components/ComputedExample.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /computed_filter/src/components/ComputedForm.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 69 | -------------------------------------------------------------------------------- /computed_filter/src/components/FilterExample.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | -------------------------------------------------------------------------------- /computed_filter/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: h => h(App), 8 | }).$mount('#app'); 9 | -------------------------------------------------------------------------------- /docs/.vuepress/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const client = axios.create({ 4 | baseURL: '/api', 5 | }); 6 | 7 | export default client; 8 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | locales: { 3 | '/': { 4 | lang: 'ja', 5 | title: '現場で使えるVue.js tips集のサポートページ', 6 | description: '現場で使えるVue.js tips集のサポートページです。書籍内で書ききれなかった解説などが書かれる予定です。', 7 | } 8 | }, 9 | themeConfig: { 10 | nav: [ 11 | { text: 'Home', link: '/' }, 12 | { text: 'Guide', link: '/guide/' }, 13 | { text: 'Demo', link: '/demo/' }, 14 | ], 15 | sidebar: [], 16 | repo: 'mya-ake/vue-tips-samples', 17 | repoLabel: 'GitHub', 18 | }, 19 | } -------------------------------------------------------------------------------- /docs/.vuepress/enhanceApp.js: -------------------------------------------------------------------------------- 1 | import VueAxios from 'vue-axios'; 2 | 3 | import axios from './axios'; 4 | import { 5 | i18n, 6 | allowLanguage, 7 | extractLanguage, 8 | setLang, 9 | loadLocaleMessage, 10 | } from './i18n'; 11 | import store from './store'; 12 | import routes from './routes'; 13 | import { LayoutDemo, DemoModal, DemoVuexTransition } from './views'; 14 | 15 | const setI18nRouterHook = router => { 16 | // 遷移時に必要な言語ファイルを取りにいく 17 | router.beforeEach(async (to, from, next) => { 18 | if ('params' in to === false || 'lang' in to.params === false) { 19 | // lang パラメータがなければスキップ 20 | next(); 21 | return; 22 | } 23 | 24 | const { lang } = to.params; 25 | 26 | if (allowLanguage(lang) === false) { 27 | next(`/demo/i18n/${i18n.locale}`); 28 | return; 29 | } 30 | 31 | const { locale } = to.meta; 32 | await setLang(lang); 33 | await loadLocaleMessage(lang, locale.category); 34 | next(); 35 | }); 36 | }; 37 | 38 | export default ({ Vue, router, options }) => { 39 | // components 40 | Vue.component('layout-demo', LayoutDemo); 41 | Vue.component('demo-modal', DemoModal); 42 | Vue.component('demo-vuex-transition', DemoVuexTransition); 43 | 44 | // add store 45 | store.axios = axios; 46 | options.store = store; 47 | 48 | // demo router 49 | router.addRoutes(routes); 50 | router.afterEach((to, from) => { 51 | if ('r' in to.query) { 52 | const redirect = to.query.r; 53 | const nextPath = `/${redirect.split('-').join('/')}/`; 54 | Vue.nextTick().then(() => { 55 | router.push(nextPath); 56 | }) 57 | } 58 | }) 59 | 60 | // axios 61 | Vue.use(VueAxios, axios); 62 | 63 | // i18n 64 | options.i18n = i18n; 65 | setI18nRouterHook(router); 66 | // 最初の言語を決めるためにマウント前に言語ファイルを取りにいく 67 | (async () => { 68 | const lang = extractLanguage(); 69 | await setLang(lang); 70 | await loadLocaleMessage(lang, 'common'); 71 | })(); 72 | }; 73 | -------------------------------------------------------------------------------- /docs/.vuepress/routes.js: -------------------------------------------------------------------------------- 1 | import { i18n } from './i18n'; 2 | 3 | import { DemoVuexTransition, DemoForm, DemoI18n } from './views'; 4 | import I18nHome from './components/I18nHome'; 5 | import I18nAbout from './components/I18nAbout'; 6 | import VuexTransitionLayoutExpect from './components/VuexTransitionLayoutExpect'; 7 | import VuexTransitionLayoutProblem from './components/VuexTransitionLayoutProblem'; 8 | import VuexTransitionHome from './components/VuexTransitionHome'; 9 | import VuexTransitionExpectAsc from './components/VuexTransitionExpectAsc'; 10 | import VuexTransitionExpectDesc from './components/VuexTransitionExpectDesc'; 11 | import VuexTransitionProblemAsc from './components/VuexTransitionProblemAsc'; 12 | import VuexTransitionProblemDesc from './components/VuexTransitionProblemDesc'; 13 | import ContactForm from './components/ContactForm' 14 | import ContactConfirm from './components/ContactConfirm' 15 | import ContactComplete from './components/ContactComplete' 16 | 17 | const i18nBasePath = '/demo/i18n'; 18 | const i18nOriginRoutes = [ 19 | { 20 | path: '', 21 | name: 'i18n-home', 22 | component: I18nHome, 23 | meta: { 24 | locale: { 25 | category: 'home', 26 | }, 27 | }, 28 | }, 29 | { 30 | path: 'about', 31 | name: 'i18n-about', 32 | component: I18nAbout, 33 | meta: { 34 | locale: { 35 | category: 'about', 36 | }, 37 | }, 38 | }, 39 | ]; 40 | 41 | const i18nLocalesRoutes = i18nOriginRoutes.map(route => { 42 | return { 43 | ...route, 44 | name: undefined, 45 | }; 46 | }); 47 | 48 | i18nOriginRoutes.forEach(route => { 49 | delete route.component; 50 | route.redirect = to => { 51 | const fullPath = to.fullPath.replace(new RegExp(`^${i18nBasePath}`), ''); 52 | const path = `${i18nBasePath}/${i18n.locale}${fullPath}/`.replace( 53 | /\/\/$/, 54 | '/', 55 | ); 56 | return { 57 | path, 58 | params: { 59 | lang: i18n.locale, 60 | }, 61 | }; 62 | }; 63 | }); 64 | 65 | const i18nRoutes = [ 66 | { 67 | path: i18nBasePath, 68 | component: DemoI18n, 69 | children: i18nOriginRoutes, 70 | }, 71 | { 72 | path: `${i18nBasePath}/:lang`, 73 | component: DemoI18n, 74 | children: i18nLocalesRoutes, 75 | }, 76 | { 77 | path: `${i18nBasePath}/*`, 78 | redirect() { 79 | return `${i18nBasePath}/${i18n.locale}`; 80 | }, 81 | }, 82 | ]; 83 | 84 | const vuexTransitionRoutes = [ 85 | { 86 | path: '/demo/vuex_transition_problem', 87 | component: DemoVuexTransition, 88 | children: [ 89 | { 90 | path: '', 91 | component: VuexTransitionHome, 92 | }, 93 | { 94 | path: 'problem', 95 | component: VuexTransitionLayoutProblem, 96 | children: [ 97 | { 98 | path: 'asc', 99 | component: VuexTransitionProblemAsc, 100 | }, 101 | { 102 | path: 'desc', 103 | component: VuexTransitionProblemDesc, 104 | }, 105 | ], 106 | }, 107 | { 108 | path: 'expect', 109 | component: VuexTransitionLayoutExpect, 110 | children: [ 111 | { 112 | path: 'asc', 113 | component: VuexTransitionExpectAsc, 114 | }, 115 | { 116 | path: 'desc', 117 | component: VuexTransitionExpectDesc, 118 | }, 119 | ], 120 | }, 121 | { 122 | path: '*', 123 | redirect: '/demo/vuex_transition_problem/', 124 | }, 125 | ], 126 | }, 127 | ]; 128 | 129 | const formRoutes = [ 130 | { 131 | path: '/demo/form', 132 | component: DemoForm, 133 | children: [ 134 | { 135 | path: '', 136 | component: ContactForm, 137 | }, 138 | { 139 | path: 'confirm', 140 | component: ContactConfirm, 141 | }, 142 | { 143 | path: 'complete', 144 | component: ContactComplete, 145 | }, 146 | { 147 | path: '*', 148 | redirect: '/demo/form/', 149 | }, 150 | ], 151 | }, 152 | ]; 153 | 154 | export default [...formRoutes, ...i18nRoutes, ...vuexTransitionRoutes]; 155 | -------------------------------------------------------------------------------- /docs/.vuepress/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import * as formModule from './store/form'; 5 | import * as modalModule from './store/modal'; 6 | import * as sectionModule from './store/section'; 7 | 8 | Vue.use(Vuex); 9 | 10 | export default new Vuex.Store({ 11 | modules: { 12 | form: formModule, 13 | modal: modalModule, 14 | section: sectionModule, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /docs/.vuepress/views/DemoForm.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /docs/.vuepress/views/DemoI18n.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /docs/.vuepress/views/DemoModal.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | -------------------------------------------------------------------------------- /docs/.vuepress/views/DemoVuexTransition.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 22 | 27 | 28 | -------------------------------------------------------------------------------- /docs/.vuepress/views/LayoutDemo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /docs/.vuepress/views/index.js: -------------------------------------------------------------------------------- 1 | export { default as LayoutDemo } from './LayoutDemo'; 2 | export { default as DemoForm } from './DemoForm'; 3 | export { default as DemoI18n } from './DemoI18n'; 4 | export { default as DemoModal } from './DemoModal'; 5 | export { default as DemoVuexTransition } from './DemoVuexTransition'; 6 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 現場で使える Vue.js tips 集のサポートページ 2 | 3 | ## 書籍について 4 | 5 | Vue.js の実装例をケースに基づき解説している書籍になります。 6 | 筆者が現場で得てきた知見を元にしています。 7 | 8 | 主に Form 周りに関して、UX を向上させるような処理をサポートするようなローカルのライブラリを作り、 Form の複雑さに立ち向かう内容となっています。 9 | 合わせて Vue.js の周辺ライブラリの Vuex、Vue Router、vue-test-utils、vue-i18n などについても書かせていただきました。 10 | 11 | 次のサイトにネットから購入可能な店舗の情報が記載されているので、合わせてご確認ください。 12 | 13 | [https://nextpublishing.jp/book/10057.html](https://nextpublishing.jp/book/10057.html) 14 | 15 | ## このサイトについて 16 | 17 | 主にこのサイトはデモページの提供と書籍内で不足していると思われる情報を発信するために用意しました。 18 | Twitter などで見かけた声を元に充実させていけたらと考えています。 19 | 20 | また、このサイトは筆者が個人的に運営しているサイトです。 21 | 出版元のインプレス R&D は関わっておりません。 22 | 23 | このサイトの記載内容に関するお問い合わせは筆者の[Twitter](https://twitter.com/mya_ake)、または GitHub のリポジトリの [Issues](https://github.com/mya-ake/vue-tips-samples/issues)にお願いします。 24 | 25 | ## サンプルコード 26 | 27 | 書籍内で記述しているコードはほぼ [GitHub のリポジトリ](https://github.com/mya-ake/vue-tips-samples)で公開されています。 28 | ライセンスは MIT ライセンスとなっているので、コピーしてお使いいただいても問題ありません。 29 | -------------------------------------------------------------------------------- /docs/demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | 一部無理やり拡張して表示しているデモもあるため、リロードすると 404 になる場合があります。 4 | 5 | ## 1章 computedとfilter 6 | 7 | - [computedとfilterの例](/demo/computed_filter/) 8 | 9 | ## 2章, 3章 フォーム 10 | 11 | - [formの例](/demo/form/) 12 | 13 | ## 4章 Vuexのtips 14 | 15 | - [UIの操作にストアを使う例](/demo/modal_store/) 16 | - [遷移前に表示が変わる問題の例](/demo/vuex_transition_problem/) 17 | 18 | ## 6章 vue-i18nのLazy loadingとvue-router 19 | 20 | - [i18nの例](/demo/i18n/) 21 | -------------------------------------------------------------------------------- /docs/demo/computed_filter/README.md: -------------------------------------------------------------------------------- 1 | # computed と filter の例 2 | 3 | ## computed 4 | 5 | 6 | 7 | ## filter 8 | 9 | 10 | 11 | ## Computed Form 12 | 13 | -------------------------------------------------------------------------------- /docs/demo/modal_store/README.md: -------------------------------------------------------------------------------- 1 | # Vuexを使ったモーダルの表示 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | 3 | 準備中🐈 -------------------------------------------------------------------------------- /form/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ] 5 | } -------------------------------------------------------------------------------- /form/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/prettier' 9 | ], 10 | rules: { 11 | 'prettier/prettier': [ 12 | 'error', 13 | { 14 | 'singleQuote': true, 15 | 'trailingComma': 'all', 16 | } 17 | ], 18 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 20 | 'vue/attribute-hyphenation': [ 21 | 'error', 22 | 'never' 23 | ], 24 | 'vue/html-end-tags': 'error', 25 | 'vue/html-indent': [ 26 | 'error', 27 | 2 28 | ], 29 | 'vue/html-self-closing': 'error', 30 | 'vue/require-default-prop': 'error', 31 | 'vue/require-prop-types': 'error', 32 | 'vue/attributes-order': 'error', 33 | 'vue/html-quotes': [ 34 | 'error', 35 | 'double' 36 | ], 37 | 'vue/order-in-components': 'error' 38 | }, 39 | parserOptions: { 40 | parser: 'babel-eslint' 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /form/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | -------------------------------------------------------------------------------- /form/.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "autoprefixer": {} 4 | } 5 | } -------------------------------------------------------------------------------- /form/README.md: -------------------------------------------------------------------------------- 1 | # 使いまわせるフォーム 2 | 3 | ## デモ 4 | 5 | https://vue-tips-support-page.mya-ake.org/demo/?r=demo-form 6 | 7 | ## 章と対象フォルダー 8 | 9 | ### 3 章 フォーム実践編 10 | 11 | src フォルダー以下が主な対象になります。 12 | 13 | ### 4 章 Vuex の tips 14 | 15 | src フォルダー以下の次のものが対象になります。 16 | 17 | - [store.js](https://github.com/mya-ake/vue-tips-samples/blob/master/form/src/store.js) 18 | - [store/form.js](https://github.com/mya-ake/vue-tips-samples/blob/master/form/src/store/form.js) 19 | - [helpers/store.js](https://github.com/mya-ake/vue-tips-samples/blob/master/form/src/helpers/store.js) 20 | 21 | ### 5 章 vue-test-utils でなにをテストするか 22 | 23 | tests/unit フォルダー以下が主な対象になります 24 | 25 | ## commands 26 | 27 | ### yarn 28 | 29 | #### インストール 30 | 31 | ``` 32 | $ yarn 33 | ``` 34 | 35 | #### アプリケーションの動作確認 36 | 37 | ``` 38 | $ yarn serve 39 | ``` 40 | 41 | #### テストの実行 42 | 43 | ``` 44 | $ yarn test 45 | ``` 46 | 47 | ### npm 48 | 49 | #### インストール 50 | 51 | ``` 52 | $ npm install 53 | ``` 54 | 55 | #### アプリケーションの動作確認 56 | 57 | ``` 58 | $ npm run serve 59 | ``` 60 | 61 | #### テストの実行 62 | 63 | ``` 64 | $ npm run test 65 | ``` 66 | -------------------------------------------------------------------------------- /form/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testURL: 'http://localhost', 3 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 4 | transform: { 5 | '^.+\\.vue$': 'vue-jest', 6 | '^.+\\.jsx?$': 'babel-jest', 7 | }, 8 | moduleNameMapper: { 9 | '^@/(.*)$': '/src/$1', 10 | '^~resources/(.*)$': '/tests/resources/$1', 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | }; 14 | -------------------------------------------------------------------------------- /form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form", 3 | "version": "0.2.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --open", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "test:unit:watch": "vue-cli-service test:unit --watch", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "vue": "^2.5.17", 14 | "vue-router": "^3.0.1", 15 | "vuex": "^3.0.1" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.0.0", 19 | "@vue/cli-plugin-eslint": "^3.0.0", 20 | "@vue/cli-plugin-unit-jest": "^3.0.0", 21 | "@vue/cli-service": "^3.0.0", 22 | "@vue/eslint-config-prettier": "^3.0.0", 23 | "@vue/test-utils": "^1.0.0-beta.24", 24 | "babel-core": "^7.0.0-0", 25 | "babel-jest": "^22.0.4", 26 | "node-sass": "^4.9.3", 27 | "sass-loader": "^6.0.6", 28 | "vue-template-compiler": "^2.5.17" 29 | }, 30 | "browserslist": [ 31 | "> 1%", 32 | "last 2 versions", 33 | "not ie <= 8" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /form/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mya-ake/vue-tips-samples/4f1de429dfafb646e9379ef4f557f8147a06964b/form/public/favicon.ico -------------------------------------------------------------------------------- /form/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | form 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /form/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /form/src/components/FormInput.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 40 | 41 | 43 | -------------------------------------------------------------------------------- /form/src/components/FormSelect.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 56 | 57 | 59 | -------------------------------------------------------------------------------- /form/src/components/FormTextarea.vue: -------------------------------------------------------------------------------- 1 |