vscode console.log() 빨리치기 (snippets extension)

cas→ console alert method console.assert(expression, object)
ccl→ console clear console.clear()
cco→ console count console.count(label)
cdb→ console debug console.debug(object)
cdi→ console dir console.dir
cer→ console error console.error(object)
cgr→ console group console.group(label)
cge→ console groupEnd console.groupEnd()
clg→ console log console.log(object)
clo→ console log object with name console.log(‘object :>> ‘, object);
ctr→ console trace console.trace(object)
cwa→ console warn console.warn
cin→ console info console.info
clt→ console table console.table
cti→ console time console.time
cte→ console timeEnd console.timeEnd

  • clg 를 치고 tab키를 console.log가 자동완성된다
  • 이밖에도 import from 자동완성 등 여러가지가 있다
  • ~ snippet 익스텐션을 깔면 사용할 수 있다
  • 나는 다양한 스니펫을 제공하는 ES7 React/Redux/GraphQL/React-Native snippets 익스텐션을 설치해서 사용하고 있다

참고

js 이진수 앞에 0넣기

방법1 ; substr

1
2
3
4
let N = 7;
let padding = '00000000';

let result = (padding + N.toString(2)).substr(-padding.length);

“00000111”

  • 2진수, 16진수 등 표현할 때 앞에 0을 추가해서 보여주고싶을 때가 있다
  • 8자리로 표현하고싶으면, padding 변수에 0으로 8자리를 채운다
  • padding 과 이진수를 더하고 substr로 뒤에서부터 8자리만큼 잘라주면된다

방법2 ; padStart

1
2
3
4
let N = 7;
let padding = '00000000';

let result = N.toString(2).padStart(8, '0');
  • 오후 3:25 2021-03-24 추가
  • 훨씬 더 간단한 방법이 있었다
  • padStart() 첫 번째 인자로 자릿수를 지정하고,
  • 두 번째 인자로 추가할 문자열을 입력한다
  • 만약에 첫 번째 인자보다 긴 문자열일 경우 아무 일도 일어나지 않는다
  • 비슷한 메서드로 padEnd() 가 있는데 이거는 패딩을 뒤에 추가한다

참고

chrome inactive tab setTimeout, setInterval 실행 느림

  • 브라우저에서 js 코드 실행속도가 이상하게 느려져서 어떤 문제인가했는데,
  • 개발자도구를 새창으로 열고(콘솔보는용도로)
  • 탭을 백그라운드로 보내버렸더니 생긴문제였다
  • setTimeout, setInterval 메서드의 경우 탭이 인액티브 상태이면 1초 이상으로 제한된다고 한다
  • 인액티브 상태는 최소화하는 등 화면에서 사라지는 경우를 말하는 것 같다
1
2
3
setInterval(() => {
log(1);
}, 100);
  • 위코드를 작성하고 개발자도구 콘솔창을 새창으로 열고
  • 탭을 최소화하면 1초 간격으로 실행되는 것을 확인 할 수 있다

참고

console.log 축약쓰기, 줄여쓰기, 별명 붙여 쓰기

1
2
3
4
const log = console.log;

log(12312313);
log('안녕');
  • console. 쓰는 수고를 덜어준다
  • log 까지지면 vscode의 경우 console.log(); 자동완성 시켜주는데
  • 가끔 자동완성 우선순위가 첫번째가 아닌경우가 있다
  • 그래서 그냥 별명을 붙여 쓰는게 맘편한 것 같다

clg (코드 스니펫)

정규식 : 특정 문자열을 포함하는 줄, 라인 판단

상황

log
1
2
3
4
5
6
7
"GET / HTTP/1.1" 200 hello world 1
"GET / HTTP/1.1" 200 hello world 2
"GET /a HTTP/1.1" 404 hello world 1
"POST / HTTP/1.1" 404 hello world 1
"GET / HTTP/1.1" 200 hello world 2
"GET / HTTP/1.1" 200 hello world 1
"POST /asd HTTP/1.1" 404 hello world 2
  • 이런 웹 로그가 있다고 하자
  • 중간에 보이는 200, 404 는 status code 이다
  • 404 에러인 라인만 뽑아서 보고싶을 때..

로그파일을 한줄씩 읽으며 정규식으로 확인하기

app.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
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';

