리엑트/뷰 프로젝트에서 테일윈드 CSS 적용하기 (React/Vue + Tailwind CSS)

UI 프레임워크 몇 가지를 써봤는데 결국 커스텀하는 부분에서 어려움을 많이 겪었습니다.
그러던 가운데 Tailwind CSS를 알게 되어 써보니 미리 정의되어 있는 클래스 이름만 사용하면 되기 때문에 괜찮은 접근 방법이었습니다.

React + Tailwind CSS 설정

React 프로젝트 생성

1
2
$ create-react-app ${PROJECT_NAME}
$ cd ${PROJECT_NAME}

Tailwind CSS 패키지 설치

1
$ yarn add --dev tailwindcss autoprefixer postcss-cli

/tailwind.config.js 생성

1
$ node_modules/.bin/tailwind init tailwind.config.js

/postcss.config.js 생성 및 설정코드 작성

1
$ vi postcss.config.js
1
2
3
4
5
6
7
const tailwindcss = require('tailwindcss');
module.exports = {
plugins: [
tailwindcss('./tailwind.config.js'),
require('autoprefixer'),
],
};

/src/styles/index.css 파일 및 tailwind 설정

1
2
3
@tailwind preflight;
@tailwind utilities;
/* Your custom CSS here */

/src/index.js 파일 수정

index.js 파일에 import ‘./index.css’ 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import './index.css' // 추가
import RouterIndex from './routers'
import store from './stores'
import registerServiceWorker from './registerServiceWorker'

ReactDOM.render(
<Provider store={ store }>
<RouterIndex />
</Provider>,
document.getElementById('root')
)
registerServiceWorker()

/package.json - scripts 항목 수정

