Dan Abramov๋ Devtools v4๊ฐ react-hot-loader
์ธ๋ชจ ์๊ฒ ๋ง๋ค ๊ฒ์ด๋ผ๊ณ ์ธ๊ธํ์ต๋๋ค : https://twitter.com/dan_abramov/status/1144715740983046144?s=20
๋๋ฅผ:
์ด ํํฌ๊ฐ ์์ต๋๋ค.
require("react-reconciler")(hostConfig).injectIntoDevTools(opts);
๊ทธ๋ฌ๋ HMR์ ํญ์ ๊ทธ๊ฒ ์์ด๋ ์์ ํ ์๋ํ์ต๋๋ค. ์ด๊ฒ์ ์ด์ ์๋ก์ด ์๊ตฌ ์ฌํญ์ ๋๊น?
๋จ:
์, ๊ทธ๊ฒ์ด ์๋ก์ด ๋ฉ์ปค๋์ฆ์ด ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ์๋ก์ด ๋ฉ์ปค๋์ฆ์๋ "react-hot-loader"๊ฐ ํ์ํ์ง ์์ผ๋ฏ๋ก ์ ๋ฐ์ดํธ ํ ๋ ํด๋น ํจํค์ง๋ฅผ ์ ๊ฑฐํด์ผํฉ๋๋ค. (๊ทธ๊ฒ์ ๊ฝค ์นจ๋ต์ ์ ๋๋ค)
๊ทธ๋ฌ๋ Devtools ๋ฌธ์์์ HMR์ ๋ํ ์ธ๊ธ์ ๋ณผ ์ ์์ต๋๋ค. ์ด์ react-hot-loader
๊ฐ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ต๋๋ค (๊ทธ๋ฆฌ๊ณ require("react-hot-loader/root").hot
๋ฉ์๋์ ํจ๊ป). HMR ์ฉ ์ฑ์ ๋ค์์์ ์ด๋ป๊ฒ ์ค์ ํด์ผํฉ๋๊น?
ํนํ react-hot-loader
๋ฅผ ํตํด HMR์ ์ด๋ฏธ ์ค์ ํ ์ฌ์ฉ์๋ฅผ์ํ ๋ง์ด๊ทธ๋ ์ด์
๊ฐ์ด๋์ ํนํ ๊ด์ฌ์ด ์์ต๋๋ค.
๋ํ HMR์ ๊ฒฝ์ฐ ๋ ๋ฆฝ ์คํ ํ Devtools๋ฅผ ์ฌ์ฉํ๋์ง ๋ธ๋ผ์ฐ์ ํ์ฅ Devtools๋ฅผ ์ฌ์ฉํ๋์ง๊ฐ ์ค์ํฉ๋๊น?
์ฝ๊ฐ์ ํผ๋์ด ์์ต๋๋ค. ์๋ก์ด DevTools๋ ํซ ๋ฆฌ๋ก๋ฉ์ ํ์ฑํํ์ง ์์ต๋๋ค (๋๋ ๋ฆฌ๋ก๋ฉ๊ณผ ๊ด๋ จ์ด ์์). ์คํ๋ ค Dan์ด ์์ ํ๊ณ ์๋ ํซ ๋ฆฌ๋ก๋ฉ ๋ณ๊ฒฝ์ DevTools์ React๊ฐ ํต์ ์ ์ฌ์ฉํ๋ "ํํฌ"๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ค์๋ก๋ ํ ์ ์๋๋ก ์ค๊ฐ์ ์ถ๊ฐ๋ฉ๋๋ค.
DevTools์ ๋ํ ์ธ๊ธ์ ์ ๊ฑฐํ๊ธฐ ์ํด ์ ๋ชฉ์ ํธ์งํ์ต๋๋ค (ํผ๋์ ์ผ์ผํฌ ์ ์์ผ๋ฏ๋ก).
์๋ก์ด HMR์ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํ๋์ง์ ๋ํ ์ง๋ฌธ์ ๋ํด์๋ ๊ฑฐ๊ธฐ์์ ์ต์ ์๊ฐ์ ์์ง ๋ชปํ๋ ๊ฒ ๊ฐ์ต๋๋ค. @gaearon ์ด CRA repo์ ๋ํ PR์ ๊ฐ์ง๊ณ ์์์
https://github.com/facebook/create-react-app/pull/5958
์๋ก์ด HMR์ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํ๋์ง์ ๋ํ ์ง๋ฌธ์ ๋ํด์๋ ๊ฑฐ๊ธฐ์์ ์ต์ ์๊ฐ์ ์์ง ๋ชปํ๋ ๊ฒ ๊ฐ์ต๋๋ค. @gaearon ์ด CRA repo์ ๋ํ PR์ ๊ฐ์ง๊ณ ์์์
๋ ์๋ฅผ ์ํด ๋ช ํํํ๊ธฐ ์ํด PR์ ๋งค์ฐ ๊ตฌ์์ด๋ฉฐ ๋ ์ด์ ๊ด๋ จ์ด ์์ต๋๋ค.
Fast Refresh์ ์๋ ๋ฐฉ์๊ณผ ํตํฉ ๋ฐฉ๋ฒ์ ๋ํด ์ ์ด์ผํฉ๋๋ค. ์์ง ์๊ฐ์ด ์์์ต๋๋ค.
์ข์, ๊ฐ๋ค.
React์ ์์ ํ ์ง์๊ณผ ํจ๊ป "hot reloading"์ ์ฌ ๊ตฌํ์
๋๋ค. ์๋ React Native ์ฉ์ผ๋ก ์ ๊ณต ๋์์ง๋ง ๋๋ถ๋ถ์ ๊ตฌํ์ ํ๋ซํผ ๋
๋ฆฝ์ ์
๋๋ค. ๊ณํ์ ์์ ํ ์ฌ์ฉ์ ์์ญ ์๋ฃจ์
(์ react-hot-loader
)์ ๋์ฒดํ๊ธฐ ์ํด ์ ์ฒด์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค.
์ด๋ก ์ ์ผ๋ก ๋ค, ๊ทธ๊ฒ ๊ณํ์ ๋๋ค. ์ค์ ๋ก ๋๊ตฐ๊ฐ๋ ์น์์ ์ผ๋ฐ์ ์ธ ๋ฒ ๋ค๋ฌ (์ : Webpack, Parcel)์ ํตํฉํด์ผํฉ๋๋ค. ๋๋ ์์ง ๊ทธ ์ผ์ํ์ง ์์์ต๋๋ค. ๋๊ตฐ๊ฐ๊ฐ ๊ทธ๊ฒ์ ์ง์ด ๋ค๊ณ ์ถ์ด ํ ์๋ ์์ต๋๋ค. ์ด ์๊ฒฌ์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ์ง์ ๋ํ ๋๋ต์ ์ธ ๊ฐ์ด๋์ ๋๋ค.
๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ ํจ๊ป ์๋ํ๋ ์ฌ๋ฌ ๋ถ๋ถ์ ์์กดํฉ๋๋ค.
module.hot
API๋ฅผ ์ฌ์ฉํ๋ฉด์ด ์์
์ ์ํ ํ ์ ์์ต๋๋ค.[email protected]
์ด์react-refresh/runtime
์ง์
์ react-refresh/babel
Babel ํ๋ฌ๊ทธ์ธํตํฉ ๋ถ๋ถ์์ ์์
ํ๊ณ ์ถ์ ๊ฒ์
๋๋ค. ์ฆ, react-refresh/runtime
๋ฅผ Webpack "ํซ ๋ชจ๋ ๊ต์ฒด"๋ฉ์ปค๋์ฆ๊ณผ ํตํฉํฉ๋๋ค.
โ ๏ธโ ๏ธโ ๏ธ ๋ช ํํ๊ฒํ๊ธฐ ์ํด์ด ๋ฌธ์๋ ํตํฉ ํ ๋ง๋ฅผ ๊ตฌํํ๋ ค๋ ์ฌ๋๋ค์์ํ ๊ฐ์ด๋์ ๋๋ค. ์์ ์์ฃผ์์ ๋ฐ๋ผ ์งํํ์ญ์์ค!
์ต์ํ์ผ๋กํ๊ณ ์ถ์ ๋ช ๊ฐ์ง ์์ ์ด ์์ต๋๋ค.
react-refresh/babel
์ถ๊ฐ์ด ์์ ์์ ์ฑ์ด ์ถฉ๋ํด์ผํฉ๋๋ค. ์ ์๋์ง ์์ $RefreshReg$
๋ฐ $RefreshSig$
ํจ์์ ๋ํ ํธ์ถ์ด ํฌํจ๋์ด์ผํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์ react-dom
(!)๋ฅผ ํฌํจ ํ์ฌ ์ฑ์ ์ฝ๋๋ณด๋ค ๋จผ์ ์คํํด์ผํ๋ ์ JS ์ง์
์ ์ react-dom
์ดํ์ ์คํ๋๋ฉด ์๋ฌด๊ฒ๋ ์๋ํ์ง ์์ต๋๋ค. ํด๋น ์ง์
์ ์ ๋ค์๊ณผ ๊ฐ์ดํด์ผํฉ๋๋ค.
if (process.env.NODE_ENV !== 'production' && typeof window !== 'undefined') {
const runtime = require('react-refresh/runtime');
runtime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => type => type;
}
์ด๋ ๊ฒํ๋ฉด ์ถฉ๋์ด ํด๊ฒฐ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฌํ $RefreshReg$
๋ฐ $RefreshSig$
๊ตฌํ์ noops์ด๋ฏ๋ก ์ฌ์ ํ ์๋ฌด ์์
๋ ์ํํ์ง ์์ต๋๋ค. ์ด๋ค์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ํตํฉ ์์
์ ํต์ฌ์
๋๋ค.
์ด๋ฅผ ์ํํ๋ ๋ฐฉ๋ฒ์ ๋ฒ ๋ค๋ฌ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค. ์นํฉ์ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ๋ชจ๋์ด ์คํ๋๊ธฐ ์ ํ์ ์ผ๋ถ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ ๋ก๋๋ฅผ ์์ฑํ ์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋๋ ๋ชจ๋ ํ ํ๋ฆฟ์ ๋ฌด์ธ๊ฐ๋ฅผ ์ฃผ์ ํ๋ ํํฌ๊ฐ์์ ์ ์์ต๋๋ค. ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ๋ฌ์ฑํ๋ ค๋ ๊ฒ์ ๋ชจ๋ ๋ชจ๋์ด ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ด๋ ๊ฒ์ ๋๋ค.
// BEFORE EVERY MODULE EXECUTES
var prevRefreshReg = window.$RefreshReg$;
var prevRefreshSig = window.$RefreshSig$;
var RefreshRuntime = require('react-refresh/runtime');
window.$RefreshReg$ = (type, id) => {
// Note module.id is webpack-specific, this may vary in other bundlers
const fullId = module.id + ' ' + id;
RefreshRuntime.register(type, fullId);
}
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
try {
// !!!
// ...ACTUAL MODULE SOURCE CODE...
// !!!
} finally {
window.$RefreshReg$ = prevRefreshReg;
window.$RefreshSig$ = prevRefreshSig;
}
์ฌ๊ธฐ์ ์์ด๋์ด๋ Babel ํ๋ฌ๊ทธ์ธ์ด์ด ํจ์์ ๋ํ ํธ์ถ์ ๋ด ๋ณด๋ธ ๋ค์ ์์ ํตํฉ์ด ํด๋น ํธ์ถ์ ๋ชจ๋ ID์ ์ฐ๊ฒฐํ๋ค๋ ๊ฒ์
๋๋ค. ๋ฐ๋ผ์ ๋ฐํ์์ ๊ตฌ์ฑ ์์๊ฐ ๋ฑ๋ก ๋ ๋ "path/to/Button.js Button"
์ ๊ฐ์ ๋ฌธ์์ด์ ์์ ํฉ๋๋ค. (๋๋ ์นํฉ์ ๊ฒฝ์ฐ ID๋ ์ซ์์
๋๋ค.) Babel ๋ณํ๊ณผ์ด ๋ํ์ ๋ชจ๋ ๊ฐ๋ฐ ๋ชจ๋ ์์๋ง ๋ฐ์ํด์ผํ๋ค๋ ๊ฒ์ ์์ง ๋ง์ญ์์ค.
๋ชจ๋ ์ฝ๋๋ฅผ ๋ํํ๋ ๋์ ๋ฒ ๋ค๋ฌ๊ฐ ์ค์ ๋ก ๋ชจ๋ ํฉํ ๋ฆฌ๋ฅผ ์ด๊ธฐํํ๋ ์์น์ ์ด์ ๊ฐ์ try / finally๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ด์์ ์ ์์ต๋๋ค. ์ฌ๊ธฐ Metro (RN ๋ฒ ๋ค๋ฌ)์์ํ๋ ๊ฒ์ฒ๋ผ. ์ฐ๋ฆฌ๋ ํฌ์ฅ ํ ๋ ์๋ฅผ ๋ค์ด, ๋ถ์ ํ ๊ตฌ๋ฌธ์ ๋์
์ ๋ํด ๋ชจ๋ ๋ชจ๋ ๋๋ ๊ฑฑ์ ์ ๋ถํ๊ฒ ํ ํ์๊ฐ ์์ต๋๋ค ๋๋ฌธ์ ์ด๊ฒ์ ์๋ง ๋ ์ข์ ๊ฒ์ด๋ค import
์์๋ก try / finally
.
์ด๊ฒ์ ์ฐ๊ฒฐํ๋ฉด ๋ง์ง๋ง ๋ฌธ์ ๊ฐ ํ๋ ์์ต๋๋ค. ๋ฒ ๋ค๋ฌ๋ ์ ๋ฐ์ดํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ ์๋ค๋ ์ฌ์ค์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ์ด์จ๋ ํ์ด์ง๋ฅผ ๋ค์๋ก๋ ํ ๊ฒ์ ๋๋ค. ํ์ง ๋ง๋ผ๊ณ ํด์ผํฉ๋๋ค. ์ด๊ฒ์ ๋ค์ ๋ฒ ๋ค๋ฌ์ ๋ฐ๋ผ ๋ค๋ฅด์ง๋ง ๋ด๊ฐ ์ ์ํ๋ ์ ๊ทผ ๋ฐฉ์์ ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ๊ฐ React ๊ตฌ์ฑ ์์ ์ธ์ง ํ์ธ
// ...ALL MODULE CODE...
const myExports = module.exports;
// Note: I think with ES6 exports you might also have to look at .__proto__, at least in webpack
if (isReactRefreshBoundary(myExports)) {
module.hot.accept(); // Depends on your bundler
enqueueUpdate();
}
isReactRefreshBoundary
๋ฌด์์
๋๊น? ์ค๋ฒ ์ต์คํฌํธ๋ฅผ ์๊ฒ ์ด๊ฑฐํ๊ณ React ์ปดํฌ๋ํธ ๋ง ์ต์คํฌํธํ ์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๊ฒ์
๋๋ค. ์ด๊ฒ์ด ์
๋ฐ์ดํธ ์๋ฝ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐฉ๋ฒ์
๋๋ค. ์ฌ๊ธฐ์ ๋ถ์ฌ ๋ฃ์ง๋ ์์์ง๋ง ์ด ๊ตฌํ ์ด ์ข์ ์์ Refresh
๋ react-refresh/runtime
๋ด๋ณด๋ด๊ธฐ๋ฅผ ๋ํ๋
๋๋ค.)
Babel ๋ณํ์ ํจ์์ ๋ํด $RefreshReg$
๋ง ํธ์ถํ๋ฏ๋ก ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ ๋ฅผ
๋ง์ง๋ง์ผ๋ก enqueueUpdate()
ํจ์๋ ์ค์ React ์
๋ฐ์ดํธ๋ฅผ ๋ ๋ฐ์ด์คํ๊ณ ์ํํ๋ ๋ชจ๋๊ฐ์ ๊ณต์ ๋๋ ๊ธฐ๋ฅ์
๋๋ค.
const runtime = require('react-refresh/runtime');
let enqueueUpdate = debounce(runtime.performReactRefresh, 30);
์ด ์์ ์์ ๋น์ ์ ๋ญ๊ฐ ์๋ํด์ผํฉ๋๋ค.
"๋น ๋ฅธ ์๋ก ๊ณ ์นจ"๋ธ๋๋ฉ์ ๊ด์ฌ์ด์๋ ๋ช ๊ฐ์ง ๊ธฐ๋ณธ ๊ฒฝํ ๊ธฐ๋์น๊ฐ ์์ต๋๋ค. ๊ตฌ๋ฌธ ์ค๋ฅ, ๋ชจ๋ ์ด๊ธฐํ ์ค๋ฅ ๋๋ ๋ ๋๋ง ์ค๋ฅ์์ ์ ์์ ์ผ๋ก ๋ณต๊ตฌ ํ ์ ์์ด์ผํฉ๋๋ค. ์ด๋ฌํ ๋ฉ์ปค๋์ฆ์ ๋ํด ์์ธํ ์ค๋ช ํ์ง๋ ์๊ฒ ์ง ๋ง ์ด๋ฌํ ๊ฒฝ์ฐ๋ฅผ ์ ์ฒ๋ฆฌ ํ ๋๊น์ง ์คํ์ "Fast Refresh"๋ผ๊ณ ๋ถ๋ฅด์ง ๋ง์์ผํ๋ค๊ณ ๋งค์ฐ ๊ฐ๋ ฅํ๊ฒ ๋๋๋๋ค.
์ํ๊น๊ฒ๋ ์นํฉ์ด์ด ๋ชจ๋ ๊ฒ์ ์ง์ํ ์ ์๋์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง, ์ด๋ ์ ๋ ์๋ ์ํ์ ์ด๋ฅด๋ ๋ค๊ฐ ๋งํ๋ฉด ๋์์ ์์ฒญํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์นํฉ์ accept()
API๊ฐ ์ค๋ฅ ๋ณต๊ตฌ๋ฅผ ๋ ์ด๋ ต๊ฒ ๋ง๋ ๋ค๋ ๊ฒ์ ์์์ต๋๋ค (์ค๋ฅ ํ ๋ชจ๋์ _previous_ ๋ฒ์ ์ ์๋ฝํด์ผ ํจ). ๋ค์ ๋์ ๊ฐ์ผ ํ ๋ ๋ค๋ฅธ ์ฌํญ์ Babel ํ๋ฌ๊ทธ์ธ์์ ์ฐพ์ ๊ฒ๋ฟ๋ง ์๋๋ผ ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์๋์ผ๋ก "๋ฑ๋ก"ํ๋ ๊ฒ์
๋๋ค. ์ง๊ธ์ ์ด๊ฒ์ ๋ฌด์ํด ๋ด
์๋ค. ์๋ฅผ ๋ค์ด webpack์์ ์๋ํ๋ ๊ฒ์ด ์๋ค๋ฉด ๊ทธ๊ฒ์ ๋ค๋ฌ์ ์ ์์ต๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก Create React App์์๋ react-error-overlay
์ ์ ์ฌํ "์ค๋ฅ ์์"ํ๊ฒฝ๊ณผ ํตํฉํด์ผํฉ๋๋ค. ์ค๋ฅ๋ฅผ ์์ ํ๋ฉด ์ค๋ฅ ์์๊ฐ ์ฌ๋ผ์ง๋ ๊ฒ๊ณผ ๊ฐ์ ์ฝ๊ฐ์ ๋์์ค๊ฐ ์์ต๋๋ค. ์ฌ๋จ์ด ๋ง๋ จ๋๋ฉด ์ฐ๋ฆฌ๊ฐ ํ ์์๋ ์ถ๊ฐ ์์
๋ ํ์ํฉ๋๋ค.
๊ถ๊ธํ ์ ์ด ์์ผ๋ฉด ์๋ ค์ฃผ์ธ์!
๊ตฌ๋ฌธ ์ค๋ฅ / ์ด๊ธฐํ ์ค๋ฅ๋ React์๊ฒ ๋ ๋๋ง์ ์์ํ๋๋ก ์ง์ํ๊ธฐ ์ ์ ์ด๋ค ๋ฐฉ์ ์ผ๋ก๋ ์ฒ๋ฆฌํ๊ธฐ์ "์ถฉ๋ถํ ์ฌ์ธ ๊ฒ"์ด์ด์ผํ์ง๋ง ๋ ๋๋ง ์ค๋ฅ๋ ์ค๋ฅ ๊ฒฝ๊ณ์ ์ด๋ป๊ฒ ์ํธ ์์ฉํ ๊น์?
๋ ๋๋ง ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๊ฐ์ฅ ๊ฐ๊น์ด ์ค๋ฅ ๊ฒฝ๊ณ๊ฐ ํธ๋ฆฌ๊ฑฐ๋์ด ์์ฒด์ ์ผ๋ก ์ค๋ฅ ์ํ๋ก ์ค๋ ์ท์ด ์์ฑ๋๋ฉฐ, ์ค์๊ฐ ์๋ก ๊ณ ์นจ ํ ์์์ด ๋ง์ ์ฒ๋ผ _ ๊ฐ๋ฅํ๊ฒ _ ์์ ๋์์์ ์ค๋ฅ ๊ฒฝ๊ณ์ ์๋ฆฌ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ ์์ต๋๋ค. ๋ชจ๋ ์๋ก ๊ณ ์นจ ๊ฐ๋ฅํ ๊ตฌ์ฑ ์์๊ฐ ๋ฌด๋ฃ๋ก ์์ฒด ์ค๋ฅ ๊ฒฝ๊ณ๋ฅผ ๊ฐ์ ธ์ผํฉ๋๊น? ์๋๋ฉด ์ด๊ธฐํ์ ๋ฐํ์ ์ง์์ด ๊ฐ์ง๋๋ฉด ์กฐ์ ์์์ ์ค๋ฅ ์ฒ๋ฆฌ๊ฐ ๋ค๋ฅด๊ฒ ์๋ํฉ๋๊น?
๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ๋ React ๊ตฌ์ฑ ์์์ด๋ฉฐ์ด ๊ฒฝ์ฐ ์ ๋ฐ์ดํธ๋ฅผ "์๋ฝ"ํฉ๋๋ค.
์ด๋ฌํ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์งํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น? ๋ด๊ฐ ์ดํดํ๋ ํ-์๋์. export.toString().indexOf('React')>0
์ ์ธํ๊ณ ์ ์ฉ๋๋ ๋ชจ๋ HOC๋ก ์๋์ด ์ค์ง๋ฉ๋๋ค.
๋ํ ํ์ผ ๋์์๋ _self accepting_์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ์ง ์์ต๋๋ค. ์๋ก์ด ์๋ฝ ํธ๋ค์ด ์ค์ ๋์ง ์๊ณ ๋ค์ ์
๋ฐ์ดํธ๊ฐ ๋ ๋์ ๊ฒฝ๊ณ๋ก ๋ฒ๋ธ ๋ง๋๋ฏ๋ก require("react-hot-loader/root").hot
๊ฐ ์์ฑ๋์์ต๋๋ค.
์ด์จ๋ - react-hot-loader
์์ ๋ชจ๋ ๋ฐ์ ๊ด๋ จ ์ฝ๋๋ฅผ ๋์ ธ ์ธ๋ถ API๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ค๋ฉด ์ถฉ๋ถํ๊ณ ๊ธฐ์กด์ ๋ชจ๋ ์ค์น์ ์ ์ฉ ํ ์์๋ ๊ฒ ๊ฐ์ต๋๋ค.
react-refresh/babel
0.4.0์ ์ฌ์ฉํ๋ฉด ๋ง์ ํ์ผ์์์ด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
ERROR in ../orbit-app/src/hooks/useStores.ts
Module build failed (from ../node_modules/babel-loader/lib/index.js):
TypeError: Cannot read property '0' of undefined
at Function.get (/Users/nw/projects/motion/orbit/node_modules/@babel/traverse/lib/path/index.js:115:33)
at NodePath.unshiftContainer (/Users/nw/projects/motion/orbit/node_modules/@babel/traverse/lib/path/modification.js:191:31)
at PluginPass.exit (/Users/nw/projects/motion/orbit/node_modules/react-refresh/cjs/react-refresh-babel.development.js:546:28)
๊ทธ ํ์ผ์ ์์ธ์ด๋๋ ๊ฐ์ฅ ๊ฐ๋จํ ๊ฒ์ผ๋ก ์ขํ์ต๋๋ค.
import { useContext } from 'react'
export default () => useContext()
๋ ๋๋ง ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ๊ฐ์ฅ ๊ฐ๊น์ด ์ค๋ฅ ๊ฒฝ๊ณ๊ฐ ํธ๋ฆฌ๊ฑฐ๋์ด ์์ฒด์ ์ผ๋ก ์ค๋ฅ ์ํ๋ก ์ค๋ ์ท์ด ์์ฑ๋๋ฉฐ, ์ค์๊ฐ ์๋ก ๊ณ ์นจ ํ ์์์ด ๋ง์ ์ฒ๋ผ ์์ ๋ ์ ์์์ ์ค๋ฅ ๊ฒฝ๊ณ์ ์๋ฆฌ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ ์์ต๋๋ค.
React ๋ด๋ถ์ Fast Refresh ์ฝ๋๋ ํ์ฌ ์คํจํ ๊ฒฝ๊ณ๋ฅผ ๊ธฐ์ตํฉ๋๋ค. ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ์ ๋ฐ์ดํธ๊ฐ ์์ฝ ๋ ๋๋ง๋ค ํญ์ ๋ค์ ๋ง์ดํธ๋ฉ๋๋ค.
๊ฒฝ๊ณ๊ฐ ์์ง๋ง ๋ฃจํธ๊ฐ ์ ๋ฐ์ดํธ์ ์คํจํ ๊ฒฝ์ฐ Fast Refresh๋ ํด๋น ๋ฃจํธ๋ฅผ ๋ง์ง๋ง ์์๋ก ๋ค์ ๋ ๋๋งํฉ๋๋ค.
๋ง์ดํธ์์ ๋ฃจํธ๊ฐ ์คํจํ๋ฉด runtime.hasUnrecoverableErrors()
์๋ ค์ค๋๋ค. ๊ทธ๋ฐ ๋ค์ ๊ฐ์ ๋ก ์ฌ์ฅ ์ ํด์ผํฉ๋๋ค. ๋์ค์ ๊ทธ ์ฌ๊ฑด์ ์ฒ๋ฆฌ ํ ์ โโ์์์ง๋ง ์์ง ๊ณ ์น ์๊ฐ์ด ์์์ต๋๋ค.
react-refresh / babel 0.4.0์ ์ฌ์ฉํ๋ฉด ๋ง์ ํ์ผ์์์ด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์๋ก์ด ๋ฌธ์ pls๋ฅผ ์ ์ถ ํ์๊ฒ ์ต๋๊น?
์ด๋ฌํ ๊ตฌ์ฑ ์์๋ฅผ ๊ฐ์งํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น?
์์ฒด์ ์ผ๋ก Runtime.isLikelyAReactComponent()
์ฌ์ฉํ๋ ๋ด ๊ตฌํ์ ์ฐ๊ฒฐํ์ต๋๋ค. ์๋ฒฝํ์ง๋ ์์ง๋ง ์ถฉ๋ถํฉ๋๋ค.
์๋ก์ด ์๋ฝ ํธ๋ค์ด ์ค์ ๋์ง ์๊ณ ๋ค์ ์ ๋ฐ์ดํธ๊ฐ ๋ ๋์ ๊ฒฝ๊ณ๋ก ๋ฒ๋ธ ๋ง๋ฉ๋๋ค.
๋ชจ๋ฒ์ ๋ณด์ผ ์ ์์ต๋๊น? ๋๋ ๋ฐ๋ฅด์ง ์๋๋ค. ์ด์จ๋ ๊ทธ๊ฒ์ ๋ฒ ๋ค๋ฌ์ ํน์ ํ ๊ฒ์ ๋๋ค. ๋ด๊ฐ ์ํ๋๋๋ก Metro๋ฅผ ๋ง๋ค์์ต๋๋ค. API๊ฐ ๋๋ฝ ๋ ๊ฒฝ์ฐ webpack์ ์ถ๊ฐ๋ฅผ ์์ฒญํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์์ ๋ชฉํ๋ ์ผ๊ด์ฑ์ ๋ณด์ฅํ๋ฉด์ ๊ฐ๋ฅํ ํ ์ ์ ์์ ๋ชจ๋์ ๋ค์ ์คํํ๋ ๊ฒ์ ๋๋ค. ๋๋ถ๋ถ์ ํธ์ง์ ๋ํด ๋ฃจํธ์ ๋ํ ๋ฒ๋ธ ์ ๋ฐ์ดํธ๋ฅผ ์ํ์ง ์์ต๋๋ค .
react-hot-loader์์ ๋ชจ๋ ๋ฐ์ ํน์ ์ฝ๋๋ฅผ ๋์ ธ ์ธ๋ถ API๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ต์์ ์ปจํ
์ด๋๋ ์ ๊ฑฐํ๊ณ ์ถ์ง๋ง ์๋ง๋. ๋ํ ์ค๋ฅ ์์์ ๋ ๊ธด๋ฐํ๊ฒ ํตํฉ๋๊ธฐ๋ฅผ ์ํฉ๋๋ค. ์ฌ์ ํ react-hot-loader
๋ผ๊ณ ํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ๋ด๊ฐ ์์ ๋ถ๋ถ ์ธ performReactRefresh
์ ํ๋ฅผ ํฌํจํ๋๋ก ๊ฐ์ด๋๋ฅผ ํธ์งํ์ต๋๋ค. ๊ทธ๊ฒ์ด ์ค์ ๋ก ์
๋ฐ์ดํธ๋ฅผ ์์ฝํ๋ ๊ฒ์
๋๋ค.
isLikelyComponentType(type) {
return typeof type === 'function' && /^[A-Z]/.test(type.name);
},
๊ทธ๋ฐ ๋
ผ๋ฆฌ๋ก๋ ์์ ํ์ง ์์ ๊ฒ์
๋๋ค. ๋ชจ๋ CapitalizedFunctions๊ฐ ๊ฑฐ์ ํญ์ React ๊ตฌ์ฑ ์์ ์ธ ๊ฒฝ์ฐ์๋-๋ง์ ๋ชจ๋ (๋ด)์๋ ๋ค๋ฅธ ๋ด๋ณด๋ด๊ธฐ๋ ์์ต๋๋ค. ์ : _exports-for-tests_. ๊ทธ๊ฒ์ ๋ฌธ์ ๊ฐ๋์ง ์์ง๋ง ์ด๋ค _unpredictability_๋ฅผ ์์ฑํฉ๋๋ค.-ํซ ๋ฐ์ด๋๋ฆฌ๋ ์ด๋ ์์ ์์๋ ์์ฑ ๋ ์ ์์ต๋๋ค ... ๋๋ ํ ์ค ๋ณ๊ฒฝ ํ์ ์์ฑ๋์ง ์์ต๋๋ค.
isLikelyComponentType
ํ
์คํธ๋ฅผ ์ค๋จ ํ ์์๋ ์์ :
mapStateToProps
(ํ
์คํธ ์ฉ, ํ๋ก๋์
์ฝ๋์์ ์ฌ์ฉ๋์ง ์์)hook
๋ด๋ณด๋์ต๋๋ค. (๊ด์ฐฎ์ต๋๋ค)Class
๋ด๋ณด๋ (์๋์ง๋ง ๊ทธ๋์ผ ํจ)๋ฐ๋ผ์-ํซ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ค์ ๋์ง๋ง ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๊ฐ ์์ผ๋ฉฐ ํซ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ค์ ๋์ง๋ง ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ฐ๋ฆฌ ๋ ๋ค ์ข์ํ์ง ์๋ ์ค๋๋๊ณ ๋ถ์์ ํ ํซ ๋ฆฌ๋ก๋ฉ์ฒ๋ผ ๋ค๋ฆฝ๋๋ค. :)
ํซ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ ์ฉํ๋ ๊ฒ์ด ๊ทธ๋ ๊ฒ ์์ธกํ ์์๊ณ ์์ ํ ์์๋ ํ ๊ณณ์ด ์์ต๋๋ค. thing
๋๋ domain
๋ฐ์ด๋๋ฆฌ ๋๋ ๋๋ ํ ๋ฆฌ ์ธ๋ฑ์ค, ์ฆ index.js
๋ด๋ณด๋ด๊ธฐ ๋์ผํ ๋๋ ํ ๋ฆฌ์์๋ Component.js
์ "๊ณต์ฉ API"(Facebook ์คํ์ผ afaik ์๋).
์ฆ, Metro์์ํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋ชจ๋ ๊ฒ์ด์ง๋ง ๋ ๋ง์ ์ ํ์ด ์ ์ฉ๋ฉ๋๋ค. ์ง์ฐ๋ก๋ ๋ ๊ตฌ์ฑ ์์์ ๋ํด ์ด๋ฌํ ๊ฒฝ๊ณ๋ฅผ ์ค์ ํ๋ linting ๊ท์น๊ณผ ๊ฐ์ ๋ค๋ฅธ ๋ชจ๋ ๊ฒ์ ์ฌ๋ฐ๋ฅธ ๋์์ ์ ์ฉํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
๋งํ๋ฉด ํซ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ด Lazy๋ฅผ ์ฒ๋ฆฌํ ๊น์? import
์ ๋ค๋ฅธ ์ชฝ์์ ๊ฒฝ๊ณ๊ฐ์์ ๊ฒ์ผ๋ก ์์๋ฉ๋๊น?
๋ธ๋ผ์ฐ์ ์์ ๋ง๋ฒ์ ํ์ธํ๊ธฐ ์ํด ์ฌ๋นจ๋ฆฌ ์๋ํด ๋ณด์์ต๋๋ค. ์ ๋ง ๋ฉ์ง๋๋ค. :) ๊ฐ๋ฅํ ๊ฐ์ฅ ๊ฐ๋จํ ์ผ์ํ์ต๋๋ค. ์ฆ, ๋ชจ๋ ๊ณ์ธก ์ฝ๋๋ฅผ ํ๋ ์ฝ๋ฉ ํ์ผ๋ฏ๋ก ์์ง ์นํฉ ํ๋ฌ๊ทธ์ธ์ด ์์ต๋๋ค.
์ฌ๊ธฐ Repo : https://github.com/pekala/react-refresh-test
ํธ๊ธฐ์ฌ์ด ๋ง์ง๋ง webpack์ ๊ฒฝ์ฐ try / finally๋ฅผ ๋ํํ๋ babel ํ๋ฌ๊ทธ์ธ์ ๊ฐ์ง ์ ์์ต๋๊น? ์๋ํ๊ธฐ ์ ์ ๋ด๊ฐ ๋ญ๊ฐ๋ฅผ ๋์น๊ณ ์์ง ์์์ง ํ์ธํ๊ณ ์ถ์๋ฟ์ ๋๋ค.
Babel ํ๋ฌ๊ทธ์ธ์ ํน์ ํ๊ฒฝ์ด ์๋๋๋ค. ๋๋ ๊ทธ๊ฒ์ ์ ์งํ๊ณ ์ถ๋ค. ๋ชจ๋์ด๋ ์ ๋ฐ์ดํธ ์ ํ ๋ฉ์ปค๋์ฆ์ ๋ํด ์ ํ ์์ง ๋ชปํฉ๋๋ค. ๋ฒ ๋ค๋ฌ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค.
์๋ฅผ ๋ค์ด Metro์๋ try / finally ๋ํ ๋ณํ์ด ์ ํ ์์ต๋๋ค. ๋์ ๋ชจ๋ ํฉํ ๋ฆฌ๋ฅผ ํธ์ถํ๋ ๋ฒ ๋ค๋ฌ ๋ฐํ์ ์์ฒด์ try / finally๋ฅผ ๋ฃ์์ต๋๋ค. ๊ทธ๊ฒ์ webpack์์๋ ์ด์์ ์ด์ง๋ง ๊ทธ๋ ๊ฒ ๋ฐํ์์ ์ฐ๊ฒฐํ ์ ์๋์ง ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
๋ฌผ๋ก ๋ํ์ ์ํด ๋ค๋ฅธ Babel ํ๋ฌ๊ทธ์ธ์ ๋ง๋ค ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ทธ๊ฒ์ webpack์ ํตํด ๊ทธ๋ ๊ฒํ๋ ๊ฒ๋ณด๋ค ์๋ฌด๊ฒ๋ ์ฌ์ง ์์ต๋๋ค. ์ด์จ๋ webpack ํน์ ์ด๊ธฐ ๋๋ฌธ์. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ด ์๋ฏธ๊ฐ์๋ ๋ค๋ฅธ ํ๊ฒฝ (์นํฉ์ด ์๋)์์ ์ค์๋ก ๊ทธ Babel ํ๋ฌ๊ทธ์ธ์ ์คํํ ์ ์๋ค๋ ๊ฒ์ ํผ๋ ์ค๋ฌ์ธ ์ ์์ต๋๋ค.
compilation.mainTemplate.hooks.require
ํญํฌ ํํฌ์ ์ฐ๊ฒฐํ๋ฉด๋ฉ๋๋ค. ์ด์ ํธ์ถ์ __webpack_require__
ํจ์์ ๊ธฐ๋ณธ ๋ณธ๋ฌธ์ด๋ฏ๋ก ํํฌ๋ฅผ ํญํ์ฌ ์ฝํ
์ธ ๋ฅผ try/finally
๋ธ๋ก์ผ๋ก ๋ํ ํ ์ ์์ต๋๋ค.
๋ฌธ์ ๋ __webpack_require__
๋ด๋ถ์์ React์ ๋ํ ์ฐธ์กฐ๋ฅผ ์ป๋ ๊ฒ์
๋๋ค. ๊ฐ๋ฅํ์ง๋ง ์ด๋ ์ ๋์ ์ฌ์ง์
๋ฐ ์ฌ๊ท ๋ณดํธ๊ฐ ํ์ํ ์ ์์ต๋๋ค.
์์ธํ ๋ด์ฉ์ ์นํฉ ์์ค ์ฝ๋์์ MainTemplate.js
๋ฐ web/JsonpMainTemplatePlugin.js
๋ฅผ ํ์ธํ์ธ์. JsonpMainTemplatePlugin
์์ฒด๋ MainTemplate.js
์์ ์ฌ๋ฌ ๊ฐ์ ํํฌ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์๋ง๋ ์ด๊ฒ์ด ๋น์ ์ด ํด๊ฒฐํด์ผ ํ "๊ณ ๊ธฐ"์ผ ๊ฒ์
๋๋ค.
์ฌ๊ธฐ Dan์ด ์์์ ์ค๋ช ํ ๊ฒ์ ํจ๊ณผ์ ์ผ๋ก ์ํํ๋ ํดํน ๋ ํ๋กํ ํ์ ์ด ์์ต๋๋ค. ๋งค์ฐ ๋ถ์์ ํ์ง๋ง ์นํฉ์์ lo-fi ๊ตฌํ์ ์ฆ๋ช ํฉ๋๋ค : https://gist.github.com/maisano/441a4bc6b2954205803d68deac04a716
๋ช ๊ฐ์ง ์ฐธ๊ณ ์ฌํญ :
react-dom
์ ์ฌ๊ธฐ์ ํ๋ ์ฝ๋ฉ๋์ด ์์ผ๋ฏ๋ก ์ฌ์ฉ์ ์ ์ ๋ ๋๋ฌ ๋๋ ํ์ ํจํค์ง (์ : react-dom/profiling
)์์๋ ์๋ํ์ง ์์ต๋๋ค.umd
๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ด ์์ ๊ฐ ์๋ํ๋์ง ํ์คํ์ง ์์ต๋๋ค.๋ฌธ์ ๋ __webpack_require__ ๋ด๋ถ์์ React์ ๋ํ ์ฐธ์กฐ๋ฅผ ์ป๋ ๊ฒ์ ๋๋ค. ๊ฐ๋ฅํ์ง๋ง ์ด๋ ์ ๋์ ์ฌ์ง์ ๋ฐ ์ฌ๊ท ๋ณดํธ๊ฐ ํ์ํ ์ ์์ต๋๋ค.
Refresh Runtime์ ๋ํ ์ฐธ์กฐ๋ฅผ ์ป๋ ๊ฒ์ ์๋ฏธํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
Metro์์๋ ๊ฐ๋ฅํ ํ ๋นจ๋ฆฌ require.Refresh = RefreshRuntime
๋ฅผ ์ํํ์ฌ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. ๊ทธ๋ฐ ๋ค์ require
๊ตฌํ ๋ด์์ require
ํจ์ ์์ฒด์์ ์์ฑ์ ์ฝ์ ์ ์์ต๋๋ค. ์ฆ์ ์ฌ์ฉํ ์๋ ์์ง๋ง ์ถฉ๋ถํ ์ผ์ฐ ์ค์ ํด๋ ๋ฌธ์ ๊ฐ๋์ง ์์ต๋๋ค.
@maisano ์ฌ๋ฌ ๊ฐ์ง๋ฅผ ๋ณ๊ฒฝํด์ผํ๋๋ฐ ๊ถ๊ทน์ ์ผ๋ก webpack์์ ํธ์ถํ๋ .accept ํจ์๊ฐ ํ์๋์ง ์์ต๋๋ค. .accept(module.i, () => {})
๋ฐ .accept(() => {})
๋ ๋ค ์๋ํ์ต๋๋ค (์นํฉ์์ ์๋ํ์ง ์๋ ์ ์ ์ ์ธํ๊ณ ๋ ์์ฒด ์น์ธ). hot
์์ฑ์ด ํ์ฑํ๋์ด ์์ผ๋ฉฐ ํ์ฉ ๋ ๋ชจ๋์ ํตํด ์คํ๋ฉ๋๋ค.
๊ทธ๋์ ๋๋ ๊ฒฐ๊ตญ ์์ฒด ์์ฉ ๋ชจ๋์ ํธ์ถํ๊ธฐ ์ํด webpack์ ํจ์นํ๊ณ ์ด๊ฒ์ด ์ต์ข ์์ ์ด์์ต๋๋ค.
ํจ์น๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
diff --git a/node_modules/webpack/lib/HotModuleReplacement.runtime.js b/node_modules/webpack/lib/HotModuleReplacement.runtime.js
index 5756623..7e0c681 100644
--- a/node_modules/webpack/lib/HotModuleReplacement.runtime.js
+++ b/node_modules/webpack/lib/HotModuleReplacement.runtime.js
@@ -301,7 +301,10 @@ module.exports = function() {
var moduleId = queueItem.id;
var chain = queueItem.chain;
module = installedModules[moduleId];
- if (!module || module.hot._selfAccepted) continue;
+ if (!module || module.hot._selfAccepted) {
+ module && module.hot._selfAccepted()
+ continue;
+ }
if (module.hot._selfDeclined) {
return {
type: "self-declined",
์ ๋ ์ด๊ฒ์ด "errorCallback"์ด๋๊ธฐ๋ฅผ ์ํ๋ ๊ทธ๋ค์ API์ ์๋ฐฐ๋๋ค๋ ๊ฒ์ ์๊ณ ์์ต๋๋ค. ์ ๋ ํนํ ์๋ ์ ์ ๋ด๋ถ HMR์์ ์์ ํ๋ฉด์์ด ๋ฌธ์ ๋ฅผ ๊ฒช์๋ ๊ฒ์ ๊ธฐ์ตํฉ๋๋ค. ๊ฒฐ๊ตญ ์ฐ๋ฆฌ๋ ์์ฒด ๋ฒ ๋ค๋ฌ๋ฅผ ์์ฑํ๊ฒ๋์์ต๋๋ค. ๋๋ parcel์ด "self-accepting"์ฝ๋ฐฑ API๋ฅผ ์ง์ํ๋ค๊ณ ๋ฏฟ์ต๋๋ค. ์๋ง๋ ์นํฉ์์ ์ด์๋ฅผ ์ด์ด์ ๋ณํฉ ํ ์ ์๋์ง ์ดํด๋ณผ ๊ฐ์น๊ฐ ์์๊น์? @sokra
๊ทธ๋์ ... @ maisano ์ ์์
์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฌ๊ทธ์ธ์ ๋์ฑ
https://github.com/pmmmwh/react-refresh-webpack-plugin
(์์ํ ๋ ์นํฉ ๋ด๋ถ๋ฅผ ์กฐ์ํ๋ ๊ฒ์ ๋ฏฟ์ง ์๊ธฐ ๋๋ฌธ์ TypeScript๋ก ์์ฑํ์ต๋๋ค. ์ผ๋ฐ JS / Flow๋ก ๋ณํ ํ ์ ์์ต๋๋ค.)
์นํฉ Dependency
ํด๋์ค๋ก ํซ ๋ชจ๋ ์ฝ๋๋ฅผ ์ฝ์
ํ๊ธฐ์ํ ๋ก๋์ ํ์์ฑ์ ์ ๊ฑฐํ๋ ค๊ณ ์๋ํ์ง๋ง ๋ชจ๋ ๋ชจ๋์ ๋ค์ ๊ตฌ๋ฌธ ๋ถ์ํด์ผํ๋ ๊ฒ ๊ฐ์ต๋๋ค (๋ชจ๋ ํจ์๊ฐ ์ธ๋ผ์ธ๋์ด ์์ด๋ ์ฌ์ ํ ์ด๋๊ฐ์ react-refresh/runtime
์ฐธ์กฐ).
๋ ๋ค๋ฅธ ๋ฌธ์ ๋ webpack์์ ์๋ฐ ์คํฌ๋ฆฝํธ์ ์ ์ฌํ ํ์ผ์ ๊ฐ์งํ๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ (afaik)์ด ์๋ค๋ ๊ฒ์
๋๋ค. ์๋ฅผ ๋ค์ด html-webpack-plugin
๋ javascript/auto
์ ํ๋ ์ฌ์ฉํ๋ฏ๋ก ๋ณด์ด๋ ๊ฒ์ ํ๋ ์ฝ๋ฉํ์ต๋๋ค. ๋ก๋ ์ฃผ์
์ ํ์ฉ๋๋ ํ์ผ ๋ง์คํฌ (JS / TS / Flow)
๋ํ์ด 5 ๋
๋ ์ค๋ ๋ ์์ @gaearon์ ์ฃผ์์ ๊ธฐ๋ฐ์ผ๋ก ์ค๋ฅ ๋ณต๊ตฌ (์ต์ํ ๊ตฌ๋ฌธ ์ค๋ฅ)๋ฅผ ์ถ๊ฐํ์ต๋๋ค. ๋ค์์ ๋ฐ์ ์ค๋ฅ์์ ๋ณต๊ตฌํ๋ ๊ฒ์
๋๋ค. ์ ์ญ ์ค๋ฅ ๊ฒฝ๊ณ ( AppWrapper
of react-hot-loader
์ ๊ฐ์ ์ข
๋ฅ)๋ฅผ ์ฝ์
ํ์ฌ์ด ์์
์ ์ํ ํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.์ด ์์
์ ์ค๋ฅ ์์ ์ธํฐํ์ด์ค๋ ๋ค๋ฃจ์ง ๋ง ๊ทธ๋ ์ง ์์ต๋๋ค. ์์ง๊น์ง๋ ๊ทธ๊ฒ์ ๋๋ฌ ํ ์๊ฐ์ด ์์ต๋๋ค.
@natew ์ ์ํด ์ ๊ธฐ ๋ ๋ฌธ์ ๋ ํผํ ์ ์์ต๋๋ค. enqueueUpdate
ํธ์ถ๊ณผ hot.accpet(errorHandler)
ํธ์ถ์ ๋ถ๋ฆฌํจ์ผ๋ก์จ ๋ฌ์ฑ๋ฉ๋๋ค.
@pmmmwh ๋ฌด์จ ํ์ด๋ฐ! ์์ ์์ ๊ณต์ ํ ์์ ์ ์ผ๋ถ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก / ์กฐ์ ํ ์ ์ฅ์ ๋ฅผ ๋ฐฉ๊ธ ๋ง๋ค์์ต๋๋ค.
์ฌ๊ธฐ์ ํ๋ฌ๊ทธ์ธ์ ๋ด๊ฐ ์ทจํ ์ด๊ธฐ ์ ๊ทผ ๋ฐฉ์๋ณด๋ค ์ฝ๊ฐ ๋ ๊ฒฌ๊ณ ํ์ง๋ง ์ด๋ค ๊ฒฝ์ฐ์๋ ์ค๋ฅ ์ฒ๋ฆฌ์ ์ต์ํ์ง ์์์ต๋๋ค.
๋ค์์ ๋ฐ์ ์ค๋ฅ์์ ๋ณต๊ตฌํ๋ ๊ฒ์ ๋๋ค. ์ ์ญ ์ค๋ฅ ๊ฒฝ๊ณ (react-hot-loader์ AppWrapper์ ๋น์ท ํจ)๋ฅผ ์ฝ์ ํ์ฌ์ด ์์ ์ ์ํ ํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.์ด ์์ ์ ์ค๋ฅ ์์ ์ธํฐํ์ด์ค๋ ๋ค๋ฃจ์ง ๋ง ๋๋ฌ ํ ์๊ฐ์ด ์์์ต๋๋ค. ์์ง์.
๊ทธ๊ฒ์ ์ด๋ฏธ ์์์์ ์๋ํฉ๋๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉ์ ์ ์ ์ค๋ฅ ๊ฒฝ๊ณ ๋๋ ๋ํ์ด ํ์ํ์ง ์์ต๋๋ค.
๋ค์์ ๋ฐ์ ์ค๋ฅ์์ ๋ณต๊ตฌํ๋ ๊ฒ์ ๋๋ค. ์ ์ญ ์ค๋ฅ ๊ฒฝ๊ณ (react-hot-loader์ AppWrapper์ ๋น์ท ํจ)๋ฅผ ์ฝ์ ํ์ฌ์ด ์์ ์ ์ํ ํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.์ด ์์ ์ ์ค๋ฅ ์์ ์ธํฐํ์ด์ค๋ ๋ค๋ฃจ์ง ๋ง ๋๋ฌ ํ ์๊ฐ์ด ์์์ต๋๋ค. ์์ง์.
๊ทธ๊ฒ์ ์ด๋ฏธ ์์์์ ์๋ํฉ๋๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉ์ ์ ์ ์ค๋ฅ ๊ฒฝ๊ณ ๋๋ ๋ํ์ด ํ์ํ์ง ์์ต๋๋ค.
@gaearon ์ด์ํ. ๋ ๋๋ง ํจ์ ๊ตฌ์ฑ ์์์์ ์ค๋ฅ๋ฅผ ๋์ก์ต๋๋ค. return
์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด HMR์ด ์๋ํ์ง๋ง ๋ค๋ฅธ ๊ณณ์์ ๋ฐ์ํ๋ฉด ๋๋๋ก ์๋ํ์ง ์์ต๋๋ค.
@pmmmwh ๋ฌด์จ ํ์ด๋ฐ! ์์ ์์ ๊ณต์ ํ ์์ ์ ์ผ๋ถ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก / ์กฐ์ ํ ์ ์ฅ์ ๋ฅผ ๋ฐฉ๊ธ ๋ง๋ค์์ต๋๋ค.
์ฌ๊ธฐ์ ํ๋ฌ๊ทธ์ธ์ ๋ด๊ฐ ์ทจํ ์ด๊ธฐ ์ ๊ทผ ๋ฐฉ์๋ณด๋ค ์ฝ๊ฐ ๋ ๊ฒฌ๊ณ ํ์ง๋ง ์ด๋ค ๊ฒฝ์ฐ์๋ ์ค๋ฅ ์ฒ๋ฆฌ์ ์ต์ํ์ง ์์์ต๋๋ค.
@maisano ๋ฌด์จ ๋ง์ํด์ผํฉ๋๊น? ๋๋ ์ค์ ๋ก ์ด๊ฒ์ ๋ํด ์ผํ๊ธฐ ์์ํ๊ณ ์ง๋ ์ฃผ๋ง์ ์์กด์ฑ ์ฃผ์ ๋ฌธ์ ์ ๊ฐํ์ต๋๋ค ... ๋น์ ์ ์์ง๋ ๋์๊ฒ ํ์ถ๊ตฌ๋ฅผ ์ ๊ณตํ์ต๋๋ค : tada :
๊ทธ ๋๊ฐ๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด HMR์ ์๋ํ์ง๋ง ๋ค๋ฅธ ๊ณณ์์ ๋ฐ์ํ๋ฉด ์๋ํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
์ ํํ ๋ฌด์์ ์๋ํ๋์ง, "์๋ ํจ"๊ณผ "์๋ํ์ง ์์"์ด ์๋ฏธํ๋ ๋ฐ์ ๋ํ ์์ธํ ๋ด์ฉ์ด ํ์ํฉ๋๋ค.
๋ชจ๋ ๋ฒ ๋ค๋ฌ ํตํฉ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ง ์์ ๊ฒฝ์ฐ (์ฃผ์ ๋๋์ด ์ค๋ ๋) ์๋ชป ๋ ์์๋ ๋ง์ ๊ฒ๋ค์ด ์์ต๋๋ค. ํธ์ง ์ค์ ๋ฐ์ํ ์ค๋ฅ๋ก๋ถํฐ ๋ณต๊ตฌ๋ฅผ ๋ง๋ React ์์ฒด์๋ ์๋ฌด๊ฒ๋ ์์ ๊ฒ์ผ๋ก ์์๋ฉ๋๋ค. React Native 0.61 RC3์์ ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
@pmmmwh , @maisano ๋ค์ ๊ฒ์ฌ๋ ๋ช ๋ช ๋ ๋ด๋ณด๋ด๊ธฐ๋ก ๊ตฌ์ฑ ์์๊ฐ์๋ ๋ชจ๋์ ๊ฑด๋ ๋ฐ๊ณ ์๋ก ๊ณ ์นจ ๊ฒฝ๊ณ๊ฐ ์ค์ ๋์ง ์์ต๋๋ค.
const desc = Object.getOwnPropertyDescriptor(moduleExports, key);
if (desc && desc.get) {
// Don't invoke getters as they may have side effects.
return false;
}
Metro
์์ ์ ์ด๊ฒ์ด ํ์ํ์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง webpack
getters์์๋ ๋ช
๋ช
๋ ๋ด๋ณด๋ด๊ธฐ ๋ง ๋ฐํํ๊ณ ๋ถ์์ฉ์ด ์๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ ๊ฑฐํ๋ ๊ฒ์ด ์์ ํด์ผํฉ๋๋ค.
@gaearon React.lazy
๊ตฌ์ฑ ์์ (์ : ์ฝ๋ ๋ถํ ๊ฒฝ๋ก)๊ฐ ๋ค์ ๋ ๋๋ง๋์ง ์์ต๋๋ค. ๊ทธ๊ฒ์ ์๋์ ์ธ ๊ฒ์
๋๊น? ์๋ก ๊ณ ์นจ ๊ฒฝ๊ณ๊ฐ ์ค์ ๋์์ง๋ง performReactRefresh()
๊ฐ ์๋ฌด ์์
๋ ์ํํ์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๊ฒ์ผ๋ฅธ ์์ด๋ค์ ๋ํ ๋ณ๊ฒฝ ์ฌํญ์ ์ ์์ ์ผ๋ก ์๋ก ๊ณ ์นจ๋๋ฏ๋ก ํฐ ๋ฌธ์ ๋ ์๋์ง๋ง ์ฐ๋ฆฌ๊ฐ ๋ญ๊ฐ ์๋ชปํ๊ณ ์๋์ง ๊ถ๊ธํฉ๋๋ค ...
lazy
๋ ์์ ์ํ ๋จธ์ ์
๋๋ค. ์ด์ ๊ตฌ์ฑ ์์์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ณด์ ํ๊ณ ์์ผ๋ฉฐ ํด๋น ์ฐธ์กฐ๋ฅผ ์
๋ฐ์ดํธํด์ผํฉ๋๋ค.
์ด์ ๊ทธ๊ฒ์ด ์๋ค๊ณ ์์ํด ๋ด
์๋ค. ๊ทธ๋ฆฌ๊ณ ์ง๊ธ์ ์๋ก์ด lazy
๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ๊ณ ์์ต๋๋ค)- loading
๋จ๊ณ๋ฅผ ๋ค์ ์๊ฐํด์ผ ํ ๊ฒ์ด๊ณ , ๊ทธ๊ฒ์ ์๋ง๋ ๋ชจ๋ ์ค์ฒฉ ๋ ํธ๋ฆฌ๋ฅผ ํ๊ดด ํ ๊ฒ์
๋๋ค.
๊ฒ์ผ๋ฅธ ์ผ์ ๊ธฐ๋ํฉ๋๋ค. ๋ญ๊ฐ ๊ณ ์ฅ ๋ฌ์ ์๋ ์์ต๋๋ค. ์ฌํ ์ฌ๋ก๋ฅผ ํ์ธํด์ผํฉ๋๋ค.
ํ๋กํ ํ์ ์ด ๋ช ๊ฐ ์์๋๋ฐ ํ๋๋ฅผ ์ ํํ ๋ค์์ด ํ ๋ก ์ ๋ฌธ์ ๋ก ์ฎ๊ฒจ์ผํ ๊น์? ๊ทธ๋ฆฌ๊ณ ๊ฑฐ๊ธฐ์์ ๋ฐ๋ณตํ์ญ์์ค.
๋ค์์ด ์์ต๋๋ค.
https://github.com/maisano/react-refresh-plugin
๊ณผ:
https://github.com/pmmmwh/react-refresh-webpack-plugin
[email protected]
์ ํจ๊ป ์๋ํ๋ pmmmwh ํ๋ฌ๊ทธ์ธ ํฌํฌ๋ฅผ ์ค์ ํ์ต๋๋ค (๋ด๋ณด๋ด๊ธฐ ์ด๋ฆ ์์ ๋ ํฌํจ).
https://github.com/WebHotelier/webpack-fast-refresh
react-hot-loader
์ด๋ป์ต๋๊น?
react-hot-loader
fast refresh
์์ ๊ฑฐ์ ๋ชจ๋ ๊ธฐ๋ฅ์ ๋ฐฑ ํฌํธํ์ง๋ง ๋ชจ๋ ๊ธฐ๋ฅ์ ๋ฐฑ ํฌํธ ํ ์์๋ ์ญ์ฌ์ ๋ฐ ํตํฉ์ ์๊ฐ์ด ๊ฑฐ์ ์์ผ๋ฉฐ ์์งํ "rhl"์ฉ์ด๋ก ๋ค์ ๊ตฌํํ ์๋ฏธ๊ฐ ์์ต๋๋ค. . ๊ทธ๋ฌ๋ ์ํดํ์.
๋ค๋ฅธ ํ๋ : https://github.com/yiminghe/react-refresh-loader
์ ํํ ๋ฌด์์ ์๋ํ๋์ง, "์๋ ํจ"๊ณผ "์๋ํ์ง ์์"์ด ์๋ฏธํ๋ ๋ฐ์ ๋ํ ์์ธํ ๋ด์ฉ์ด ํ์ํฉ๋๋ค.
๋ชจ๋ ๋ฒ ๋ค๋ฌ ํตํฉ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์ง ์์ ๊ฒฝ์ฐ (์ฃผ์ ๋๋์ด ์ค๋ ๋) ์๋ชป ๋ ์์๋ ๋ง์ ๊ฒ๋ค์ด ์์ต๋๋ค. ํธ์ง ์ค์ ๋ฐ์ํ ์ค๋ฅ๋ก๋ถํฐ ๋ณต๊ตฌ๋ฅผ ๋ง๋ React ์์ฒด์๋ ์๋ฌด๊ฒ๋ ์์ ๊ฒ์ผ๋ก ์์๋ฉ๋๋ค. React Native 0.61 RC3์์ ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
๋ช ๊ฐ์ง ์กฐ์ ํ ์๋ํ๋์ง ํ์ธํ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋-babel ํ๋ฌ๊ทธ์ธ์ด ์์
์์ ์๋ํ์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋ชจ๋ ์ฝ์
๋ ์ฝ๋์ react-refresh/runtime
๊ฐ ์ ๋๋ก ์๋ํ๊ธฐ ๋๋ฌธ์ ํ์ธํ ๊ฒฐ๊ณผ ๊ตฌํ์ ๊ด๊ณ์์ด ๋ฐ์ํ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ด๊ฒ์ด ์๋ ๋ ๊ฒ์ธ์ง ์๋๋ฉด ์นํฉ์ ํน์ ํ ๊ฒ์ธ์ง ํ์คํ์ง ์์ต๋๋ค. ํ์๋ผ๋ฉด ๋ด์ผ ์์ ์ ์๋ ํ ์ ์์ต๋๋ค. (๋๋ ๋ํ ๋ฉํธ๋ก ํ๋ฆฌ์
์ผ๋ก ์ด๊ฒ์ ํ
์คํธํ์ผ๋ฉฐ ์ฌ๊ธฐ์์ ์์ ์ ์ฌํ
๋๋ ๊ทธ๊ฒ์ด RN์์ ์๋ํ๋ค๊ณ ํ์ ํ์ง๋ง ํ์ฌ์ ์ปดํจํฐ์๋ RN์์ ํ ์คํธํ๊ธฐ์ ํธ๋ฆฌํ ํ๊ฒฝ์ด ์์ผ๋ฏ๋ก ๋ฉํธ๋ก์์ ๋ฐ๋ฒจ ํ๋ฌ๊ทธ์ธ ๊ตฌํ์ด๋ ์ ๋ง ๋์์ด ๋ ๋ณํ์ ๋ํด ์๋ ค์ค ์ ์๋ค๋ฉด.
ํ๋กํ ํ์ ์ด ๋ช ๊ฐ ์์๋๋ฐ ํ๋๋ฅผ ์ ํํ ๋ค์์ด ํ ๋ก ์ ๋ฌธ์ ๋ก ์ฎ๊ฒจ์ผํ ๊น์? ๊ทธ๋ฆฌ๊ณ ๊ฑฐ๊ธฐ์์ ๋ฐ๋ณตํ์ญ์์ค.
์ฌ๊ธฐ ๊ฐ ์ ์์๊น์? ๋ง์ง๋ง ๋๊ธ ์ดํ๋ก ์ ์ฒด ํ๋ก์ ํธ๋ฅผ ์ผ๋ฐ JS๋ก ์ด์ํ๊ณ ์ ๋ฐ์ดํธ ๋๊ธฐ์ด์ ๋ํ ๋ช ๊ฐ์ง ์์ ์ฌํญ์ ์ถ๊ฐํ์ต๋๋ค. ๋๋์ ๋ํ ํ๋ฌ๊ทธ์ธ ์ด์์ ๋์ฐฉํ์ง ์์ 5 @ ์นํฉ์ ํ์ง๋ง, @apostolos์ผ๋ก ํฌํฌ์์ ์๋ก์ด HMR ๋ก์ง ์ฝ์ ํ ์นํฉ @ ๋ค์์ , ์์ ์ ์์งํด์ผํ๋ค.
์, Babel ํ๋ฌ๊ทธ์ธ์ ์์
์ ๋ฑ๋กํ์ง ์์ต๋๋ค. ์๋๋ ์ด๊ฒ์ด ๋ชจ๋ ์์คํ
์์ค์์ ๋ฐ์ํ๋ ๊ฒ์
๋๋ค. ๊ฐ ๋ด๋ณด๋ด๊ธฐ๋ React ์ปดํฌ๋ํธ๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ์๋์ง ํ์ธํด์ผํฉ๋๋ค. (๋ฐํ์์์ ํ์ธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.) ์ฐธ์ด๋ฉด Babel ํ๋ฌ๊ทธ์ธ์ฒ๋ผ ๋ด๋ณด๋ด๊ธฐ๋ฅผ ๋ฑ๋กํฉ๋๋ค. filename exports%export_name
์ ๊ฐ์ ID๋ฅผ ์ง์ ํฉ๋๋ค. ์ด๊ฒ์ด ๋ฐ๋ก Babel ํ๋ฌ๊ทธ์ธ์ด ํด๋์ค๋ฅผ ์ฐพ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ Metro์์ ํด๋์ค๊ฐ ์๋ํ๊ฒํ๋ ์ด์ ์
๋๋ค.
์ฆ, ์ด์จ๋ ํด๋์ค ์ํ๋ฅผ ๋ณด์กด ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ณํ์ ์ฌ์ฉํ์ฌ ์์ค ์ฝ๋์์ ์ธ๋ผ์ธ์ผ๋ก ์ฐพ๋ ๋์ ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ ๊ฒฝ๊ณ์ "์ฐพ๊ธฐ"๋ฅผ ๋งก๊ธฐ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋ด๋ณด๋ด๊ธฐ๋ ํ๋ฌ๊ทธ์ธ์ผ๋ก ์ฐพ์ง ๋ชปํ ๊ตฌ์ฑ ์์์ ๋ํ "๋ชจ๋ ์ก๊ธฐ"์ญํ ์ํด์ผํฉ๋๋ค.
Mailchimp๋ ๋ง์ง๋ง์ผ๋ก ๊ณต์ ํ ํ๋ฌ๊ทธ์ธ์ ํฌํฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์์ํ์ต๋๋ค. ๊ทธ๊ฒ์ ์กฐ๊ธ ๋ ๊ตฌ์ฒดํ๋์๊ณ ๊ทธ๊ฒ์ ์ฌ์ฉํ๊ธฐ๋ก ์ ํํ ์ฌ๋๋ค์ ๊ฝค ํ๋ณตํด ๋ณด์ ๋๋ค. ์ฐ๋ฆฌ๋ ๊ณ์ํด์ ๋ก์ปฌ์์ ๋ฐ๋ณต ํ ๊ฒ์ ๋๋ค. ํฌํฌ๋ฅผ ์ ๊ฑฐํ๊ณ ์กฐ๊ธ ๋ ์งํ๋๋ฉด ์ ์คํธ๋ฆผ ์ ๋ฐ์ดํธ๋ฅผ ๊ฒ์ ํ ๊ณํ์ ๋๋ค.
@gaearon ์ฌ๋ณผ์ ์ถ๊ฐํ๋
export default create({
id: '100'
})
export const View = () => <div />
create
๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํฉ๋๋ค. ์ง๊ธ์ ํจ์น๋ฅผ ์ ์ฉํ์ง๋ง ๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ ๊ฐ์ฒด์ ์์ ํ ํ์ผ์์ ๋ํ๋ด๋ ๊ธฐํธ๋ฅผ ์ฝ๊ฒ ์ถ๊ฐ ํ ์ ์์ต๋๋ค. ์ต๊ณ ์ ํจํด์ด ์ ํํ ํ์คํ์ง ์์ต๋๋ค.
ํธ์ง : ์ด๊ฒ์ด ์๋ก ๊ณ ์นจ ๊ตฌํ์ ๋ค์ด๊ฐ ์ ์๋ค๋ ๊ฒ์ ๊นจ๋ฌ์์ต๋๋ค! ๋๋ ๊ทธ๊ฒ์ด ๋ฐํ์์ ๋ ์ข์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง๋ง ์๋ง๋ ๊ทธ๋ ์ง ์์ ๊ฒ์ ๋๋ค. ๋ก๋์ ๋ค์ํ impls๊ฐ ๋๋ฌด ๋ง์ผ๋ฉด ํ์ค ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์ ์ ์์ต๋๋ค.
10 ๋ ์ ์์ผ๋ก ๊ฐ์๋ค. ์ฝ๋๋ฒ ์ด์ค๋ ์ด๋ป๊ฒ ์๊ฒผ์ต๋๊น? ์ฌ๊ธฐ์์ ํ์ฉํ๊ณ ๊ฑฐ๊ธฐ์์ ํ์ฉํ์ง ์์ต๋๊น? ์ด ํ๋๊ทธ๋ฅผ ์ต์ ์ํ๋ก ์ ์งํ๋ ๋ฐฉ๋ฒ์ ๋ฌด์์ ๋๊น? ์ถ๋ก ํ๋ ๋ฐฉ๋ฒ? _ ์ ๋ฐ์ดํธํ๊ธฐ์ ์์ ํ _ ์์น์ _unsafe_๊ฐ์๋ ๊ฒ์ฒ๋ผ, ๋ณด์กดํด์ผํ๊ฑฐ๋ ์ด๋ค ์ด์ ๋ก ๋ ์ ์ ํ๊ฒ ์กฐ์ ํ ์ ์์ต๋๋ค. ๊ฐ ๊ฒฝ์ฐ์ ์ ํจํ ์ด์ ๋ ๋ฌด์์ ๋๊น?
symbols
๋ ๊ฐ์ง ๊ฒ-์ฝ force allow
reload ๋๋ force disallow
reload์๋ ํ์ธ์ ์ฌ๋ฌ๋ถ ๐ ์ ๋ ์ฌ๊ธฐ์ ๋์์ ๋๋ฆฌ๊ณ ์ถ์ต๋๋ค. ๋จ์ผ ๋ฆฌํฌ / ๋ ธ๋ ฅ์ ๋์ ํ์ต๋๊น?
@pmmmwh๊ฐ ๊ณต์ ํ๋ ์ ์ฅ์์
๋๊น?
https://github.com/pmmmwh/react-refresh-webpack-plugin
์๋๋ฉด @maisano๊ฐ ๊ณต์ ํ๋ ์ ์ฅ์์
๋๊น?
https://github.com/maisano/react-refresh-plugin
@pmmmwh ๊ฐ ๋ ์ต๊ทผ์ ์ฝ์ ํ ๊ฒ
Parcel 2์ ๊ตฌํ์ ์ฌ๊ธฐ์์ ์์๋์์ต๋๋ค : https://github.com/parcel-bundler/parcel/pull/3654
์ฌ๋ฆ!
๊ทธ๊ฒ์ ์ฐพ๋ ๋ชจ๋ ์ฌ๋์ ์ํด ๊ฐ๋ฐ์ ์ํด Nollup์ ์ฌ์ฉํ๋ Rollup ํ๋ก์ ํธ ์ฉ React Refresh ๊ตฌํ : https://github.com/PepsRyuu/rollup-plugin-react-refresh
์๋ง๋ ๊ฐ์ฅ ๊นจ๋ํ ๊ตฌํ์ ์๋์ง๋ง ์๋ํฉ๋๋ค.
์นํฉ ์๋ฃจ์ ์ ๊ฒฝ์ฐ ์ ํ๋ฌ๊ทธ์ธ์ ๊ณต์ ๋ฆด๋ฆฌ์ค๊ฐ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๋ฏ๋ก ๋ฐ์์์ํ ์ต๊ณ ์ HMR ์๋ฃจ์ ์ ์ฌ์ ํ โโDan์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธ ๊ฒ ๊ฐ์ต๋๋ค : https://github.com/gaearon/react-hot-loader
Fast Refresh๋ฅผ ์ง์ํ๋ Parcel 2 alpha 3๋ฅผ ์ฆ์ ๋ฐฐ์กํ์ต๋๋ค! ์์ ๋กญ๊ฒ ์ฌ์ฉํด๋ณด์ญ์์ค. ๐ https://twitter.com/devongovett/status/1197187388985860096?s=20
๐ฅณ RHL์ ์ง์ ์ค๋จ ๋ฉ๋ชจ ์ถ๊ฐ ๐ฅณ
@pmmmwh ์ ์งํ์ค์ธ ์์
, react-app-rewired
๋ฐ customize-cra
์ฌ์ฉํ์ฌ CRA ์ฑ ์์์ด ์์
์ ์๋ํ๋ ๋ฐ ์ฌ์ฉํ ๋ ์ํผ :
npx create-react-app <project_dir> --typescript
npm install -D react-app-rewired customize-cra react-refresh babel-loader https://github.com/pmmmwh/react-refresh-webpack-plugin
./package.json
:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
./config-overrides.js
ํ์ผ ์ถ๊ฐ :
// eslint-disable-next-line
const { addBabelPlugin, addWebpackPlugin, override } = require('customize-cra');
// eslint-disable-next-line
const ReactRefreshPlugin = require('react-refresh-webpack-plugin');
/* config-overrides.js */
module.exports = override(
process.env.NODE_ENV === 'development'
? addBabelPlugin('react-refresh/babel')
: undefined,
process.env.NODE_ENV === 'development'
? addWebpackPlugin(new ReactRefreshPlugin())
: undefined,
);
์ง๊ธ๊น์ง ๊ฒฝํ์ ์ฆ๊ธฐ๊ณ ์์ต๋๋ค. ๊ด๋ จ๋ ๋ชจ๋ ์ฌ๋์ ๋ชจ๋ ์์ ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค!
๊ฐ์ฌํฉ๋๋ค @ drather19 !
๊ทํ์ ์ง์นจ์ ๋ฐ๋ผ ์ ์ฅ์๋ฅผ ๋ง๋ค์์ต๋๋ค.https://github.com/jihchi/react-app-rewired-react-refresh๋๊ตฐ๊ฐ๊ฐ ์๋ํด๋ณด๊ณ ํ์ดํ์ ์ ์ฅํ๊ณ ์ถ๋ค๋ฉด repo๋ฅผ ์์ ๋กญ๊ฒ ๋ณต์ ํ์ญ์์ค.
https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/cra-kitchen-sink ๋ฅผ
๊ทธ๋ฆฌ๊ณ ... v0.1.0
for Webpack์ด ๋ฐฉ๊ธ ๋ฐฐ์ก๋์์ต๋๋ค ๐
ํธ์ ๋ด์ ๊ฐ๊ธฐ
์ด ๋ฒ์ ์ผ๋ก ์ ํํ๊ณ ์ถ์ ์๋ ์์ต๋๋ค.์ด ๋ฒ์ ์๋ ์ด๊ธฐ ๊ตฌํ์ ๋ํ ๋ง์ ๋ฒ๊ทธ ์์ ๋ฟ๋ง ์๋๋ผ๋ณด๋ค ํตํฉ ๋ ๊ฒฝํ์ด ํฌํจ๋์ด ์์ต๋๋ค.
@pmmmwh ๋ ts-loader
+ babel-loader
ํฉ๋๊น?
@pmmmwh ๋
ts-loader
+babel-loader
ํฉ๋๊น?
๋๋ Babel๋ก๋ง TS์ ๋ํด ํ ์คํธํ๊ณ ์๋ํ๋ฏ๋ก ts + babel ๋ก๋๋ฅผ ์ฌ์ฉํ ๋ ์๋ํ์ง ์์ผ๋ฉด ๋ฌธ์ ๋ฅผ ์ ์ถํ์ญ์์ค. :)
@ drather19 ์ ์ฅ์ ๋ณต์ ๋ฐ ์คํ์ ์๋ํ์ง๋ง ๊ฐ๋ฐ ์๋ฒ๊ฐ ์์๋์ง ์์ต๋๋ค.
ํ๊ฒฝ,
OS-OSX 10.14.6
๋
ธ๋-v12.13.0
์์ฌ -1.19.2
@pmmmwh- ์ฐธ๊ณ ๋ก
react-app-rewired-react-refresh on ๎ master is ๐ฆ v0.1.0 via โฌข v12.13.0
โฏ yarn start
yarn run v1.19.2
$ react-app-rewired start | cat
โน ๏ฝขwds๏ฝฃ: Project is running at http://192.168.1.178/
โน ๏ฝขwds๏ฝฃ: webpack output is served from /
โน ๏ฝขwds๏ฝฃ: Content not from webpack is served from /Users/seanmatheson/Development/temp/react-app-rewired-react-refresh/public
โน ๏ฝขwds๏ฝฃ: 404s will fallback to /index.html
Starting the development server...
@ drather19 ์ ์ฅ์ ๋ณต์ ๋ฐ ์คํ์ ์๋ํ์ง๋ง ๊ฐ๋ฐ ์๋ฒ๊ฐ ์์๋์ง ์์ต๋๋ค.
ํ๊ฒฝ,
OS-OSX 10.14.6
๋ ธ๋-v12.13.0
์์ฌ -1.19.2@pmmmwh- ์ฐธ๊ณ ๋ก
react-app-rewired-react-refresh on ๎ master is ๐ฆ v0.1.0 via โฌข v12.13.0 โฏ yarn start yarn run v1.19.2 $ react-app-rewired start | cat โน ๏ฝขwds๏ฝฃ: Project is running at http://192.168.1.178/ โน ๏ฝขwds๏ฝฃ: webpack output is served from / โน ๏ฝขwds๏ฝฃ: Content not from webpack is served from /Users/seanmatheson/Development/temp/react-app-rewired-react-refresh/public โน ๏ฝขwds๏ฝฃ: 404s will fallback to /index.html Starting the development server...
์ด๋ ํ๋ฌ๊ทธ์ธ์ master
๋ธ๋์น์์ ์์ ๋์์ผ๋ฉฐ ๋ด์ผ ์ถ์ ๋ ์์ ์
๋๋ค.
๋๋ babel์ ์ฌ์ฉํ์ฌ TypeScript React ์ฑ๊ณผ ํจ๊ป ์๋ํ๋ @pmmmwh์ webpack ํ๋ฌ๊ทธ์ธ์ ์ป์ ์ต๋๋ค . ๊ทธ๋ฌ๋ ์ฆ๋ถ ๋น๋๋ ts-loader๋ง์ผ๋ก 2 ์ด๊ฐ ์๋๋ผ 12 ์ด ์ ๋ ๊ฑธ๋ฆฝ๋๋ค. ์ฑ๋ฅ์ ๋ ๊ฐ๊น๊ฒ ๋ง๋๋ ๋ฐ๋ฒจ ๊ตฌ์ฑ ์ธก๋ฉด์์ ๋ฌด์ธ๊ฐ๊ฐ ๋๋ฝ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ๊ณ์ํด์ ์ฌ์ํ ๊ฒ์ ๋๋ค.
@IronSean ํด๋น ํ๋ฌ๊ทธ์ธ์ ์ ์ฅ์์ ์ ๊ณ ํด์ฃผ์ธ์. ์ด๊ฒ์ ์ ์์ ์ด์ง ์์ต๋๋ค.
์ฑ๋ฅ์ ๋ ๊ฐ๊น๊ฒ ๋ง๋๋ ๋ฐ๋ฒจ ๊ตฌ์ฑ ์ธก๋ฉด์์ ๋ฌด์ธ๊ฐ๊ฐ ๋๋ฝ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ๊ณ์ํด์ ์ฌ์ํ ๊ฒ์ ๋๋ค.
๊ตฌ์ฑ / ์ค์ ์ ๊ฑฐ๊ธฐ์ ๊ฒ์ ํ์๊ฒ ์ต๋๊น? ๋ ๋ง์ ๋งฅ๋ฝ ์์ด๋ ๋ฌธ์ ๋ฅผ ํ์ ํ ์ ์์ต๋๋ค.
@pmmmwh ๋๋์ด ๋ฌธ์ ๋ฅผ ์ด์ด์ ์ค์ ๋ก ํ๋ฌ๊ทธ์ธ์ด ์ฐจ์ด๋ฅผ ๋ง๋๋ ๊ฒ์ ํ์ธํ ํ ํ ๋ก ์ ์ ์ฅ์๋ก ์ด๋ํ์ต๋๋ค.
https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/20
react-refresh
( React Fast Refresh
?)์ด (๊ฐ) Preact์ ํจ๊ป ์๋ํฉ๋๊น, ์๋๋ฉด react-hot-loader
๊ฐ Preact์ ์ฅ๊ธฐ ์๋ฃจ์
์
๋๊น?
@Jumblemuddle ์
๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ผ๋ก ์คํํ๋ ค๋ CRA ์ฌ๋๋ค์ ์ํด ๋ค์ craco.config.js
ํตํด craco (vs. react-app-rewired + customize-cra)๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋์ ํ์ด์ ์ป์์ต๋๋ค.
// eslint-disable-next-line
const { whenDev } = require('@craco/craco');
// eslint-disable-next-line
const ReactRefreshPlugin = require('react-refresh-webpack-plugin');
module.exports = {
webpack: {
configure: webpackConfig => {
if (process.env.NODE_ENV === 'development') {
webpackConfig.module.rules.push({
test: /BabelDetectComponent\.js/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
});
webpackConfig.module.rules.push({
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
presets: [
'@babel/react',
'@babel/typescript',
['@babel/env', { modules: false }],
],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator',
'react-refresh/babel',
],
},
},
],
});
}
return webpackConfig;
},
plugins: [
...whenDev(
() => [new ReactRefreshPlugin({ disableRefreshCheck: false })],
[],
),
],
},
};
ํนํ webpackConfig.optimization.runtimeChunk = false;
์ ์ถ๊ฐํ๋ฉด ํํฌ๋ฅผ ์ถ๊ฐ / ์ ๊ฑฐ ํ ์ ์์ผ๋ฉฐ ์ฌ์ ํ ์ฐ์ํ๊ณ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ด์ ๋์ฑ ํฅ์๋ ๊ฒฝํ์ ์ฆ๊ธฐ์ญ์์ค. https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/25 ๋ฅผ ํตํด ํ์ ์ฃผ์ @ mmhand123 ์๊ฒ ๊ฐ์ฌ๋๋ฆฝ๋๋ค ! (<-ํด๊ฒฐ๋์์ต๋๋ค!)
@ drather19์ ์ ์์ ๋ฐ๋ผ ๋ ์ฝ๊ฒ ๋ง๋ค๊ธฐ ์ํด customize-cra ํ๋ฌ๊ทธ์ธ์ ๊ฒ์ํ์ต๋๋ค. esetnik / customize-cra-react-refresh๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
@ drather19 ๋๋ถ์ ์ด์ ์ฝ๋๋ฅผ ์ฝ๊ฐ ์์ ํ์ฌ yarn ์์ ๊ณต๊ฐ monorepo ์ค์ ์์ ์๋ ํ ์ ์์ต๋๋ค.
๋จผ์ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ ์ฌ์ฉํ๋ ค๋ ํ์ ํจํค์ง์ ๋ค์์ ์ค์นํ์ญ์์ค.
"@craco/craco": "^5.6.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
"webpack-hot-middleware": "^2.25.0"
๊ทธ๋ฐ ๋ค์ craco.config.js
.
;(function ForbidCRAClearConsole() {
try {
require('react-dev-utils/clearConsole')
require.cache[require.resolve('react-dev-utils/clearConsole')].exports = () => {}
} catch (e) {}
})()
const { whenDev } = require('@craco/craco')
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
webpack: {
configure: webpackConfig => {
whenDev(() => {
// Work around monorepo setup when using yarn workspace hoisted packages
// without the need to list 'babel-loader' and 'babel-preset-react-app' as
// dependencies to avoid duplication since 'react-scripts' already has them.
const reactLoaderConfig = webpackConfig.module.rules
.find(x => Array.isArray(x.oneOf))
.oneOf.find(
x =>
x.options &&
x.options.presets &&
x.options.presets.some(p => p.includes('babel-preset-react-app')) &&
x.loader &&
typeof x.loader.includes === 'function' &&
x.loader.includes('babel-loader') &&
x.test &&
typeof x.test.test === 'function' &&
x.test.test('x.tsx') &&
x.test.test('x.jsx'),
)
if (reactLoaderConfig) {
webpackConfig.module.rules.push({
test: /BabelDetectComponent\.js/,
use: [
{
loader: reactLoaderConfig.loader,
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
})
webpackConfig.module.rules.push({
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: reactLoaderConfig.loader,
options: {
presets: reactLoaderConfig.options.presets,
plugins: [require.resolve('react-refresh/babel')],
},
},
],
})
} else {
console.error('cannot find react app loader')
}
// console.debug(require('util').inspect(webpackConfig.module.rules, { colors: true, depth: null }))
})
return webpackConfig
},
plugins: [whenDev(() => new ReactRefreshPlugin({ disableRefreshCheck: false }))].filter(Boolean),
},
}
@gaearon ํน์ ์์ ์์ ๊ธฐ๋ณธ์ ์ผ๋ก CRA์์ Fast Refresh๋ฅผ ์ฌ์ฉํ ์์์ ๊ฒ์ผ๋ก ์์ํฉ๋๊น?
๊ทธ๋ ๋ค๋ฉด ๊ทธ๊ฒ์ ํ์ํ ๊ฒ์ ๋ฌด์์
๋๊น?
ํ์ฌ ์ํ์ค์ธ ์์ ์๋ ์ฝ๊ฐ์ ์์ ์ด ํ์ํฉ๋๋ค.
HMR ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด ํธ์ถ๋ฉ๋๊น? ์๋ฅผ ๋ค์ด componentDidMount.
react-proxy๋ฅผ ์ฌ์ฉํ๊ณ componentDidMount๊ฐ ํธ์ถ๋ฉ๋๋ค.
๋ฐ์ 15.X๋ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ ์ฌ์ฉํ ์ ์์ต๋๊น?
componentDidMount
๊ฐ ํธ์ถ๋ฉ๋๋ค. ๋ฟ๋ง ์๋๋ผ unmount
-ํด๋์ค๊ฐ ์ ์ฒด๋ก ๋ค์๋ก๋๋ฉ๋๋ค.react-proxy
์ฌ์ฉ์ ์ค์งํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ธ์์, ์๋ง ๋ช ๋
์ ์ ๋ฉ์ถฐ์ผ ํ์ด์.15.X
? - ์ ๋์ ์ผ๋กํ์ง. Fast Refresh __๋ react์ ์ผ๋ถ์ด๋ฉฐ, ๋ฐ๋ผ์ ์ต์ ๋ฒ์ ์๋ง ์กด์ฌํฉ๋๋ค.๋น ๋ฅธ ์๋ก ๊ณ ์นจ ๋๋ react-hot-loader๋ฅผ ์ฌ์ฉํ์ฌ react-proxy๋ฅผ ๋์ฒดํด์ผํฉ๋๊น?
HMR์์ ํจ์ (componentDidMount)๊ฐ ์คํ๋๋ ๊ฒ์ ๋ฐฉ์ง ํ ์์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น? -์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์ป๊ธฐ ์ํด ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
JIT์์ react-hot-loader๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํฉ๋๊น? -๋ธ๋ผ์ฐ์ ์ค์๊ฐ ์ปดํ์ผ
๋น ๋ฅธ ์๋ก ๊ณ ์นจ ๋๋ react-hot-loader๋ฅผ ์ฌ์ฉํ์ฌ react-proxy๋ฅผ ๋์ฒดํด์ผํฉ๋๊น?
๋จผ์
fast refresh
์๋ํ ๋ค์RHL
์๋
HMR์์ ํจ์ (componentDidMount)๊ฐ ์คํ๋๋ ๊ฒ์ ๋ฐฉ์ง ํ ์์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๊น? -์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์ป๊ธฐ ์ํด ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
(ํํฌ ์ฌ์ฉ ...), ๊ตฌ์ฑ ์์ lifeCycle์ ์์กดํ์ง ์๊ณ ํ์ํ ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
react-query
,swr
๋๋ ๋ค๋ฅธ ์๋ฃจ์ ์ ์ฌ์ฉํด๋ณด์ญ์์ค.
์๋ก์ด HMR์ ์ด๋ป๊ฒ ์ฌ์ฉํด์ผํ๋์ง์ ๋ํ ์ง๋ฌธ์ ๋ํด์๋ ๊ฑฐ๊ธฐ์์ ์ต์ ์๊ฐ์ ์์ง ๋ชปํ๋ ๊ฒ ๊ฐ์ต๋๋ค. @gaearon ์ด CRA repo์ ๋ํ PR์ ๊ฐ์ง๊ณ ์์์
๋ ์๋ฅผ ์ํด ๋ช ํํํ๊ธฐ ์ํด PR์ ๋งค์ฐ ๊ตฌ์์ด๋ฉฐ ๋ ์ด์ ๊ด๋ จ์ด ์์ต๋๋ค.
Fast Refresh์ ์๋ ๋ฐฉ์๊ณผ ํตํฉ ๋ฐฉ๋ฒ์ ๋ํด ์ ์ด์ผํฉ๋๋ค. ์์ง ์๊ฐ์ด ์์์ต๋๋ค.
์ค๋๋ถํฐ ๊ทธ PR์ ์์ง ์ด๋ ค ์์ต๋๋ค. ๋ ๋์ ๊ฐ์๋ฅผ ๋ณผ ์ ์๋๋ก ์์ง ๋ณํฉ ํ ๊ธฐํ๊ฐ์๋ ๊ด๋ จ PR ๋ง ์ด์ด๋๋ฉด ์ข์ ๊ฒ์ ๋๋ค. ์ฐธ์กฐ ์ฉ์ผ๋ก ๋ณด๊ดํ๋ ๊ฒฝ์ฐ ํญ๋ชฉ์ ๋ธ๋์น, ํ๊ทธ ๋๋ ๋ค๋ฅธ ์ ์ฅ์๋ก ์ด๋ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๊ณ์ Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly.
๋ฌธ์๋ฅผ ๋ฐ๋์ง๋ง ๋์น ๊ฒ ๊ฐ์ต๋๊น?
๊ณ์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค. [React Refresh] HMR (Hot Module Replacement)์ด ํ์ฑํ๋์ง ์์์ต๋๋ค! React Refresh๊ฐ ์ ๋๋ก ์๋ํ๋ ค๋ฉด HMR์ด ํ์ํฉ๋๋ค. ๋ฌธ์๋ฅผ ๋ฐ๋์ง๋ง ๋์น ๊ฒ ๊ฐ์ต๋๊น?
@silkfire ๋๋ ๋น์ ์ด webpack ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ๊ณ ์๋ค๊ณ ๊ฐ์ ํ๊ณ ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์นํฉ ํ๋ฌ๊ทธ์ธ ์ ์ฅ์ ( https://github.com/pmmmwh/react-refresh-webpack-plugin/)์ ์ง๋ฌธ์ ์ ์ถ
์ค๋๋ถํฐ ๊ทธ PR์ ์์ง ์ด๋ ค ์์ต๋๋ค. ๋ ๋์ ๊ฐ์๋ฅผ ๋ณผ ์ ์๋๋ก ์์ง ๋ณํฉ ํ ๊ธฐํ๊ฐ์๋ ๊ด๋ จ PR ๋ง ์ด์ด๋๋ฉด ์ข์ ๊ฒ์ ๋๋ค. ์ฐธ์กฐ ์ฉ์ผ๋ก ๋ณด๊ดํ๋ ๊ฒฝ์ฐ ํญ๋ชฉ์ ๋ธ๋์น, ํ๊ทธ ๋๋ ๋ค๋ฅธ ์ ์ฅ์๋ก ์ด๋ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๊ทํ์ ์ ์์ ๊ฐ์ฌ ๋๋ฆฌ์ง๋ง, ์ฝ์ง ์์ ์์ฒ ๊ฐ์ ์๋ฆผ์ผ๋ก ์ธํด ์ด์ PR์ ๋ค์ ๋ฐฉ๋ฌธํ๋ ๊ฒ์ ๊ธฐ์ตํ๊ธฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค. Create React App ์ ์ฅ์ ๊ด๋ฆฌ์๊ฐ ์ฌ๋ฐ๋ฅธ ์ผ์ํ๊ณ ๋ ์ด์ ์ ์ฉํ์ง ์๋ค๊ณ ์๊ฐํ๋ฉด ๋ซ์ ๊ฒ์ด๋ผ๊ณ ๋ฏฟ์ต๋๋ค.
์ด๊ฑธ ๋ซ๊ฒ ์ต๋๋ค.
์นํฉ์ ๋ํ ์ฐธ์กฐ ๊ตฌํ์ผ๋ก https://github.com/pmmmwh/react-refresh-webpack-plugin/ ์ด
๊ทธ๋ฆฌ๊ณ https://github.com/facebook/react/issues/16604#issuecomment -528663101์์๋ ์ฌ์ฉ์ ์ง์ ํตํฉ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ค๋ช
ํฉ๋๋ค.
๊ณ์
Error: [React Refresh] Hot Module Replacement (HMR) is not enabled! React Refresh requires HMR to function properly.
๋ฌธ์๋ฅผ ๋ฐ๋์ง๋ง ๋์น ๊ฒ ๊ฐ์ต๋๊น?
webpack HMR์ ํ์ฑํํ์ง ์์ ๊ฒ ๊ฐ์ต๋๋ค. ์ถ๊ฐ ๋์์ด ํ์ํ๋ฉด ํ๋ฌ๊ทธ์ธ ์ ์ฅ์์ ๋ฌธ์ ๋ฅผ ์ ์ถํ์ธ์.
Hot Replacement๋ ์ด์ React์ ์ผ๋ถ์ด๋ฏ๋ก React ๋ฌธ์์ ๋ณ๋์ ์์น๊ฐ ์์ด์ผํ๋ฉฐ ํน์ ๋ฒ ๋ค๋ฌ ๋ฐ ํ๋ซํผ์์ ์ฌ์ฉํ ์ถ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์์ฒด ์ ๋ฐ์ดํธ CSS์ ๊ฐ์ ์ผ๋ถ ์ฌ์ ํ ์กด์ฌํ๋ ๋ฌธ์ ์ ์ ์ค๋ช ํด์ผํฉ๋๋ค. ๋ชจ๋.
์ด์ ๊ฐ์ ์ ๋ณด๋ github ๋ฌธ์ ๋ฐ ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ๋ฌปํ๋ฉด ์๋ฉ๋๋ค.
@theKashey ๊ทธ๊ฒ์ React์ ์์ง๋ง react-dom ๊ตฌํ์ ์คํ์ ์
๋๋ค.
๋ํ create-react-app๊ณผ ํจ๊ป ๋ฒ๋ค๋ก ์ ๊ณต๋๋ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ๊ตฌํ์ด ์์ง๋ง ์์ง ์ถ์๋์ง ์์์ต๋๋ค : pmmmwh / react-refresh-webpack-plugin # 7. ์๋ง๋ ๋ค์ ๋ฐ์ ์คํฌ๋ฆฝํธ ๋ฒ์ ์์์ ๊ฒ์
๋๋ค.
๋ฐ๋ผ์ ์๋ง๋ React ํ์ ์์ง์ด ์คํ ๋จ๊ณ์์ react-dom์ ๋ํ Fast Refresh์ ๋ํด ์ด์ผ๊ธฐํ๋ ๊ฒ์ด ์ณ์ง ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
React์ ์์ง๋ง react-dom ๊ตฌํ์ ์คํ์ ์ผ๋ฟ์ ๋๋ค.
๋ช
ํํ๊ฒ ๋งํ๋ฉด react-dom
์์ฒด์ ๊ตฌํ์ React Native์ ๋ง์ฐฌ๊ฐ์ง๋ก ์์ ์ ์
๋๋ค. ํตํฉ ์ด ๋ชจ๋ ์์ ์ ์ด์ง ์๋ค๋ ๊ฒ์
๋๋ค.
React ๋ฌธ์์ ๋ณ๋์ ์์น๊ฐ ์์ด์ผํ๋ฉฐ, ํน์ ๋ฒ ๋ค๋ฌ ๋ฐ ํ๋ซํผ์์ ์ฌ์ฉํ ์ถ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์์ฒด ์ ๋ฐ์ดํธ CSS ๋ชจ๋๊ณผ ๊ฐ์ ์ผ๋ถ ์ฌ์ ํ ์กด์ฌํ๋ ๋ฌธ์ ๋ฅผ ์ค๋ช ํฉ๋๋ค.
์ด๊ฒ์ ํฉ๋ฆฌ์ ์ผ๋ก ๋ค๋ฆฝ๋๋ค. ์ ์ฌํ RN ํ์ด์ง๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ณ ๊ธ ๊ฐ์ด๋ ์น์ ์ PR์ ์ถ๊ฐํ์ฌ ๊ธฐ์๊ฒ ์๊ฐํฉ๋๋ค.
@gaearon
๋ด ๋ฐ์ ์ฑ์ ์คํ์ผ์ด ์ง์ ๋ ์ผ๋ถ ๊ตฌ์ฑ ์์ ๋ณ๊ฒฝ์ผ๋ก ๋ฌธ์ ์์ด ํด๋น ๋ณ๊ฒฝ ์ฌํญ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ ์ฉํฉ๋๋ค.
๊ทธ๋ฌ๋ Redux์ ๊ฐ์๊ธฐ์์ ์ผ๋ถ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๋ฉด ์ ์ฒด ์ฑ์ด ์์ ํ ์๋ก ๊ณ ์ณ์ง๊ณ ๋ชจ๋ redux ์ํ๊ฐ ์์ค๋ฉ๋๋ค.
react-fast-refresh
์ ํจ๊ป ํ์ฌ ์ํ๋ฅผ ์ ์ฅํ๋ ค๋ฉด redux-persist
์ ๊ฐ์ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผํฉ๋๊น?
์ฐ๋ฆฌ๋ ์์ ํ ์์ ๊ฐ๊ณ ์ฌ๊ธฐ์ ๋ค์ ๊ฐ๋ค ๐
์ด๊ฒ์ด ์ ์์ค HMR์ด ์๋ํ๋ ๋ฐฉ์์ด๋ฉฐ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ์ฑ ์์ด ์์ต๋๋ค. redux ๋๋ webpack ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
์ฐ๋ฆฌ๋ ์์ ํ ์์ ๊ฐ๊ณ ์ฌ๊ธฐ์ ๋ค์ ๊ฐ๋ค ๐
์ด๊ฒ์ด ์ ์์ค HMR์ด ์๋ํ๋ ๋ฐฉ์์ด๋ฉฐ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ์ฑ ์์ด ์์ต๋๋ค. redux ๋๋ webpack ๋ฌธ์๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
์ ์ฒด ์ ์ฐธ์กฐ๋ฅผ ์ฐ๊ฒฐ ํ์๊ฒ ์ต๋๊น?
@ jwchang0206๋ ๊ฐ์ ์ฝ๋๊ฐ ์๋์ง ํ์ธ ์ด ์์ ์์.
์ ์ฒด ์ ์ฐธ์กฐ๋ฅผ ์ฐ๊ฒฐ ํ์๊ฒ ์ต๋๊น?
React Hot Loader์ ๋ํด์๋ ๋์ผํ ์ง๋ฌธ์ด ์ ๊ธฐ๋์์ต๋๋ค. ๊ฐ์ ๋๋ต์ด ์ฃผ์ด์ก์ต๋๋ค. ์ฐ๋ฆฌ๋ ์๋ก์ด์ฃผ๊ธฐ์ ์์์ ์์ต๋๋ค.
@ jwchang0206 ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์์ฑํ ์์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ธ redux-reducers-injector๋ฅผ ๋ณด์ญ์์ค.
ํซ ๋ฆฌ๋ก๋ฉ์ผ๋ก ๋ฆฌ๋ก๋ฉ ๋ฆฌ๋์๋ฅผ ์ง์ํ ์ ์์ต๋๋ค.
๊ฐ์๊ธฐ์์ ๋ถ๋ณ์ฑ์ redux ์์น์ ๋ฐ๋ฅด๊ณ ์ํํ๊ฒ ์๋ํ๋์ง ํ์ธํ์ญ์์ค ๐ฏ
sagas๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ redux-sagas-injector๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@gaearon window
์ฌ์ฉ์ ์ฝ๊ฐ ํผ๋์ค๋ฌ์ํฉ๋๋ค. ๊ตฌํ์ด ๊ต์ฒด๋์ด ์ ๋ง ํ์ํ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง ์์ต๋๊น? ๊ทธ ์์ ์ ๋ฌด์์
๋๊น?
var prevRefreshReg = window.$RefreshReg$; // these are dummies
var prevRefreshSig = window.$RefreshSig$; // these are dummies
var RefreshRuntime = require('react-refresh/runtime');
window.$RefreshReg$ = (type, id) =>{ /*...*/ }
window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
try {
// ...
} finally {
window.$RefreshReg$ = prevRefreshReg; // these are dummies again
window.$RefreshSig$ = prevRefreshSig; // these are dummies again
}
๋๋ ๋ด ์ฌ์ฉ์ ์ ์ ๋ฒ ๋ค๋ฌ๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ ์ด๊ฒ์ ๊ตฌํํ๋ ๊ณผ์ ์ด์ง๋ง ๊ทธ๊ฒ์ด ์ ๋์ ์ผ๋ก ํ์ํ ์ด์ ๋๋ ๊ทธ ์์ ์ด ๋ฌด์์ธ์ง ์ ์ ์์ต๋๋ค ... ์ฒ์์๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ / ๋์ค ์ต์ ํ๋ฅผ ์์ฌํ์ง๋ง ์ด๊ฒ๋ค์ RefreshRuntime
์ฐฉ์ ์ ํ๋ฉ๋๋ค.
@leidegre ์ฐฝ ๊ฐ์ฒด์ $ RefreshSig $๋ฅผ ์ค์ ํ๊ธฐ๋ก ํ ๊ฒฐ์ ์ ๋ํด์๋ ์ธ๊ธ ํ ์ ์์ง๋ง ๋ธ๋ผ์ฐ์ ํ๊ฒฝ๊ณผ์ ๊ฒฐํฉ์ผ๋ก ์ธํด React NativeScript์์ Fast Refresh๋ฅผ ์ฌ์ฉํ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. @pmmmwh ๋ Fast Refresh์ ๋ธ๋ผ์ฐ์ ์ ๋ํ ๊ฒฐํฉ์ ๊ทน๋ณตํ๊ธฐ ์ํด Fast Refresh Webpack ํ๋ฌ๊ทธ์ธ์ ์กฐ์ ํ์ฌ ๊ตฌ์กฐํ์ต๋๋ค (์ด ์ค๋ ๋์์ ๋ฐ์ํ๊ณ ๊ทน๋ณต ํ ๋ฌธ์ ์ ๋ํด ๋ ผ์ํ์ต๋๋ค : https://github.com/pmmmwh/react-refresh-webpack-plugin/). ๋ฌธ์ / 79). ์ฌ์ฉ ๋ ์ ๊ทผ ๋ฐฉ์์ด ์ฌ์ฉ์ ์ง์ ๋ฒ ๋ค๋ฌ์ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ํตํฉ์ ์ ์ฉํ ์ง ๊ถ๊ธํฉ๋๋ค.
๋ด ๋ฒ ๋ค๋ฌ๋ ๋๋ถ๋ถ TypeScript ์ปดํ์ผ๋ฌ๋ฅผ ๋๋ฌ์ผ ๋ํผ์
๋๋ค. ๊ตฌํ์ ๋๋ถ๋ถ react-refresh/babel
๋ฐฉ๋ฌธ์๋ก๋ถํฐ ์กฐ์ ๋ ๊ฒ์
๋๋ค.
์ด๊ฒ์ ์๋ํ๋ ๊ฐ๋จํ ์ผ์ด์ง๋ง react-refresh/bable
๋ฐฉ๋ฌธ์๋งํผ ์์ ํ์ง๋ ์์ต๋๋ค.
import ts = require("typescript")
import { IndexModule } from "./registry"
/** Enables the use of `react-refresh` for hot reloading of React components. */
export function hotTransform(m: IndexModule, hot: boolean) {
// see https://github.com/facebook/react/issues/16604#issuecomment-528663101
return (ctx: ts.TransformationContext) => {
return (sourceFile: ts.SourceFile) => {
const refreshRuntime = ts.createUniqueName("ReactRefreshRuntime")
const createSignatureFunctionForTransform = ts.createPropertyAccess(
refreshRuntime,
"createSignatureFunctionForTransform"
)
const register = ts.createPropertyAccess(refreshRuntime, "register")
let hasComponents = false
function visitor(node: ts.Node): ts.VisitResult<ts.Node> {
if (ts.isFunctionDeclaration(node)) {
if (_hasModifier(node, ts.SyntaxKind.ExportKeyword)) {
// assert component naming convention
if (node.name === undefined) {
console.warn("unsupported export of unnamed function in ...")
return node
}
const name = node.name
if (!_isComponentName(name.text)) {
console.warn(
`warning: unsupported export '${name.text}' in ${m.path} (${m.id}) does not look like a function component, component names start with a capital letter A-Z. TSX/JSX files should only export React components.`
)
return node
}
if (!hot) {
return node // opt-out
}
hasComponents = true
let hookSignatureString = ""
function hookSignatureStringVisitor(
node: ts.Node
): ts.VisitResult<ts.Node> {
const hookSig = _getHookSignature(node)
if (hookSig !== undefined) {
if (0 < hookSignatureString.length) {
hookSignatureString += "\n"
}
hookSignatureString += hookSig
}
return node
}
// update function body to include the call to create signature on render
const signature = ts.createUniqueName("s")
node = ts.visitEachChild(
node,
(node) => {
if (ts.isBlock(node)) {
return ts.updateBlock(
ts.visitEachChild(node, hookSignatureStringVisitor, ctx),
[
ts.createExpressionStatement(
ts.createCall(signature, undefined, [])
),
...node.statements,
]
)
}
return node
},
ctx
)
const signatureScope = ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList(
[
ts.createVariableDeclaration(
signature,
undefined,
ts.createCall(
createSignatureFunctionForTransform,
undefined,
undefined
)
),
],
ts.NodeFlags.Const
)
)
const createSignature = ts.createExpressionStatement(
ts.createCall(signature, undefined, [
name,
ts.createStringLiteral(hookSignatureString),
])
)
const registerComponent = ts.createExpressionStatement(
ts.createCall(register, undefined, [
name,
ts.createStringLiteral(m.path + " " + name.text),
])
)
return [signatureScope, node, createSignature, registerComponent]
}
}
if (!hot) {
// if hot reloading isn't enable, remove hot reloading API calls
if (ts.isExpressionStatement(node)) {
const call = node.expression
if (ts.isCallExpression(call)) {
if (
_isPropertyAccessPath(
call.expression,
"module",
"hot",
"reload"
)
) {
return undefined
}
}
}
}
return node
}
sourceFile = ts.visitEachChild(sourceFile, visitor, ctx)
if (hot && hasComponents) {
let reactIndex = sourceFile.statements.findIndex((stmt) => {
if (ts.isImportEqualsDeclaration(stmt)) {
const ref = stmt.moduleReference
if (ts.isExternalModuleReference(ref)) {
const lit = ref.expression
if (ts.isStringLiteral(lit)) {
return lit.text === "react"
}
}
}
return false
})
if (reactIndex === -1) {
console.warn(`cannot find import React = require('react') in ...`)
reactIndex = 0
}
// insert after
sourceFile = ts.updateSourceFileNode(sourceFile, [
...sourceFile.statements.slice(0, reactIndex + 1),
ts.createImportEqualsDeclaration(
undefined,
undefined,
refreshRuntime,
ts.createExternalModuleReference(
ts.createStringLiteral("react-refresh/runtime")
)
),
...sourceFile.statements.slice(reactIndex + 1),
ts.createExpressionStatement(
ts.createCall(
ts.createPropertyAccess(
ts.createPropertyAccess(
ts.createIdentifier("module"),
ts.createIdentifier("hot")
),
ts.createIdentifier("reload")
),
undefined,
undefined
)
),
ts.createExpressionStatement(
ts.createBinary(
ts.createPropertyAccess(
ts.createIdentifier("globalThis"),
ts.createIdentifier("__hot_enqueueUpdate")
),
ts.createToken(ts.SyntaxKind.AmpersandAmpersandToken),
ts.createCall(
ts.createPropertyAccess(
ts.createIdentifier("globalThis"),
ts.createIdentifier("__hot_enqueueUpdate")
),
undefined,
undefined
)
)
),
])
}
return sourceFile
}
}
}
function _hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
const modifiers = node.modifiers
if (modifiers !== undefined) {
for (let i = 0; i < modifiers.length; i++) {
if (modifiers[i].kind === kind) {
return true
}
}
}
return false
}
function _isComponentName(name: string): boolean {
// ^[A-Z]
const ch0 = name.charCodeAt(0)
return 0x41 <= ch0 && ch0 <= 0x5a
}
function _isPropertyAccessPath(
node: ts.Expression,
...path: ReadonlyArray<string>
): node is ts.PropertyAccessExpression {
for (let i = 0; i < path.length; i++) {
if (ts.isPropertyAccessExpression(node)) {
if (!(node.name.text === path[path.length - (i + 1)])) {
return false
}
node = node.expression
}
}
return true
}
function _getHookSignature(node: ts.Node): string | undefined {
if (ts.isExpressionStatement(node)) {
const call = node.expression
if (ts.isCallExpression(call)) {
const prop = call.expression
if (ts.isPropertyAccessExpression(prop)) {
const text = prop.name.text
if (text.startsWith("use") && 3 < text.length) {
// todo: add additional checks and emit warnings if the hook usage looks non standard
return text
}
}
}
}
return undefined
}
์ฒ์์๋ createSignatureFunctionForTransform
๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ ๋ชฐ๋์ง๋ง ๊ฐ ๊ตฌ์ฑ ์์์ ๋ํด ์์ ์ํ ๋จธ์ ์ ์์ฑํ๋ ํฉํ ๋ฆฌ ํจ์์ผ๋ฟ์
๋๋ค. ๋ฐ๋ผ์ ์ ์ ํํฌ ์๊ทธ๋์ฒ (ํด์์ ์ ์ฌํ ๋ถํฌ๋ช
ํ ๊ฐ)๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ํจ์์ ๋ํด ํ ๋ฒ์ฉ ํธ์ถํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๋ ๋๋ง์์ ํธ์ถํ์ฌ ์ค์ ์์
์ ์๋ฃํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝ๋ฉ๋๋ค.
import React = require("react")
export function App() {
const [state, setState] = React.useState(0)
return (
<React.Fragment>
<p>
Click Count !!!<strong>{state}</strong>!!!
<br />
<button onClick={() => setState((acc) => acc + 1)}>Click me</button>
</p>
</React.Fragment>
)
}
์ด๊ฒ์ผ๋ก :
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const ReactRefreshRuntime_1 = require(6);
const s_1 = ReactRefreshRuntime_1.createSignatureFunctionForTransform();
function App() {
s_1();
const [state, setState] = React.useState(0);
return (React.createElement(React.Fragment, null,
React.createElement("p", null,
"Click Count !!!",
React.createElement("strong", null, state),
"!!!",
React.createElement("br", null),
React.createElement("button", { onClick: () => setState((acc) => acc + 1) }, "Click me"))));
}
exports.App = App;
s_1(App, "useState");
ReactRefreshRuntime_1.register(App, "./App App");
module.hot.reload();
globalThis.__hot_enqueueUpdate && globalThis.__hot_enqueueUpdate();
๋ฐฉ๋ฌธ์๊ฐ ๋ถ์์ ํฉ๋๋ค. ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ์ฌ๋ก ๋ง ๋ค๋ฃน๋๋ค.
window
์ฌ์ฉ์ ์ฝ๊ฐ ํผ๋ ์ค๋ฝ์ต๋๋ค. ๊ตฌํ์ด ๊ต์ฒด๋์ด ์ ๋ง ํ์ํ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง ์์ต๋๊น? ๊ทธ ์์ ์ ๋ฌด์์ ๋๊น?
๋ฟก๋ฟก
Metro์ ๊ตฌํ์ window
์๋๋ผ ์ค์ global
๋ฒ์๋ฅผ ์ฌ์ฉํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
์ด ๊ตฌํ์ ๋ํ ์๋์ ๊ทผ๊ฑฐ๋ ๋ชจ๋ฅด์ง๋ง ์ ๊ฒฝํ์ผ๋ก๋ ์ ์ฉํ์ต๋๋ค. ์ค์ ์๊ตฌ ๋
ผ๋ฆฌ๊ฐ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ ๋
ผ๋ฆฌ์ ๋
๋ฆฝ์ ์์ ๋ณด์ฅํฉ๋๋ค (์ฆ, react-refresh/babel
๋ณํ์ ๊ฑฐ์ ๋ชจ๋ ๋ฒ ๋ค๋ฌ). ์ค์ํ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฐํ์์ ์ํด ์ฒ๋ฆฌ๋์ง ์์์ผํ๋ ๋ชจ๋์ด ์ฒ๋ฆฌ๋์ง ์๋๋กํ๋ ๊ฐ๋ ์ญํ ๋ํฉ๋๋ค.
@babel/runtime
๊ฐ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ์ญ์์ค.์ด ๊ฒฝ์ฐ ํฌํผ๋ฅผ ๋ฒ๋ค์ ๊ฐ์ ธ ์ค๊ธฐ๋ก ์ฃผ์
ํ๊ณ node_modules
์๋ ์ฝ๋ ๋ง HMRํ๋ ค๊ณ ํฉ๋๋ค. ๋น ํฌํผ๋ฅผ ๋จผ์ ์ด๊ธฐํํ์ง ์๊ณ ๊ธ๋ก๋ฒ ์ค์ฝํ์ ํฌํผ๋ฅผ ํ ๋นํ๋ฉด ๋๋ฌผ๊ฒ ์ฌ์ฉ์ ์์ญ ๋ชจ๋์ด ์ด๊ธฐํ๋ฅผ ์๋ฃํ๊ธฐ ์ ์ Babel ์ฃผ์
ํฌํผ๊ฐ cleanup
๋ฅผ ํธ์ถํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์์
).
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ข์, ๊ฐ๋ค.
๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ด๋ ๋ฌด์์ ๋๊น?
React์ ์์ ํ ์ง์๊ณผ ํจ๊ป "hot reloading"์ ์ฌ ๊ตฌํ์ ๋๋ค. ์๋ React Native ์ฉ์ผ๋ก ์ ๊ณต ๋์์ง๋ง ๋๋ถ๋ถ์ ๊ตฌํ์ ํ๋ซํผ ๋ ๋ฆฝ์ ์ ๋๋ค. ๊ณํ์ ์์ ํ ์ฌ์ฉ์ ์์ญ ์๋ฃจ์ (์
react-hot-loader
)์ ๋์ฒดํ๊ธฐ ์ํด ์ ์ฒด์ ์ผ๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค.์น์์ ๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ ์ฌ์ฉํ ์ ์์ต๋๊น?
์ด๋ก ์ ์ผ๋ก ๋ค, ๊ทธ๊ฒ ๊ณํ์ ๋๋ค. ์ค์ ๋ก ๋๊ตฐ๊ฐ๋ ์น์์ ์ผ๋ฐ์ ์ธ ๋ฒ ๋ค๋ฌ (์ : Webpack, Parcel)์ ํตํฉํด์ผํฉ๋๋ค. ๋๋ ์์ง ๊ทธ ์ผ์ํ์ง ์์์ต๋๋ค. ๋๊ตฐ๊ฐ๊ฐ ๊ทธ๊ฒ์ ์ง์ด ๋ค๊ณ ์ถ์ด ํ ์๋ ์์ต๋๋ค. ์ด ์๊ฒฌ์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ์ง์ ๋ํ ๋๋ต์ ์ธ ๊ฐ์ด๋์ ๋๋ค.
๋ฌด์์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๊น?
๋น ๋ฅธ ์๋ก ๊ณ ์นจ์ ํจ๊ป ์๋ํ๋ ์ฌ๋ฌ ๋ถ๋ถ์ ์์กดํฉ๋๋ค.
module.hot
API๋ฅผ ์ฌ์ฉํ๋ฉด์ด ์์ ์ ์ํ ํ ์ ์์ต๋๋ค.[email protected]
์ด์react-refresh/runtime
์ง์ ์ react-refresh/babel
Babel ํ๋ฌ๊ทธ์ธํตํฉ ๋ถ๋ถ์์ ์์ ํ๊ณ ์ถ์ ๊ฒ์ ๋๋ค. ์ฆ,
react-refresh/runtime
๋ฅผ Webpack "ํซ ๋ชจ๋ ๊ต์ฒด"๋ฉ์ปค๋์ฆ๊ณผ ํตํฉํฉ๋๋ค.ํตํฉ์ ์ด๋ค ๋ชจ์ต์ผ๊น์?
โ ๏ธโ ๏ธโ ๏ธ ๋ช ํํ๊ฒํ๊ธฐ ์ํด์ด ๋ฌธ์๋ ํตํฉ ํ ๋ง๋ฅผ ๊ตฌํํ๋ ค๋ ์ฌ๋๋ค์์ํ ๊ฐ์ด๋์ ๋๋ค. ์์ ์์ฃผ์์ ๋ฐ๋ผ ์งํํ์ญ์์ค!
์ต์ํ์ผ๋กํ๊ณ ์ถ์ ๋ช ๊ฐ์ง ์์ ์ด ์์ต๋๋ค.
react-refresh/babel
์ถ๊ฐ์ด ์์ ์์ ์ฑ์ด ์ถฉ๋ํด์ผํฉ๋๋ค. ์ ์๋์ง ์์
$RefreshReg$
๋ฐ$RefreshSig$
ํจ์์ ๋ํ ํธ์ถ์ด ํฌํจ๋์ด์ผํฉ๋๋ค.๊ทธ๋ฐ ๋ค์
react-dom
(!)๋ฅผ ํฌํจ ํ์ฌ ์ฑ์ ์ฝ๋๋ณด๋ค ๋จผ์ ์คํํด์ผํ๋ ์ JS ์ง์ ์ ์react-dom
์ดํ์ ์คํ๋๋ฉด ์๋ฌด๊ฒ๋ ์๋ํ์ง ์์ต๋๋ค. ํด๋น ์ง์ ์ ์ ๋ค์๊ณผ ๊ฐ์ดํด์ผํฉ๋๋ค.์ด๋ ๊ฒํ๋ฉด ์ถฉ๋์ด ํด๊ฒฐ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฌํ
$RefreshReg$
๋ฐ$RefreshSig$
๊ตฌํ์ noops์ด๋ฏ๋ก ์ฌ์ ํ ์๋ฌด ์์ ๋ ์ํํ์ง ์์ต๋๋ค. ์ด๋ค์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ํตํฉ ์์ ์ ํต์ฌ์ ๋๋ค.์ด๋ฅผ ์ํํ๋ ๋ฐฉ๋ฒ์ ๋ฒ ๋ค๋ฌ์ ๋ฐ๋ผ ๋ค๋ฆ ๋๋ค. ์นํฉ์ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ๋ชจ๋์ด ์คํ๋๊ธฐ ์ ํ์ ์ผ๋ถ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ ๋ก๋๋ฅผ ์์ฑํ ์ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ๋๋ ๋ชจ๋ ํ ํ๋ฆฟ์ ๋ฌด์ธ๊ฐ๋ฅผ ์ฃผ์ ํ๋ ํํฌ๊ฐ์์ ์ ์์ต๋๋ค. ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ๋ฌ์ฑํ๋ ค๋ ๊ฒ์ ๋ชจ๋ ๋ชจ๋์ด ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ด๋ ๊ฒ์ ๋๋ค.
์ฌ๊ธฐ์ ์์ด๋์ด๋ Babel ํ๋ฌ๊ทธ์ธ์ด์ด ํจ์์ ๋ํ ํธ์ถ์ ๋ด ๋ณด๋ธ ๋ค์ ์์ ํตํฉ์ด ํด๋น ํธ์ถ์ ๋ชจ๋ ID์ ์ฐ๊ฒฐํ๋ค๋ ๊ฒ์ ๋๋ค. ๋ฐ๋ผ์ ๋ฐํ์์ ๊ตฌ์ฑ ์์๊ฐ ๋ฑ๋ก ๋ ๋
"path/to/Button.js Button"
์ ๊ฐ์ ๋ฌธ์์ด์ ์์ ํฉ๋๋ค. (๋๋ ์นํฉ์ ๊ฒฝ์ฐ ID๋ ์ซ์์ ๋๋ค.) Babel ๋ณํ๊ณผ์ด ๋ํ์ ๋ชจ๋ ๊ฐ๋ฐ ๋ชจ๋ ์์๋ง ๋ฐ์ํด์ผํ๋ค๋ ๊ฒ์ ์์ง ๋ง์ญ์์ค.๋ชจ๋ ์ฝ๋๋ฅผ ๋ํํ๋ ๋์ ๋ฒ ๋ค๋ฌ๊ฐ ์ค์ ๋ก ๋ชจ๋ ํฉํ ๋ฆฌ๋ฅผ ์ด๊ธฐํํ๋ ์์น์ ์ด์ ๊ฐ์ try / finally๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ด์์ ์ ์์ต๋๋ค. ์ฌ๊ธฐ Metro (RN ๋ฒ ๋ค๋ฌ)์์ํ๋ ๊ฒ์ฒ๋ผ. ์ฐ๋ฆฌ๋ ํฌ์ฅ ํ ๋ ์๋ฅผ ๋ค์ด, ๋ถ์ ํ ๊ตฌ๋ฌธ์ ๋์ ์ ๋ํด ๋ชจ๋ ๋ชจ๋ ๋๋ ๊ฑฑ์ ์ ๋ถํ๊ฒ ํ ํ์๊ฐ ์์ต๋๋ค ๋๋ฌธ์ ์ด๊ฒ์ ์๋ง ๋ ์ข์ ๊ฒ์ด๋ค
import
์์๋กtry / finally
.์ด๊ฒ์ ์ฐ๊ฒฐํ๋ฉด ๋ง์ง๋ง ๋ฌธ์ ๊ฐ ํ๋ ์์ต๋๋ค. ๋ฒ ๋ค๋ฌ๋ ์ ๋ฐ์ดํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ ์๋ค๋ ์ฌ์ค์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ์ด์จ๋ ํ์ด์ง๋ฅผ ๋ค์๋ก๋ ํ ๊ฒ์ ๋๋ค. ํ์ง ๋ง๋ผ๊ณ ํด์ผํฉ๋๋ค. ์ด๊ฒ์ ๋ค์ ๋ฒ ๋ค๋ฌ์ ๋ฐ๋ผ ๋ค๋ฅด์ง๋ง ๋ด๊ฐ ์ ์ํ๋ ์ ๊ทผ ๋ฐฉ์์ ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ๊ฐ React ๊ตฌ์ฑ ์์ ์ธ์ง ํ์ธ
isReactRefreshBoundary
๋ฌด์์ ๋๊น? ์ค๋ฒ ์ต์คํฌํธ๋ฅผ ์๊ฒ ์ด๊ฑฐํ๊ณ React ์ปดํฌ๋ํธ ๋ง ์ต์คํฌํธํ ์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ด ์ ๋ฐ์ดํธ ์๋ฝ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ฌ๊ธฐ์ ๋ถ์ฌ ๋ฃ์ง๋ ์์์ง๋ง ์ด ๊ตฌํ ์ด ์ข์ ์์Refresh
๋react-refresh/runtime
๋ด๋ณด๋ด๊ธฐ๋ฅผ ๋ํ๋ ๋๋ค.)Babel ๋ณํ์ ํจ์์ ๋ํด
$RefreshReg$
๋ง ํธ์ถํ๋ฏ๋ก ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ ๋ฅผ๋ง์ง๋ง์ผ๋ก
enqueueUpdate()
ํจ์๋ ์ค์ React ์ ๋ฐ์ดํธ๋ฅผ ๋ ๋ฐ์ด์คํ๊ณ ์ํํ๋ ๋ชจ๋๊ฐ์ ๊ณต์ ๋๋ ๊ธฐ๋ฅ์ ๋๋ค.์ด ์์ ์์ ๋น์ ์ ๋ญ๊ฐ ์๋ํด์ผํฉ๋๋ค.
๋์์ค
"๋น ๋ฅธ ์๋ก ๊ณ ์นจ"๋ธ๋๋ฉ์ ๊ด์ฌ์ด์๋ ๋ช ๊ฐ์ง ๊ธฐ๋ณธ ๊ฒฝํ ๊ธฐ๋์น๊ฐ ์์ต๋๋ค. ๊ตฌ๋ฌธ ์ค๋ฅ, ๋ชจ๋ ์ด๊ธฐํ ์ค๋ฅ ๋๋ ๋ ๋๋ง ์ค๋ฅ์์ ์ ์์ ์ผ๋ก ๋ณต๊ตฌ ํ ์ ์์ด์ผํฉ๋๋ค. ์ด๋ฌํ ๋ฉ์ปค๋์ฆ์ ๋ํด ์์ธํ ์ค๋ช ํ์ง๋ ์๊ฒ ์ง ๋ง ์ด๋ฌํ ๊ฒฝ์ฐ๋ฅผ ์ ์ฒ๋ฆฌ ํ ๋๊น์ง ์คํ์ "Fast Refresh"๋ผ๊ณ ๋ถ๋ฅด์ง ๋ง์์ผํ๋ค๊ณ ๋งค์ฐ ๊ฐ๋ ฅํ๊ฒ ๋๋๋๋ค.
์ํ๊น๊ฒ๋ ์นํฉ์ด์ด ๋ชจ๋ ๊ฒ์ ์ง์ํ ์ ์๋์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง, ์ด๋ ์ ๋ ์๋ ์ํ์ ์ด๋ฅด๋ ๋ค๊ฐ ๋งํ๋ฉด ๋์์ ์์ฒญํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์นํฉ์
accept()
API๊ฐ ์ค๋ฅ ๋ณต๊ตฌ๋ฅผ ๋ ์ด๋ ต๊ฒ ๋ง๋ ๋ค๋ ๊ฒ์ ์์์ต๋๋ค (์ค๋ฅ ํ ๋ชจ๋์ _previous_ ๋ฒ์ ์ ์๋ฝํด์ผ ํจ). ๋ค์ ๋์ ๊ฐ์ผ ํ ๋ ๋ค๋ฅธ ์ฌํญ์ Babel ํ๋ฌ๊ทธ์ธ์์ ์ฐพ์ ๊ฒ๋ฟ๋ง ์๋๋ผ ๋ชจ๋ ๋ด๋ณด๋ด๊ธฐ๋ฅผ ์๋์ผ๋ก "๋ฑ๋ก"ํ๋ ๊ฒ์ ๋๋ค. ์ง๊ธ์ ์ด๊ฒ์ ๋ฌด์ํด ๋ด ์๋ค. ์๋ฅผ ๋ค์ด webpack์์ ์๋ํ๋ ๊ฒ์ด ์๋ค๋ฉด ๊ทธ๊ฒ์ ๋ค๋ฌ์ ์ ์์ต๋๋ค.๋ง์ฐฌ๊ฐ์ง๋ก Create React App์์๋
react-error-overlay
์ ์ ์ฌํ "์ค๋ฅ ์์"ํ๊ฒฝ๊ณผ ํตํฉํด์ผํฉ๋๋ค. ์ค๋ฅ๋ฅผ ์์ ํ๋ฉด ์ค๋ฅ ์์๊ฐ ์ฌ๋ผ์ง๋ ๊ฒ๊ณผ ๊ฐ์ ์ฝ๊ฐ์ ๋์์ค๊ฐ ์์ต๋๋ค. ์ฌ๋จ์ด ๋ง๋ จ๋๋ฉด ์ฐ๋ฆฌ๊ฐ ํ ์์๋ ์ถ๊ฐ ์์ ๋ ํ์ํฉ๋๋ค.๊ถ๊ธํ ์ ์ด ์์ผ๋ฉด ์๋ ค์ฃผ์ธ์!