const input_log_file_name = 'nohup20201107.out';

const input_file_path = path.join(
__dirname,
`../input files/${input_log_file_name}`
);
const output_file_path = path.join(
__dirname,
`../output files/${input_log_file_name}`
);
console.log(input_file_path);

async function processLineByLine() {
const fileStream = fs.createReadStream(input_file_path);

const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});

// 정규식
const reg404 = /^.*(404).*/;
let result_string = '';

for await (const line of rl) {
// console.log(`Line from file: ${line}`);

if (reg404.test(line)) {
// console.log(`hello : ${line}`);
result_string += line + '\r\n';
}
}

fs.writeFile(output_file_path, result_string, 'utf8', function (err) {
if (err) throw err;
console.log('file write complete');
});
}

processLineByLine();
readline으로 한줄한줄 읽기
1
2
3
4
5
6
const fileStream = fs.createReadStream(input_file_path);

const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
  • 우선, 파일을 한줄씩 읽기위해 readline을 사용했다
정규식으로 404 문자열 포함 여부 확인
1
2
3
4
5
6
7
8
9
10
11
12
// 정규식
const reg404 = /^.*(404).*/;
let result_string = '';

for await (const line of rl) {
// console.log(`Line from file: ${line}`);

if (reg404.test(line)) {
// console.log(`hello : ${line}`);
result_string += line + '\r\n';
}
}
  • ^.*(404).* 로 정규식을 새웠다
  • ^.* ; 아무 문자로 시작하는 0개 이상의 문자열로 시작하고
  • (404) ; 404 문자열을 중간에 포함하며
  • .* ; 아무 문자열로 끝나는가
  • reg404.test(line)로 한줄한줄 확인하고
  • 참인 경우에 result_string 에 더하기
result_string 결과 새파일로 쓰기
1
2
3
4
fs.writeFile(output_file_path, result_string, 'utf8', function (err) {
if (err) throw err;
console.log('file write complete');
});
  • result_string에는 404 문자열을 포함한느 라인만 추출되었고
  • output_file_path에 해당 내용을 쓴다

일렉트론으로 OBS input overlay 클론코딩

  • 오버워치 강의 영상을 보다가 영상 속 키보드 마우스 뷰어? 를 가끔 보았었다
  • 일단 깔끔한 키보드 뷰어로 흥미가 생겼었는데
  • 찾아보니까 OBS input overlay라는 OBS 전용 플러그인이었다
  • 일렉트론이라는 프레임워크를 사용하여
  • html, css, js로 데스크탑 앱으로 만들어보았다

신경 쓴 것

글로벌 키 훅?

  • 내가 만든 앱 밖에서 키가 눌리고 떼지는 걸 알아야 한다
index.js
1
2
3
4
5
const ioHook = require('iohook');

ioHook.on('keydown', (event) => {
mainWindow.webContents.send('keydown', event.rawcode);
});
  • iohook 패키지를 사용해서 해결하게 되었다
  • 그런데 iohook이 최신 버전의 일렉트론에서 실행이 안되어서 일렉트론 8.x버전으로 낮추어 진행하게 되었다

overlay window

  • 오버레이 화면에 마우스 클릭이 되지 않도록 했다
  • 오버레이 화면이 프레임을 가지지 않도록 했다
  • 오버레이 화면이 모니터 크기를 구하여 우측 하단에 생기도록 하였다 (여러 해상도 대응)

일렉트론

  • 웹 기술로 데스크탑앱을 만들 수 있게해주는 마법이다
  • 나는 요즘 js만 해왔기 때문에 일렉트론에 대해 찾아보았다
  • 일렉트론으로 만들어진 대표적인 앱으로는 비주얼 스튜디오 코드가 있다

일렉트론 시작

일렉트론 단점?

  • 엄청나게 간단한 앱일지라도 nodejs + Chromium 조합으로 용량이 상당히크다

소스코드

nodemon 변화감지 디렉터리 지정

cmd
1
nodemon --watch app app/server.js
  • nodemon을 사용해서 코드수정마다 서버를 자동으로 재시작하는데,
  • view와 관련한 코드를 수정할 때 서버가 재시작되는 것은 불필요하고, 세션이 날라가는 등 오히려 불편할 수 있다
  • nodemon이 모든 프로젝트 디렉터리가 아닌 특정 디렉터리의 변화를 감시하고 재시작되는 것을 알아보자