1
2
3
4
5
6
7
8
9
10
11
12
{
...
"scripts": {
"build:css": "postcss src/styles/index.css -o src/index.css",
"watch:css": "postcss src/styles/index.css -o src/index.css -w",
"start": "npm run watch:css & node scripts/start.js",
"build": "npm run build:css && node scripts/build.js",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
...
}

Vue + Tailwind CSS 설정

Vue 프로젝트 생성

1
2
$ vue init webpack ${PROJECT_NAME}
$ cd ${PROJECT_NAME}

Tailwind CSS 패키지 설치

1
$ npm install tailwindcss --save-dev

/tailwind.config.js 생성

1
$ node_modules/.bin/tailwind init tailwind.config.js

/.postcssrc.js 생성 및 설정코드 작성

1
$ vi .postcssrc.js
1
2
3
4
5
6
7
module.exports = {
"plugins": [
require('postcss-import')(),
require('tailwindcss')('./tailwind-config.js'),
require('autoprefixer')(),
]
}

/src/assets/styles/main.css 파일 생성 및 tailwind 설정

1
2
3
@tailwind preflight;
@tailwind utilities;
/* Your custom CSS here */

/src/App.vue 파일 수정

App.vue 파일에 import ‘@/assets/styles/main.css’ 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
</div>
</template>

<script>
import '@/assets/styles/main.css' //추가

export default {
name: 'app'
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Resource

Vue.js + vue-router 프로젝트 세팅

일반적으로 SPA 개발을 하기위해 라우터는 필수 입니다.
vue-cli에서 vue-router까지 같이 구성할 수 있기 때문에 편하게 작업할 수 있습니다.

우선 vue cli를 사용하기 위해서 아래와 같이 관련 패키지를 설치합니다.

vue-cli 설치

1
2
$ yarn global add @vue/cli
$ yarn global add @vue/cli-init

vue init webpack 프로젝트 구성

Install vue-router? 항목에서 Yes를 선택하면 vue-router 프로젝트 세팅이 완료됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ vue init webpack ${PROJECT_NAME}

? Project name ${PROJECT_NAME}
? Project description A Vue.js project
? Author hun <xxx@xxxx.com>
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Standard
? Set up unit tests Yes
? Pick a test runner jest
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) yarn

vue-cli · Generated "client".


# Installing project dependencies ...
# ========================

yarn install v1.3.2
info No lockfile found.
[1/5] 🔍 Validating package.json...
[2/5] 🔍 Resolving packages...

CentOS7에서 MariaDB 설치 방법

MariaDB 설치

yum을 통해 MariaDB를 설치합니다.

1
$ sudo yum install mariadb-server

설치가 끝난 후 MariaDB 데몬을 실행합니다.

1
$ sudo systemctl start mariadb

OS 부팅시 MariaDB가 자동 실행되도록 설정합니다.

1
$ sudo systemctl enable mariadb

보안 스크립트를 실행하여 관련 설정을 합니다.

1
$ sudo mysql_secure_installation

MaridDB에 접속 후 유저를 추가합니다.

1
$ mysql -u root -p

1
2
MariaDB> CREATE USER 'myuser'@'%' IDENTIFIED BY 'mypass';
MariaDB> CREATE USER 'myuser'@'localhost' IDENTIFIED BY 'mypass';
1
2
MariaDB> GRANT ALL ON *.* TO 'myuser'@'localhost';
MariaDB> GRANT ALL ON *.* TO 'myuser'@'%';
1
MariaDB> flush privileges;

MAC Brew 설치

1
2
3
4
5
6
7
$ brew install mariadb

$ mysql.server start

$ brew services start mariadb

$ mysql -u root

기존 mysql 삭제

1
2
3
4
5
$ brew services stop mysql

$ rm -rf /usr/local/var/mysql

$ brew uninstall mysql

터미널에서 현재 경로로 IntelliJ 실행하는 방법

터미널에서 git clone 후 해당경로로 IntelliJ를 열고 싶은 경우가 많이 있습니다.
VS Code에서는 플러그인 형식으로 존재를 하는데 IntelliJ는 없습니다.
그래서 원하는 경로로 IntelliJ를 실행할 수 있도록 스크립트를 만들었습니다.

IntelliJ 경로 확인

아래 경로로 IntelliJ가 실행되면 정상적인 경로입니다.
OS, 버전에 따라 경로가 다를 수 있습니다.

1
/Applications/IntelliJ\ IDEA.app/Contents/MacOS/idea

스크립트 생성

현재 디렉토리로 인텔리제이를 실행 시키기 위한 스크립트 생성합니다.
아래 스크립트는 i로 인텔리제이를 실핼 시키기 위해 만들었습니다.

1
$ echo 'open -a /Applications/IntelliJ\ IDEA.app/Contents/MacOS/idea $1' > intellij

실행 권한부여

1
$ chmod 770 intellij

/usr/local/bin 경로로 이동

1
$ mv intellij /usr/local/bin

터미널에서 IntelliJ 실행

1
2
$ git clone https://github.com/facebook/react.git
$ intellij react

AWS Lambda개발 - Serverless Framework

AWS Lambda를 개발할 때 CLI로 개발할 수 있는 Serverless, APEX 같은 툴로 개발을 하게됩니다.
Serverless는 AWS, Google, MS, IBM등 주요 서버리스 서비스들을 다 제공하기 때문에
추후 확장성 면에서 좋을 것으로 예상되어 Serverless 사용법을 알아보려고 합니다.

serverless 설치

1
$ npm i -g serverless

aws credentials 설정

  • AWS Console에서 IAM에 들어 갑니다.
  • Users - Add user 클릭

    1. Details
      – User name에 serverless-cli 입력
      – Access type에서 Programmatic access체크
      – Next: Permissions 클릭

    2. Permissions
      – Attach exisiting policies directly 클릭
      – AdministratorAccess 체크
      – Next: Review 클릭

    3. Review
      – Create user 클릭

    4. Complete
      – Access key ID와 Secret access key 확인

  • Access Ky와 Secret access key 정보를 가지고 serverless의 credentials 설정 명령을 실행합니다.

1
$ sls config credentials --provider aws --key xxxxx --secret xxxx

AWS Node.js Template 생성

1
$ sls create --template aws-nodejs --path lambda-test --name lambda-test

위 명령을 실행하면 아래롸 같이 2개의 파일이 생성됩니다.

|-handler.js
|-serverless.yml

serverless.yml 파일 수정

AWS에 배포하기 위해서 serverless.yml 파일을 아래와 같이 수정 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
service: lambda-test # 서비스이름

provider:
name: aws
runtime: nodejs6.10
stage: dev
region: ap-northeast-2 # 서울 rigion
environment: # 전체 함수에서 사용할 수 있는 환경변수
variable1: value1

functions:
hello:
handler: handler.hello
events: # API Gateway 생성을 위한 설정
- http:
path: lambda-test
method: get
cors: true
environment: # 해당 함수에서 사용할 수 있는 환경변수
variable2: value1

resources:
Resources:
NewResource:
Type: AWS::S3::Bucket
Properties: # ${self:service}와 같이 변수로 사용가능
BucketName: ${self:service}-${self:provider.stage}-upload

get parameter를 받기 위해 hander.js 파일 수정

get parameter는 event객체의 queryStringParameters에 있습니다.

1
2
3
4
5
6
7
8
9
10
const param = event.queryStringParameters;

const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Your name is ' + param.name
}),
};

