npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@logan-jun/cd1-sample-feature

v0.3.0

Published

CD1 Agent - iframe-embedded micro-frontend sample module (Vue 2.7, closed-network friendly).

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.tsALLOWED_ORIGIN'*' → 호스트 도메인으로 변경 후 재빌드
  • [ ] RemoteModule.vueallowedOrigin prop이 정확한 호스트 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 로그 확인. 호스트 측 RemoteModuleonLoad/onMessage(ready) 발화 여부 확인 | | iframe 높이가 안 늘어남 | 모듈 ResizeObserver는 document.documentElement 기준. 모듈 내부 CSS가 height 100% 강제하면 보고 값이 작게 나옴 — App.vue 루트에 고정 높이 제거 | | 모듈 내부 모달이 호스트 navbar를 못 가림 | iframe 영역 밖으로 못 나가는 게 정상 (격리 비용). 풀스크린 모달이 필요하면 모듈에서 notifyNavigate 후 호스트가 별도 라우트로 가도록 설계 | | npm publish 시 403 | 스코프 패키지인데 --access public 빠짐. 또는 npm whoami 결과가 스코프 owner와 다름 |