명령어

terminal
1
nodemon ./bin/www
  • 보통 이렇게 아무것도 없이 사용하면, 거의 모든 변화마다 서버가 재시작된다
terminal
1
nodemon --watch routes ./bin/www
  • --watch routes watch옵션을 넣어줌으로써 해당 디렉터리에서 변화가 있으면 재시작하도록 할 수 있다
terminal
1
nodemon --watch routes --watch libs ./bin/www
  • 이런식으로 나열할 수 있다
  • 기본적으로 디렉터리명을 적어야하며, 하위디렉터리는 자동으로 포함된다
  • 그런데 여러개의 디렉터리를 나열을 해야하면은
  • 반대로 무시할 디렉터리를 설정하는게 빠를 수 있다

config file로 설정

nodemon.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"restartable": "rs",
"ignore": [".git", "node_modules/**/node_modules"],
"verbose": true,
"execMap": {
"js": "node --harmony"
},
"events": {
"restart": "osascript -e 'display notification \"App restarted due to:\n'$FILENAME'\" with title \"nodemon\"'"
},
"watch": ["test/fixtures/", "test/samples/"],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json"
}
  • nodemon.json이름으로 파일을 하나 만들고 커스텀할 수 있다
  • 무시할 디렉터리를 ignore 배열에 나열하면 된다

참고

Promise 연습, js 비동기 연습

js
1
2
3
4
// 프로미스 병렬처리 ; all
await Promise.all([delay2(1000), delay2(2000)]).then((result) => {
console.log(result.join(' + '));
});
  • 처음에는 익숙해지기 어렵지만, 꿀인 비동기

promise

  • js에는 promise라는게 있다
  • 비동기 흐름에서 중요하다
  • 프로미스는 선언과 동시에 실행이된다
  • 프로미스 결과를 처리하려면 then을 통해 받아 볼 수 있다

async await

js
1
2
3
4
5
6
7
8
async function delay2(ms) {
await new Promise((resolve) => {
setTimeout(() => {
resolve(ms);
}, ms);
});
return ms;
}
  • await 키워드는 async 함수 내에서만 사용가능하다
  • await 를 통해서 promise가 Fulfilled상태가 되야지만 다음줄의 코드가 실행된다

병렬처리

Promise.race

js
1
2
3
await Promise.race([delay2(1000), delay2(2000)]).then((result) => {
console.log(result);
});
  • 1초뒤에 1000만 반환하는 코드이다

Promise.all

js
1
2
3
await Promise.all([delay2(1000), delay2(2000)]).then((result) => {
console.log(result.join(' + '));
});
  • 2초뒤에 1000 + 2000을 반환하는 코드이다

요상한 문법

js
1
2
3
(async () => {
console.log(await delay2(1000));
})();

sam에서 노드 모듈을 레이어로 빼기

  • 람다는 가벼운게 최고다
  • 노드 모듈은 따로 빼버리기!

Lambda Layer

  • nodejs로 치면 node_modules이다
  • 람다 함수에서 노드 모듈같은 종속성을 람다 함수 밖에으로 뺄 수 있음
  • 왜 쓰냐?
      1. 공통적으로 쓰이는 패키지들을 묶어서 관리할 수 있음
      1. 람다함수가 커지면 브라우저로 aws console에서 코드 조회를 못함

따라하기

외부 모듈을 사용해보기

  • sam template.yaml에서 layer설정할 수 있다
  • 지난번에 만든 hello world에서 이어서 진행해 보겠다
hello-world/app.js
1
2
3
// const axios = require('axios')
// const url = 'http://checkip.amazonaws.com/';
let response;
hello-world/app.js
1
2
3
const axios = require('axios');
const url = 'http://checkip.amazonaws.com/';
let response;
  • 맨 윗 2줄 주석 해제한다
hello-world/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.lambdaHandler = async (event, context) => {
try {
// const ret = await axios(url);
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
// location: ret.data.trim(),
}),
};
} catch (err) {
console.log(err);
return err;
}