callback(null, response);

Local에서 Lambda 실행

생성된 함수를 AWS에 배포하기 전에 로컬에서 실행 시킬 수 있습니다.

1
$ sls invoke local -f hello

파라미터를 전달하기 위해서는 –path 옵션으로 json파일 경로를 지정해주는 방법과
–data 옵션으로 전달하는 방법이 있습니다.

1
2
$ sls invoke local -f hello --path data.json 
$ sls invoke local -f hello --data '{"queryStringParameters":{"name":"test"}}'

실행하면 아래 결과를 확인할 수 있습니다.

1
2
3
4
{
"statusCode": 200,
"body": "{\"message\":\"Your name is test\"}"
}

Local에서 Emulator 실행

로컬에서 에뮬레이터를 실행 할 수 있습니다.
http://localhost:4000/lambda-test
파이어베이스와 다르게 live-reload가 되지않아 조금 불편합니다.

1
$ sls run

배포 명령 실행

1
$ sls deploy

배포 완료 후 아래와 같은 정보가 나오게 되며 endpoints에 있는 주소로 접근하면 됩니다.

1
2
3
4
5
6
...

endpoints:
GET - https://7otlb5sfi5.execute-api.ap-northeast-2.amazonaws.com/dev/lambda-test
functions:
hello: lambda-test-dev-hello

삭제

Serverless로 배포한 모든 리소스를 삭제하기 위해서는 AWS console - CloudFormation에 들어갑니다.
Stack Name이 lambda-test-dev를 선택한 후 Action에서 Delete Stack을 클릭하면 관련 리소스들을 삭제합니다.

참고사항

  • S3버킷에는 총 2개의 버킷이 생성됩니다.
    (severless framework이 쓰는 버킷 1, serverless.yml파일의 resource에서 설정한 버킷 1)
  • Serverless를 통해 배포를 하면 Lambda, API Gateway, S3, CloudFormation을 사용하게 됩니다.

Resource

아이오닉 캐시 버스팅 설정

html-webpack-plugin 설치

아이오닉 캐시 버스팅 설정을 위해 html-webpack-plugin 설치를 합니다.

1
$ npm install html-webpack-plugin --save-dev

웹팩 설정파일 추가

./package.json 에서 config 설정을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "ps3-ionic",
"version": "0.0.1",
"author": "Ionic Framework",
"homepage": "http://ionicframework.com/",
"private": true,

...

"config": {
"ionic_webpack": "./config/webpack.config.js" // 추가
}
}

웹팩 설정

./config/webpack.config.js
HtmlWebpackPlugin template 경로 설정을 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...

var HtmlWebpackPlugin = require('html-webpack-plugin'); // 추가

...

plugins: [
ionicWebpackFactory.getIonicEnvironmentPlugin(),
ionicWebpackFactory.getCommonChunksPlugin(),
new ModuleConcatPlugin(),
new PurifyPlugin(),
new HtmlWebpackPlugin({
title: 'Your Title',
template: './src/index_cache_busting.html', // 추가
filename: '../index.html',
hash:true,
}),
]

...

index_cash_busting.html 파일 생성

./src/index_cash_busting.html 파일 생성 후 아래 내용을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#4e8ef7">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<script src="cordova.js"></script>
<link href="build/main.css" rel="stylesheet">
</head>
<body>
<ion-app></ion-app>
<script src="build/polyfills.js"></script>
</body>
</html>

Resource

아아오닉, 파이어베이스 네이티브 푸시 개발

Ionic Native 플러그인 설치

1
2
$ ionic cordova plugin add phonegap-plugin-push
$ npm install --save @ionic-native/push

사용방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { Push, PushObject, PushOptions } from '@ionic-native/push';

constructor(private push: Push) { }

...


// to check if we have permission
this.push.hasPermission()
.then((res: any) => {

if (res.isEnabled) {
console.log('We have permission to send push notifications');
} else {
console.log('We do not have permission to send push notifications');
}

});

// to initialize push notifications

const options: PushOptions = {
android: {},
ios: {
alert: 'true',
badge: true,
sound: 'false'
},
windows: {},
browser: {
pushServiceURL: 'http://push.api.phonegap.com/v1/push'
}
};

const pushObject: PushObject = this.push.init(options);

pushObject.on('notification').subscribe((notification: any) => console.log('Received a notification', notification));

pushObject.on('registration').subscribe((registration: any) => console.log('Device registered', registration));

pushObject.on('error').subscribe(error => console.error('Error with Push plugin', error));

