๐ค intro
์ต๊ทผ FrontEnd ๊ณต๋ถ๋ฅผ ํ๋ฉด์ channel.io
, crisp
์ ๊ฐ์ ํ๋ฌ๊ทธ์ธ ์๋น์ค๋ ํน์ ์ฌ์ดํธ, ์๋น์ค ์์ ์ถ๊ฐ ๋์ด์ ๋์์ ํ๊ฒ ๋๋๋ฐ ์ด๋ ์ด๋ค ํํ๋ก ๊ตฌํ์ด ๋์ด์ ์๋์ด ๋์๋์ง ๊ถ๊ธ์ฆ๊ณผ ํธ๊ธฐ์ฌ์ด ๋ฐ์ ํ์ฌ์ ๋ถ์์ ํด๋ณด์์ต๋๋ค.
๐ฌ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ํตํด ํ์ธ ํด๋ณด๊ธฐ!
์ผ๋จ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ฐ ํํ์ ์๋น์ค๋ iframe
๋ฅผ ํตํด ๊ตฌํ์ด ๋์ด ์๋ ๊ฒ์ผ๋ก ์๊ณ ์๋๋ฐ ํด๋น ๋ถ๋ถ์ด ๋ง๋์ง ์ง์ ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ด์ฉํด์ ํ์ธ์ ํด๋ด
๋๋ค.
Chrome ๊ฐ๋ฐ์ ๋๊ตฌ๋ฅผ ์ผ์ ์ง์ ํด๋น element ๋ฅผ ํ์ธ์ ํด๋ณด๋ ์์ ๊ฐ์ด iframe ์ผ๋ก ๊ตฌ์ฑ ๋์ด ์๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
์ผ๋จ iframe
๋ฅผ ํตํด ๋ค๋ฅธ ์๋น์ค ๋ด๋ถ์์ ๋์์ ํ๋ค๋ ๊ฒ์ ์์์ง๋ง ์ด์ ์ด๋ค ํํ๋ก ์ฌ์ดํธ ๋ด๋ถ์์ ์ํธ ์์ฉํ๋ฉด์ ํต์ ์ ํ๋ ๊ฑฐ์ง์ ๋ํ ์๋ฌธ์ ๊ฐ์ง์ ์๊ฒ ๋๋๋ฐ ์ด๋ channel.io
์ ์ ์ฉ script๋ฅผ ๋ถ์ํด๋ณด๋ฉด์ ์๋ฌธ์ ์ ํด๊ฒฐ ํด๋ณด๊ฒ ์ต๋๋ค.
๐ง Channel Plugin Scripts ๋ถ์ ํ๊ธฐ!
์ผ๋จ channel.io
์ ๊ฐ๋ฐ์ ๊ฐ์ด๋ ๋ฌธ์๋ก ๊ฐ์ Installation ํ์ด์ง์์ ์๋ดํ๋ ์คํฌ๋ฆฝํธ๋ฅผ ๋ถ์ ํฉ๋๋ค!
// ChannelService.js
class ChannelService {
// ํด๋น Class ์์ฑ์ ํธ์ถ์ `loadScript` ํจ์๋ฅผ ํธ์ถ ํฉ๋๋ค.
constructor() {
this.loadScript()
}
loadScript() {
var w = window
// ์ด๋ฏธ window ์ ์ญ ๊ฐ์ฒด์ `ChannelIO` ๊ฐ ์กด์ฌ ํ๋ ์ง๋ฅผ ์ฒดํฌ ํ์ฌ ์ค๋ณต์ผ๋ก Load ๋๋ ๊ฒ์ ๋ฐฉ์ง ํฉ๋๋ค.
if (w.ChannelIO) {
return (window.console.error || window.console.log || function () {})(
"ChannelIO script included twice."
)
}
// ch ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ๋ด๋ถ์ cํจ์๋ฅผ ์ ๋ฌ ๋ฐ์ `arguments` ๋ฅผ ์ ๋ฌ ํ์ฌ ์คํ ํฉ๋๋ค.
var ch = function () {
ch.c(arguments)
}
// ch ๊ฐ์ฒด ๋ด๋ถ์ q array๋ฅผ ๋ง๋ค์ด ์ค๋๋ค.
ch.q = []
// ์ ๋ฌ ๋ฐ์ args๋ฅผ q์ ์ ์ฅํฉ๋๋ค.
ch.c = function (args) {
ch.q.push(args)
}
// window.ChannelIO ๋ฅผ ch๋ก ์ค์ ํฉ๋๋ค.
w.ChannelIO = ch
// boot script ๋ฅผ ์ ์ ํฉ๋๋ค.
function l() {
// ๋ง์ฝ channelIo๊ฐ ์ด๊ธฐํ ๋์๋ค๋ฉด ์๋ฌด ์์
์ ํ์ง ์๋๋ก ํฉ๋๋ค.
if (w.ChannelIOInitialized) {
return
}
// ์ด๊ธฐํ ์์
์๋ฃ flag๋ฅผ ์ธ์์ค๋๋ค.
w.ChannelIOInitialized = true
// script element๋ฅผ ์์ฑํฉ๋๋ค.
var s = document.createElement("script")
// javascript ๊ตฌ๋ฌธ์ผ๋ก ํด์ ๋ ์ ์๋๋ก ์ ์ ํฉ๋๋ค.
s.type = "text/javascript"
// ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ๋ถ๋ฌ์ฌ ์ ์๋๋ก ์ค์ ํฉ๋๋ค.
s.async = true
// channel.io๊ฐ ํต์ฌ์ ์ผ๋ก ๋์ํ๊ฒ๋ bunddle๋ script src๋ฅผ ์ง์ ํฉ๋๋ค.
s.src = "https://cdn.channel.io/plugin/ch-plugin-web.js"
// UTF-8๋ก ํต์ ๋ ์ ์๋๋ก ์๋ ค์ค๋๋ค.
s.charset = "UTF-8"
// document ๋ด๋ถ์ ์๋ ์ฒซ๋ฒ์งธ script element๋ฅผ ๊ฐ์ ธ ์ต๋๋ค.
var x = document.getElementsByTagName("script")[0]
// ์์์ ์ ์ํ channel.io ์คํฌ๋ฆฝํธ ํ๊ทธ๋ฅผ x ์ด์ ์ ์ฝ์
๋๋๋ก ํฉ๋๋ค.
x.parentNode.insertBefore(s, x)
}
// Document์ ์ํ๊ฐ complete ๋๋, ๋ก๋๊ฐ ๋์ด ์๋ค๋ฉด l๋ฅผ ํธ์ถ ํ๋ค.
if (document.readyState === "complete") {
l()
} else if (window.attachEvent) {
window.attachEvent("onload", l)
} else {
window.addEventListener("DOMContentLoaded", l, false)
window.addEventListener("load", l, false)
}
}
// ChannelIO์ boot ์์
์ ์ธ์๋ก ๋์ด์จ settings ์ค๋ธ์ ํธ๋ฅผ ํตํด ์คํํ๋ค.
boot(settings) {
window.ChannelIO("boot", settings)
}
// ChannelIO ์ข
๋ฃ
shutdown() {
window.ChannelIO("shutdown")
}
}
export default new ChannelService()
ํด๋น ์คํฌ๋ฆฝํธ๋ฅผ ์ดํด ๋ณด๋ฉด CDN ์์ script ๋ฅผ ๋ถ๋ฌ์ ์คํ ํ ์ ์๋๋ก ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ด์ ChannelService
๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋์ ๊ฐ์ด pluginKey ์ ์ต์
์ ํจ๊ป ์ ๋ฌํ์ฌ channel.io
๋ฅผ ์คํ ํ ์ ์๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
// Boot Channel as an anonymous user
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY", //please fill with your plugin key
})
// Boot Channel as a registered user
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY", //please fill with your plugin key
profile: {
name: "YOUR_USER_NAME", //fill with user name
mobileNumber: "YOUR_USER_MOBILE_NUMBER", //fill with user phone number
CUSTOM_VALUE_1: "VALUE_1", //any other custom meta data
CUSTOM_VALUE_2: "VALUE_2",
},
})
// Shutdown Channel
ChannelService.shutdown()
์ด์ ํ๋ฒ CDN ์์ ๋ถ๋ฌ์จ script ๋ฅผ ๋ถ์ ํด๋ด ๋๋ค!
// https://cdn.channel.io/plugin/ch-plugin-web.js
!(function () {
var e
if (
// ์ง์ ํ๋ ๋ธ๋ผ์ฐ์ ์ ๋ฒ์ ์ ์ํด ์๋์ง ๊ฒ์ฆ
((e = window.navigator.userAgent),
!/(Opera\/.+Opera Mobi.+Version\/((10|11)\.0|11\.1|11\.5|12\.(0|1)))|(Opera\/((10|11)\.0|11\.1|11\.5|12\.(0|1)).+Opera Mobi)|(Opera Mobi.+Opera(?:\/|\s+)((10|11)\.0|11\.1|11\.5|12\.(0|1)))|(SamsungBrowser\/((4|5)\.0|5\.4))|(IEMobile[ /](10|11)\.0)|(Android Eclair)|(Android Froyo)|(Android Gingerbread)|(Android Honeycomb)|(PlayBook.+RIM Tablet OS (7\.0|10\.0)\.\d+)|((Black[bB]erry|BB10).+Version\/(7\.0|10\.0)\.\d+)|(Trident\/6\.0)|(Trident\/5\.0)|(Trident\/4\.0)|(([MS]?IE) (5\.5|([6-9]|10)\.0))/.test(
e
) &&
// cookie๋ฅผ ๊ฐ์ง๊ณ ์ฌ์ฉ ํ ์ ์๋์ง์ ๋ํด์ ํ
์คํธ
window.navigator.cookieEnabled &&
(!window.document.documentMode ||
((document.cookie = "ch-session-test=1"),
document.cookie.split("ch-session-test=").length >= 2 &&
((document.cookie = "ch-session-test=; Max-Age=0"), 1))))
) {
// ch-plugin Element๊ฐ ์๋ค๋ฉด ํด๋น Element ๋ง๋ค์ด์ body์ ์ฝ์
if (!document.getElementById("ch-plugin")) {
var i = document.createElement("div")
;(i.id = "ch-plugin"), document.body.appendChild(i)
}
// ch-plugin Element์ ๋ ๋๋ง์ ํ์ํ ํ์ Element์ Iframe ๊ฐ์ฒด ์ฝ์
document.getElementById("ch-plugin").innerHTML +=
'<div id="ch-plugin-core"></div><div id="ch-plugin-script" style="display:none"><iframe id="ch-plugin-script-iframe" style="position:relative!important;height:100%;width:100%!important;border:none!important;"></iframe></div>'
// ์์์ ์์ฑํ ch-plugin-script-iframe Element๋ฅผ n์ ์ ์ฅ
var n = document.getElementById("ch-plugin-script-iframe"),
// flag ์ฉ t false๋ก ์ด๊ธฐํ
t = !1,
// iframe ์ด๊ธฐํ ์์
์ ํ๋ ํจ์ o ์ ์ธ
o = function () {
// ch-plugin-script-iframe์ ๋ด๋ถ์ document ๊ฐ์ฒด๋ฅผ e์ ์ ์ฅ
var e = n.contentDocument || n.contentWindow.document
// iframe ๋ด๋ถ๋ฅผ writing ๋ชจ๋๋ก ์คํ
e.open(),
// ํต์ฌ ๊ธฐ๋ฅ์ธ `https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js` script ์ค์
e.write(
'<script async type="text/javascript" src="https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js" charset="UTF-8"></script>'
),
// React Rendering๋ฅผ ์ํ ํ์ HTML ์ฝ์
e.write(
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body><div id="main"></div></body></html>'
),
// iframe ๋ด๋ถ writing ์์ฑ ์๋ฃ
e.close(),
// flag ture๋ก ์ค์
(t = !0)
}
// Document๊ฐ onload์ iframe ์ด๊ธฐํ ์์
์คํ.
n.onload || o(),
(n.onload = function () {
t || o()
})
}
})()
์ด์ ์ฌ๊ธฐ์ iframe
๋ฅผ ์ด๊ธฐํ ํ๋ ์์
์ ์งํ ํ๊ฒ ๋๋๋ฐ ์ฌ๊ธฐ์ ์ฃผ์ํ๊ฒ ๋ด์ผ ํ๋ ๋ถ๋ถ์ CORS
์ browser ํธํ์ฑ
์ ๊ณ ๋ คํ์ฌ iframe์ src ๋ฅผ ํตํด ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ์ฌ์ดํธ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์๋ ๋ฒ๋ค๋ง๋ script๋ฅผ ์ด์ฉํ์ฌ ์ง์ SPA๋ฅผ ๋ ๋๋ง ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค!
์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก iframe๋ฅผ ๊ด๋ฆฌํ๊ฒ ๋๋ฉด CORS์ ์ ์ฑ
์ ๋ฐ๋ฅธ script api access
์ ํ์ด ์์ด์ง๋ฉฐ ๋ํ CORS์ ๋ฐ๋ฅธ X-Frame-Options
header๋ฅผ ์๋ฒ ๋จ์ ์ค์ ํ๋ ๊ฒ์ ๋ํ ์๋ชจ๋ฅผ ์ค ์ผ ์ ์์ต๋๋ค!
์ด์ ํ๋ฒ ๋ฒ๋ค๋ง๋ script๋ฅผ ๊ฐ๋จํ๊ฒ ์ดํด ๋ด ๋๋ค!
์ผ๋จ https://cdn.channel.io/plugin/ch-plugin-core-20210923190001.js
ํ์ผ์ ๋ค์ด ํ minify์ ๋๋
ํ๊ฐ ์ผ๋ถ๋ถ ์ ์ฉ์ด ๋ ์ํ์ด๋ฏ๋ก formatter์ ํน์ object์ key๋ฅผ ๊ฒ์ํ๋ ํํ๋ก ๋ถ์์ ํด๋ด
๋๋ค!
์ผ๋จ ๊ฐ์ฅ ๋จผ์ ๋ณด์ด๊ฒ ๋๋ ๋ถ๋ถ์ ์๋์ ๊ฐ์ด ์ฃผ์์ ๋ณผ ์ ์๋๋ฐ ๋ผ์ด์ ์ค์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ ํด๋น ํ์ผ์ ํ์ธ ํ๋ฉด ๋ด๋ถ์๋ react
์ ํจ๊ป ์ฌ๋ฌ๊ฐ์ง์ ๋ถ๊ฐ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ตฌ์ฑ๋์ด ์๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
/*! For license information please see ch-plugin-core-20210923190001.js.LICENSE.txt */
// ch-plugin-core-20210923190001.js.LICENSE.txt
...
/** @license React v16.13.0
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
...
์ผ๋จ ํด๋น ๊ธฐ์ ์คํ์ react
๋ผ๋ ๊ฒ์ ์๊ฒ ๋์์ผ๋ window.ChannelIO
๊ฐ์ฒด์ ๋ํ ์ ์ ํ๋ ๋ถ๋ถ์ ์ดํด ๋ด
๋๋ค!
// ch-plugin-core-20210923190001.js
...
var n = yl();
(n.ChannelIO = n.ChannelIO || {}),
(this.commandQueue = n.ChannelIO.q || []),
(this.pendingQueue = []),
(this.callbacks = {}),
(this.methods = Ch(
{
show: function () {
if (!t.pushQueue({ apiName: "show", apiArray: ["show"] }))
if ((tl(nl("show", "showMessenger")), Ip)) {
var e = Af.channelSelectors.getChannel(t.getState());
wp.redirectToEdgeBrowser(e);
} else
t.dispatch(
Tr.uiActions.setShowMessenger({ showMessenger: !0 })
);
},
hide: function () {
t.pushQueue({ apiName: "hide", apiArray: ["hide"] }) ||
(tl(nl("hide", "hideMessenger")),
t.dispatch(
Tr.uiActions.setShowMessenger({ showMessenger: !1 })
));
},
lounge: function () {
if (
!t.pushQueue({ apiName: "lounge", apiArray: ["lounge"] })
) {
tl(nl("lounge"));
var e = Xf.getHistory();
if ("/lounge" !== e.location.pathname)
if (Af.uiSelectors.showMessenger(t.getState()))
e.push("/lounge");
else if (Ip) {
var n = Af.channelSelectors.getChannel(t.getState());
wp.redirectToEdgeBrowser(n);
} else
t.dispatch(
Tr.uiActions.setShowMessenger({
showMessenger: !0,
disableRedirect: !0,
})
),
e.push("/lounge");
}
},
boot: function (e) {
var n =
arguments.length > 1 && void 0 !== arguments[1]
? arguments[1]
: Mn.a;
if (!Tn()(n) || !Sn()(e))
return (
tl("boot failed: Invalid arguments."), On.a.resolve()
);
t.methods.shutdown(), Uf.setSettings(e);
var r = Uf.getPluginKey();
if (!r)
return (
tl("boot failed: pluginKey not found."), On.a.resolve()
);
var o = Uf.getLanguage(),
i = Uf.getLanguageOverrided(),
a = Uf.getBootOption();
return "YOUR_USER_ID" === a.memberId
? (tl("boot failed: Invalid memberId"), On.a.resolve())
: (o && t.dispatch(Tr.geoActions.setLocale({ locale: o })),
i &&
(bh.setLanguage(i),
Mp && t.methods.updateUser({ language: i })),
t
.dispatch(
Tr.pluginActions.requestCheckIn({
pluginKey: r,
option: a,
})
)
.promise.then(function (e) {
var r = wn()(e, "payload.user", {});
n(null, r),
_n()(t.pendingQueue) ||
t.execQueue(t.pendingQueue).then(function () {
t.pendingQueue = [];
});
})
.catch(function (e) {
var t = wn()(
e,
"payload.body",
new Error("boot failed")
);
n(t, null);
}));
},
shutdown: function () {
t.dispatch(Tr.pluginActions.checkOut({}));
}
...
์ฝ๋์ ์์ด ๋ฐฉ๋ ํจ์ผ๋ก ์ผ๋ถ๋ถ๋ง ํ์ธ์ ํด๋ณด๋ window.ChannelIO
์ ์ธ๋ถ์์ channel.io
์ ์ํธ ์์ฉ์ด ๊ฐ๋ฅํ ํจ์๋ค์ ์ ์ ํ๋ ๊ฒ์ ๋ณผ ์ ์๋๋ฐ dispatch
, actions
, dispatch chaining promise
๋ฅผ ํตํด ์ ์ญ์ผ๋ก ์ํ๊ด๋ฆฌ๋ฅผ ์ํ redux
, redux-saga
์ ๊ฐ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ด๋ฒ์๋ https://
์ ๊ฐ์ ํค์๋๋ฅผ ์ด์ฉํด์ ์ด๋์ ํต์ ์ ์ฃผ๊ณ ๋ฐ๋์ง ๋ถ์์ ํด๋ด
๋๋ค.
...
/**
* https://api.channel.io์ ๋ํ ์์ฒญ์ ์คํ ํ๋ ํจ์ ์ ์ ๋ถ๋ถ!
* credentials: "omit" ๋ฅผ ํตํด fetch API๋ผ๋ ๊ฒ์ ์ ์ถ ํ ์ ์์ต๋๋ค.
*/
function n() {
var e;
return (
he()(this, n),
(e = t.call(this, "https://api.channel.io", {
credentials: "omit",
})),
ve()(bu()(e), "language", Uf.getLanguageOverrided()),
e
);
}
...
/**
* https://browser.sentry-cdn.com/5.6.2/bundle.min.js ๋ฅผ ํตํด
* sentry๋ฅผ ์ด์ฉํ์ฌ ํ๋ก ํธ ์๋ฌ ๋ก๊ทธ๋ฅผ ์์ง ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
*/
key: "install",
value: function () {
var e = window.document.createElement("script");
(e.onload = function () {
var e, t;
null === (e = window.Sentry) ||
void 0 === e ||
e.init({
dsn: "https://[email protected]/306179",
environment: In()(
(t = "".concat("production", "|"))
).call(t, "App"),
release: "7.0.5",
whitelistUrls: [/cdn\.channel\.io/, /cdn\.ravenjs\.com/],
normalizeDepth: 5,
});
}),
(e.async = !0),
(e.crossOrigin = "anonymous"),
(e.integrity =
"sha384-H4chu/XQ3ztniOYTpWo+kwec6yx3KQutpNkHiKyeY05XCZwCSap7KSwahg16pzJo"),
(e.src =
"https://browser.sentry-cdn.com/5.6.2/bundle.min.js"),
window.document.head.appendChild(e);
},
...
/**
* ๋ํ msg์ ๊ฐ์ด ์ค์๊ฐ์ผ๋ก ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํ websocket๋ ์ค์ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค!
*/
{
key: "connect",
value: function () {
var e = this;
this.isConnected() && this.disconnect({ forceDisconnect: !0 }),
Zc(),
(this.connecting = !0),
(this.forceDisconnect = !1),
(this.ready = !1),
(this.readyQueue = []),
(this.socket = (0, v_.a)("https://ws.channel.io/front-v4", {
transports: ["websocket"],
upgrade: !0,
reconnectionDelay: 5e3,
reconnectionDelayMax: 1e4,
})),
this.socket.on("ready", this.onReady),
this.socket.on("connect", this.onConnect),
this.socket.on("connect_error", this.onConnectError),
this.socket.on("connect_timeout", this.onConnectTimeout),
this.socket.on("reconnect_error", this.onReconnectError),
this.socket.on("reconnect_failed", this.onReconnectFailed),
this.socket.on("reconnect_attempt", this.onReconnectAttempt),
this.socket.on("reconnect", this.onReconnect),
this.socket.on("error", this.onError),
this.socket.on("disconnect", this.onDisconnect),
window.addEventListener("offline", function () {
Zc(),
e.isConnected() &&
(Zc(), e.disconnect({ forceDisconnect: !1 }));
}),
window.addEventListener("beforeunload", function () {
Zc(), e.disconnect({ forceDisconnect: !0 });
});
},
},
...
์์ ์ ์ ๋ถ๋ถ ๋ง๊ณ ๋ ์ฌ๋ฌ๊ฐ์ง Error ์ ๋ํ ๋ฉ์์ง๋ฅผ ํตํด redux
, styled-components
, core-decorators
, date-fns
๋ฑ ์ฌ๋ฌ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
์ด์ ๋๋ต์ ์ผ๋ก ์ด๋ค ํํ๋ก ๋์ ๊ฐ๋ ์ง์ ๋ํด์ ํ์ธ์ ํ์ผ๋ ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉํ ๊ธฐ์ ์คํ์ ๋ํด์ ์กฐ์ฌ๋ฅผ ํด๋ด ๋๋ค!
โ๏ธ ๊ธฐ์ ์คํ ์กฐ์ฌํ๊ธฐ!
์ผ๋จ ๊ธฐ์ ์คํ์ ์กฐ์ฌํ๋ ๋ฐฉ๋ฒ์ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์กด์ฌ ํ๋๋ฐ ๊ฐ์ฅ ์ผ๋จ ํด๋น ๊ธฐ์ ๋ธ๋ก๊ทธ๋ ์ฑ์ฉ ์ฌ์ดํธ์ ๋ค์ด๊ฐ์ ๊ธฐ์ ์คํ์ ๋ํด์ ์กฐ์ฌ๋ฅผ ํด๋ด ๋๋ค.
์ฑ์ฉ๊ณต๊ณ ๋ฅผ ํ์ธ ํด ๋ณด๋ ๋ด๋ถ์์ ์ฌ์ฉ ํ๋ ๊ธฐ์ ์คํ์ ๋ํด์ ์์ธํ ์ค๋ช
์ ํ๊ณ stackshare
๋ฅผ ํตํด ์์ธํ๊ฒ ๊ณต์ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค!
์๋์ ๊ธฐ์ ๋ธ๋ก๊ทธ์์ ์ค๋ช
ํ๋ frontend-stack
๊ธ์ ํตํด window์ macos์ native app๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด electron
๋ฅผ ์ฌ์ฉํ๋ค๋ ์ ๋ณด๋ฅผ ์ถ๊ฐ๋ก ์ป์ ์ ์์์ต๋๋ค!
https://channel.io/ko/blog/frontend-stack
์ด๋ฒ์๋ ๋์ฑ๋ ์์ธํ๊ฒ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด๋ค ๊ฒ์ ์ฌ์ฉ ํ๋์ง์ ๋ํด ํ์ธ์ ํ๋๋ก ์คํ์์ค ๋ผ์ด์ ์ค
์ฌ์ดํธ์ ๋ค์ด๊ฐ ํ์ธ์ ํด๋ด
๋๋ค.!
ํด๋น ๋ผ์ด์ ์ค ๋ฌธ์๋ฅผ ํตํด ์ธ๋ถ ์ ์ผ๋ก ์ฌ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ธ ํ ์ ์๋ค๋ ๊ฒ์ ์ฝ๊ฒ ์ ์ ์์ต๋๋ค!
๋ง์ง๋ง์ผ๋ก wappalyzer
์ ๊ฐ์ ๋ถ์ ๋๊ตฌ๋ฅผ ์ด์ฉํด์ ๋ถ์์ ํด๋ด
๋๋ค.
์ผ๋จ iframe ๋ด๋ถ๋ ๋ถ์์ ์ ๋๋ก ํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ channel.io
์ ๋ฉ์ธ ํํ์ด์ง๋ฅผ ๋ถ์์ ํด๋ด
๋๋ค.!
์์ ๊ฐ์ด ํํ์ด์ง์ ๊ฒฝ์ฐ Next.js
๋ฅผ ์ด์ฉํ์ฌ SSR
๋ฅผ ์ ๊ณต ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๐ ๋ถ์ ํด๋ณด๋ฉด์ ์๊ฒ๋ ์ฌ์ค!
๊ธฐ์กด ์ด๋ฐ ํน์ ์๋น์ค ์์์ ๋์๊ฐ๊ฒ ๋๋ ํ๋ฌ๊ทธ์ธ ์๋น์ค๋ iframe
์์์ ๋์์ ํ๊ฒ ๋๋ค๋ผ๊ณ ์๊ณ ์์๋๋ฐ ์ ๊ทธ๋ฌํ ๋ฐฉ์์ ์ฑํ ํ๋์ง์ ๋ํด์๋ ์์ธํ ๋ชจ๋ฅด๋ ๋ถ๋ถ์ด ์์๋๋ฐ, ์ด๋ฒ์ ๋ถ์์ ํตํ ํ์ต์ ํ๋ฉด์ iframe
๋ฅผ ์ฌ์ฉํ๋ฉด namespace
, css scope
๋ฑ์ด ๋ถ๋ฆฌ ๋๊ธฐ ๋๋ฌธ์ ์ฑํ์ ํ๋ค๋ ๊ฒ๊ณผ iframe
์ cors
์ ์ฑ
์ ํด๊ฒฐํ๊ธฐ ์ํด script
๋ฅผ ์ง์ iframe
๋ด๋ถ์ ์ฝ์
ํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค.
๋ํ ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ํ ํฅ์์ ์ํด SPA
ํํ๋ก ์ ๊ณต์ ํ๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค!
๊ฒฐ๋ก : ํ๋ฌ๊ทธ์ธ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๊ธฐ ์ํด์๋ ๋ธ๋ผ์ฐ์ ์ ์๋๋ฐฉ์๊ณผ ํธํ์ฑ์ ๋ํด์ ๊น๊ฒ ๊ณต๋ถ๋ฅผ ํด์ผ ํ๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด
SPA
๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ด ์ข๋ค!