@logan-jun/cd1-sample-feature
v0.3.0
Published
CD1 Agent - iframe-embedded micro-frontend sample module (Vue 2.7, closed-network friendly).
Maintainers
Readme
@logan-jun/cd1-sample-feature
폐쇄망 Vue 2.7 + Vuetify 호스트에 iframe 격리로 통합되는 외부 빌드 프론트엔드 모듈입니다.
npm publish로 공개 npm에 올린 뒤, .tgz를 다운로드해 폐쇄망에 반입하는 워크플로우를 가정합니다.
전체 흐름 한 장 요약
[외부 인터넷]
┌──────────────────────────────────────────────┐
│ 1) npm publish │
│ → registry.npmjs.org 에 .tgz 업로드 │
│ 2) npm pack @logan-jun/cd1-sample-feature │
│ → logan-jun-cd1-sample-feature-X.Y.Z.tgz │
└─────────────────┬────────────────────────────┘
│ 반입 (USB/승인된 채널)
▼
[폐쇄망]
┌──────────────────────────────────────────────┐
│ 3) tar -xzf 로 풀어 dist/ 추출 │
│ 4) 호스트 정적 서버(nginx 등)의 경로에 배포 │
│ 5) 호스트 Vue 2.7 앱에 RemoteModule.vue 통합 │
│ + 라우트/메뉴 추가 │
└──────────────────────────────────────────────┘A. 외부 환경: npm publish
1) 최초 1회 — npm 로그인
npm login
# 사용자명/비밀번호/이메일 입력
# 2FA 활성화돼 있으면 OTP까지
npm whoami # 로그인 확인2) 스코프 패키지 충돌 확인 (선택)
npm view @logan-jun/cd1-sample-feature
# 404가 떨어지면 이름 사용 가능3) publish
cd embedded-modules/sample-feature
# package.json의 version 업데이트 (publish할 때마다 새 버전 필요)
npm version patch # 0.1.0 → 0.1.1
# 또는 minor / major / 직접 편집
# 빌드는 prepublishOnly가 자동 실행하므로 따로 안 해도 됨
npm publish --access public성공하면 https://www.npmjs.com/package/@logan-jun/cd1-sample-feature 에 게시됩니다.
주의
- 한 번 publish한 버전은 72시간 내에만
npm unpublish가능. 그 이후엔 영구.- 같은 버전 재publish 불가. 항상 version bump 후 publish.
- 패키지에 비밀 정보 들어가면 안 됨 (
.npmignore또는files화이트리스트로 통제). 현재는files: ["dist", "host-integration", "README.md"]만 ship.
4) publish된 .tgz 다운로드
PyPI의 .whl 다운로드와 동일한 방식 3가지 중 택1:
방법 A: CLI (가장 간단)
npm pack @logan-jun/[email protected]
# → logan-jun-cd1-sample-feature-0.1.0.tgz방법 B: 직접 URL
curl -O https://registry.npmjs.org/@logan-jun/cd1-sample-feature/-/cd1-sample-feature-0.1.0.tgz방법 C: npm 웹사이트
- https://www.npmjs.com/package/@logan-jun/cd1-sample-feature
- 우측 사이드바 "Download tarball" 클릭
이 .tgz 파일을 USB 등으로 폐쇄망에 반입합니다.
B. 폐쇄망 환경: 세팅 (반입 후)
.tgz만 가져왔다고 가정합니다. npm install이 필요 없음 — 정적 파일만 꺼내서 서버에 두면 됩니다.
B-1. tarball 풀어서 dist/ 꺼내기
# 반입한 파일
ls
# → logan-jun-cd1-sample-feature-0.1.0.tgz
tar -xzf logan-jun-cd1-sample-feature-0.1.0.tgz
# → package/ 디렉토리가 생성됨
# package/
# ├── package.json
# ├── README.md
# ├── dist/
# │ ├── index.html
# │ └── assets/
# │ ├── index-<hash>.js
# │ └── index-<hash>.css
# └── host-integration/
# └── RemoteModule.vue참고: npm tarball은 항상 최상위가
package/입니다 (PyPI .whl이 단순 zip인 것과 약간 다른 점).
B-2. 호스트 정적 서버에 배포
호스트 정적 파일 서버(Nginx, Apache, 또는 호스트 백엔드의 static dir) 어디든 OK.
Nginx 사용 예
sudo mkdir -p /var/www/embedded/sample-feature
sudo cp -r package/dist/* /var/www/embedded/sample-feature/
sudo chown -R nginx:nginx /var/www/embedded/sample-feature
ls /var/www/embedded/sample-feature/
# → index.html, assets/Nginx 설정에 location 추가:
server {
# ... 기존 호스트 포털 설정
# 외부 빌드 모듈 정적 서빙
location /embedded/sample-feature/ {
alias /var/www/embedded/sample-feature/;
try_files $uri $uri/ /embedded/sample-feature/index.html;
# 캐시 (선택)
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}sudo nginx -t && sudo nginx -s reload확인:
curl -I https://host-portal.internal/embedded/sample-feature/index.html
# → HTTP/1.1 200 OK권장: 모듈을 호스트와 동일 도메인 하위 경로에 배포하세요. 그래야 origin이 호스트와 같아져서 postMessage origin 검증/CORS/쿠키 처리가 모두 간단해집니다.
B-3. 호스트 프론트엔드 (Vue 2.7) 통합
1) RemoteModule.vue 복사
.tgz 안에 포함된 package/host-integration/RemoteModule.vue 를 호스트 프로젝트로 복사:
cp package/host-integration/RemoteModule.vue \
~/host-frontend-project/src/components/RemoteModule.vue이 컴포넌트는 Vue 2.7 Options API로 작성되어 있고 외부 의존성 없습니다 (Vuetify도 안 씀). 그대로 사용 가능.
2) 라우트 등록 (router/index.js)
import Vue from 'vue'
import VueRouter from 'vue-router'
import RemoteModule from '@/components/RemoteModule.vue'
Vue.use(VueRouter)
const routes = [
// ... 기존 라우트
{
// pathMatch로 sub-route까지 모두 잡음 (모듈 내부 라우팅 대응)
path: '/features/sample/:pathMatch(.*)*',
name: 'sample-feature',
component: {
components: { RemoteModule },
template: `
<v-container fluid class="pa-0 ma-0" style="height: calc(100vh - 64px)">
<RemoteModule
src="/embedded/sample-feature/index.html"
:token="token"
:base-url="apiBase"
:user="user"
sync-route-prefix="/features/sample"
@request-token="onTokenRequest"
@navigate="onModuleNavigate"
/>
</v-container>
`,
computed: {
token() { return this.$store.state.auth.token },
user() { return this.$store.state.auth.user },
apiBase() { return window.location.origin },
},
methods: {
async onTokenRequest() {
await this.$store.dispatch('auth/refresh')
},
onModuleNavigate(path) {
// RemoteModule이 sync-route-prefix 지정 시 내부에서 $router.replace 자동 호출하지만
// 추가 로깅이나 분석 필요 시 여기서.
console.log('[host] module navigated to', path)
},
},
},
},
]
export default new VueRouter({ mode: 'history', routes })3) 메뉴(Vuetify Drawer)에 항목 추가
<v-list-item :to="{ name: 'sample-feature' }">
<v-list-item-icon>
<v-icon>mdi-flask-outline</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Sample Feature (외부 모듈)</v-list-item-title>
</v-list-item-content>
</v-list-item>4) Keycloak 토큰 연동 확인 (Vuex 예시)
// store/modules/auth.js
export default {
namespaced: true,
state: () => ({ token: '', user: null }),
mutations: {
SET_TOKEN: (s, t) => (s.token = t),
SET_USER: (s, u) => (s.user = u),
},
actions: {
initFromKeycloak({ commit }, kc) {
commit('SET_TOKEN', kc.token)
commit('SET_USER', kc.tokenParsed)
kc.onAuthRefreshSuccess = () => commit('SET_TOKEN', kc.token)
kc.onTokenExpired = () => kc.updateToken(30).then(() => commit('SET_TOKEN', kc.token))
},
async refresh({ commit }) {
await window.$keycloak.updateToken(30)
commit('SET_TOKEN', window.$keycloak.token)
},
},
}호스트 부트스트랩(main.js)에서:
keycloak.init({ onLoad: 'login-required' }).then(authenticated => {
if (authenticated) store.dispatch('auth/initFromKeycloak', keycloak)
window.$keycloak = keycloak
new Vue({ router, store, render: h => h(App) }).$mount('#app')
})이후 RemoteModule은 :token="token" prop이 store와 바인딩되어, 토큰 갱신 시 자동 재전송됩니다.
5) 호스트 프론트엔드 재빌드 & 배포
cd ~/host-frontend-project
npm run build # 또는 yarn build (호스트 프로젝트의 빌드 명령어대로)
# dist/ 가 호스트 정적 서버에 반영되도록 배포배포 후 브라우저로 호스트 포털 접속 → 사이드바에서 "Sample Feature" 클릭 → iframe으로 모듈이 자연스럽게 표시됩니다.
C. 업데이트 워크플로우 (버전업 시)
[외부] [폐쇄망]
npm version patch (다음 작업만)
↓
npm publish --access public
↓
npm pack @logan-jun/[email protected]
↓ 반입
tar -xzf ...-0.1.1.tgz
sudo cp -r package/dist/* /var/www/embedded/sample-feature/
(호스트 nginx reload 불필요, 정적 파일 캐시만 무효화)
※ 호스트 Vue 앱 재빌드/재배포 불필요!
(라우트와 RemoteModule.vue는 그대로)핵심: 모듈만 .tgz로 교체 → 호스트는 재빌드 없음. 메뉴 라우트와 통합 코드는 한 번만 작업하면 됩니다.
D. 메시지 프로토콜 (참고)
| 방향 | type | payload | 용도 |
|---|---|---|---|
| Host → Module | init | { token, baseUrl, user } | 초기 컨텍스트 |
| Host → Module | token | { token } | 토큰 갱신 알림 |
| Host → Module | route | { path } | 호스트 라우트 변경 동기화 |
| Module → Host | ready | — | 마운트 완료 |
| Module → Host | height | { value } | iframe 자동 리사이즈 |
| Module → Host | navigate | { path } | 모듈 내부 라우팅 변경 |
| Module → Host | request-token | — | 토큰 만료 시 갱신 요청 |
E. 보안 체크리스트 (운영 전)
- [ ] 모듈
bridge.ts의ALLOWED_ORIGIN을'*'→ 호스트 도메인으로 변경 후 재빌드 - [ ]
RemoteModule.vue의allowedOriginprop이 정확한 호스트 origin - [ ] 모듈을 호스트와 동일 도메인 하위 경로에 배포
- [ ] 호스트 응답 헤더에
Content-Security-Policy: frame-src 'self'추가 - [ ] 토큰을 query string으로 전달하지 말 것 (postMessage만 사용 — 현재 구조가 그러함)
- [ ] iframe sandbox 검토: 필요 시
sandbox="allow-scripts allow-same-origin"추가
F. 트러블슈팅
| 증상 | 원인/해결 |
|---|---|
| iframe 안에서 자산이 404 | nginx try_files의 fallback이 /embedded/sample-feature/index.html 인지 확인. base: './' 빌드로 상대경로 자산이므로 alias 끝의 / 빠지면 깨짐 |
| 토큰이 모듈에 안 들어옴 | 브라우저 콘솔에서 [sample-feature] session updated 로그 확인. 호스트 측 RemoteModule의 onLoad/onMessage(ready) 발화 여부 확인 |
| iframe 높이가 안 늘어남 | 모듈 ResizeObserver는 document.documentElement 기준. 모듈 내부 CSS가 height 100% 강제하면 보고 값이 작게 나옴 — App.vue 루트에 고정 높이 제거 |
| 모듈 내부 모달이 호스트 navbar를 못 가림 | iframe 영역 밖으로 못 나가는 게 정상 (격리 비용). 풀스크린 모달이 필요하면 모듈에서 notifyNavigate 후 호스트가 별도 라우트로 가도록 설계 |
| npm publish 시 403 | 스코프 패키지인데 --access public 빠짐. 또는 npm whoami 결과가 스코프 owner와 다름 |
