Docker上でSpring bootのビルド&実行するまで
はじめに
JavaというかSpringを触っている今日このごろ。
ついでにDockerで動かそうと思ったのでその設定をしていく。
前提
Dockerが使えること。 設定方法については割愛。
環境
Java(Spring boot)の設定
雛形ダウンロード
サクッと雛形をSpring Initializrで作成する。
指定したのは下記の通り。書いてないとこは何でも良いと思う。
項目 | 内容 |
---|---|
Project | Gradle project |
Language | Java |
Spring boot | 2.2.0(SNAPSHOT) |
packaging | jar |
Java version | 11 |
Dependencies | Lombok,Web |
ちょっとアプリ改修
このままでも動くけど動くだけなので軽く修正を加えることにする。
内容はただ単にレスポンスを返せるようにするだけ。
まず、自動生成されたクラスと同列にindexController.java
を作成する。
作成したファイルの内容は下記の通り。
$ touch src/main/java/...(package)/IndexContoroller.java
package hoge; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @GetMapping("index") @Override public String getIndexPage() { return "Hello World!"; } }
Dockerの設定
Dockerfileをちまちま書いていく。
調べた感じみんなローカルでビルドした後Dockerで動かしているのが多かったけど、
全部Dockerでやりたいので必要なファイルをコピってビルド & 実行。
Dockerfile
FROM openjdk:latest RUN mkdir /app WORKDIR /app COPY ./gradlew /app COPY ./build.gradle /app COPY ./settings.gradle /app COPY ./src /app/src COPY ./gradle /app/gradle ENTRYPOINT ["sh", "./gradlew", "bootRun"]
実行
準備が終わったので実行してみる。
ローカルから叩いてちゃんとレスポンスが取れればOK。
$ docker build -t spring-docker ./ # Springがport8080で起動するのでローカルからは3000にする $ docker run -p 3000:8080 spring-docker
問題なく起動したら適当にcurlで確認してみる。
下記のようになればOK。
$ curl localhost:3000/index Hello World!
まとめ
今回はDocker + Spring bootの設定をしてみた。
思ってたより楽だったし、久しぶりにJavaも触りたいのでもうちょっと何かを作ってみたいところ。
上でも書いたけど、意外とDocker上でビルドとかしないんだろうか。。。
それでは今回はこの辺で。
React+Redux+TypeScript事始め
はじめに
普段はAngularを使っているので、他のフレームワークを触ってみようと思った今日このごろ。
Vueは超絶軽く触ったことがあるので今回はReact。
JSXがパッと見とっつきにくかったので後回しにしてたけど、ちゃんと向き合うことにする。
TL;DR.
インストール
手作業でファイルを作ってもいいけど、Facebook作成のcreate-react-appがあるのでそれを使う。
$ npm install -g create-react-app # TSを使いたいからオプション指定 $ create-react-app react-sample --typescript
サンプルプログラムを作る
いつも?通りToDoリストを作ることにする。
今回Reactのサンプルなので、せっかくだからReduxも導入することにする。
インストールは下記の通りでOK。
# 必要なライブラリのインストール $ npm install -save redux react-redux # 実装時楽したいので型定義もインストール $ npm install --save-dev @types/react @types/react-redux
サンプルの実装はReduxの公式サイトのサンプルプログラムを参考にしてTSへリファクタしていくことにする。
今回のリファクタリングの方針は下記の通りにした。
ちなみにこれがベストかどうかはわからないので、あくまで今回の方針とする。
- DOM作ってるところは
.tsx
にする - component周りはclassにする
- propTypesの代わりにTSのinterfaceを使う
サンプルコード
全量は流石に多いのでGitに上げた。
ActionとかReducerは型つけたぐらいなので、Componentとinterfasceをサンプルとして載せとく。
Component
import React, { Component } from 'react'; import { ITodoProps } from '../commons/interfaces'; export default class Todo extends Component<ITodoProps, {}> { render() { return ( <li onClick={this.props.onClick} style={{ textDecoration: this.props.completed ? 'line-through' : 'none' }} > {this.props.text} </li> ); } }
interface
export interface ITodoProps { completed: boolean; id: number; onClick: any; text: string } export interface ITodoListProps { todos?: Array<ITodoProps>; toggleTodo: any; } export interface ILinkProps { active: boolean; children: string; onClick: any; filter: string } export interface IState { todos: Array<any>; visibilityFilter: string; }
まとめ
今回は簡単にReact + Reduxのチュートリアルをやってみた。
JSX(TSX)の書き方はとっつき肉かなって思ってたけど、意外とそうでもなかった。
Reactが学習コスト低めって言われている理由がちょっとわかった気がする。
そのうちオリジナルの何かを作ろうと思った。
それでは今回はこの辺で。
参考サイト
IntelliJ+Gradle+Springbootセットアップ
はじめに
久しぶりにJavaを触りたくなったので、セットアップするまでの備忘録。
せっかく?なのでIntelliJ + Gradle + Spring bootで作ってみる。
前準備
IntelliJ
公式サイト からダウンロード可。 有料版のUltimateがあるけど、今回はCommunityの方をインストール。
プロジェクト作成
Spring Initializrを使ってテンプレを作成する。
特別変えたところは、Gradle Project
とDependenciesにはWeb
を選択肢といた。
ちなみにSpringのバージョンは2.2.1
にした。
プログラム作成
プロジェクトインポート
上記で作ったプロジェクトをインポートする。
IntelliJを起動し、Import Project
で読み込む。
build.gradle
多分デフォルトだとJava8が対象だと思うので、Java11に対応するように少しbuild.gradle
を書き換える。
buildscript { ext { springBootVersion = '2.1.2.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'idea' group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 [compileJava, compileTestJava]*.options*.encoding = "UTF-8" repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
Java変更
デフォルトでも動くは動くが何も返ってこないはずなので、せめてメッセージが帰ってくるようにする。
ExampleApplication.java
(名前は多少違うかも)と同じフォルダにHelloControler.java
を作成する。
package com.example.example; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; @RestController public class HelloController { @RequestMapping("/") public String hello() { return "Hello World!"; } }
ビルド & 実行
IntelliJの場合、左下のメニューからGradle
を選択。
右側にGradleのメニューが出てくるので、リフレッシュ後「Tasks > application > bootRun」を実行。
エラーにならず起動できればOK。
ちゃんと動いてることを確認するために適当にリクエストを飛ばす。
下記の通り返ってくれば終了。
$ curl localhost:8080/ > Hello World!
まとめ
とりあえずメッセージが返ってくるところまで作った。
次はDocker環境で試したり、API増やしたり、HTML返したりできるようにしたいところ。
それでは今回はこの辺で。
おまけ
JavaとかGradleとか大昔に入れてたから、どこまで必要かわからないけどとりあえずインストールメモ
# とりあえずアップデート $ brew update # Javaインストール(OpenJDKのv11がインストールされる) $ brew cask install java # Gradleインストール $ brew install gradle # インストール確認 $ java -version $ gradle -v
参考資料
clasp + typescriptでAPIを作ってみる
はじめに
GoogleAppsScriptがほぼjsなのにes6でかけなかったり、
tsで書けなかったりちょっと残念だなとか思ってたら公式でサポートされてた。
今回はclaspを使ってtypescriptで書いてAPIを公開してみる。
TL;DR.
インストールする
$ npm install --save-dev @google/clasp $ clasp -v >1.7.0
インストールはこれで完了。
globalにインストールしない場合はフォルダ構成に合わせてpathを通してください。
※macならbash_profile
に下記追記でOK。
export PATH=$PATH:./node_modules/.bin
プロジェクト作成
# ブラウザが開くと思うので任意のGoogleアカウントでログイン $ clasp login # ソース置いとくフォルダ作成 $ mkdir src # rootDirを"src"に指定して"sample"という名前のプロジェクト作成 $ clasp create --title "sample" --rootDir ./src # どれか聞かれるから好みのものを選ぶ(今回はapi) # APIはデフォルトだとエラーになるので下記URLから事前に「オンにしておく」 # https://script.google.com/home/usersettings >? Clone which script? > standalone > docs > sheets > slides > forms > webapp >❯ api # サンプルのプログラムを作成 # claspではES6もTypeScriptも問題ない $ touch src/sample.ts
これでプロジェクトの作成は問題ないはず。
サンプルプログラムは下記の通りにした。
(と言っても公式のサンプル丸コピだけど)
// Optional Types let isDone: boolean = false; let height: number = 6; let bob: string = "bob"; let list1: number[] = [1, 2, 3]; let list2: Array<number> = [1, 2, 3]; enum Color {Red, Green, Blue}; let c: Color = Color.Green; let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean function showMessage(data: string): void { // Void console.log(data); } showMessage('hello'); // Classes class Hamburger { constructor() { // This is the constructor. } listToppings() { // This is a method. } } // Template strings var name = 'Sam'; var age = 42; console.log(`hello my name is ${name}, and I am ${age} years old`); // Rest arguments const add = (a, b) => a + b; let args = [3, 5]; add(...args); // same as `add(args[0], args[1])`, or `add.apply(null, args)` // Spread operator (array) let cde = ['c', 'd', 'e']; let scale = ['a', 'b', ...cde, 'f', 'g']; // ['a', 'b', 'c', 'd', 'e', 'f', 'g'] // Spread operator (map) let mapABC = { a: 5, b: 6, c: 3}; let mapABCD = { ...mapABC, d: 7}; // { a: 5, b: 6, c: 3, d: 7 } // Destructure map let jane = { firstName: 'Jane', lastName: 'Doe'}; let john = { firstName: 'John', lastName: 'Doe', middleName: 'Smith' } function sayName({firstName, lastName, middleName = 'N/A'}) { console.log(`Hello ${firstName} ${middleName} ${lastName}`) } sayName(jane) // -> Hello Jane N/A Doe sayName(john) // -> Helo John Smith Doe // Export (The export keyword is ignored) export const pi = 3.141592; // Google Apps Script Services var doc = DocumentApp.create('Hello, world!'); doc.getBody().appendParagraph('This document was created by Google Apps Script.'); // Decorators function Override(label: string) { return function (target: any, key: string) { Object.defineProperty(target, key, { configurable: false, get: () => label }); } } class Test { @Override('test') // invokes Override, which returns the decorator name: string = 'pat'; } let t = new Test(); console.log(t.name); // 'test'
pushしてみる
# rootDirに指定したフォルダの内容を全てpush $ clasp push └─ src/appsscript.json └─ src/sample.ts Pushed 2 files.
pushされたprojectを確認すると下記のようなgsに変化される。
var exports = exports || {}; var module = module || { exports: exports }; var __assign = (this && this.__assign) || Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; // 型定義 var isDone = false; var height = 6; var bob = "bob"; var list1 = [1, 2, 3]; var list2 = [1, 2, 3]; var Color; (function (Color) { Color[Color["Red"] = 0] = "Red"; Color[Color["Green"] = 1] = "Green"; Color[Color["Blue"] = 2] = "Blue"; })(Color || (Color = {})); var c = Color.Green; var notSure = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean function showMessage(data) { // Void Logger.log(data); } showMessage("hello"); // クラス var Hamburger = /** @class */ (function () { function Hamburger() { // コンストラクタ } Hamburger.prototype.listToppings = function () { // メソッド }; return Hamburger; }()); // テンプレート文字列 var name = "Sam"; var age = 42; console.log("hello my name is " + name + ", and I am " + age + " years old"); // Rest arguments var add = function (a, b) { return a + b; }; var args = [3, 5]; add.apply(void 0, args); // same as `add(args[0], args[1])`, or `add.apply(null, args)` // スプレッド構文 (array) var cde = ["c", "d", "e"]; var scale = ["a", "b"].concat(cde, ["f", "g"]); // ['a', 'b', 'c', 'd', 'e', 'f', 'g'] // スプレッド構文 (map) var mapABC = { a: 5, b: 6, c: 3 }; var mapABCD = __assign({}, mapABC, { d: 7 }); // { a: 5, b: 6, c: 3, d: 7 } // 分割代入 var jane = { firstName: "Jane", lastName: "Doe" }; var john = { firstName: "John", lastName: "Doe", middleName: "Smith" }; function sayName(_a) { var firstName = _a.firstName, lastName = _a.lastName, _b = _a.middleName, middleName = _b === void 0 ? "N/A" : _b; console.log("Hello " + firstName + " " + middleName + " " + lastName); } sayName(jane); // -> Hello Jane N/A Doe sayName(john); // -> Helo John Smith Doe // Export (The export keyword is ignored) exports.pi = 3.141592; // Google Apps Script の独自サービスの利用 var doc = DocumentApp.create("Hello, world!"); doc .getBody() .appendParagraph("This document was created by Google Apps Script."); // デコレータ(高階関数) function Override(label) { return function (target, key) { Object.defineProperty(target, key, { configurable: false, get: function () { return label; } }); }; } var Test = /** @class */ (function () { function Test() { this.name = "pat"; } __decorate([ Override("test") // invokes Override, which returns the decorator ], Test.prototype, "name"); return Test; }()); var t = new Test(); console.log(t.name); // 'test'
APIを作ってみる
とりあえず今回は外部から叩けることを確認する。
src/api.ts
としてファイルを作った。
中身は下記の通り。ただ単に適当なJSONを返した。
// doGetはGETで叩かれたときに実行される関数 function doGet() { const resData = JSON.stringify({ message: 'Hello World!' }); // ContentServiceを利用して、responseを作成 // そのまま返してもエラーになる ContentService.createTextOutput(); const output = ContentService.createTextOutput(); output.setMimeType(ContentService.MimeType.JSON); output.setContent(resData); // return response-data return output; }
APIを使えるように設定する
「公開 > 実行可能APIとして導入」からAPIは初公開するとき警告出るけどここではスルーして続行。
スルーするとAPIIDが出てくるのでコピっておく。
スクリプトにアクセスできるユーザは「全員」にしておく。
次に「公開 > ウェブアプリケーションとして導入」から設定する。
プロジェクトバージョンは先程公開したAPIのバージョン、
次のユーザーとしてアプリケーションを実行は「自分」、
アプリケーションにアクセスできるユーザーは「全員(匿名ユーザを含む)」にして導入。
導入すると「現在のウェブ アプリケーションの URL」が出てくるからコピっておく。
APIを叩いてみる
ブラウザから直接叩くかcurl -L 【コピったURL】
でOK。
{"message":"Hello World!"}
みたいなレスポンスが帰ってくれば成功。
まとめ
今回はclasp + typescriptでGoogle Apps ScriptのAPIを作ってみた。
見た目はtsだったりするけど、中身はGASなのでちゃんとスプレッドシートとかGmailとかと連携はできるはず。
Googleのなんかを使うのはまたいずれ。
それでは今回はこの辺で。
Webpackのビルドをフォルダ構成を変えずに行う
TL;DR.
はじめに
前回Webpackでビルドするところまでやったけど、
やっぱりファイルまとめたくなかったので色々したのが今回。
準備
前回の状態にしておく。
ちなみにフォルダ構成はこんな感じ。(tssrver→srcにしました。)
. ├── node_modules ├── package-lock.json ├── package.json └── src ├── app.ts ├── bin │ └── www.ts ├── public │ ├── images │ ├── javascripts │ └── stylesheets │ └── style.css ├── routes │ ├── index.ts │ └── users.ts └── views ├── error.jade ├── index.jade └── layout.jade
webpack.config.jsを修正
各種ファイルを纏めたくは無いので、entry
を分けておく。
ついでにフォルダ構成をそのままにしたいので、key
に出力後のファイル名、value
に元ファイルを記載する。
const path = require('path'); module.exports = { mode: 'development', entry: { 'bin/www': path.join(__dirname, 'src/bin/www.ts'), 'routes/index.js': path.join(__dirname, 'src/routes/index.ts'), 'routes/users.js': path.join(__dirname, 'src/routes/users.ts'), 'app.js': path.join(__dirname, 'src/app.ts') }, target: 'node', node: { // ビルド後の位置を参照したいのでfalseにする __dirname: false }, module: { rules: [ { test: /\.ts$/, use: 'ts-loader' } ] }, resolve: { modules: [ 'node_modules' ], extensions: [ '.ts', '.js' ] }, output: { // 出力するファイル名(entryのkeyを指定) filename: '[name]', path: path.join(__dirname, 'dist') } };
ビルド結果後のフォルダ構成はこんな感じ。
一部置いてかれているけど、今回は無視する。
必要に応じて別途コピーすれば問題ないと思う。
. ├── dist │ ├── app.js │ ├── bin │ │ └── www │ └── routes │ ├── index.js │ └── users.js ├── node_modules ├── package-lock.json ├── package.json └── src ├── app.ts ├── bin │ └── www.ts ├── public │ ├── images │ ├── javascripts │ └── stylesheets │ └── style.css ├── routes │ ├── index.ts │ └── users.ts └── views ├── error.jade ├── index.jade └── layout.jade
まとめ
ようやく分けてビルドすることができた。
ただ、ファイルが増える度にconfigを書き換えなきゃ行けないので、そのうち何か考えたいです。
Vue.jsとfirebaseでサーバーレスを試してみる
はじめに
最近何かとよく聞くサーバレス。
firebaseで簡単にできるみたいな記事をよく見るので今回は自分でやってみる。
構成はfirebase + Vue.jsでやる。
内容としては超絶簡単なプロフィールみたいなものを作る。
TL;DR.
https://mypage-e6eae.firebaseapp.com/
前提
Nodeが入っている。
Googleアカウントがある。
firebaseの設定
まずはコンソールにアクセスしてプロジェクトを作っておく。
名前等は何でもOK。
プロジェクトを作れたらnpm install -g firebase-tools
でCLIをインストール。
インストールできたら下記手順で設定する。
$ firebase login > #ブラウザが立ち上がるので、任意のGoogleアカウントにログイン $ firebase init # ホスティングで使いたいので、Hostingにチェック ? Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices. ◯ Database: Deploy Firebase Realtime Database Rules ◯ Firestore: Deploy rules and create indexes for Firestore ◯ Functions: Configure and deploy Cloud Functions ❯◯ Hosting: Configure and deploy Firebase Hosting sites ◯ Storage: Deploy Cloud Storage security rules # deployするアプリ(今回だとVueのビルド結果)のディレクトリpublicにするか聞かれる What do you want to use as your public directory? # SPAにするか聞かれる Configure as a single-page app (rewrite all urls to /index.html)? # index.htmlあるけど、上書きするか(ファイルある場合。無いと勝手に生成される) File public/index.html already exists. Overwrite?
これで設定は問題ないはず。
Vueの設定
VueCLIで作ったプロジェクトをそのまま使う。
具体的なVueの設定は前にやったので、そちらを参照してください。
コードは一部内容をプロフィールっぽくするのとfirebaseの設定を記載。
画像みたいなアイコンをクリックするとウェブアプリにfirebaseを追加
みたいなポップアップが出てくる。
// Initialize Firebase
って書いてあるconfig
をmain.ts
に追記する。
ビルドの出力先を変えたいのでnpm run build -- --dest public
で実行。
build
の内容はデフォルトのまま。
デプロイしてみる
firebase deploy
でOK。
デプロイが完了するとURLが表示されるのでそこにアクセスする。
$ firebase deploy === Deploying to 'mypage-e6eae'... i deploying hosting i hosting[mypage-e6eae]: beginning deploy... i hosting[mypage-e6eae]: found 14 files in public ✔ hosting[mypage-e6eae]: file upload complete i hosting[mypage-e6eae]: finalizing version... ✔ hosting[mypage-e6eae]: version finalized i hosting[mypage-e6eae]: releasing new version... ✔ hosting[mypage-e6eae]: release complete ✔ Deploy complete! Project Console: https://console.firebase.google.com/project/mypage-e6eae/overview Hosting URL: https://mypage-e6eae.firebaseapp.com
まとめ
今回はVue + firebaseでサーバレスを試してみた。
firebaseが思ってたより簡単だし、無料で使えるしとっつきやすいと思った。
そのうちこのプロフィールページも充実させていこう……
参考サイト
SCSSまとめ
はじめに
CSS書く時にいちいち調べながら書いてたけど、いい加減にちゃんと勉強しようと思った今日この頃。
素のCSSを書くのも良いかも知れないけど、せっかくなのでSass(SCSS)で書けるようにまとめる。
Sass(SCSS)とは
CSSのメタ言語。
SassとSCSSの違いは書き方の違いのみ。
CSSっぽく書いたSassがSCSSといった具合。
同じものなので、どちらの記法で書いたとしても同じCSSが生成される。
以降のサンプルは全部SCSSで書いた場合。
SCSSの機能一覧
機能1: 変数
SCSSでは変数が使える。
$変数名: 値
で変数を定義できる。
色とか共通のものあるだろうし、一括定義できるのは便利。
$red = #FF3333; .text { color: $red; }
機能2: ネスト
HTMLみたくネストして書くことができる。
元のCSSでも一応ネストしてかけるけど、
入れ子でかけるほうがわかりやすいと思う。
div { ul { background-color: #000; li { color: #fff; } } } // ちなみに普通のCSSでは下記みたいに書く div ul { background-color; } div ul li { color: #fff; }
機能3: パーシャル化
1つのCSSを書く際に機能毎に分割することができる。
分割されたファイル名を_ファイル名.scss
の様にすることによってCSSに変換する際に変換対象外にできる。
簡単に言うとコイツは別のとこで読み込むから変換しないでファイル。
分割されたファイルは使いたいSCSSで@import 'ファイル名'
でimportして使う。(_はなくていい)
// _importedFile.scss div { color: #000; } // main.scss // importして使う @import 'importedFile'; span { color:#fff; }
機能4: Import
上の通り。
別に_付きのファイルじゃなくてもimportはできる。
ちなみに素のCSSにもimport機能はあるけど、読み込む度にHTTPリクエストが発生する欠点があった。
SCSSでのimportは読み込んだ上で単一のCSSとして生成されるのでリクエストが発生するデメリットはない。
機能5: Mixin
サイト内で使いまわしたい宣言を使い回せるよっていう機能。
使い回される宣言に@mixin
と付ければ使い回せるようになる。
使う際には@incliude
を付けてることで@mixin
で宣言した内容を呼び出せる。
ちなみに@mixin(変数)
とすることで変数を受け取ることもできる。優秀。
@mixin transform($property) { -webkit-transform: $property; -ms-transform: $property; transform: $property; } @mixin box_define($width:50px, $height:50px, $bg_color:white) { width: $width; height: $height; background: $bg_color; } .box { @include transform(rotate(30deg)); } .box2 { /* 引数には初期値も設定できる。省略するとそれが使われる */ @include box_define(); }
機能6: Extend/Inheritance
@extend
を使うことであるセレクタのプロパティを別のセレクタに共有できる。
HTMLに複数のクラス名を記述する必要がなくなるとかメリットがある。
/* この定義を他のセレクタで使い回せる。 */ %message-shared { border: 1px solid #ccc; padding: 10px; color: #333; } .message { @extend %message-shared; } .success { @extend %message-shared; border-color: green; } .error { @extend %message-shared; border-color: red; } .warning { @extend %message-shared; border-color: yellow; }
機能7: 四則演算
+
,-
,*
,/
,%
みたいなよくある四則演算が使える。
内容もよくある四則演算なので割愛。
作ったサンプル
ここ
ちなみに全部は使ってない。
まとめ
素のCSS書くのやめようと思った。
今回使えなかったものも組み込んで使えるようにしたい。