미들웨어와 플러그인의 차이점을 알기 위해 웹팩을 배워본다. webpack 공식문서
webpack은 굉장히 거대하고, 사실상 웹팩이 해주는 일은 그렇게 많지 않다. 플랫폼과 같은 느낌이고, 실제 서버(node)에서 돌아가는 node 실행 파일이다.
웹팩은 일반적으로 webpack.config.js 이라는 설정 파일을 기준으로 한다.
entry
진입점 파일을 설정해준다.
core-js는 폴리필 라이브러리이며, 하위 브라우저 호환을 위해 사용한다.module
리덕스의 미들웨어와 같은 역할을 하는 영역이며,
entry에서 다 읽어들이면 Loader가 컨버팅 후 리턴한다.
loader는 module 내에 작성되며, rules 배열 아래에 각각의 설정을 입력한다.
Typescript를 사용하려면 이전에는 ts-loader를 사용했으나 지금은 babel이 지원한다.plugin
loader의 실행물을 가지고 후처리 한다.
종류가 매우 많으며 필요한 기능을 라이브러리에 설치하여 추가한다.
(ex. CleanWebpackPlugin, DefinePlugin, HtmlWebpackPlugin ...)const config = {
// ...
entry: {
main: ["core-js", "./src/index.tsx"],
},
module: {
rules: [
{
test: /\\.(ts|js)x?$/, // 원하는 파일 형식만 받겠다.
include: path.resolve("src"),
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
{
test: /\\.(png|svg|jpg|gif)$/,
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath: "./images/",
},
},
],
},
plugins: [
new webpack.SourceMapDevToolPlugin({}),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
}),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
],
// ...
};
module.export = config; // config 객체를 webpack이 받아서 빌드한다.
.babelrc 등에서 상세한 플러그인 설정을 해준다. (preset-typescript, preset-react 등)
import { createStore, applyMiddleware } from "redux"; // redux는 특정 라이브러리에 종속되지 않는다.
import { Provider } from "react-redux"; // redux를 React 내에서 사용하기 위해 react-redux 사용
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
reducer
에 사용된 초기 상태의 인터페이스는 아래와 같이 설정했다.export interface StoreState {
monitoring: boolean;
success: number;
failure: number;
};
import { StoreState } from "../types";
const initializeState: StoreState = {
monitoring: false,
success: 0,
failure: 0
};
typesafe-action
라는 라이브러리를 이용해 action
을 정리할 수 있다.import { createAction } from "typesafe-actions";
export const startMonitoring = createAction(
"@command/monitoring/start",
resolve => {
return () => resolve();
}
);
export const stopMonitoring = createAction(
"@command/monitoring/stop",
resolve => {
return () => resolve();
}
);
export const fetchSuccess = createAction("@fetch/success", resolve => {
return () => resolve();
});
export const fetchFailure = createAction("@fetch/failure", resolve => {
return () => resolve();
});
typesafe-action
에서 각종 액션을 정의하면서,
별도의 상수를 만들지 않고 액션을 리듀서에 정의할 수 있게 되었다.import { ActionType, getType } from "typesafe-actions";
import * as Actions from "../actions";
// ...
// 상태 인터페이스 및 상태 선언
// ...
export default (
state: StoreState = initializeState,
action: ActionType<typeof Actions>
) => {
switch (action.type) {
case getType(Actions.fetchSuccess):
return {
...state,
success: state.success + Math.floor(Math.random() * (100 - 1) + 1)
};
case getType(Actions.fetchFailure):
return {
...state,
failure: state.failure + Math.floor(Math.random() * (2 - 0))
};
default:
console.log(action.type);
return Object.assign({}, state);
}
};
redux-saga
// ▼ saga 제너레이터의 기본 구조
function* rootSaga(){
const resp = yield { action: 'api/users' };
console.log(resp);
yield { id: "@init" };
yield { id: "@inc" };
yield { id: "@put" };
}
const root = rootSaga();
root.next();
root.next('123');
console.log(root.next()); // { value: { id: "@init" }, done: false }
console.log(root.next()); // { value: { id: "@inc" }, done: false }
console.log(root.next()); // { value: { id: "@put" }, done: false }
console.log(root.next()); // { value: undefined, done: true }
fork(monitoringWorkflow), take(monitoringWorkflow) 와 같은 헬퍼함수의 결과는 모두 객체이다.
각각의 함수는 특정 역할을 가지고 있으며 이는 saga의 스펙에 맞게 사용한다.
put
: 액션을 dispatch하기 위한 함수이다.
all
: all을 이용해 여러 개의 액션을 put으로 dispatch 해준다.
delay
: Promise와 같이 사용되며, 정해진 시간 뒤 다시 코드가 실행되도록 해준다.
race
: 인자 객체의 값 중 먼저 응답이 온 값을 반환하는 역할을 한다.
race
메서드 내부를 보면..
import { fork, all, take, race, delay, put } from "redux-saga/effects";
import { getType } from "typesafe-actions";
import * as Actions from "../actions";
function* monitoringWorkflow() {
while (true) {
yield take(getType(Actions.startMonitoring));
let loop = true;
while (loop) {
yield all([
put({ type: getType(Actions.fetchSuccess) }),
put({ type: getType(Actions.fetchFailure) })
]);
// ▼ 200ms 동안 기다리거나, stopMonitoring 액션이 실행된다.
const { stoped } = yield race({
waiting: delay(200),
stoped: take(getType(Actions.stopMonitoring))
});
if (stoped) {
loop = false;
}
}
}
}
// ▼ 비동기 액션을 처리하기 위해 제너레이터 형태를 가진다.
export default function* () {
yield fork(monitoringWorkflow);
}
import { createStore, applyMiddleware } from "redux";
import { StoreState } from "./types";
import reducer from "./reducers";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas";
const sagaMiddleware = createSagaMiddleware();
const store: StoreState = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);