- 投稿日:2021-08-09T21:48:45+09:00
VueCLIでグローバルなscssファイルを読み込む方法
vueCLI3で作ったプロジェクトで、vue.config.jsでのオプション設定でグローバルなscssファイルを読み込もうとしたがうまくいかず。 動かなかった設定 当初のvue.config.jsでの記述は以下。 vue.config.js module.exports = { css: { loaderOptions: { scss: { prependData: '@import "./src/styles/common/common.scss";' } } } } vueCLI公式を見てみる 公式を見るのが一番だろうと思い以下を参照。 https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders vueCLI公式のサンプルコード vue.config.js // vue.config.js module.exports = { css: { loaderOptions: { // pass options to sass-loader // @/ is an alias to src/ // so this assumes you have a file named `src/variables.sass` // Note: this option is named as "prependData" in sass-loader v8 sass: { additionalData: `@import "~@/variables.sass"` }, // by default the `sass` option will apply to both syntaxes // because `scss` syntax is also processed by sass-loader underlyingly // but when configuring the `prependData` option // `scss` syntax requires an semicolon at the end of a statement, while `sass` syntax requires none // in that case, we can target the `scss` syntax separately using the `scss` option scss: { additionalData: `@import "~@/variables.scss";` }, // pass Less.js Options to less-loader less:{ // http://lesscss.org/usage/#less-options-strict-units `Global Variables` // `primary` is global variables fields name globalVars: { primary: '#fff' } } } } } あれ、prependDataじゃなくてadditionalDataが正しいの? よく読むとscss構文の上に書いてあるsass構文のコメントに以下の記述が。 // Note: this option is named as "prependData" in sass-loader v8 どうやらsass-loaderのバージョンによって記述方法が異なるっぽい。 v8系はprependData、それ以降はadditionalDataということか? 自分はvuetifyを使っている都合でv7.3.1を入れていたのですが、prependDataもadditionalDataも指定してみたが動かない。 sass-loaderのgithubドキュメントで解決 sass-loaderのgithubでv7.3.1のREADMEにあったwebpack.configの記述方法の中に「data」を指定してねと書いてある。 https://github.com/webpack-contrib/sass-loader/tree/v7.3.1#data vue.config.js module.exports = { css: { loaderOptions: { scss: { data: '@import "./src/styles/common/common.scss";' } } } } これでグローバルなscssファイルが読み込めました! 調べる過程で技術ブログやナレッジコミュニティの投稿など色々見たのですが、結局公式とgithubが一番役に立つというのを実感する良い機会になりました。
- 投稿日:2021-08-09T21:29:50+09:00
【個人開発】栃木SCのパートナー企業を検索できるサイトを作ってみた。
はじめに JリーグのJ2に所属している栃木SCのパートナー企業の一覧表示と絞り込みなどで検索を行えるサイトを、自分の勉強も兼ねて作成してみました。 ※企業数は全然網羅できていません。手作業でまとめているので心が折れました・・・。随時追加していく予定。 作ったサービス GitHub:https://github.com/ur-oot/PartnerList 作ったきっかけは? ずっと前からWebアプリを何か作りたいなと思っていたところに、たまたま以下のツイートを見かけました。 自分自身も栃木SCのサポーターで、サポーターというのは日頃からどうしたらクラブ・サポート企業に還元できるかを考えているものです。このツイートを見て栃木SCのパートナー様でも同じようなサイトがあると面白いんじゃないかと思い、全面乗っかりで作成しました。 話が逸れますが、栃木SCは2018年5月に界隈で良くも悪くも?何かと話題の江藤美帆氏(えとみほ氏)@etomihoをマーケティング戦略部長に招き入れ、J2リーグの中でもいち早くデジタル化に舵を切ったクラブです。IT業界に身を置く私としてもITに力を入れているクラブは少なく、とても話題性があるクラブではないかと思っています。 気になる方はぜひチェックしてみてください。 使い方 話を戻して、アプリの使い方です。 凝った機能は実装していない(というよりも、一覧表示・詳細ページ・ちょっとした検索機能ができたとこで初版をリリースした)ので、使い方で困ることはないと思います。 使用技術 インフラ GitHub Netlify フロントエンド HTML CSS(SCSS) JavaScript Vue.js BootstrapVue フレームワークの選定にあたり色々調べたところ学習コストが低く、初心者でも比較的扱いやすいということでVue.jsを選びました。Vue.jsを使っていることもありSPAでアプリを実装しています。また、過去に少しだけBootstrapを触ったことがあったのでBootstrapVueを選びました。 今後の対応予定 インフラ 現状だと企業情報を直接JSONファイルに記載して読み込んでいるので、AWSのDynamoDBで管理して、API Gatewayで取得できるようにしたいと思う。 フロントエンド Bootstrapで実装してみたが、カードスタイルなど簡単に実装できるがカスタマイズがしにくかったので、Vuetifyに徐々に移行していきたいと思う。 ついでにデザイン面でもそれなりのデザインにしたいと思う。 機能 実装している機能が全然ないので以下を追加できればと思う。 ※随時追加更新予定。 お気に入り機能 ジャンル別表示 企業のソート機能 絞り込みしてから詳細ページに遷移したあと戻ると絞り込みが初期化されるので保持するようにする アプリ上から登録・編集を行える機能
- 投稿日:2021-08-09T13:58:06+09:00
Vueでインクリメンタルインプット
HTMLのinputタグで入力する際に、入力するごとに候補が絞られるインクリメンタルサーチのようなものを作ろうと思ったのですが、jQueryを使わないようにしたかったのと、Vueの勉強のため自作しました。 VueとBootstrap3.4.1を使っています。 サンプル含めたもろもろは以下にあります。 poruruba/VueIncrementalInput サンプルは以下で参照できます。 Vueコンポーネントだけを見たい場合は以下で見れます。 ソースコード ソースコードを示しておきます。 Vueのコンポーネント化しています。 js/comp_history.js var comp_history = { // mixins: [mixins_bootstrap], props: ['value', 'list'], template: ` <div class="dropdown input-group"> <input class="form-control" type="text" data-toggle="dropdown" v-model="input_text" v-on:input="input_change"> <ul class="dropdown-menu"> <li v-for="(item, index) in item_list" value="item" v-on:click="item_select(index)"> <a style="padding-right: 0px;">{{item}} <span class="pull-right" v-on:click.stop="call_item_delete(index)"> × </span></a> </li> </ul> <span class="input-group-addon" v-on:click="input_clear">×</span> </div> `, data: function(){ return { input_text: this.value, all_list: [], item_list: [], } }, methods: { call_list_add: function (list) { this.list_add(list); this.$emit('update:list', this.all_list); this.input_change(); }, call_item_add: function (item) { this.item_add(item); this.$emit('update:list', this.all_list); this.input_change(); }, call_list_clear: function () { this.all_list = []; this.$emit('update:list', this.all_list); this.input_change(); }, call_item_delete: function (index) { this.all_list.splice(index, 1); this.$emit('update:list', this.all_list); this.input_change(); }, list_add: function (list) { list.forEach(item => { this.item_add(item); }); }, input_clear: function () { this.input_text = ""; this.input_change(); }, item_add: function (value) { var item = this.all_list.find(item => (item == value)); if (!item) this.all_list.push(value); }, item_select: function (index) { this.input_text = this.item_list[index]; this.input_change(); }, input_change: function () { if (!this.input_text) { this.item_list = this.all_list; } else { var list = []; this.all_list.forEach(item =>{ if (item.startsWith(this.input_text)) list.push(item); }); this.item_list = list; } this.$emit('input', this.input_text); }, }, mounted: function () { if (this.list) this.list_add(this.list); this.input_change(); } }; //export default comp_history; Vueの双方向データバインディングを使っています。 使い方 HTMLで以下のように指定します。 index.html <comp_history v-model="selected" v-bind:list.sync="list"></comp_history> v-modelを指定すると、入力フォームに入力された値を親側がリアルタイムに取得・設定できます。 属性listに文字列の配列を指定すると、インクリメンタルサーチの候補を初期値設定できます。さらに、.syncを付けると、候補が追加されたり削除されたりしたときに親側に反映されます。 また、refを付けると、テンプレート参照でき、候補の追加削除などが親側からの関数呼び出しでできるようになります。(サンプルをご参照ください) おわりに 以下もご参考まで。 Vueのカスタムコンポーネントで双方向データバインディングを入れてみた 以上
- 投稿日:2021-08-09T07:21:15+09:00
vue3.0でTDDしたくて試行錯誤
vue3.0でアプリを作成しようとしてますが、vue-cliは使わず、viteを使っています。 そのためテスト環境は自分で用意する必要がありました。 環境は windows10+WSL2 Docker+node:16-buster-slim+VScode+typescriptです。 まずはviteでプロジェクトを作ります。 yarn create vite my-vue-app --template vue 起動出来ることを確認しておきましょう。 vue公式ページに紹介されているテストツールは2種類あり、2つとも入れてしまいます。 yarn add --dev @babel/core @babel/preset-env @testing-library/jest-dom @types/jest @vue/test-utils@next babel-jest jest ts-jst vue-jest@next 古い記事だとvue2.0用の物もあり、@nextを後ろにつけないとvue3.0のものになりません。 インストールが終わったら、少し手直しをします。 jest.config.js const path = require('path') module.exports = { rootDir: path.resolve(__dirname), clearMocks: true, coverageDirectory: 'coverage', coverageProvider: 'v8', moduleFileExtensions: ['vue', 'js', 'json', 'jsx', 'ts', 'tsx', 'node'], // The alias set moduleNameMapper: { '@/(.*)$': '<rootDir>/src/components/$1' }, preset: 'ts-jest', testEnvironment: 'jsdom', // The test file testMatch: ['<rootDir>/tests/unit/*.spec.ts?(x)'], transform: { '^.+\\.vue$': 'vue-jest', '^.+\\js$': 'babel-jest', '^.+\\.(t|j)sx?$': 'ts-jest' } } babel.config.js module.exports = { presets: [ [ '@babel/preset-env', { 'modules': 'false', 'useBuiltIns': 'usage', 'targets': '> 0.25%, not dead', } ] ], env: { test: { presets: [['@babel/preset-env', {targets: {node: 'current'}}]], }, }, }; ターミナルから"yarn test"コマンド入力でテスト実行できるようにこちらも追加 package.json "scripts": { "test": "jest" }, 現時点で動かそうとするとエラーが出ます。 FAIL src/tests/HelloWorld.spec.ts ● Test suite failed to run TypeError: Cannot destructure property 'config' of 'undefined' as it is undefined. jest,ts-jestのバージョンが27だとダメなようで、 jest@26 ts-jest@26 26に落とします。 後は、srcフォルダの下にでもtestsフォルダ作成、その下にテストファイルを作成していけばOKです。 以上がテスト環境構築になります。 次に、TDDするにあたって、DIするために試行錯誤してみました。 コンポーネントに必要なデータを渡すためにprovide/inject機能を使えるように触ってみました。 公式ページは何度も読みましたがいまいち分かりません笑 実プロジェクトをイメージしたときに、ログイン機能を実現するときには活用できそうです。 お手軽に本格webアプリっぽいデザインを使いたかったので、Prime-vueのprestigeを有料購入しました。 vue3.0対応と書かれていましたが中身はjavascript+OptionAPIで書かれてて少し残念だったので、自分好みに書き直していくことに prestigeの構成としてはmain.tsからAppWrapperを呼び出し、そこからログインページやログイン後のページをコントロールする形です。 ログイン後のアイコンやメニューなどはAppコンポーネントで行っています。 なので、AppWrapperはじめ複数のコンポーネントで状態管理をする必要があります。 そのため、provice/inject機能を使っていこうと思います。 各コンポーネントで共通する情報は「types」フォルダを作成しデータの型とします。 types/login.ts export interface ILogin { name: string; readonly isLogin: boolean; jobTitle: string; } テストでmock化することを考えてinterfaceにしています。またログインしたかどうかisLoginは勝手に書き換えられないようにreadonlyにしています。 各コンポーネントで利用するためにはデータとKeyを組み合わせなければなりません。 同じファイルにKeyも載せてしまいます。 types/login.ts import { InjectionKey } from "vue"; export const LoginKey: InjectionKey<ILogin> = Symbol("Login"); export interface ILogin { name: string; readonly isLogin: boolean; jobTitle: string; } 使い方は簡単。親になるコンポーネントのsetupにてprovideします。 interfaceをどこかで実装したデータとKeyを渡します。 AppWrapper.vue import { LoginKey, ILogin } from "./types/login"; const login: ILogin = reactive({ name: '山田 太郎', isLogin: false, jobTitle: '係長', }); export default defineComponent({ name: "AppWrapper", setup(props, context) { provide(LoginKey,login); 受け取るためのuseLoginStoreを作成します(作成しなくても良い) vue-routerと同じような使い勝手になります。 stores\loginStore.ts import { inject } from 'vue'; import { LoginKey } from '../types/login'; export function useLoginStore() { const store = inject(LoginKey); if(!store) { throw new Error('store not found'); } return store; } 受け取るコンポーネントでは AppTopBar.vue <template> {{ userName }} {{ jobTitle }} </template> <script lang="ts"> import { defineComponent, toRefs } from 'vue'; import { useRouter } from 'vue-router'; import { useLoginStore } from './stores/loginStore'; export default defineComponent({ name: 'AppTopbar', setup(props, context) { const loginUser = useLoginStore(); const { name, jobTitle } = toRefs( loginUser ); const goToHome = () => { const router = useRouter(); router.push('/'); }; return{ userName: name, jobTitle, }; }, }); loginUserにAppWrapperでprovideしたデータが入ってくれるのですが、データの一部を利用する場合、そのままではリアクティブにならないため、toRefsを利用して取り出します。 vuexとの使い分けなど正直分からないのですが、 provide/inject機能を使えばvuexは必要ないような気がします。 また、interfaceを利用している例が少なくこれが正解かは分かりませんがテストしやすいことを考えるとこのようになりました。 テスト自体は時間か要望があるときに。 っと書いてましたが、少し進めてみました。 App.spec.ts import { shallowMount } from '@vue/test-utils'; import { reactive } from 'vue'; import testComponent from '../App.vue'; import { LoginKey, ILogin } from "../types/login"; const login: ILogin = reactive({ name: 'nanashi'; などなどを記載 }); test('inject test', () => { const wrapper = shallowMount(testComponent, { global: { provide: { [LoginKey as symbol]: login, }, } } ); console.log(wrapper.html()); }); やっててハマったこと。 * LoginKeyをコードで書いてはNG.provideとinject両方で同じファイルから取得すること(Symbolの基本なのかもしれませんが知らなかった。。) * symbolをキーにする場合は、[]で囲むこと。また、 [LoginKey as symbol]: login, as symbolを入れないとエラー。