return response;
};
hello-world/app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.lambdaHandler = async (event, context) => {
try {
const ret = await axios(url);
response = {
statusCode: 200,
body: JSON.stringify({
message: 'hello world',
location: ret.data.trim(),
}),
};
} catch (err) {
console.log(err);
return err;
}

return response;
};
  • 여기도 2곳 주석을 해제한다

package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
"author": "SAM CLI",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0"
},
"scripts": {
"test": "mocha tests/unit/"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.1.4"
}
}
  • 필요없는 devDependencies 를 없앤다
  • test 할때 필요한 패키지인데, 우리는 안쓸거다
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "hello_world",
"version": "1.0.0",
"description": "hello world sample for NodeJS",
"main": "app.js",
"repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs",
"author": "SAM CLI",
"license": "MIT",
"dependencies": {
"axios": "^0.18.0"
},
"scripts": {
"test": "mocha tests/unit/"
}
}
  • 없애면 이런 모양이 된다

cmd
1
2
cd hello-world
npm i
  • app.js가 있는 hello-world 디렉터리로 와서 종속성을 설치한다
1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── .aws-sam/
├── events
│ └── event.json
├── hello-world
│ ├── node_modules/
│ ├── tests/
│ ├── .npmignore
│ ├── app.js
│ └── package.json
├── .gitignore
├── README.md
└── template.yaml
  • node_modules 폴더가 생기면서 현재 디렉터리 구조는 이렇게 된다
  • hello-world/tests 폴더를 삭제한다. 우리는 안쓴다
  • events 폴더를 삭제한다. 우리는 안쓴다
  • .aws-sam 폴더를 삭제한다. 이 폴더가 있으면 local start-api 했을때 .aws-sam를 우선 참조하기때문에 소스 코드에 변경사항이 발생해도 반영되지 않는다
1
2
3
4
5
6
7
8
9
.
├── hello-world
│ ├── node_modules/
│ ├── .npmignore
│ ├── app.js
│ └── package.json
├── .gitignore
├── README.md
└── template.yaml
  • 현재까지 디렉터리 구조는 이렇게 된다

cmd
1
2
cd ..
sam local start-api --skip-pull-image
cmd log
1
2
3
4
5
6
7
C:\tmp\hello-world\hello-world>cd ..

C:\tmp\hello-world>sam local start-api --skip-pull-image
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM
CLI if you update your AWS SAM template
2020-09-21 14:49:47 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
  • 이렇게 location에 자신의 공인 IP가 뜨면 성공이다

  • 주석 해제한 소스 코드는

  • axios라는 모듈로 http://checkip.amazonaws.com/ 에서 공인 ip를 얻어와 response 객체에 location이라는 이름으로 담아서 반환한 것이다

  • http://checkip.amazonaws.com/ 에 접속해 보면 알 수 있듯이 자신의 공인 ip를 알려주는 api다

  • 여기까지 axios라는 모듈을 이용해봤다

  • 이제 sam build & sam deploy 를 통해 aws에 올려보자

cmd
1
sam build & sam deploy
  • 배포가 완료되면 엔드 포인트를 통해서 aws에 올린 람다를 실행해보자
  • 그러면 이상한 아이피가 나온다
  • 이게 aws 컴퓨터중에서 현재 람다가 실행된 컴퓨터의 공인 ip 주소이다
  • 여기까지 잘 작동되는 것이 확인 되었다

외부 모듈을 layer로 빼기

  • 이제 브라우저를 열고 aws console > lambda > HelloWorldFunction 으로 가서 함수가 어떻게 생겻는지 확인한다
  • 함수 안에 노드 모듈이 포함되어 있는 것을 확인할 수 있다
  • 이제 이 글의 핵심인 node_modules 폴더를 layer로 빼볼것이다
  • 레이어를 만드는 방법이 여러가지가 있지만 나는 내 방식을 설명해 보겠다
  • 나는 cmd명령을 사용할 것이다

  • template.yaml에서 빨간 네모 부분을 추가한다
template.yaml
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
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
hello-world

