PageSourceSearch

https://ianccy.com/createsdk/

html ianccy.com collected 2026-10-02 01:31:54 UTC 76,153 bytes, 351 lines download raw bytes

1<!DOCTYPE html><html lang="en" class="theme-dark"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="ie=edge"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta name="generator" content="Gatsby 5.13.1"/><meta data-react-helmet="true" name="description" content="去年在公司做了一個滿好玩的專案,要建立一個 JavaScript tracking sdk,透過客戶的 google tag manager 安裝,收集客戶的 user 使用資料,能做到部署 code 後,每個客戶都自動更新成最新的版本,額外要做到版本控制,讓部分客戶選擇安裝舊版的 tracking sdk…"/><meta data-react-helmet="true" name="og:description" content="去年在公司做了一個滿好玩的專案,要建立一個 JavaScript tracking sdk,透過客戶的 google tag manager 安裝,收集客戶的 user 使用資料,能做到部署 code 後,每個客戶都自動更新成最新的版本,額外要做到版本控制,讓部分客戶選擇安裝舊版的 tracking sdk…"/><meta data-react-helmet="true" name="og:title" content="How to create JavaScript web sdk"/><meta data-react-helmet="true" name="og:type" content="website"/><meta data-react-helmet="true" name="og:image" content="/static/4145ff3a80750cd01cb79e68371a4484/createlibrary.png"/><meta data-react-helmet="true" name="twitter:card" content="summary"/><meta data-react-helmet="true" name="twitter:title" content="How to create JavaScript web sdk"/><meta data-react-helmet="true" name="twitter:description" content="去年在公司做了一個滿好玩的專案,要建立一個 JavaScript tracking sdk,透過客戶的 google tag manager 安裝,收集客戶的 user 使用資料,能做到部署 code 後,每個客戶都自動更新成最新的版本,額外要做到版本控制,讓部分客戶選擇安裝舊版的 tracking sdk…"/><style data-href="/styles.acebf6de3a796af9dd36.css" data-identity="gatsby-global-css">/*
2! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com
3*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}
3[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}html{background:#333;font-family:San Francisco,PingFangSC-Regular,Microsoft JhengHei,Open Sans,sans-serif,Heiti TC}h1{font-size:2.25rem;line-height:2.5rem}.article>*{margin-bottom:.5rem}.article>p{line-height:1.625;margin-bottom:1.25rem}.article>h2{color:var(--text-secondary);font-size:1.5rem;font-weight:500;line-height:2rem;margin-top:2rem}.article>h3{font-size:1.25rem;margin-top:1.5rem}.article>h3,.article>h4,.article>h5,.article>h6{color:var(--text-secondary);font-weight:500;line-height:1.75rem}.article>h4,.article>h5,.article>h6{font-size:1.125rem;margin-top:1rem}.article>.gatsby-highlight{margin-bottom:1.25rem}.article a{--tw-shadow:0px 1px 0px 0px;--tw-shadow-colored:0px 1px 0px 0px var(--tw-shadow-color);--tw-shadow-color:currentColor;--tw-shadow:var(--tw-shadow-colored);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-sha
3dow)}.article a.anchor.before{box-shadow:none;transform:translate(-100%,50%)}.article ul li{list-style:inside}a{color:var(--textLink)}a:hover{opacity:.75}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.link{color:var(--text-main);display:block;padding-left:.5rem;padding-right:.5rem}.link-active{color:var(--text-secondary)}.fixed{position:fixed!important}.absolute{position:absolute!important}.sticky{position:sticky!important}.inset-x-0{left:0!important}.inset-x-0,.right-0{right:0!important}.right-7{right:1.75rem!important}.top-0{top:0!important}.top-10{top:2.5rem!important}.z-10{z-index:10!important}.col-span-2{grid-column:span 2/span 2!important}.-my-2{margin-bottom:-.5rem!important;margin-top:-.5rem!important}.mx-6{margin-left:1.5rem!important;margin-right:1.5rem!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-10{margin-bottom:2.5rem!important;margin-top:2.5rem!important}.my-2{margin-bottom:.5rem!important;margin-top:.5rem!important}.my-4{margin-bottom:1rem!important;margin-top:1rem!important}.-mr-2{margin-right:-.5rem!important}.mb-10{margin-bottom:2.5rem!important}.mb-4{margin-bottom:1rem!important}.mb-6{margin-bottom:1.5rem!important}.mb-8{margin-bottom:2rem!important}.mr-2{margin-right:.5rem!important}.mr-4{margin-right:1rem!important}.mt-4{margin-top:1rem!important}.line-clamp-3{-webkit-box-orient:vertical!important;-webkit-line-clamp:3!important;display:-webkit-box!important;overflow:hidden!important}.flex{display:flex!important}.inline-flex{display:inline-flex!important}.grid{display:grid!important}.hidden{display:none!important}.h-6{height:1.5rem!important}.h-fit{height:-moz-fit-content!important;height:fit-content!important}.h-full{height:100%!important}.min-h-screen{min-height:100vh!important}.w-52{width:13rem!important}.w-6{width:1.5rem!important}.w-full{width:100%!important}.grow{flex-grow:1!important}.origin-top-right{transform-origin:top right!important}.-translate-x-1\/3{--tw-translate-x:-33.333333%!important}.-translate-x-1\/3,.translate-y-28{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.translate-y-28{--tw-translate-y:7rem!important}.scale-100{--tw-scale-x:1!important;--tw-scale-y:1!important}.scale-100,.scale-75{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.scale-75{--tw-scale-x:.75!important;--tw-scale-y:.75!important}.scale-95{--tw-scale-x:.95!important;--tw-scale-y:.95!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.content-center{align-content:center!important}.items-center{align-items:center!important}.justify-start{justify-content:flex-start!important}.justify-end{justify-content:flex-end!important}.justify-center{justify-content:center!important}.justify-between{justify-content:space-between!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-4{gap:1rem!important}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0!important;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))!important;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))!important}.divide-y-2>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0!important;border-bottom-width:calc(2px*var(--tw-divide-y-reverse))!important;border-top-width:calc(2px*(1 - var(--tw-divide-y-reverse)))!important}.divide-gray-50>:not([hidden])~:not([hidden]){--tw-divide-opacity:1!important;border-color:rgb(249 250 251/var(--tw-divide-opacity))!important}.self-center{align-self:center!important}.justify-self-end{justify-self:end!important}.overflow-hidden{overflow:hidden!important}.text-ellipsis{text-overflow:ellipsis!important}.whitespace-nowrap{white-space:nowrap!important}.rounded{border-radius:.25rem!important}.rounded-lg{border-radius:.5rem!important}.rounded-md{border-radius:.375rem!important}.bg-primary{background-color:var(--primary)!important}.bg-secondary{background-color:var(--secondary)!important}.p-2{padding:.5rem!important}.px-0{padding-left:0!important;padding-right:0!important}.px-0\.5{padding-left:.125rem!important;padding-right:.125rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.px-5{padding-left:1.25rem!important;padding-right:1.25rem!important}.py-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.py-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.py-4{padding-bottom:1rem!important;padding-top:1rem!important}.py-6{padding-bottom:1.5rem!important;padding-top:1.5rem!important}.pb-10{padding-bottom:2.5rem!important}.pb-2{padding-bottom:.5rem!important}.pb-20{padding-bottom:5rem!important}.pb-6{padding-bottom:1.5rem!important}.pt-2{padding-top:.5rem!important}.pt-6{padding-top:1.5rem!important}.text-center{text-align:center!important}.text-2xl{font-size:1.5rem!important;line-height:2rem!important}.text-base{font-size:1rem!important;line-height:1.5rem!important}.text-lg{font-size:1.125rem!important;line-height:1.75rem!important}.text-sm{font-size:.875rem!important;line-height:1.25rem!important}.text-xl{font-size:1.25rem!important;line-height:1.75rem!important}.font-bold{font-weight:700!important}.font-medium{font-weight:500!important}.font-semibold{font-weight:600!important}.font-thin{font-weight:100!important}.leading-6{line-height:1.5rem!important}.leading-tight{line-height:1.25!important}.tracking-widest{letter-spacing:.1em!important}.text-main-text{color:var(--text-main)!important}.opacity-0{opacity:0!important}.opacity-100{opacity:1!important}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1)!important;
3--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)!important;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)!important}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)!important}.transition{transition-duration:.15s!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter!important;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important}.transition-all{transition-duration:.15s!important;transition-property:all!important;transition-timing-function:cubic-bezier(.4,0,.2,1)!important}.duration-100{transition-duration:.1s!important}.duration-150{transition-duration:.15s!important}.duration-200{transition-duration:.2s!important}.duration-300{transition-duration:.3s!important}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)!important}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)!important}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)!important}.theme-light{--primary:#f8f8f8;--secondary:#e5e7eb;--text-main:#555;--text-secondary:#393838;--textLink:#000;--accent:#009ffd}.theme-dark{--primary:#333;--secondary:#4d4d4d;--text-main:#e6e6e6;--text-secondary:#e6e6e6;--textLink:#fff;--accent:#fc7753}.toc-list{display:none;opacity:.6}.toc-list:hover{opacity:1}@media (min-width:1280px){.toc-list{display:block}}.toc-list a{color:inherit}.toc-list a:hover{color:var(--text-secondary)}.toc-list li,.toc-list p{margin-bottom:.25rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toc-list ul ul{padding-left:1rem}body .token.cdata,body .token.comment,body .token.prolog{color:#8d8d8d}.hover\:opacity-75:hover{opacity:.75!important}.focus\:outline-none:focus{outline:2px solid transparent!important;outline-offset:2px!important}@media (min-width:640px){.sm\:px-4{padding-left:1rem!important;padding-right:1rem!important}}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2!important}.md\:col-span-4{grid-column:span 4/span 4!important}.md\:mx-0{margin-left:0!important;margin-right:0!important}.md\:mt-0{margin-top:0!important}.md\:block{display:block!important}.md\:flex{display:flex!important}.md\:hidden{display:none!important}.md\:w-auto{width:auto!important}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))!important}.md\:flex-row{flex-direction:row!important}.md\:justify-between{justify-content:space-between!important}.md\:gap-20{gap:5rem!important}.md\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0!important;margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))!important;margin-right:calc(2rem*var(--tw-space-x-reverse))!important}.md\:py-0{padding-bottom:0!important;padding-top:0!important}.md\:pr-12{padding-right:3rem!important}.md\:text-sm{font-size:.875rem!important;line-height:1.25rem!important}.md\:font-medium{font-weight:500!important}}@media (min-width:1024px){.lg\:max-w-screen-lg{max-width:1024px!important}.lg\:max-w-screen-md{max-width:768px!important}}@media (min-width:1536px){.\32xl\:-translate-x-full{--tw-translate-x:-100%!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}}.switch-theme{-webkit-user-select:none;-moz-user-select:none;user-select:none}.switch-theme label{background-color:#000;border-radius:45px;cursor:pointer;display:block;height:28px;margin:0 auto;overflow:hidden;position:relative;width:64px}.switch-theme input[type=checkbox]+label:before{display:none}.switch-container{display:flex}#toggle_checkbox_1,#toggle_checkbox_2{display:none}#toggle_checkbox_1+label,#toggle_checkbox_2+label{background-color:#000}#toggle_checkbox_1+label #star,#toggle_checkbox_2+label #star{background-color:#ff0;right:4px;top:1px;transform:scale(.5)}#star{background-color:#ff0;border-radius:50%;height:20px;left:31px;position:absolute;top:4px;transform:scale(1);transition:top .3s ease,left .3s ease,transform .3s ease;width:20px;z-index:1}#toggle_checkbox_1+label .star,#toggle_checkbox_2+label .star{color:#ff0}#star-1{position:relative}.star{color:#fbff01;font-size:28px;left:-4px;line-height:28px;top:-6px;transition:color .3s ease}#star-2{position:absolute;transform:rotate(36deg)}#toggle_checkbox_1:checked+label #moon,#toggle_checkbox_2:checked+label #moon{bottom:-52px}#moon{background-color:#fff;bottom:2px;height:25px;right:5px;transition:bottom .3s ease;width:25px}#moon,#moon:before{border-radius:50%;position:absolute}#moon:before{content:"";height:35px;left:-20px;top:-12px;width:35px}#moon:before,#toggle_checkbox_1+label #moon:before,#toggle_checkbox_2+label #moon:before{background-color:#000}#toggle_checkbox_1:checked+label #star,#toggle_checkbox_2:checked+label #star{left:5px;top:4px;transform:scale(.8)}code[class*=language-],pre[class*=language-]{background:#272b33;color:#aab1bf;direction:ltr;
3font-family:Fira Code,Fira Mono,Menlo,Consolas,DejaVu Sans Mono,monospace;-webkit-hyphens:none;hyphens:none;line-height:1.5;-moz-tab-size:2;-o-tab-size:2;tab-size:2;text-align:left;text-shadow:0 1px rgba(0,0,0,.3);white-space:pre;word-break:normal;word-spacing:normal}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection{background:#3e4450;color:inherit;text-shadow:none}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection{background:#3e4450;color:inherit;text-shadow:none}pre[class*=language-]{border-radius:.3em;margin:.5em 0;overflow:auto;padding:1em}:not(pre)>code[class*=language-]{border-radius:.3em;padding:.2em .3em;white-space:normal}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}.token.cdata,.token.comment,.token.prolog{color:#5b6270}.token.doctype,.token.entity,.token.punctuation{color:#aab1bf}.token.atrule,.token.attr-name,.token.boolean,.token.class-name,.token.constant,.token.number{color:#d19965}.token.keyword{color:#c578dd}.token.deleted,.token.important,.token.property,.token.symbol,.token.tag{color:#df6b75}.token.attr-value,.token.attr-value>.token.punctuation,.token.builtin,.token.char,.token.inserted,.token.regex,.token.selector,.token.string{color:#97c279}.token.function,.token.operator,.token.variable{color:#61afef}.token.url{color:#56b5c2}.token.attr-value>.token.punctuation.attr-equals,.token.special-attr>.token.attr-value>.token.value.css{color:#aab1bf}.language-css .token.selector{color:#df6b75}.language-css .token.property{color:#aab1bf}.language-css .token.function,.language-css .token.url>.token.function{color:#56b5c2}.language-css .token.url>.token.string.url{color:#97c279}.language-css .token.atrule .token.rule,.language-css .token.important,.language-javascript .token.operator{color:#c578dd}.language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#be5046}.language-json .token.operator{color:#aab1bf}.language-json .token.null.keyword{color:#d19965}.language-markdown .token.url,.language-markdown .token.url-reference.url>.token.string,.language-markdown .token.url>.token.operator{color:#aab1bf}.language-markdown .token.url>.token.content{color:#61afef}.language-markdown .token.url-reference.url,.language-markdown .token.url>.token.url{color:#56b5c2}.language-markdown .token.blockquote.punctuation,.language-markdown .token.hr.punctuation{color:#5b6270;font-style:italic}.language-markdown .token.code-snippet{color:#97c279}.language-markdown .token.bold .token.content{color:#d19965}.language-markdown .token.italic .token.content{color:#c578dd}.language-markdown .token.list.punctuation,.language-markdown .token.strike .token.content,.language-markdown .token.strike .token.punctuation,.language-markdown .token.title.important>.token.punctuation{color:#df6b75}.token.bold{font-weight:700}.token.comment,.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.8}.token.token.cr:before,.token.token.lf:before,.token.token.space:before,.token.token.tab:not(:empty):before{color:hsla(220,14%,71%,.15);text-shadow:none}div.code-toolbar>.toolbar.toolbar>.toolbar-item{margin-right:.4em}div.code-toolbar>.toolbar.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar.toolbar>.toolbar-item>span{background:#393f4a;border-radius:.3em;color:#818896;padding:.1em .4em}div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:hover{background:#3e4450;color:#aab1bf}.line-highlight.line-highlight{background:rgba(153,187,255,.04)}.line-highlight.line-highlight:before,.line-highlight.line-highlight[data-end]:after{background:#393f4a;border-radius:.3em;box-shadow:0 2px 0 0 rgba(0,0,0,.2);color:#aab1bf;padding:.1em .6em}pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(153,187,255,.04)}.command-line .command-line-prompt,.line-numbers.line-numbers .line-numbers-rows{border-right-color:hsla(220,14%,71%,.15)}.command-line .command-line-prompt>
3span:before,.line-numbers .line-numbers-rows>span:before{color:#626d82}.rainbow-braces .token.token.punctuation.brace-level-1,.rainbow-braces .token.token.punctuation.brace-level-5,.rainbow-braces .token.token.punctuation.brace-level-9{color:#df6b75}.rainbow-braces .token.token.punctuation.brace-level-10,.rainbow-braces .token.token.punctuation.brace-level-2,.rainbow-braces .token.token.punctuation.brace-level-6{color:#97c279}.rainbow-braces .token.token.punctuation.brace-level-11,.rainbow-braces .token.token.punctuation.brace-level-3,.rainbow-braces .token.token.punctuation.brace-level-7{color:#61afef}.rainbow-braces .token.token.punctuation.brace-level-12,.rainbow-braces .token.token.punctuation.brace-level-4,.rainbow-braces .token.token.punctuation.brace-level-8{color:#c578dd}pre.diff-highlight>code .token.token.deleted:not(.prefix),pre>code.diff-highlight .token.token.deleted:not(.prefix){background-color:rgba(255,82,102,.15)}pre.diff-highlight>code .token.token.deleted:not(.prefix) ::-moz-selection,pre.diff-highlight>code .token.token.deleted:not(.prefix)::-moz-selection,pre>code.diff-highlight .token.token.deleted:not(.prefix) ::-moz-selection,pre>code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection{background-color:rgba(251,86,105,.25)}pre.diff-highlight>code .token.token.deleted:not(.prefix) ::selection,pre.diff-highlight>code .token.token.deleted:not(.prefix)::selection,pre>code.diff-highlight .token.token.deleted:not(.prefix) ::selection,pre>code.diff-highlight .token.token.deleted:not(.prefix)::selection{background-color:rgba(251,86,105,.25)}pre.diff-highlight>code .token.token.inserted:not(.prefix),pre>code.diff-highlight .token.token.inserted:not(.prefix){background-color:rgba(25,255,91,.15)}pre.diff-highlight>code .token.token.inserted:not(.prefix) ::-moz-selection,pre.diff-highlight>code .token.token.inserted:not(.prefix)::-moz-selection,pre>code.diff-highlight .token.token.inserted:not(.prefix) ::-moz-selection,pre>code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection{background-color:rgba(56,224,98,.25)}pre.diff-highlight>code .token.token.inserted:not(.prefix) ::selection,pre.diff-highlight>code .token.token.inserted:not(.prefix)::selection,pre>code.diff-highlight .token.token.inserted:not(.prefix) ::selection,pre>code.diff-highlight .token.token.inserted:not(.prefix)::selection{background-color:rgba(56,224,98,.25)}.prism-previewer-gradient.prism-previewer-gradient div,.prism-previewer.prism-previewer:before{border-color:#252830}.prism-previewer-color.prism-previewer-color:before,.prism-previewer-easing.prism-previewer-easing:before,.prism-previewer-gradient.prism-previewer-gradient div{border-radius:.3em}.prism-previewer.prism-previewer:after{border-top-color:#252830}.prism-previewer-flipped.prism-previewer-flipped.after{border-bottom-color:#252830}.prism-previewer-angle.prism-previewer-angle:before,.prism-previewer-easing.prism-previewer-easing,.prism-previewer-time.prism-previewer-time:before{background:#30353f}.prism-previewer-angle.prism-previewer-angle circle,.prism-previewer-time.prism-previewer-time circle{stroke:#aab1bf;stroke-opacity:1}.prism-previewer-easing.prism-previewer-easing circle,.prism-previewer-easing.prism-previewer-easing line,.prism-previewer-easing.prism-previewer-easing path{stroke:#aab1bf}.prism-previewer-easing.prism-previewer-easing circle{fill:transparent}</style><style>.gatsby-image-wrapper{position:relative;overflow:hidden}.gatsby-image-wrapper picture.object-fit-polyfill{position:static!important}.gatsby-image-wrapper img{bottom:0;height:100%;left:0;margin:0;max-width:none;padding:0;position:absolute;right:0;top:0;width:100%;object-fit:cover}.gatsby-image-wrapper [data-main-image]{opacity:0;transform:translateZ(0);transition:opacity .25s linear;will-change:opacity}.gatsby-image-wrapper-constrained{display:inline-block;vertical-align:top}</style><noscript><style>.gatsby-image-wrapper noscript [data-main-image]{opacity:1!important}.gatsby-image-wrapper [data-placeholder-image]{opacity:0!important}</style></noscript>
3<script type="module">const e="undefined"!=typeof HTMLImageElement&&"loading"in HTMLImageElement.prototype;e&&document.body.addEventListener("load",(function(e){const t=e.target;if(void 0===t.dataset.mainImage)return;if(void 0===t.dataset.gatsbyImageSsr)return;let a=null,n=t;for(;null===a&&n;)void 0!==n.parentNode.dataset.gatsbyImageWrapper&&(a=n.parentNode),n=n.parentNode;const o=a.querySelector("[data-placeholder-image]"),r=new Image;r.src=t.currentSrc,r.decode().catch((()=>{})).then((()=>{t.style.opacity=1,o&&(o.style.opacity=0,o.style.transition="opacity 500ms linear")}))}),!0);</script>
3<title data-react-helmet="true">How to create JavaScript web sdk | Ian Chu</title><link rel="sitemap" type="application/xml" href="/sitemap-index.xml"/><link rel="icon" href="/favicon-32x32.png?v=2105df10e00917735a11a00db004e7fd" type="image/png"/><link rel="manifest" href="/manifest.webmanifest" crossorigin="anonymous"/><link rel="apple-touch-icon" sizes="48x48" href="/icons/icon-48x48.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="96x96" href="/icons/icon-96x96.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="144x144" href="/icons/icon-144x144.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="192x192" href="/icons/icon-192x192.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="256x256" href="/icons/icon-256x256.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="384x384" href="/icons/icon-384x384.png?v=2105df10e00917735a11a00db004e7fd"/><link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png?v=2105df10e00917735a11a00db004e7fd"/><style type="text/css">
4    .anchor.before {
5      position: absolute;
6      top: 0;
7      left: 0;
8      transform: translateX(-100%);
9      padding-right: 4px;
10    }
11    .anchor.after {
12      display: inline-block;
13      padding-left: 4px;
14    }
15    h1 .anchor svg,
16    h2 .anchor svg,
17    h3 .anchor svg,
18    h4 .anchor svg,
19    h5 .anchor svg,
20    h6 .anchor svg {
21      visibility: hidden;
22    }
23    h1:hover .anchor svg,
24    h2:hover .anchor svg,
25    h3:hover .anchor svg,
26    h4:hover .anchor svg,
27    h5:hover .anchor svg,
28    h6:hover .anchor svg,
29    h1 .anchor:focus svg,
30    h2 .anchor:focus svg,
31    h3 .anchor:focus svg,
32    h4 .anchor:focus svg,
33    h5 .anchor:focus svg,
34    h6 .anchor:focus svg {
35      visibility: visible;
36    }
37  </style>
37<script>
38    document.addEventListener("DOMContentLoaded", function(event) {
39      var hash = window.decodeURI(location.hash.replace('#', ''))
40      if (hash !== '') {
41        var element = document.getElementById(hash)
42        if (element) {
43          var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
44          var clientTop = document.documentElement.clientTop || document.body.clientTop || 0
45          var offset = element.getBoundingClientRect().top + scrollTop - clientTop
46          // Wait for the browser to finish rendering before scrolling.
47          setTimeout((function() {
48            window.scrollTo(0, offset - 0)
49          }), 0)
50        }
51      }
52    })
53  </script>
53<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl+'';f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer', 'GTM-KH6W3QV');</script>
53<link rel="canonical" href="https://ianccy.com/createsdk/" data-baseprotocol="https:" data-basehost="ianccy.com"/></head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-KH6W3QV" height="0" width="0" style="display: none; visibility: hidden" aria-hidden="true"></iframe></noscript><div id="___gatsby"><div style="outline:none" tabindex="-1" id="gatsby-focus-wrapper"><div class="bg-primary text-main-text transition-all duration-300 min-h-screen flex flex-col"><nav class="mx-6 sm:px-4 py-4 rounded"><div class="container flex flex-wrap justify-between items-center mx-auto"><a class="flex items-center" href="/"><span class="self-center text-xl font-semibold whitespace-nowrap tracking-widest">Ian Chu</span></a><div class="flex items-center -mr-2 -my-2 md:hidden"><div class="flex mr-2 items-center"><div class="switch-container"><div class="switch-theme"><input id="toggle_checkbox_1" type="checkbox"/><label for="toggle_checkbox_1"><div id="star"><div class="star" id="star-1">★</div><div class="star" id="star-2">★</div></div><div id="moon"></div></label></div></div></div><button type="button" title="menu-button" class="inline-flex items-center justify-center p-2 rounded-md focus:outline-none transition duration-150 ease-in-out"><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg></button></div><div class="hidden w-full md:flex md:w-auto items-center" id="navbar-default"><ul class="flex flex-col mt-4 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium mr-4"><li><a class="link py-4 false" href="/">Home</a></li><li><a class="link py-4 false" href="/about/">About</a></li><li><a href="https://www.facebook.com/chu1228" class="link py-4">Facebook</a></li></ul><div class="flex items-center"><div class="switch-container"><div class="switch-theme"><input id="toggle_checkbox_2" type="checkbox"/><label for="toggle_checkbox_2"><div id="star"><div class="star" id="star-1">★</div><div class="star" id="star-2">★</div></div><div id="moon"></div></label></div></div></div></div></div></nav><div class="grow mx-6 md:mx-0"><div><div class="lg:max-w-screen-lg mx-auto"><header><h1 class="text-center py-4 my-10 font-bold leading-tight" itemProp="headline">How to create JavaScript web sdk</h1><div><div data-gatsby-image-wrapper="" class="gatsby-image-wrapper gatsby-image-wrapper-constrained mb-10"><div style="max-width:1280px;
53display:block"><img alt="" role="presentation" aria-hidden="true" src="data:image/svg+xml;charset=utf-8,%3Csvg%20height=&#x27;679.5562599049128&#x27;%20width=&#x27;1280&#x27;%20xmlns=&#x27;http://www.w3.org/2000/svg&#x27;%20version=&#x27;1.1&#x27;%3E%3C/svg%3E" style="max-width:100%;display:block;position:static"/></div><div aria-hidden="true" data-placeholder-image="" style="opacity:1;transition:opacity 500ms linear;background-color:#181818;position:absolute;top:0;left:0;bottom:0;right:0"></div><picture><source type="image/webp" data-srcset="/static/4145ff3a80750cd01cb79e68371a4484/a3156/createlibrary.webp 158w,/static/4145ff3a80750cd01cb79e68371a4484/a418d/createlibrary.webp 316w,/static/4145ff3a80750cd01cb79e68371a4484/334c8/createlibrary.webp 631w" sizes="(min-width: 631px) 631px, 100vw"/><img data-gatsby-image-ssr="" data-main-image="" style="opacity:0" sizes="(min-width: 631px) 631px, 100vw" decoding="async" loading="lazy" data-src="/static/4145ff3a80750cd01cb79e68371a4484/3e766/createlibrary.jpg" data-srcset="/static/4145ff3a80750cd01cb79e68371a4484/ef064/createlibrary.jpg 158w,/static/4145ff3a80750cd01cb79e68371a4484/7135c/createlibrary.jpg 316w,/static/4145ff3a80750cd01cb79e68371a4484/3e766/createlibrary.jpg 631w" alt="How to create JavaScript web sdk"/></picture><noscript><picture><source type="image/webp" srcSet="/static/4145ff3a80750cd01cb79e68371a4484/a3156/createlibrary.webp 158w,/static/4145ff3a80750cd01cb79e68371a4484/a418d/createlibrary.webp 316w,/static/4145ff3a80750cd01cb79e68371a4484/334c8/createlibrary.webp 631w" sizes="(min-width: 631px) 631px, 100vw"/><img data-gatsby-image-ssr="" data-main-image="" style="opacity:0" sizes="(min-width: 631px) 631px, 100vw" decoding="async" loading="lazy" src="/static/4145ff3a80750cd01cb79e68371a4484/3e766/createlibrary.jpg" srcSet="/static/4145ff3a80750cd01cb79e68371a4484/ef064/createlibrary.jpg 158w,/static/4145ff3a80750cd01cb79e68371a4484/7135c/createlibrary.jpg 316w,/static/4145ff3a80750cd01cb79e68371a4484/3e766/createlibrary.jpg 631w" alt="How to create JavaScript web sdk"/></picture></noscript>
53<script type="module">const t="undefined"!=typeof HTMLImageElement&&"loading"in HTMLImageElement.prototype;if(t){const t=document.querySelectorAll("img[data-main-image]");for(let e of t){e.dataset.src&&(e.setAttribute("src",e.dataset.src),e.removeAttribute("data-src")),e.dataset.srcset&&(e.setAttribute("srcset",e.dataset.srcset),e.removeAttribute("data-srcset"));const t=e.parentNode.querySelectorAll("source[data-srcset]");for(let e of t)e.setAttribute("srcset",e.dataset.srcset),e.removeAttribute("data-srcset");e.complete&&(e.style.opacity=1,e.parentNode.parentNode.querySelector("[data-placeholder-image]").style.opacity=0)}}</script>
53</div></div></header></div><div class="sticky top-10 translate-y-28"><div class="toc-list absolute w-52 right-0 -translate-x-1/3 overflow-hidden 2xl:-translate-x-full"><ul>
54<li><a href="#planning">Planning</a></li>
55<li><a href="#develop-setup">Develop Setup</a></li>
56<li><a href="#bundle-setup">Bundle Setup</a></li>
57<li><a href="#cicd-setup">CI/CD Setup</a></li>
58<li><a href="#%E6%8E%A5%E4%B8%8B%E4%BE%86%E8%A8%AD%E5%AE%9A-custom-domain">接下來設定 custom domain</a></li>
59<li><a href="#push-tag-and-create-release-with-tag">Push Tag and Create Release with Tag</a></li>
60<li><a href="#%E5%BF%83%E5%BE%97">心得</a></li>
61</ul></div></div><div><div class="container mx-auto py-4 sm:px-4 lg:max-w-screen-md"><div class="mb-4"><ol class="flex gap-1" itemtype="http://schema.org/BreadcrumbList"><li class="flex gap-1 false" itemprop="itemListElement" itemtype="https://schema.org/ListItem"><a class="font-medium false" itemprop="item" href="/categories/javascript/"><span itemprop="name">javascript</span></a><meta itemprop="position" content="1"/><div class="font-thin	">/</div></li><li class="flex gap-1 whitespace-nowrap overflow-hidden" itemprop="itemListElement" itemtype="https://schema.org/ListItem"><a aria-current="page" class="font-medium text-ellipsis whitespace-nowrap overflow-hidden" itemprop="item" href="/createsdk/"><span itemprop="name">How to create JavaScript web sdk</span></a><meta itemprop="position" content="2"/></li></ol></div><div class="flex justify-between mb-8"><div class="flex gap-1"><p class="font-medium">February 11, 2024</p><div>·</div><p>11 min read</p></div><div class="flex gap-2"><button quote="去年在公司做了一個滿好玩的專案,要建立一個 JavaScript tracking sdk,透過客戶的 google tag manager 安裝,收集客戶的 user 使用資料,能做到部署 code 後,每個客戶都自動更新成最新的版本,額外要做到版本控制,讓部分客戶選擇安裝舊版的 tracking sdk…" class="react-share__ShareButton h-fit hover:opacity-75" style="background-color:transparent;border:none;padding:0;font:inherit;color:inherit;cursor:pointer"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg></button><button class="react-share__ShareButton h-fit hover:opacity-75" style="background-color:transparent;border:none;padding:0;font:inherit;color:inherit;cursor:pointer"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg></button></div></div><article class="mb-8" itemscope="" itemType="http://schema.org/Article"><section class="article" itemProp="articleBody"><p>去年在公司做了一個滿好玩的專案,要建立一個 JavaScript tracking sdk,透過客戶的 google tag manager 安裝,收集客戶的 user 使用資料,能做到部署 code 後,每個客戶都自動更新成最新的版本,額外要做到版本控制,讓部分客戶選擇安裝舊版的 tracking sdk,uglify code 不讓邏輯被輕易了解,至於後面業務細節就不提太多了。</p>
62<p>後面會分享 JavaScript sdk 我是如何規劃、建立這個專案的,然後在把整個想法實作出來。</p>
63<h2 id="planning" style="position:relative;"><a href="#planning" aria-label="planning permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Planning</h2>
64<p>先從最基本的 coding tool 來想,現況前端都會要求程式碼要有型別,日後維運會方便很多,所以要有 TypeScript,再來是 bundler,打包程式碼並且 minify 加上 uglify,能減少 code file size,不要讓 code 業務邏輯輕易被看光,這邊我選用 rollup,理由是搭配 typescript setup 容易,有 tree shaking 來減低打包檔案大小,提供的 plugin 也足夠使用。</p>
65<p>再來就是 deploy 要 deploy 到哪裡,這邊選擇相對多,可以是 s3、gcs 或是 github hosting,公司專案我是選用 gcs,因為專案都是用 google cloud 的,另外用 cloud storage 還可以額外 setup meta header。</p>
66<p>還有 CI/CD 要能自動化 deploy,避免手動作業流程,再來還有 javascript 搭配 cdn,提高我們 file 被載入的速度,順便還可以處理 custom domain。</p>
67<p>也要有清除 cdn cache 的機制,當更新版本後,能讓客戶快速取得最新版本的 file。</p>
68<p>所以這邊我需要有 bundler、build version control、CI/CD deploy、hosting javascript file、CDN setup。</p>
69<p><figure class="gatsby-resp-image-figure" style="margin: 2vw 0;">
70    <span
71      class="gatsby-resp-image-wrapper"
72      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1980px; "
73    >
74      <a
75    class="gatsby-resp-image-link"
76    href="/static/111dea7607a2b937429cba3e2a6f9b30/d8012/createlibrary-flow.png"
77    style="display: block"
78    target="_blank"
79    rel="noopener"
80  >
81    <span
82    class="gatsby-resp-image-background-image"
83    style="padding-bottom: 7.07070707070707%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAABCAYAAADeko4lAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAOklEQVR42j2LwQ4AIAhC+/9PbWtuKlonMw4dGOMBw5BlsUvb1YPuP4OdqFMWr2veDHm4mUtKDOW5+bs3Uk0x8p8ZCwAAAABJRU5ErkJggg=='); background-size: cover; display: block;"
84  ></span>
85  <picture>
86          <source
87              srcset="/static/111dea7607a2b937429cba3e2a6f9b30/a805d/createlibrary-flow.webp 495w,
88/static/111dea7607a2b937429cba3e2a6f9b30/36741/createlibrary-flow.webp 990w,
89/static/111dea7607a2b937429cba3e2a6f9b30/41a9f/createlibrary-flow.webp 1980w,
90/static/111dea7607a2b937429cba3e2a6f9b30/b8d13/createlibrary-flow.webp 2258w"
91              sizes="(max-width: 1980px) 100vw, 1980px"
92              type="image/webp"
93            />
94          <source
95            srcset="/static/111dea7607a2b937429cba3e2a6f9b30/b3db0/createlibrary-flow.png 495w,
96/static/111dea7607a2b937429cba3e2a6f9b30/8ddfd/createlibrary-flow.png 990w,
97/static/111dea7607a2b937429cba3e2a6f9b30/7c078/createlibrary-flow.png 1980w,
98/static/111dea7607a2b937429cba3e2a6f9b30/d8012/createlibrary-flow.png 2258w"
99            sizes="(max-width: 1980px) 100vw, 1980px"
100            type="image/png"
101          />
102          <img
103            class="gatsby-resp-image-image"
104            src="/static/111dea7607a2b937429cba3e2a6f9b30/7c078/createlibrary-flow.png"
105            alt="createlibrary flow"
106            title="createlibrary flow"
107            loading="lazy"
108            decoding="async"
109            style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
110          />
111        </picture>
112  </a>
113    </span>
114    <figcaption class="gatsby-resp-image-figcaption">createlibrary flow</figcaption>
115  </figure></p>
116<h2 id="develop-setup" style="position:relative;"><a href="#develop-setup" aria-label="develop setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Develop Setup</h2>
117<p>這邊就不寫上介紹,直接列出步驟,主要都是設定 project 的工具,從最基本的 eslint、prettier、husky、jest、typescript 這些,可以直接看我的 sample project 是怎樣設定的</p>
118<ul>
119<li>Basic setup project (install develop environment)</li>
120</ul>
121<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript">mkdir create<span class="token operator">-</span>javaScript<span class="token operator">-</span>sdk
122cd create<span class="token operator">-</span>javaScript<span class="token operator">-</span>sdk
123npm init
124
125<span class="token comment">// setup eslint. select env browser, npm and typescript</span>
126npm init @eslint<span class="token operator">/</span>config
127
128<span class="token comment">// install lint-staged</span>
129npm install <span class="token operator">-</span><span class="token constant">D</span> prettier lint<span class="token operator">-</span>staged jest
130
131<span class="token comment">// install husky</span>
132npx husky init
133
134<span class="token comment">// install rollup typescript</span>
135npm install <span class="token operator">-</span><span class="token constant">D</span> typescript
136
137<span class="token comment">// init typescript config</span>
138npx tsc <span class="token operator">--</span>init
139
140<span class="token comment">// install jest</span>
141npm install <span class="token operator">-</span><span class="token constant">D</span> jest
142npm init jest@latest
143npm install <span class="token operator">-</span><span class="token constant">D</span> babel<span class="token operator">-</span>jest @babel<span class="token operator">/</span>core @babel<span class="token operator">/</span>preset<span class="token operator">-</span>env @babel<span class="token operator">/</span>preset<span class="token operator">-</span>typescript @jest<span class="token operator">/</span>globals ts<span class="token operator">-</span>jest jest<span class="token operator">-</span>environment<span class="token operator">-</span>jsdom ts<span class="token operator">-</span>node</code></pre></div>
144<p>其餘設定 husky (.husky/pre-commit)、jest 跟 prettier 的 config,再麻煩幫我看 git repo 了,篇幅真的有點太長,我不想花太多篇幅在寫設定專案。官方插件都查得到教學,工具依序是 typescript、eslint、prettier、babel、lint-staged、husky、jest。比較不一樣會有個人化的會是 linter、prettier,在各自修改自己喜歡的 coding style。</p>
145<p>改完之後可以 npm lint-staged 看看能不能正常跑 linter,通常都會卡在 jest 處理不了 typescript,解法就是 jest transform 要設定 <code class="language-text">'^.+\\.ts?$': 'ts-jest'</code>
145。建議直接 clone 下面 sample repo 比較快XDD,我整個手動一個一個安裝,跑完加上 debug 也花了一點時間,另外如果我 repo 上的插件太舊再記得 <code class="language-text">npm update</code>。</p>
146<p><a href="https://github.com/ianccy/create-javascript-sdk/tree/chore/setup-develop" title="github setup sample">Github Repo sample</a></p>
147<h2 id="bundle-setup" style="position:relative;"><a href="#bundle-setup" aria-label="bundle setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Bundle Setup</h2>
148<p>這邊我們是選用 rollup,因為實際 build 出來的 file size 遠比 webpack 還小很多,開發 plugin 也足夠讓我們使用。</p>
149<ul>
150<li>install bundler rollup</li>
151</ul>
152<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token comment">// install rollup</span>
153npm install <span class="token operator">-</span><span class="token constant">D</span> rollup rollup<span class="token operator">-</span>plugin<span class="token operator">-</span>typescript2 @rollup<span class="token operator">/</span>plugin<span class="token operator">-</span>node<span class="token operator">-</span>resolve @rollup<span class="token operator">/</span>plugin<span class="token operator">-</span>commonjs @rollup<span class="token operator">/</span>plugin<span class="token operator">-</span>babel @rollup<span class="token operator">/</span>plugin<span class="token operator">-</span>terser rollup<span class="token operator">-</span>plugin<span class="token operator">-</span>version<span class="token operator">-</span>injector</code></pre></div>
154<ul>
155<li>Add script on package.json</li>
156</ul>
157<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token literal-property property">script</span><span class="token operator">:</span> <span class="token punctuation">{</span>
158  <span class="token operator">...</span>
159  <span class="token string-property property">"build-dev"</span><span class="token operator">:</span> <span class="token string">"rollup -c -w"</span><span class="token punctuation">,</span>
160  <span class="token string-property property">"build"</span><span class="token operator">:</span> <span class="token string">"rollup -c --prod"</span><span class="token punctuation">,</span>
161<span class="token punctuation">}</span></code></pre></div>
162<p>我在設定卡了滿久的時間,大概 1、2 小時,不知道怎設定 rollup config 讓他吃到 config.ts,就算強制用 <code class="language-text">rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript</code> 反而會卡在 tsconfig 各種沒有 apply 到…,索性直接把 file type 改成 mjs,rollup 就會自己 apply 到了。</p>
163<p>網路上也滿多人也在 rollup version 4 上遇到一樣問題,也沒看到什麼很好的解法。</p>
164<p>Error: <code class="language-text">[!] TypeError: Unknown file extension ".ts" for /Users/ian/Documents/create-javaScript-sdk/rollup.config.ts</code></p>
165<p>強烈建議大家要用 <code class="language-text">format: iife</code>,這是實際踩過的血淚經驗,iife 是立即函示的意思,這代表會讓我們作用域封裝起來,確保我們在這個 sdk 內用得的變數都是內部變數,而不會被外部所覆蓋污染。因為 bundle 後,變數、function name 都會被 uglify 變成 a, b, c,這就很容易跟其他人的 bundle file 衝突到。</p>
166<p><code class="language-text">rollup-plugin-version-injector</code> 則是幫我們把 version inject 到 file 上,方便我們檢查檔案的版本,<code class="language-text">@rollup/plugin-terser</code> 則是 minify code 減少 file size。</p>
167<p>都完成之後就可以嘗試 <code class="language-text">npm run build</code> 來檢查是否有建立 file 到 dist folder 內。</p>
168<ul>
169<li>create rollup.config.mjs</li>
170</ul>
171<div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">
171import</span> resolve <span class="token keyword">from</span> <span class="token string">'@rollup/plugin-node-resolve'</span><span class="token punctuation">;</span>
172<span class="token keyword">import</span> commonjs <span class="token keyword">from</span> <span class="token string">'@rollup/plugin-commonjs'</span><span class="token punctuation">;</span>
173<span class="token keyword">import</span> rollupTypescript <span class="token keyword">from</span> <span class="token string">'rollup-plugin-typescript2'</span><span class="token punctuation">;</span>
174<span class="token keyword">import</span> babel <span class="token keyword">from</span> <span class="token string">'@rollup/plugin-babel'</span><span class="token punctuation">;</span>
175<span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">DEFAULT_EXTENSIONS</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@babel/core'</span><span class="token punctuation">;</span>
176<span class="token keyword">import</span> terser <span class="token keyword">from</span> <span class="token string">'@rollup/plugin-terser'</span><span class="token punctuation">;</span>
177<span class="token keyword">import</span> versionInjector <span class="token keyword">from</span> <span class="token string">'rollup-plugin-version-injector'</span><span class="token punctuation">;</span>
178
179<span class="token keyword">import</span> pkg <span class="token keyword">from</span> <span class="token string">'./package.json'</span> <span class="token keyword">assert</span> <span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'json'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
180
181<span class="token keyword">const</span> folder <span class="token operator">=</span> <span class="token string">'./dist/'</span><span class="token punctuation">;</span>
182
183<span class="token keyword">const</span> <span class="token function-variable function">config</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">prod</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
184  <span class="token keyword">return</span> <span class="token punctuation">{</span>
185    <span class="token literal-property property">input</span><span class="token operator">:</span> <span class="token string">'src/index.ts'</span><span class="token punctuation">,</span>
186    <span class="token literal-property property">output</span><span class="token operator">:</span> <span class="token punctuation">[</span>
187      <span class="token punctuation">{</span>
188        <span class="token literal-property property">file</span><span class="token operator">:</span> folder <span class="token operator">+</span> pkg<span class="token punctuation">.</span>main<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'.js'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-dev.js</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
189        <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'iife'</span><span class="token punctuation">,</span>
190        <span class="token literal-property property">strict</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
191      <span class="token punctuation">}</span><span class="token punctuation">,</span>
192      <span class="token operator">...</span><span class="token punctuation">[</span>
193        prod <span class="token operator">&amp;&amp;</span> <span class="token punctuation">{</span>
194          <span class="token literal-property property">file</span><span class="token operator">:</span> folder <span class="token operator">+</span> pkg<span class="token punctuation">.</span>main<span class="token punctuation">,</span>
195          <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'iife'</span><span class="token punctuation">,</span>
196          <span class="token literal-property property">strict</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
197        <span class="token punctuation">}</span><span class="token punctuation">,</span>
198        prod <span class="token operator">&amp;&amp;</span> <span class="token punctuation">{</span>
199          <span class="token literal-property property">file</span><span class="token operator">:</span> folder <span class="token operator">+</span> pkg<span class="token punctuation">.</span>main<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'.js'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-v</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>pkg<span class="token punctuation">.</span>version<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.js</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span>
200          <span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">'iife'</span><span class="token punctuation">,</span>
201          <span class="token literal-property property">strict</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
202        <span class="token punctuation">}</span><span class="token punctuation">,</span>
203      <span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">v</span><span class="token punctuation">)</span> <span class="token operator">=></span> v<span class="token punctuation">)</span><span class="token punctuation">,</span>
204    <span class="token punctuation">]</span><span class="token punctuation">,</span>
205    <span class="token literal-property property">plugins</span><span class="token operator">:</span> <span class="token punctuation">[</span>
206      <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
207      <span class="token function">commonjs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
208      <span class="token function">rollupTypescript</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
209      <span class="token function">babel</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
210        <span class="token literal-property property">exclude</span><span class="token operator">:</span> <span class="token string">'node_modules/**'</span><span class="token punctuation">,</span>
211        <span class="token literal-property property">extensions</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span><span class="token constant">DEFAULT_EXTENSIONS</span><span class="token punctuation">,</span> <span class="token string">'.ts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
212        <span class="token literal-property property">presets</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'@babel/preset-env'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
213      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
214      <span class="token function">terser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
215      <span class="token function">versionInjector</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
216    <span class="token punctuation">]</span><span class="token punctuation">,</span>
217  <span class="token punctuation">}</span><span class="token punctuation">;</span>
218<span class="token punctuation">}</span><span class="token punctuation">;</span>
219
220<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token parameter">args</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
221  <span class="token keyword">const</span> prod <span class="token operator">=</span> <span class="token operator">!</span><span class="token operator">!</span>args<span class="token punctuation">.</span>prod<span class="token punctuation">;</span>
222  <span class="token keyword">return</span> <span class="token function">config</span><span class="token punctuation">(</span>prod<span class="token punctuation">)</span><span class="token punctuation">;</span>
223<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>
224<p><a href="https://github.com/ianccy/create-javascript-sdk/tree/chore/rollup" title="github setup rollup">Github Repo rollup</a></p>
225<h2 id="cicd-setup" style="position:relative;"><a href="#cicd-setup" aria-label="cicd setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>CI/CD Setup</h2>
226<p>前面就大致上完成了開發環境的設定了,剩下其實都滿簡單的,我們再來是 CI/CD 的設定,我們就直接以 github action 來當範例好了,我們希望能在 push release tag 的時候去 build file,並且 upload 到 cloud storage,為了方便大家直觀這邊就直接用 github hosting js file。</p>
227<p>首先 create <code class="language-text">.github/workflows/deploy.yml</code>,開始設定我們的 github action yaml,這邊就直接用 gh-pages 來 upload dist files 到另一個 github repo。</p>
228<p>package.json 也要在 script 加上 <code class="language-text">deploy:ci: gh-pages -d dist -r https://<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a480e3ecfbf0ebefe1eae4c3cdd0ccd1c68ac7cbc9">[email&#160;protected]</a>/ianccy/sample-javascript-sdk.git</code>。</p>
229<p>ps.記得到 github Setting 建立一個 secret token,然後設定到 repo 的 setting 上面。</p>
230<p>這邊我也卡了一下,原本我是想嘗試用 github action,不過多半都是專注在 upload assets 到指定的 repo,這跟我們需求差滿多的,我們也需要同時依賴 github hosting resource。</p>
231<ul>
232<li>deploy.yml</li>
233</ul>
234<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">name: release the javascript sdk
235
236on:
237  release:
238    types: [created]
239
240env:
241  GH_TOKEN: ${{ secrets.GH_TOKEN }}
242
243jobs:
244  release-code:
245    runs-on: ubuntu-latest
246    steps:
247      - name: GitHub Config
248        run: |
249          git config --global user.email "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5c3f34296e646d691c343328313d3530723f3331">[email&#160;protected]</a>"
250          git config --global user.name "ianccy"
251      - uses: actions/checkout@v2
252      - uses: actions/setup-node@v3
253        with:
254          node-version: 18
255          registry-url: https://registry.npmjs.org/
256      - name: Install dependencies
257        run: npm install
258      - name: Unit-Test
259        run: npm run test
260      - name: Build Code
261        run: npm run build
262      - name: Upload to GitHub Repository
263        run: npm run deploy:ci</code></pre></div>
264<ul>
265<li>add deploy:ci on package.json</li>
266</ul>
267<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">  script: {
268    ...
269    "deploy:ci": "gh-pages -d dist -r https://<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5377141b0c071c18161d13343a273b26317d303c3e">[email&#160;protected]</a>/ianccy/sample-javascript-sdk.git"
270  }</code></pre></div>
271<h2 id="接下來設定-custom-domain" style="position:relative;"><a href="#%E6%8E%A5%E4%B8%8B%E4%BE%86%E8%A8%AD%E5%AE%9A-custom-domain" aria-label="接下來設定 custom domain permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>接下來設定 custom domain</h2>
272<p>再來到 <code class="language-text">sample-javascript-sdk</code> repo 去設定 custom domain,我是用 <code class="language-text">js-sdk-sample.iannccy.com</code>,接下來到 cloudflare 去設定 dns 指向 github,幾乎就可以說是大功告成哩。</p>
273<p><figure class="gatsby-resp-image-figure" style="margin: 2vw 0;">
274    <span
275      class="gatsby-resp-image-wrapper"
276      style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 1980px; "
277    >
278      <a
279    class="gatsby-resp-image-link"
280    href="/static/75d7aac51cf68f604dbb2608879e2aae/d4e2e/createlibrary-cname.png"
281    style="display: block"
282    target="_blank"
283    rel="noopener"
284  >
285    <span
286    class="gatsby-resp-image-background-image"
287    style="padding-bottom: 8.686868686868687%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAg0lEQVR42h2LWwuCQBhE/f9/S4kw1KRe1ALXzTJvezFX0HL6dh+GwxlmPKkN6leHdzdCTQbWR/kB408MQrtOqJm4oO0FSsbRtINz29uf/ShKVTfwYrbimE8Irj1ON4Mz/yEqV/jkYTEjIU+qjbgjvUuElwcOmabdFzHb6KsdU9oF2YI/XCyUFDxtapQAAAAASUVORK5CYII='); background-size: cover; display: block;"
288  ></span>
289  <picture>
290          <source
291              srcset="/static/75d7aac51cf68f604dbb2608879e2aae/a805d/createlibrary-cname.webp 495w,
292/static/75d7aac51cf68f604dbb2608879e2aae/36741/createlibrary-cname.webp 990w,
293/static/75d7aac51cf68f604dbb2608879e2aae/41a9f/createlibrary-cname.webp 1980w,
294/static/75d7aac51cf68f604dbb2608879e2aae/58212/createlibrary-cname.webp 2306w"
295              sizes="(max-width: 1980px) 100vw, 1980px"
296              type="image/webp"
297            />
298          <source
299            srcset="/static/75d7aac51cf68f604dbb2608879e2aae/b3db0/createlibrary-cname.png 495w,
300/static/75d7aac51cf68f604dbb2608879e2aae/8ddfd/createlibrary-cname.png 990w,
301/static/75d7aac51cf68f604dbb2608879e2aae/7c078/createlibrary-cname.png 1980w,
302/static/75d7aac51cf68f604dbb2608879e2aae/d4e2e/createlibrary-cname.png 2306w"
303            sizes="(max-width: 1980px) 100vw, 1980px"
304            type="image/png"
305          />
306          <img
307            class="gatsby-resp-image-image"
308            src="/static/75d7aac51cf68f604dbb2608879e2aae/7c078/createlibrary-cname.png"
309            alt="createlibrary cname"
310            title="createlibrary cname"
311            loading="lazy"
312            decoding="async"
313            style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;"
314          />
315        </picture>
316  </a>
317    </span>
318    <figcaption class="gatsby-resp-image-figcaption">createlibrary cname</figcaption>
319  </figure></p>
320<p><a href="https://js-sdk-sample.ianccy.com/index.js" title="JavaScript SDK">JavaScript SDK</a></p>
321<p>再來補上 purge cache 在 CD flow,確保我們每一次部署上去的 file 都會 purge cdn cache,確保客戶可以盡快拿到最新版的 js file。</p>
322<p>同時需要設定對應的 secrets 到 github repo 上。
323<a href="https://github.com/marketplace/actions/cloudflare-purge-cache" title="github action guide">
323github action guide</a></p>
324<ul>
325<li>deploy.yml</li>
326</ul>
327<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">      - name: Purge cache
328        uses: jakejarvis/cloudflare-purge-action@master
329        env:
330          CLOUDFLARE_ZONE: ${{ secrets.CLOUDFLARE_ZONE }}
331          CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
332          CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
333          CLOUDFLARE_KEY: ${{ secrets.CLOUDFLARE_KEY }}
334          PURGE_URLS: '["https://js-sdk-sample.ianccy.com/index.js"]'</code></pre></div>
335<h2 id="push-tag-and-create-release-with-tag" style="position:relative;"><a href="#push-tag-and-create-release-with-tag" aria-label="push tag and create release with tag permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Push Tag and Create Release with Tag</h2>
336<p>接下來就是 create tag ,再到 github repo 去建立 release,就會觸發 github action 執行 CI/CD 了。可以點開 action 來查看每個步驟是不是有正確執行完畢。</p>
337<p><a href="https://github.com/ianccy/create-javascript-sdk/actions/runs/7863309142" title="github action result">github action result</a></p>
338<p><a href="https://github.com/ianccy/create-javascript-sdk" title="github repo">github repo</a></p>
339<h2 id="心得" style="position:relative;"><a href="#%E5%BF%83%E5%BE%97" aria-label="心得 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>心得</h2>
340<p>起初是想寫我怎樣規劃設計這整個架構,也包含怎儲存資料,但礙於一些 nda 因素,不方便講太多細節,再說我覺得每一個步驟怎實作也很重要,兩者相衡之下,就選擇介紹 setup 架構了。因為我當時在網路上找不到相關教學,每個步驟都是零散的拼湊出來的…,我當時大概花了 2~3 天規劃然後寫完,想拋磚引玉寫一篇 setup 的文章、sample repo。</p>
341<p>至於我是如何選擇工具,其實想法很簡單,費用成本 > 效能 > 維運,我是選擇用 gcs,可以讓我客製化 meta header 很方便,再來搭配 cloudflare cdn cache 幾乎沒什麼成本,不選擇 webpack 是因為效能差異太大了,真的是盡可能減少 file size。維運問題上,rollup 可能雷相對多一點點,社群相對小一點,但這就是取捨了,rollup 的優點遠大於缺點。</p>
342<p>整個來說設定 repo 開發環境跟 github cloudflare 之間連動比較麻煩。</p>
343<p>一樣有問題歡迎詢問,打錯的地方也歡迎更正我。</p>
344<p>最後 murmur 一下,最近都在學 python 還有後端,所以 blog 文章嚴重拖稿中…,待寫文章堆積如山。然後久久沒寫 blog 文筆超爛 QQ。</p></section></article><div class="flex justify-end mb-4"><div class="tag-item"><a href="/tags/sdk/"><span class="text-sm pb-2">sdk</span></a><span class="px-0.5">·</span></div><div class="tag-item"><a href="/tags/rollup/"><span class="text-sm pb-2">rollup</span></a></div></div><div class="mb-4"><nav><ul class="md:flex md:justify-between"><li class="flex grow overflow-hidden py-1 md:py-0"><a class="text-ellipsis whitespace-nowrap overflow-hidden" rel="prev" href="/thirtyfiveyearsold/">← <!-- -->35 歲的我</a></li><li class="flex grow justify-end py-1 md:py-0"><a class="text-ellipsis whitespace-nowrap overflow-hidden" rel="next" href="/abortcontroller/">AbortController in React, cancel in function call<!-- --> →</a></li></ul></nav></div><div class="mb-8"></div></div></div></div></div><footer class="text-center py-2 text-sm">© <!-- -->2026<!-- --> · Built with<!-- --> <a href="https://www.gatsbyjs.com">Gatsby</a> ·<!-- --> <a href="https://github.com/ianccy/gatsby-tailwind-typescript-starter">Ian Chu</a></footer></div></div><div id="gatsby-announcer" style="position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;
344border:0" aria-live="assertive" aria-atomic="true"></div></div>
vendor: 108 bytes, line 344
344<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
344<script id="gatsby-script-loader">/*<![CDATA[*/window.pagePath="/createsdk/";/*]]>*/</script>
344<!-- slice-start id="_gatsby-scripts-1" -->
345          
345<script
346            id="gatsby-chunk-mapping"
347          >
348            window.___chunkMapping="{\"app\":[\"/app-f4de0039e5cda1cb14d0.js\"],\"component---src-pages-404-tsx\":[\"/component---src-pages-404-tsx-f12fd86cdeb2300f32cc.js\"],\"component---src-pages-about-tsx\":[\"/component---src-pages-about-tsx-d3a10ef043fa3d690132.js\"],\"component---src-pages-index-tsx\":[\"/component---src-pages-index-tsx-cbae03120dd9b12e2bb7.js\"],\"component---src-templates-categories-tsx\":[\"/component---src-templates-categories-tsx-e73efd817e34757a1bfb.js\"],\"component---src-templates-post-tsx\":[\"/component---src-templates-post-tsx-e47624f6f631538b1147.js\"],\"component---src-templates-tags-tsx\":[\"/component---src-templates-tags-tsx-10d732146331905e3fb0.js\"]}";
349          </script>
349
350        
350<script>window.___webpackCompilationHash="fe16dcca21353d86e799";</script>
350<script src="/webpack-runtime-7a147a30f5c7f9620169.js" async></script>
350<script src="/framework-7dc6e6ee187481085057.js" async></script>
350<script src="/app-f4de0039e5cda1cb14d0.js" async></script>
350<!-- slice-end id="_gatsby-scripts-1" -->
350<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"b77a5c46529640bc9d89a92e514ee4f4","r":1,"spa":2}' crossorigin="anonymous"></script>
350
351</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.