Ionic 안드로이드 푸시 설정

  • firebase - 프로젝트 설정 - 안드로이드 앱 추가 - google-services.json 다운로드 후 프로젝트 root에 복사

  • config.xml에서 android에 resource-file 추가

    1
    2
    3
    4
    <platform name="android">
    ...
    <resource-file src="google-services.json" target="google-services.json" />
    </platform>

reference

아이오닉 안드로이드 배포 설정

아이오닉에서 안드로이드 빌드시 에러가 나면 환경 변수 설정을 확인하시면 됩니다.

bash_profile 수정

1
vi ~/.bash_profile

아래 내용 추가

1
2
3
export ANDROID_HOME=/Users/{YOUR_USER_NAME}/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/platform-tools

환경변수 적용

1
source ~/.bash_profile

Resource

아이오닉 네이티브 API 사용방법

Ionic Native

아이오닉에서 Native 콜백이 Angular로 트리거하도록 보장하기 때문에
안드로이드, iOS 네이티브 API (Cordova/PhoneGap plugin) 사용을 편하게 사용할 수 있습니다.
사용방법 예제(Native Camera)

지원범위

android 4.4 이상, iOS8 이상

설치

1
$ npm install @ionic-native/core --save

1. Camera 플러그인 설치

1
2
$ npm install @ionic-native/camera --save
$ ionic cordova plugin add cordova-plugin-camera

2. app.module.ts 에 플러그인 추가

  • ./src/app/app.module.ts 파일에 추
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import { BrowserModule } from '@angular/platform-browser';
    import { ErrorHandler, NgModule } from '@angular/core';
    import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
    import { HttpModule } from '@angular/http';
    import { MyApp } from './app.component';
    import { StatusBar } from '@ionic-native/status-bar';
    import { SplashScreen } from '@ionic-native/splash-screen';
    import { BrowserAnimationsModule } from "@angular/platform-browser/animations";

    import { Camera } from '@ionic-native/camera'; // 추가

    @NgModule({
    declarations: [
    MyApp,
    ],
    imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp),
    BrowserAnimationsModule
    ],
    bootstrap: [IonicApp],
    entryComponents: [
    MyApp,
    ],
    providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Camera // 추가
    ]
    })
    export class AppModule {}

3. 사용할 페이지(native.ts) 에 플러그인 추가

  • ./src/pages/native/native.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    import { Component } from '@angular/core';
    import { IonicPage, NavController, NavParams } from 'ionic-angular';
    import { Camera, CameraOptions } from '@ionic-native/camera';
    import { LocalNotifications } from '@ionic-native/local-notifications';
    import { Toast } from '@ionic-native/toast';
    import { CallNumber } from '@ionic-native/call-number';
    import { Flashlight } from '@ionic-native/flashlight';

    /**
    * Generated class for the NativePage page.
    *
    * See https://ionicframework.com/docs/components/#navigation for more info on
    * Ionic pages and navigation.
    */

    @IonicPage()
    @Component({
    selector: 'page-native',
    templateUrl: 'native.html',
    })
    export class NativePage {

    constructor(public navCtrl: NavController
    , public navParams: NavParams
    , private camera: Camera
    , private localNotifications: LocalNotifications
    , private toast: Toast
    , private callNumber: CallNumber
    , private flashlight: Flashlight) {

    }

    ionViewDidLoad() {
    console.log('ionViewDidLoad NativePage');
    }

    handleCamera(){
    const options: CameraOptions = {
    quality: 100,
    destinationType: this.camera.DestinationType.DATA_URL,
    encodingType: this.camera.EncodingType.JPEG,
    mediaType: this.camera.MediaType.PICTURE
    };
    this.camera.getPicture(options).then((imageData) => {
    // imageData is either a base64 encoded string or a file URI
    // If it's base64:
    //let base64Image = 'data:image/jpeg;base64,' + imageData;
    alert('ok');
    }, (err) => {
    // Handle error
    alert(err);
    });
    }

    handleNotification(){
    this.localNotifications.schedule({
    text: '노티!노티!노티!다',
    at: new Date(new Date().getTime()),
    led: 'FF0000',
    sound: null
    });
    }

    handleToast(){
    this.toast.show(`하하|호호|히히`, '5000', 'center').subscribe(
    toast => {
    console.log(toast);
    }
    );
    }

    handleFlashlight(){
    this.flashlight.toggle();
    }

    handleCallNumber(){
    this.callNumber.callNumber("18001010101", true)
    .then(() => console.log('Launched dialer!'))
    .catch(() => console.log('Error launching dialer'));
    }
    }

실행

1
2
$ ionic cordova emulate ios --target iPhone-8
$ ionic cordova run ios --device

Reference