Sample SAM Template for hello-world

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3

Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
Layers:
- !Ref DependencyLayer
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
DependencyLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: HelloWorldFunction-layer
Description: Dependencies for HelloWorldFunction
ContentUri: opt/
CompatibleRuntimes:
- nodejs12.x
RetentionPolicy: Retain

Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: 'API Gateway endpoint URL for Prod stage for Hello World function'
Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/'
HelloWorldFunction:
Description: 'Hello World Lambda Function ARN'
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: 'Implicit IAM Role created for Hello World function'
Value: !GetAtt HelloWorldFunctionRole.Arn
  • 최종코드는 이러하다
  • 눈여겨 봐야할 것은
  • Resources아래에 DependencyLayer라는 이름으로 layer정의하고
  • ContentUri: opt/ 를 지정한다. 이 opt 디렉터리에 우리가 layer로 따로뺄 패키지가 들어가게 될것이다
  • 이제 배치파일을 2개 만들거다
layer.bat
1
2
3
4
5
6
7
echo layer process

set functionDir=HelloWorldFunction

echo y|rmdir /s opt\nodejs
mkdir opt\nodejs
move .aws-sam\build\%functionDir%\node_modules opt\nodejs
  • layer.bat을 sam project 최상위에 만들고 내용은 위와같이 한다
  • set functionDir=HelloWorldFunction 에서는 template.yaml에서 지정한 함수명을 적어주면 된다. template.yaml을 안거드렸으면 HelloWorldFunction이니까 그대로 사용하면 된다
template.yaml
1
2
3
Resources:
HelloWorldFunction: // <- 이름과 매칭되도록
Type: AWS::Serverless::Function
deploy.bat
1
2
3
echo deploy

sam build & layer.bat & sam deploy
  • 또 deploy.bat을 sam project 최상위에 만들고 내용은 위와같이 한다
  • build하고 방금 만든 layer.bat으로 배포될 빌드 디렉터리에서 node_modules을 opt 디렉터리로 옮기고
  • deploy한다

  • 결과는 이렇게 된다
  • 1.2. 디렉터리 구조를 봐보면 node_modules 폴더가 없다
  • 3.4. yaml에서 지정한 layer 등록되어 있는 것을 확인할 수 있다
  • 당연하게도 이 함수는 잘 동작한다
  • 여기까지 batch파일을 활용해서 노드모듈을 레이어로 빼는 방법을 알아보았다
  • 여기까지 프로젝트 파일

로컬에서 sam 테스트하기

  • 이제 테스트할 때 build, deploy 기다리기… 할 필요가 없다!

전제조건


sam local start-api

cmd
1
sam local start-api --skip-pull-image
cmd log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
C:\tmp\hello-world>sam local start-api --skip-pull-image
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM
CLI if you update your AWS SAM template
2020-09-21 13:40:36 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2020-09-21 13:40:41 127.0.0.1 - - [21/Sep/2020 13:40:41] "GET / HTTP/1.1" 403 -
2020-09-21 13:40:41 127.0.0.1 - - [21/Sep/2020 13:40:41] "GET /favicon.ico HTTP/1.1" 403 -
Invoking app.lambdaHandler (nodejs12.x)
Requested to skip pulling images ...

Mounting C:\tmp\hello-world\.aws-sam\build\HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: 00b5b190-1c8d-184f-5ab5-c0e0764041d3 Version: $LATEST
END RequestId: 00b5b190-1c8d-184f-5ab5-c0e0764041d3
REPORT RequestId: 00b5b190-1c8d-184f-5ab5-c0e0764041d3 Init Duration: 438.88 ms
Duration: 4.53 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 44 MB
No Content-Type given. Defaulting to 'application/json'.
2020-09-21 13:40:48 127.0.0.1 - - [21/Sep/2020 13:40:48] "GET /hello HTTP/1.1" 200 -
2020-09-21 13:40:48 127.0.0.1 - - [21/Sep/2020 13:40:48] "GET /favicon.ico HTTP/1.1" 403 -
  • 나는 이렇게 명령을 쓴다
  • 나는 보통 매 실행마다 3-4초 정도 걸린다
  • build, deploy 하면서 테스트할때보다 획기적으로 빠르게 로컬에서 테스트 해볼 수 있다
  • --skip-pull-image옵션을 주는 이유는 매 실행마다 이미지를 받아오는 과정이 있는게 그걸 스킵하면 2초정도 빨라진다
  • 변경 사항은 자동으로 반영된다
  • 단점 ; 근데 변경사항이 없어도 매 실행 3초이상 걸린다
  • template.yaml 파일을 수정하면 수동으로 재시작 해줘야한다