<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by 文科少女學程式 on Medium]]></title>
        <description><![CDATA[Stories by 文科少女學程式 on Medium]]></description>
        <link>https://medium.com/@pink-learn-frontend?source=rss-960694c636fc------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*Zsedt-0sErgabpb0Uk9dDQ.png</url>
            <title>Stories by 文科少女學程式 on Medium</title>
            <link>https://medium.com/@pink-learn-frontend?source=rss-960694c636fc------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 01:03:03 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@pink-learn-frontend/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[【我就廢之亂亂做】我做了一個房租繳費通知秘書]]></title>
            <link>https://pink-learn-frontend.medium.com/%E6%88%91%E5%B0%B1%E5%BB%A2%E4%B9%8B%E4%BA%82%E4%BA%82%E5%81%9A-%E6%88%91%E5%81%9A%E4%BA%86%E4%B8%80%E5%80%8B%E6%88%BF%E7%A7%9F%E7%B9%B3%E8%B2%BB%E9%80%9A%E7%9F%A5%E7%A7%98%E6%9B%B8-a88a04b29b70?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/a88a04b29b70</guid>
            <category><![CDATA[line-bot]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Tue, 28 Apr 2026 13:31:01 GMT</pubDate>
            <atom:updated>2026-04-28T13:31:01.870Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BQrkAenzyFzlonGz.png" /></figure><p>事情是這樣的～最近因為原本的租屋處房東要賣房子，所以搬離了一個租了六年的地方，近期剛搬到新房子，就腦洞大開，想說做一個自動提醒房租已經繳的小幫手來嚇嚇(?)新房東好了。於是就使用了 AI 之力，無腦做了一個小小的 line 房租提醒小助手。雖然整個流程是在 AI 幫助下，兩小時就完成的小幫手，但是還是想要稍微記錄一下，也許哪天可以拿來複用到其他情境。</p><h3><strong>我想解決的問題是？</strong></h3><p>以前繳完房租後，因為 J 人的愛管理毛病以及以前當 PM 的職業病，都會將房租繳交紀錄整理在 google sheet 裡面，另一方面也是要整理給房東看，讓房東知道我真的有繳房租。但是寫完紀錄後，我依然需要在群組裡通知房東，我已經把房租匯款給他了，請他確認。這個流程沒什麼問題，但我就是懶，想要用更懶(ㄈㄟˋ)的方式在寫完房租繳交紀錄後，就自動通知房東我繳房租給他了，這時候就會需要房租繳費通知秘書。</p><h3><strong>房租繳費通知秘書實作方向</strong></h3><p>簡單來說，</p><blockquote>「我需要把 google sheet 和 line 結合在一起，當我完成 google sheet 的房租繳交紀錄後，就自動提醒房東我繳房租了」。</blockquote><p>所以觸發點會在完成 google sheet 的填寫，並且需要串接 line 的 push message API，將我想要自動發送給房東的匯款完成通知訊息送出。</p><h3><strong>實作步驟</strong></h3><p>1. 準備好用來記錄匯款完成時間和內容的 google sheet 表格。<br>2. 進入 <a href="https://manager.line.biz/">https://manager.line.biz/</a> 建立一個 line 官方帳號，並且開啟 webhook。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iucGjAg_QfaVSqff.png" /></figure><p>3. 透過 webhook.site 產一個 Webhook網址，貼到 line 官方帳號後台的 Messaging API 設定，並儲存。（因為我這個通知工具只是給我自己使用，所以使用 webhook.site 建立 webhook 網址，如果是正式對外使用的情境，通常會自己建立對應的 endpoint 網址）</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KxZ1DwrG0vwcuYCo.png" /></figure><p>4. 接著把這個建立好的官方帳號，加到和房東的 line 群組中，加入後就可以從前面進入的 webhook.site 網址中取得 groupId。<br>5. 有 groupId 後，就可以前往 google sheet 連動的 Apps Script 中寫觸發事件後要進行的動作，並且連動到 line 群組中，發送要發送的訊息到對話群組裡面。<br>6. 最後！還需要前往 developers.line.biz ，選擇對應的 Bot Channel，進到 Messaging API，將 Auto-reply messages 關閉。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8r2qKeI-FIWOb8je.png" /></figure><p>否則會出現有人在聊天室裡講話，bot 就會回覆內容的囧境，如下是我實際使用時，發生的尷尬狀況QQ。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*M2UO9tV6Lm7l7pbg.png" /></figure><p>目前只用了一次，房東表示覺得有趣，下個月還會繼續這樣使用，不過這個做法仍然有需要改進的地方，主要需要改進的地方是 App Script 的內容，所以我還會再調整一下，但是以 MVP 概念來說，已經是達到我想要的功能的廢廢小工具了。</p><p>隨筆紀錄就到這裡啦～</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a88a04b29b70" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【我是前端工程師！？】原來我的五年經驗在市場上只被認定為是一個 CRUD 工程師…]]></title>
            <link>https://pink-learn-frontend.medium.com/%E6%88%91%E6%98%AF%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%B8%AB-%E5%8E%9F%E4%BE%86%E6%88%91%E7%9A%84%E4%BA%94%E5%B9%B4%E7%B6%93%E9%A9%97%E5%9C%A8%E5%B8%82%E5%A0%B4%E4%B8%8A%E5%8F%AA%E8%A2%AB%E8%AA%8D%E5%AE%9A%E7%82%BA%E6%98%AF%E4%B8%80%E5%80%8B-crud-%E5%B7%A5%E7%A8%8B%E5%B8%AB-8c69ef01c36c?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/8c69ef01c36c</guid>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Fri, 20 Mar 2026 11:31:00 GMT</pubDate>
            <atom:updated>2026-03-20T11:31:00.511Z</atom:updated>
            <content:encoded><![CDATA[<p>今天不寫技術學習文，上來打打字抒發一下這一段期間找工作的悲催心得。</p><p>前陣子我收到一個面試結果回信，信中內容大概是說我的技術能力跟他們預想有落差，但覺得我溝通能力不錯，所以想找我試試看一半的時間要去和外包商溝通，一半時間做簡單 CRUD 的職缺。收到這個答覆的我在某種程度的感受上其實比直接收到感謝函還要難受，因為我的能力居然只是被認定為能夠勝任「簡單的 CRUD」。雖然我溝通能力很強這件事被認可，但技術力卻被視為什麼都不是，這真的讓我覺得五味雜陳…。我知道一間公司這樣的答覆不代表所有公司都這麼認定，但這樣的答覆也確實在某種程度點醒了我。</p><p>回到簡短地自介紹！我是一個從翻譯相關領域轉職前端工程師五年的小廢廢，偶爾會透過寫寫文章來記錄學習，在開發流程上，基本上也沒有太多的問題，即使進入一個新的公司，碰了一個新的產業，還是都有能力在短時間內熟悉公司內部的專案。但我清楚知道這樣的能力依然還是距離市場上大多公司認定的資深工程師，或者是說有經驗的工程師有一段很大的距離。會有這樣的結果主要的原因我認為是我能夠參與甚至是主導專案架構，以及效能優化的經驗基本上可以說是 0。</p><p>對！我在就業市場上基本上就是被認定成一個只會 CRUD 的前端工程師！沒錯！就是這麼慘！</p><p>這個慘況是我一直有意識到，但卻一直沒有正視的問題，畢竟每天忙著做 CRUD 也是在忙，在還沒要開始找工作之前，就很難被這個殘酷的事實給打醒。當身邊的人感覺都在不斷前進時，被這個殘酷事實打擊到的自己突然有一種「我自己到底在幹麻？」「這條路我還能繼續走下去嗎？」的想法。之前參加 IT 鐵人賽得到的獎項也在這一刻讓我自己覺得格外的諷刺，雖然我自己知道自己不是因為是大大才得獎。這一刻突然又想到前陣子在面試時，面試官問了一句，可以得冠軍的人很多嗎？當時不覺得怎樣，這時候想想該不會是面試官覺得這個人技術力那麼爛怎麼能得獎，是阿貓阿狗都能得獎吧！好啦！不過多揣測對方內心在思考什麼，但結論上的確就是我努力了，但我不夠格！(打字打到這裡突然好想哭，但我把眼淚收住了！)</p><p>目前這個狀況的確是我職涯上一個最大的瓶頸，我內心也覺得我大概很難跨過這樣的瓶頸，但是生活還是要過下去，該嘗試的、該努力的我還是會繼續下去，我不知道這樣子算不算硬撐？也不知道是不是這條路對我的智商而言，早就到了盡頭？但不試怎麼會知道？如果我還有在更新，就代表我還活在這條路上，但如果哪天整個部落格都死掉了，我應該已經轉行賣車輪餅了。</p><p>不想要等到自己成功跨過這個瓶頸(會有這一天嗎？)才寫一篇文章紀錄此刻的心情，因為會讓人感覺都成功了，才在講好像只是在馬後砲什麼，也搞不好沒有成功跨過的這一天，呵呵！另外，也想透過這種文字的抒發，讓自己得到情緒的宣洩(?)，也許有哪個人路過看到這篇，說不定也可以成為某種慰藉，畢竟大多數的人只會在社群媒體上分享自己好的一面，很少人會在社群媒體上分享自己不好的一面，但當自己已經很糟了，看到別人好的一面可能會更糟，但看到有一個人也是過得滿不好的，就會覺得「看來我不孤單」。</p><p>如果你剛好跟我遇到類似的狀況，那你不孤單，但我們也不要消沈太久，繼續往前看，努力自己可以努力的吧！雖然簡少年有說今年是一個「躺平年」，但我想這個躺平應該不是真的字面上的躺平，而是承認自己的不足，正視自己的感受，讓自己去接受自己不好的那一面，並且用積極的態度，或是自己喜歡的方式去慢慢地補強自己。<br>簡單來說也就是「允許自己不完美，但不停止前進」，這個體悟我送給我自己，也送給剛好路過這篇的各位 ：）</p><p>這篇亂七八糟打的心情紀錄，就到這邊告一段落吧！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8c69ef01c36c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【前端新手日記】Tree Shaking 也許沒有真的 shaking 起來]]></title>
            <link>https://pink-learn-frontend.medium.com/%E5%89%8D%E7%AB%AF%E6%96%B0%E6%89%8B%E6%97%A5%E8%A8%98-tree-shaking-%E4%B9%9F%E8%A8%B1%E6%B2%92%E6%9C%89%E7%9C%9F%E7%9A%84-shaking-%E8%B5%B7%E4%BE%86-99cc448e6844?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/99cc448e6844</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[tree-shaking]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Tue, 30 Dec 2025 15:09:35 GMT</pubDate>
            <atom:updated>2025-12-30T15:09:35.755Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*c0wYQ-OVEFkvP28j.png" /></figure><p>這篇文章是啟發於 JSDC 2025 SerKo 大大分享的 「程式碼減重：你的 Tree-Shaking 在偷懶嗎？」，會從 SerKo 分享的內容為起點，補充一些自己本來也是很模糊的地方，以及透過一些實驗來觀察 Tree Shaking 的狀況。</p><h3><strong>什麼是 Tree Shaking？</strong></h3><p>從字面上來看就是「搖樹」的意思，但是為什麼要「搖樹」呢？目的就是把樹木上沒用的枯葉搖掉，只保留還有用途的綠葉。延伸到前端開發領域中的話，也就是「在打包的時候，把沒有用到的程式碼移除掉，只保留需要的內容，以縮減打包後檔案的大小，也就不會讓使用者在使用網頁時，因為下載一些不必要的內容，而影響使用體驗。Tree Saking 這個動作，通常都是透過 Webpack、 Rollup 這類的打包工具 (bundler) 來處理。</p><h3><strong>打包工具怎麼做到 Tree Shaking?</strong></h3><p>了解完 Tree Shaking 的用途後，我們也來進一步看看打包工具怎麼做到 Tree Shaking 這件事情。<br> 首先，打包工具為避免不小心搖掉不該搖的東西，會以最保守方向來進行 Tree Shaking。也就是說，只要被打包工具認為某些部分「有出現些微可能被使用到徵兆」，就不會被 tree shaking 掉。<br> 在進行 Tree Shaking 時，打包工具會去做靜態分析的動作。所謂的「靜態分析」白話一點來理解的話，也就是打包工具會將所有檔案和關聯檔案 (例如： 一個檔案內有 import 哪些東西進來) 之間的關係分析出來，就像是一個樹狀結構一樣，一個檔案的內容可能會有一個分支連結到另一個檔案。當這樣的靜態分析完成後，打包工具在實際上進行檔案的打包時，也就可以知道哪些變數、函式實際上並沒有在整個完整個關係圖裡面，也就可以將沒有關聯的內容給移除掉，不放在打包後的檔案內。</p><h3><strong>沒用到一定會被 Tree Shaking 掉？</strong></h3><p>延續前面說的打包工具怎麼做到 Tree Shaking 的部分，是否就可以推論為「<em>沒用到的部分，一定會被 Tree Shaking 掉</em>」呢？其實答案是<strong>否定</strong>的，這裡可以分為兩個大部分來觀察。</p><h4><strong>第一個部分、JavaScript 的模組化系統特性</strong></h4><p>JavaScript 有兩種模組化系統，分別是 CommonJS 和 ES Module，由於兩種模組化系統的特性不同，CommonJS 是動態分析，ES Module 是靜態分析，所以 Tree Shaking 的效果有差異。</p><p>這裡直接透過實際的例子來觀察這兩種模組化系統在 Tree Shaking 上的效果，主要會是 JavaScript 搭配 Webpack 這個打包工具來觀察結果。</p><h4><strong>CommonJS</strong></h4><p>這是要使用的一些函式</p><pre>// ✅ 實際會用到的小函式 <br>function add(a, b) { <br>  return a + b; <br>} </pre><pre>// 🚫 不會用到的函式 <br>function random() { <br>  return Math.random(); <br>} <br>module.exports = { add, random };</pre><p>這是實際使用的地方</p><pre>const { add } = require(&quot;./utils.cjs&quot;); <br>console.log(add(1, 2));</pre><p>雖然 random 實際上沒有被使用到，但是打包後的結果仍然可以觀察到 random 的程式碼存在於打包後的檔案內。</p><p>這是因為 CommonJS 的 require() 是在 runtime 進行載入的動作，沒辦法在打包的時候就透過靜態分析明確地知道哪些內容不會被使用到，所以在 CommonJS 的情境下，通常無法做到精準移除未使用的部分，導致未使用的函式仍可能被打包進輸出檔案中。</p><p><strong>ES Module</strong></p><p>接著來看 ES Module 的情境。<br>一樣先準備要使用的函式，其中一個函式不會使用到。</p><pre>// ✅ 實際會用到的小函式 <br>export function add(a, b) { <br>  console.log(&quot;addddddd&quot;); return a + b; <br>} </pre><pre>// 🚫 不會用到的函式 <br>export function random() { <br>  console.log(&quot;ramdooooooo&quot;); <br>  return Math.random(); <br>}</pre><p>這是實際使用的地方</p><pre>import { add } from &quot;./utils.js&quot;;<br>console.log(add(1, 2));</pre><p>在 ES Module 中，觀察打包後產生的檔案，可以發現到只有出現 add 這個 function，也就是説在這個情境下，有確實地進行 Tree Shaking。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U0Q6vv8W5OR4LJgc.png" /></figure><p>在 ES Module 中，模組的 import / export 結構在build 的階段可以進行靜態分析，因此打包工具可以在打包時明確判斷哪些內容的匯出有被使用、哪些沒有被使用，因此可以更精準地進行 Tree Shaking，移除未使用的程式碼。</p><p>由這個情境我們可以觀察到 ES Module 的確比起 CommonJS 能可以更精準地進行 Tree Shaking，但是事實上只要使用 ES Module 就可以百分之百做到完整的 Tree Shaking 嗎？</p><p>再來看另一種寫法，這裡改用 default export 的寫法匯出。</p><pre>// ✅ 實際會用到的小函式 <br>function add(a, b) { <br>  console.log(&quot;addddddd&quot;); return a + b; <br>} </pre><pre>// 🚫 不會用到的函式 <br>function random() { <br>  console.log(&quot;ramdooooooo&quot;); <br>  return Math.random(); <br>}<br>export default { add, random };</pre><p>因為不是使用 named export 的寫法了，所以只能改用以下的寫法 import 和使用。</p><pre>import utils from &quot;./utils.js&quot;; <br>console.log(utils.add(1, 2));</pre><p>這樣改寫後，再次進行打包看看，可以發現到 random 這個函式沒有被移除，Tree Shaking 失效了！</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Xs0ddHBpZVgbqk5O.png" /></figure><p>會有這樣的結果是因為對打包工具來說，等於這個檔案匯入的是 default export 的整個值，而這個值是一個物件，裡面同時包含 add 和 random，因此沒有辦法保證沒有使用到 random ，也就無法安全地把 random 從這個物件移掉。</p><p>從目前的觀察的情境可以了解到雖然 ES Module 相較於 CommonJS 能更準確地進行 Tree Shaking，但是還是有可能因為寫法而影響到 Tree Shaking 的準確性。</p><h4><strong>第二個部分、Side Effect 的影響</strong></h4><p>除了使用的模組化系統和匯出匯入的寫法會影響到 Tree Shaking 的結果外，Side Effect 也有可能會讓 Tree Shaking 沒有正確地發揮作用。常見會產生 Side Effect 的狀況有寫 console.log、setTimeout/setInterval、throw error 、修改 DOM 等。</p><p>這邊也直接透過實際的例子來觀察看看。<br>我們在原本的 utils 檔案裡建立一個 getValue 的函式，接著將執行這個函式取得的值也建立成一個變數並且匯出。</p><pre>// 新增一個取得 value 的函式<br>function getValue(value) { <br>  return value; <br>} </pre><pre>export const value = getValue(value);</pre><p>因為沒有使用到這個匯出的 value，最後 build 出來的確沒有出現 getValue 這個函式。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*AJ0E8Tz9gzmrq_2z.png" /></figure><p>但如果在函式中出現一些會產生 Side Effect 的內容後，會發生什麼事呢？<br>這裡在函式加上 throw error 的內容。</p><pre>// 新增一個取得 value 的函式<br>function getValue(value) {<br>  if (!value) { <br>    throw new Error(&quot;key is required&quot;);    <br>  }<br>  return value; <br>} </pre><pre>export const value = getValue(value);</pre><p>再次 build 之後，會發現這次出現了 getValue 這個函式。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eP-LeqiGBRKhHhE-.png" /></figure><p>在這個情境下，Tree Shaking 失敗的原因主要就是因為函式內出現了會有 Side Effect 的內容，白話一點也就是出現了會影響外部的內容。再加上這個函式，會在頂層就被執行。也就是說這個函式當整個 utils.js 被執行時，也就會執行到 getValue。</p><p>打包工具會以最保險的方式進行 Tree Shaking，所以打包工具會在確保不影響外部狀況、行為的前提下，將不需要的程式碼刪除。當出現會產生 Side Effect 的程式碼，且這段程式碼會在一開始就被執行時，打包工具為了避免影響到其他地方，就不會將其刪除，以避免影響到其他地方。所以並不是使用了 ES Module，且某段程式碼肉眼看到沒有用到，就一定會被打包工具給刪除，還是得注意這段程式碼是否有 Side Effect 的內容。</p><h3><strong>避開 Tree shaking 失敗的做法</strong></h3><p>最後也來總結一下避開 Tree Shaking 失敗的方式有哪些：<br>第一點是盡可能地使用 ES Module 這個模組化系統，並且若是在一個檔案中，有複雜的邏輯及程式碼時，盡可能地避免用 default export 的方式進行匯出。<br>第二點是避免讓有 Side Effect 的程式碼在最頂層就被執行，這樣會影響打包工具進行 Tree Shaking 的判斷，導致沒有被使用的程式碼還是被保留了下來。<br>不論是哪一點，核心重點都在於不要讓打包工具有難以判斷刪除時，是否會影響外部的模糊視角。</p><h3><strong>總結</strong></h3><p>雖然大多數時候打包工具的確會幫我們做到 Tree Shaking 的動作，但實際上卻有可能出現沒有確實做到 Tree Shaking 的狀況，特別是程式碼中出現 Side Effect 的時候。所以我們都應該要有這個意識去盡量避免寫出會造成打包工具混淆寫法，以避免打包工具因為以最保險的方式進行 Tree Shaking，而讓打包工具沒能完整地進行 Tree Shaking。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=99cc448e6844" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【JavaScript 萌新筆記】JavaScript 模組系統 — CommonJS 和 ES Module]]></title>
            <link>https://pink-learn-frontend.medium.com/javascript-%E8%90%8C%E6%96%B0%E7%AD%86%E8%A8%98-javascript-%E6%A8%A1%E7%B5%84%E7%B3%BB%E7%B5%B1-commonjs-%E5%92%8C-es-module-da5261a0c571?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/da5261a0c571</guid>
            <category><![CDATA[modules]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Tue, 30 Dec 2025 02:55:52 GMT</pubDate>
            <atom:updated>2026-02-02T03:32:11.088Z</atom:updated>
            <content:encoded><![CDATA[<h3>【JavaScript 萌新筆記】JavaScript 模組系統 — CommonJS 和 ES Module</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*dKxZO2J6jwwn_imL.png" /></figure><p>今天想來看個基礎到不行，但之前一直沒有好好認識的 JavaScript 常識，也就是 JavaScript 的模組系統 — CommonJS 和 ES Module。</p><h3><strong>什麼是 JavaScript 的模組系統？</strong></h3><p>所謂的「JavaScript 模組系統」是指能將程式碼拆分為獨立、可重複使用之模組（module）的機制。透過模組系統，不同檔案之間可以用標準化的方式進行匯入與匯出，使專案在規模變大時仍然可以保持清晰的結構、良好的維護性與重複使用性。JavaScript 的模組系統也就是大家都有聽過的 CommonJS 和 ES Module。</p><h3><strong>JavaScript 模組系統的歷史背景</strong></h3><p>接下來讓我們快速來看一下關於 JavaScript 模組系統的歷史。</p><p>在 JavaScript 被創造出來的早期，它的用途主要是替網頁加入一些簡單的互動效果，例如按鈕點擊事件或表單驗證。當時的網頁互動仍以整頁重新載入為主，JavaScript 的功能範圍很小，也沒有跨檔案共享邏輯的需求，因此語言本身並沒有內建「模組」的概念。</p><p>隨著 Web 應用越來越複雜，JavaScript 在專案中的比重增加，程式也變得難以維護。前端普遍透過多個 &lt;script&gt; 依序載入 JavaScript 檔案，但這種方式缺乏標準化的依賴管理、容易造成全域變數污染，也很難維持清楚的架構。在這樣的背景之下，「讓 JavaScript 能模組化」逐漸成為前端與後端共同的需求。</p><p>雖然一直以來大家都知道 JavaScript 需要模組化的功能，但實際上真正促使模組系統出現的則是因為 Node.js 的出現。Node 需要在後端環境中管理大型程式，因此採用了 CommonJS（CJS）作為模組系統。但是 CommonJS 只存在於 Node.js，瀏覽器仍然缺乏官方的模組標準。為了讓 JavaScript 在前端、後端與瀏覽器生態中都有一致的模組規範，ECMAScript 在 2015 年正式引入了 ES Module（ESM）。</p><p>雖然 ES Module 已成為官方標準，但由於 JavaScript 生態中仍然存在大量使用 CommonJS 的套件，加上 ESM 在 Node.js 的支援是到後來才逐步成熟，因此 CommonJS 至今都還是有被使用。這也是為什麼到了現在，仍常常同時看到 CommonJS 與 ES Module 的原因。</p><h3><strong>JavaScript 的模組系統</strong></h3><p><strong>CommonJS</strong><br>CommonJS 是使用 require() 和 module.exports 進行匯入與匯出，由於 require() 是一個函式呼叫，所以模組間的相依關係是在執行階段（runtime）才會決定，也就是說必須等程式執行到那一行時，才能確定實際載入哪個模組，這個特性被稱作為「動態加載」。<br>因為相依關係不是在編譯階段就固定，打包工具 (bundler) 無法在打包時就完全確定哪些匯出有被使用、哪些從未被引用，因此 CommonJS 大多數時候無法進行精準的 Tree Shaking，只能採取保守策略將程式碼全部打包進去。</p><p>這裡也展示實際上使用 CommonJS 的寫法<br>假設有兩個 function。</p><pre>function add(a, b) {<br>  return a + b<br>}</pre><pre>function minus(a, b) {<br>  return a - b<br>}</pre><p>匯出主要有兩個寫法，一個是匯出一個物件；一個是匯出單一的 function。</p><pre>// 匯出方式1: 匯出一個物件<br>module.exports = {<br>  add,<br>  minus,<br>}</pre><pre>// 匯出方式2：單一匯出<br>module.exports = add</pre><p>匯入的寫法也會依照是匯出物件，還是單一匯出有差異。</p><pre>// 對應「匯出物件」的匯入寫法<br>const { add, minus } = require(&#39;./functions&#39;)</pre><pre>// 對應「單一匯出」的匯入寫法：<br>const add = require(&#39;./functions&#39;)</pre><p><strong>ES Module</strong><br>ES Module 使用 import 和 export 進行匯入與匯出，這樣的語法能讓程式碼在尚未被執行前的解析階段就建立完整的相依關係圖，所以不需要像 CommonJS 一樣等到執行期（runtime）才能知道模組的相依關係，這種特性被稱為「靜態分析」。<br>因為 ES Module 的匯入與匯出都能在打包或編譯階段被靜態推導出來，所以打包工具（bundler）也就能較精準地判斷哪些匯出有被使用、哪些沒有被使用，因此能比 CommonJS 進行更有效且精準的 Tree Shaking。<br>這裡也展示實際上使用 ES Module 的寫法，一樣也準備兩個 function。</p><pre>function add(a, b) {<br>  return a + b<br>}</pre><pre>function minus(a, b) {<br>  return a - b<br>}</pre><p>在 ES Module 這裡也有兩種匯出的方式，分別是 named export 和 default export。<br>named export 的寫法可以在宣告 function 時直接匯出</p><pre>export function add(a, b) {<br>  return a + b<br>}<br><br>export function minus(a, b) {<br>  return a - b<br>}</pre><p>也可以先宣告 function 後，再透過物件的方式匯出</p><pre>export { add, minus }</pre><p>想要匯入時，則都是透過以下的這個寫法匯入。</p><pre>import { add, minus } from &#39;./functions.js&#39;</pre><p>default export 的寫法一樣可以直接宣告就匯出</p><pre>export default function add(a, b) {<br>  return a + b<br>}</pre><p>或是先宣告再匯出</p><pre>function add(a, b) {<br>  return a + b<br>}</pre><pre>export default add</pre><p>在這個情況下，就會透過以下的寫法匯入。</p><pre>import add from &#39;./functions.js&#39;</pre><h3><strong>JavaScript 如何決定使用哪一種模組</strong></h3><p>通常我們在專案中，會透過 package.json 的 type 來設定當前專案要使用哪個模組系統。如果是 “type”: “commonjs” 就會使用 CommonJS，如果是 “type”: “module” 則會使用 ES Module。如果沒有使用 package.json ，或是在 package.json 裡面沒有加上 type 的設定，就會使用預設的 CommonJS 。</p><p>若是不想要受到 package.json 的影響，也可以透過副檔名來強制使用其中一個模組系統。當副檔名為 .cjs 時會是使用 CommonJS，副檔名使用 .mjs 則會使用 ES Module。如果沒有特別設定這樣的副檔名，只是單純使用 .js 這個副檔名，就會依照 package.json 的設定決定使用那一個模組系統。</p><pre>// package.json<br>{<br>  &quot;name&quot;: &quot;my-app&quot;,<br>  &quot;type&quot;: &quot;module&quot;,<br>  &quot;main&quot;: &quot;index.js&quot;<br>}</pre><h3><strong>總結</strong></h3><p>JavaScript 在最早的時候並沒有內建模組系統，所有 JavaScript 的程式都需要靠 &lt;script&gt; 載入，這也就導致很難針對 JavaScript 相關的程式碼進行依賴管理，也容易造成全域污染。但隨著前端邏輯變複雜，也就使得模組化需求越急迫。</p><p>一開始是後端的 Node.js 先使用了 CommonJS 作為模組系統，而瀏覽器直到 ES2015 才正式以 ES Module (ESM)作為標準的模組系統，因此在當前的生態中仍同時存在兩種模組系統。</p><p>雖然 CommonJS 和 ES Module 這兩者都是 JavaScript 的模組化系統，但兩者卻有差異：</p><ul><li>CommonJS 是透過 require()、module.exports 的寫法匯入和匯出，且是於執行期(runtime)時進行動態載入。</li><li>ES Module 是透過 import、export 的寫法匯入和匯出，且是在編譯期(build time) 就解析依賴進行靜態分析。</li></ul><p>想要決定要使用哪一種模組化系統，可透過 package.json 的 &quot;type&quot; 或副檔名 .cjs / .mjs 來控制。</p><p>這次的主題就到這裡告一個段落，下一篇會延續模組系統延伸來看看 Tree Shaking。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=da5261a0c571" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【前端學 Docker 】 不是 圖片的 image — Docker Image]]></title>
            <link>https://pink-learn-frontend.medium.com/%E5%89%8D%E7%AB%AF%E5%AD%B8-docker-%E4%B8%8D%E6%98%AF-%E5%9C%96%E7%89%87%E7%9A%84-image-docker-image-c3d20b9bb481?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3d20b9bb481</guid>
            <category><![CDATA[docker-image]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Fri, 04 Apr 2025 13:02:11 GMT</pubDate>
            <atom:updated>2025-04-04T13:14:32.473Z</atom:updated>
            <content:encoded><![CDATA[<h3>【前端學 Docker 】不是圖片的 image — Docker Image</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/271/1*GUVzssBqiHcPR6xdU5O0uQ.png" /></figure><p>上次複習完 Docker Container 之後，接著再來複習一下什麼是 Docker Image，一樣從字面認識開始吧！</p><h3>什麼是 Image？</h3><p>說到 Image 這個詞，大家對這個英文單字應該都不陌生，因為在一般使用情境下，Image 就是圖片的意思。不過其實 Image 的這個詞，在電腦科學和技術領域中，還有「靜態的、不可變的檔案」的意義在，在這種時候，通常就會把這個詞稱作是「映像」或是「鏡像」。所以 Image 這個詞彙並不單純只有圖片的意思，在 Docker 的世界中，Image 這個詞，也就是剛剛提到的「映像」或是「鏡像」的意思。所謂的「映像」或是「鏡像」通常是一個完整系統或應用程式環境的快照，這樣的檔案包含了系統或軟體執行所需的所有內容，例如：程式碼、設定檔、函式庫、相依套件，甚至作業系統核心等。</p><h3>Docker Image 呢？</h3><p>說到 Docker Image，也就是所謂的「Docker 映像」，或是「Docker 鏡像」。前面在看 Image 這個詞的時候，就有提到在技術領域中，Image 這個詞有著「映像」或是「鏡像」的意思，在 Docker 世界裡的 image 指的也就是用來啟動 Docker Container 的靜態模板。往更細節的方向來說明的話，就是用來啟動應用程式及其執行環境的檔案。</p><blockquote>其實 Docker Image 就像是一個<strong>建築設計圖</strong>，建築設計圖是靜態不可變的東西，但我們可以用建築設計圖建立出多個獨立的建築物，Docker Image 一樣也是靜態且不可變的，但我們可以用它啟動多個互相獨立的 Docker Container。</blockquote><p>我們還可以用另一個和程式面更有關聯的東西來看 Docker Image，在前端世界裡，其實有一個東西和 Docker Image 有點類似，這個東西就是 <strong>Component 元件</strong> (這裡特別強調一下，這裡指的 Component，是建立元件實例的 Component .vue 檔案，或是 React 的 Component Function)，因為 Docker Image 執行出來的 Container 和 Component 實際使用時，建立出來的實例，一樣都是獨立不互相干擾的存在。差別在於我們如果想要改動創建實例的 Component 時，只要進去修改 Component 的內容就可以了(Vue 的話就是 Component 檔案，React 的話，則是去改動 Component function)，讓 Component 重新渲染，就可以修改到既有的產生出來的 Component，但是如果是 Docker Image 的話，就必須重新建立一個新的 Image，才會套用到新的內容。Component 是一個可以用肉眼看到內容的檔案，Docker Image 則是由多個 Layer 組成，並且無法直接「用肉眼看到」它的內容，而是需要用指令來查看。另外，因為 Docker Image 的分層架構特性，即使將 Docker Container 停掉，實際上 Docker Image 還是會被認定為有被使用，而無法在這個狀態下將 Image 刪除。</p><h3>Docker Image 的特性</h3><p>前面用了很多比較的例子來看什麼時 Docker Image，接著來進一步看看前面有稍微提到的一些 Docker Image 的特性。</p><p>1. 分層架構：Docker Image 是由多個 Layer 組成的，並且透過聯合檔案系統 (Union File System) 來組合這些層，每個 Layer 代表 Image 的一個變更，例如：基礎作業系統、應用程式的程式碼。裡面的分層會被重複使用，加快 Image 建立速度，並且儉省空間。進一步的詳細內容，我們後面再來看。<br>2. 唯獨性：Docker Image Layer 是唯獨的，所以不能直接修改 Image 本身，但可以透過新增 Layer 來覆蓋舊內容。<br>3. 不可變：這部分就是前面有提到過的部分，當 Docker Image 被建立後，無法再進行 Docker Image 內容的修改，如果想要修改裡面的內容，就必須重新建立一個新的 Image。創建新的 Image 後，原本的 Image 還是可以使用，不會受到影響。<br>4. 可重用性：這部分也是前面有提到過的部分，那就是一個 Image 可以用來啟動多個獨立的 Container，且彼此互不影響。<br>5. 可擴展性：可以用一個建立好的一個 Image 來建立一個新的 Image，也就是說一個新的 Image 可以繼承於另一個既有的 Image。</p><h3>Docker Image 的分層架構</h3><p>接著再來進一步看看所謂的分層架構是什麼？一個 Docker Image 其實是多層的架構，將指令層層堆疊最後變成一個完整的 Image 檔案，當 Docker Container 被建立起來時，Docker 會使用檔案聯合系統（Union Filesystem, UnionFS）把這些層這些層合併成一個可操作的檔案系統。<br>進一步透過 Docker 指令來看看實際上說的層是什麼東西。<br>透過 docker history [image name]這個指令，可以查看Docker Image 的各層歷史，這些層是由 Dockerfile 中的各條指令（如 FROM、RUN、COPY 等）所創建，每次執行指令時，Docker 會創建一個新的層。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*KOO2RueeSMmgrDVm.png" /></figure><p>​<br>另外，透過 docker inspect [image name]這個指令，也可以看到 Docker Container 不同層級和底層檔案系統的結構。GraphDriver 的 Data 包括有關該 Image Layer 的存儲位置、資料夾結構等詳細資訊。RootFS 的 Layers 則是顯示出 Image 中各個層的 hash 值，這些 hash 值代表的是每個層的唯一標示。這些層堆疊在一起，就形成了 Docker Image 檔案。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*D8eN0KnxdzSrHb4V.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*_OSlWzxsYQpw6TKU.png" /></figure><p>​<br>當建立 Docker Container 的時候，實際上就是去利用可寫層來運行 Container，並基於 Image 中的多層結構來生成最終的檔案系統。</p><h3>聯合檔案系統（Union Filesystem, UnionFS）</h3><p>都已經看了 Image Layer 是什麼了，接著也來看聯合檔案系統。所謂的「聯合檔案系統」也就是一個將多個檔案系統層疊組合在一起使用的技術，這個技術雖然是將多個檔案系統合併成一個虛擬的檔案系統，但它其實還支援不同層的讀寫操作，以利 Docker 實現高效的檔案系統管理。<br>我知道看了這段說明感覺還是很模糊，接下來讓我們用檔案的新增、更新、刪除、讀取來了解聯合檔案系統的工作原理吧！</p><p>首先，先來介紹一些在聯合檔案系統中，幾個必須要認識的角色們，其實也就是前面在看 Image Layer 的時候，就有稍微提到過的 LowerDir、UpperDir、ＭergeDir、WorkDir 這些 Layer。</p><p><strong>- LowerDir：</strong>是位在最底部的層，實際上不只一層，每層都代表 Dockerfile 中的一條指令，也就是說這一層對應於 RootFS 的 Layers，且在 LowerDir 這層的檔案只能讀取。在進行所有對檔案的讀取操作時，會先檢查 UpperDir，如果檔案不存在於 UpperDir，則會從 LowerDir 中讀取<br><strong>- UpperDir：</strong>這層是可寫層，如果要進行檔案系統的新增、更新、刪除，都會先寫入這一層，再進行相對應的操作，不會直接改動 LowerDir 的檔案。<br><strong>- ＭergeDir：</strong>這一層是合併 LowerDir 和 UpperDir 的虛擬層，Container 實際操作的會是這一層，讀取檔案時，系統會先從 UpperDir 中查找，若找不到，再從 LowerDir 中查找。<br><strong>- WorkDir：</strong>這層不存在於 LowerDir 之下，它是用來支援檔案系統的操作，並且用於處理合併過程中的中間結果。它會與 LowerDir 和 UpperDir 協同工作，但它不會直接成為檔案系統層的一部分。</p><p>最後再來細看一下，當要進行新增、讀取，更新、刪除動作時，實際上會發生哪些步驟。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*vPcfy1SBFaQ3rojS.png" /></figure><p>​<br><strong>- 新增檔案：當</strong>建立新的檔案時，會直接在 UpperDir 產生一個檔案，不會影響到 LowerDir，並且會從 MergeDir 操作到 LowerDir。<br><strong>- 讀取檔案：</strong>當在讀取檔案時，因為 MergeDir 會將 UpperDir 和 LowerDir 兩層合併，所以 Container 會先從 UpperDir 開始查找檔案，如果在這一層找不到檔案，會進一步進入 LowerDir 找檔案。<br><strong>- 更新檔案：</strong>在進行檔案的編輯更新時，會複製出一份檔案到 UpperDir 來更新，不會直接改動到 LowerDir 的檔案。<br>-<strong> 刪除檔案：</strong>在刪除檔案時，實際上也並不會把 LowerDir 的檔案刪除，而是會建立一份 whiteout file，讓 Container 在 mergeDir 讀取不到這個檔案。</p><h3>結論</h3><p>Docker Image 是一個靜態模板，由多個層（Layer）所組成，其內容包含應用程式運行所需的所有內容。它是唯讀的，無法修改的內容，如果需要改動，必須創建新的 Image。Docker 使用聯合檔案系統（UnionFS）將這些層合併，並且在 Container 運行時會基於這些層建立可寫的 UpperDir 來進行操作。當容器要進行檔案操作 (如新增、更新或刪除)，變更都會寫入 UpperDir，不會改動到位在 LowerDir 中的原始檔案。</p><p>這篇拖了好久的複習文終於完成了，雖然花了點時間，但重新複習後，還是有感覺到對相關的內容，有更進一步的認識，雖然差點就無法完成了 ！果然隔行如隔山(?)，有些很基礎的觀念，自己原本都還不是那麼懂，總之還是順利打完收工啦！那就寫些打家囉！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3d20b9bb481" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【亂亂寫筆記】詐騙案例 — 用你信箱寄信警告信箱被盜用]]></title>
            <link>https://pink-learn-frontend.medium.com/%E4%BA%82%E4%BA%82%E5%AF%AB%E7%AD%86%E8%A8%98-%E8%A9%90%E9%A8%99%E6%A1%88%E4%BE%8B-%E7%94%A8%E4%BD%A0%E4%BF%A1%E7%AE%B1%E5%AF%84%E4%BF%A1%E8%AD%A6%E5%91%8A%E4%BF%A1%E7%AE%B1%E8%A2%AB%E7%9B%9C%E7%94%A8-c1c759fb91b9?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/c1c759fb91b9</guid>
            <category><![CDATA[smtp]]></category>
            <category><![CDATA[scam]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Sat, 25 Jan 2025 08:23:32 GMT</pubDate>
            <atom:updated>2025-01-25T08:23:32.739Z</atom:updated>
            <content:encoded><![CDATA[<h3>【亂亂寫筆記】詐騙案例 — 用你信箱寄信警告信箱被盜用</h3><blockquote><strong>貼心小提醒！！</strong><br>因為是亂亂寫筆記，真的就是純紀錄之前實作時，如何解決曾經卡住過的問題，所以主要都是解決問題的方式為主，不會有太多詳細說明。</blockquote><p>​</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*Ek26SRIHcrQOJzK5.png" /></figure><p>​​今天來寫一個和程式有點關聯，但和我實際工作沒有關聯的東西，單純覺得身邊的人應該會需要知道這個資訊，就寫一篇文章記錄。剛剛無聊收信的時候，發現自己收到一個用自己信箱寄給自己的信件，標題寫著 You have been hаcked，當下就覺得是詐騙了，工程師可是沒這麼好騙的！！但是很妙的是這次寄件者居然不是看起來詭異的信箱，而是我自己的信箱，當下老實說我還是有那麼幾秒覺得該不會真的被盜用了吧！！但還是冷靜下來先 google 一下，一查就發現了一個關鍵字 「SMTP」。</p><h3><strong>收到自己信箱寄的信箱被盜警告信！？</strong></h3><h4><strong>詐騙怎麼辦到的？</strong></h4><p>為什麼詐騙明明沒有真的盜用自己的信箱，卻能用自己的信箱寄了一封信件呢？這是因為詐騙使用了 SMTP 協議進行詐騙信件的寄發， SMTP 協議的全名是 Simple Mail Transfer Protocol，也就即「簡單信件傳輸協議」。而這個協議的特性就如同他的名字，它主要想達到的目的是用簡單的方式寄發信件，以及支援多種應用場景 (例如：信件轉發、系統通知等)，因此允許使用者在發送信件的時候，自定義寄件者的信箱內容，也是因為它主要想達到的目的是簡單寄發信件，所以並不考慮安全性，也就是說 SMTP 協議並不會強制去驗證寄件者和收件者資訊的真實性。</p><h4><strong>該怎麼在這個情況下，驗證自己的信箱是否真的被盜用</strong></h4><p>1. 第一個方法很簡單，那就是去檢查自己信箱的已傳送紀錄，是不是真的有這一封自己寄給自己的信件紀錄。<br>2. 透過檢視來源，查看 email header，並且檢查 SPF、DKIM、DMARC、Received 資訊。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/0*sPwDVgzSFXBJHTmO.png" /></figure><p>​</p><ul><li>SPF 指的是 Sender Policy Framework，如果是 fail 的話，代表發信伺服器的 IP 地址（104.164.47.196）並不在 hotmail.com 授權的伺服器列表中，也就表這封信件的發送來源是偽造的來源。</li><li>DKIM 指的是 DomainKeys Identified Mail，若為 none，就代表沒有啟用驗證，也就表示信件沒有使用數位簽名來驗證內容是否由合法的寄件者發送，或內容是否被篡改。</li><li>DMARC 是 Domain-based Message Authentication, Reporting, and Conformance，若為 fail，就代表驗證失敗，也就是說這封信件未通過 SPF 或 DKIM 的驗證。</li><li>從 Recieved 資訊可以看到這封信是從哪個網域來的，域名如果不是 hotmail.com ，那就可以判斷是假冒的信件。</li></ul><p>以下內容是我從 outlook 對信件點擊「檢視來源」看到的 mail header 的內容，先來看真的是我自己寄給自己的信件，會是什麼內容。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*9KgRJ0W1aXkgzEWd.png" /></figure><p>​<br>再來也來觀察一下，詐騙者假冒我名義寄給我自己的信件的 mail header，可以發現到都是正常的 pass。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*oPpIlJ-HMTCwF4Xf.png" /></figure><p>​</p><p>以上就是這次收到一個很奇特作法的詐騙近信件的相關分享！</p><p>最後也提醒大家！遇到奇怪的信件不要太緊張，一定要好好思考這封信件的真偽，最簡單的就是檢查寄件者信箱是不是看起來怪怪的，如果信箱看起來好像是真的，也可以再多透過「檢視來源」的動作來檢查是不是假冒的信箱來源，千萬不要貿然點下信件中的連結或是依照信中要求做匯款的動作。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c1c759fb91b9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【前端新手日記】網頁渲染模式 — SSG、ISG]]></title>
            <link>https://pink-learn-frontend.medium.com/%E5%89%8D%E7%AB%AF%E6%96%B0%E6%89%8B%E6%97%A5%E8%A8%98-%E7%B6%B2%E9%A0%81%E6%B8%B2%E6%9F%93%E6%A8%A1%E5%BC%8F-ssg-isg-fd1be1f20588?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/fd1be1f20588</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[isg]]></category>
            <category><![CDATA[ssg]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Mon, 30 Dec 2024 15:01:54 GMT</pubDate>
            <atom:updated>2025-06-29T02:34:11.654Z</atom:updated>
            <content:encoded><![CDATA[<h3>【前端新手日記】網頁渲染模式 — SSG、ISR</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PXdhxLref6yzqWMuOtptyg.png" /></figure><p>之前已經看過了 CSR 和 SSR 這兩種渲染模式了，今天再我們從回顧什麼是 SSR 開始來看 SSG 和 ISR 這兩種網頁渲染模式。</p><h3><strong>回顧 SSR</strong></h3><p>SSR 指的是「<strong>伺服器端渲染 (Server-Side Rendering)」</strong>，也就是說</p><blockquote>渲染 HTML 的這件事情是交由伺服器來處理，所以瀏覽器在發送 request 後，得到的 response 會是完整頁面的 HTML 檔案。</blockquote><p>SSR 有分舊時代的 SSR 和新時代的 SSR，舊時代的 SSR 就是很單純的一個概念，那就是前面所提到的</p><p>「發送 request 後，由伺服器回完整頁面的 HTML 及相關的 CSS 給瀏覽器，每次進行互動的動作時，例如：送出表單，都會重新經歷一次這個發送 request，在次取得一份新的 HTML 的動作」。</p><p>但在新時代的 SSR 中，則是</p><p>「優化了每次做一個操作動作，就要重新發送 request 去得新的 HTML 的部分，只會在第一次進到頁面時，才進行發送 request 取得完整 HTML 及相關的 CSS 等靜態檔案的 response，在這之後的操作、互動動作，會因為 Javascript 有被綁定在 DOM 元素上(hydration)，而可以在不特別向伺服器請求新的 HTML 等靜態檔案的狀態下，就讓畫面呈現操作的效果」。</p><p>新時代的 SSR 是為了解決舊時代的 SSR 每次做一個操作都要重新等待完整的 HTML 回來的缺點，但又保持著第一次進入頁面時就可以拿到完整的 HTML 頁面，以維持有助於 SEO 的優點。</p><h3><strong>什麼是 SSG ？</strong></h3><p>前面回顧過 SSR 是什麼之後，緊接著來看一下什麼 SSG。<br>所謂的 SSG 指的是 「靜態網頁生成 (Static Site Generation)」，簡單來說就是一個</p><blockquote>在 npm run build的時候就已經產生的「靜態網站檔案」，有別於 SSR 這個渲染模式，要在發送 request 後，才產生 HTML 檔案，是在 build 的時候就產生，所以可以 cache 在 CDN 上。</blockquote><p>這裡也實際從 run build 後的檔案觀察，產生的檔案有什麼差異。</p><p>我用 Next.js 建了兩個 /new 的 index 檔案，左邊使用的渲染模式是 SSR ，右邊使用的渲染模式是 SSG。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*JrT-LY1pVqlwsM6w.png" /></figure><p>​</p><p>當執行 npm run build 之後，可以看到 SSR 的模式下，並沒有產生 new 的 HTML 檔案，SSG 模式下，則有產生出一個 new 的 HTML 檔案，這部分就如同前面所提到的一樣，</p><blockquote>「SSG 與 SSR 不同的地方在於 SSG 在 build 的時候，就會產生 HTML 檔案，不需要在發送 request 時，才透過 server 產生 HTML 檔案給瀏覽器」。</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*HuiQuN3jOuq_ZGAE.png" /></figure><p>​從前面提到的特性應該不難了解到 SSG 的優點是利於維持 SEO ，並且有較好效能，不過由於每次內容有更新，都要重新 build 一次檔案，也就比較不適合資料會頻繁有變動的頁面，因此這種形態的渲染方式，會比較適合大多時候都不會有更新的靜態頁面，例如：部落格、公司介紹頁面。</p><p>但是偶爾還是會有一些使用情境是雖然不會有頻繁的資料變動，但卻有要定期更新資料的需求，這時候雖然還是可以用 SSG，但是每次都要重 build 還是很費時及費工，這種時候就可以考慮使用另一種渲染模式。</p><h3><strong>帶有 SSG 優點並補強 SSG 缺點的 ISR</strong></h3><p>ISR 指的是「增量靜態生成 (Incremental Static Regeneration)」。簡單來說的話，</p><p><em>就是維持 SSG 在 build 的時候就產生靜態 HTML 檔案的優點，但帶有不需要透過 build 來更新頁面的特點。</em></p><blockquote>當使用者發送 request 去取得 SSG 的頁面時，會去確認這個頁面是否已經過期，如果沒有過期，會 response 回舊的檔案，如果過期，會在背景進行重新生成新靜態檔案的動作，此時若還沒產生好，會先返回舊的檔案，下次再次進入頁面，則可以取得新產生的檔案。</blockquote><p>這樣帶有靜態頁面特性，及特定時間自動更新產生新靜態頁面的彈性，讓 ISR 很適合一些需要定期更新，但不需要即時更新的頁面，例如：新聞文章頁面、活動頁面。</p><p>接下來一樣用範例來看看有什麼差異，在這個範例中，也是使用 Next.js 寫一個使用 ISR 的 /new 的 index 檔案，這裡有使用 <strong>revalidate</strong> 來讓網頁可以定期地在不重新 build 的情況下，也能更新頁面。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*gUJsc3GX5ixmbRb7.png" /></figure><p>​這次特別在 getStaticProps 函式裡面加上一個 console.log 來觀察。<br>除此之外，我們一樣先 run build 觀察一下 build 完後的檔案，可以發現就如同 SSG 的特性一樣，在 build 的時候，也會產生出 HTML。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/326/0*Ts8Hfe4urXCXfco-.png" /></figure><p>​</p><p>另外也透過 console.log 來觀察「頁面過期時，觸發生成新頁面」的動作。</p><p>可以發現到當我們第一次進入頁面時，會先顯示一個時間點 (10:54:11)，這時候因為頁面還沒有過期，所以沒有觸發 getStaticProps 這個函式，但是再次進入頁面後，因為頁面過期了，就呼叫了 getStaticProps，並在 vscode 的 terminal 內印出時間 (11:02:26)，但可以發現到 terminal 印出的時間，與當下的頁面 (10;54:11)並不一致，那是<strong>因為新的頁面並沒有馬上產生好，所以會先使用舊的頁面，等到再次進入這個網址，才會使用最新的頁面</strong>，意即再次進入這個網址才會是顯示 11:02:26 的頁面。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vmkQznmyeIKLSZWb.gif" /></figure><p>​<br>更進一步觀察 response headers 的話，也可以發現到會有 s-maxage=60 和 stale-while-revalidate=31535940 的設定值內容。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*uV1UE7yo_z2zxJgS.png" /></figure><p>​</p><h3><strong>SSG 和 ISR 的優點和缺點</strong></h3><p>最後再來回顧一下前面說提到的 SSG 及 ISR 的特點，也思考一下它們各自的優缺點吧！</p><p>SSG 有別於 SSR，會在 build 的時候就產生完整的 HTML 靜態檔案，而不需要在發送 request 後，才透過 server 產生靜態檔案，所以如果 SSG 的頁面需要更新，就需要重新再 build 一次，這樣的特性使得 SSG 在內容更新的靈活度上比較差，也因此會比較適合使用在大多數時間都不太需要更新內容的頁面。</p><p>ISR 則是一種 SSG 模式的延伸，它一樣是在 build 的時候，就會產生完整的 HTML 靜態檔案，但是不需要每次都手動 build 一次，而是可以進行一些設定，來決定頁面的過期時間，當頁面過期時，再重新在背景產生新的靜態檔案。因為這樣的特性使得 ISR 適合使用在內容雖然需要更新，但不需要頻繁或即時更新的頁面上。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*fPrL-_v6W51Tuc3n.png" /></figure><p>​​​以上就是針對 SSG 及 ISR 進一步認識的部分，打完收工！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fd1be1f20588" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【前端學 Docker 】 從認識 Container 開始]]></title>
            <link>https://pink-learn-frontend.medium.com/%E5%89%8D%E7%AB%AF%E5%AD%B8-docker-%E5%BE%9E%E8%AA%8D%E8%AD%98-container-%E9%96%8B%E5%A7%8B-ab2a08a2cff5?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/ab2a08a2cff5</guid>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[containers]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Sun, 08 Dec 2024 07:01:39 GMT</pubDate>
            <atom:updated>2024-12-08T07:01:39.833Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*bhhtBvKrrN4OBfus.png" /></figure><p>​<br>最近去上了小賴老師的 Docker 課程，在課程過程中，雖然有少許的一些部份對於身為前端的我來說有點不太好懂，會需要稍微花點時間消化一下、思考一下，但在這堂課程中，依然還是收穫滿滿，也發現到自己以前誤解的部分，趁著還有記憶的時候，就來一篇算是上課後的不專業複習文吧！</p><h3><strong>Container 是容器？</strong></h3><p>從底層概念來看的話，所謂的 Container 其實是運用兩個 linux 技術所產生的，而這兩個 linux 技術分別是 ：</p><ul><li>Namespace(負責隔離)</li><li>cgroups(負責管理資源)</li></ul><p>再更白話一點地透過產生一個 Container 的流程下去說明什麼是容器的話，也就是</p><blockquote>「利用 Namespace 技術，創建一個新的 PID Namespace，為 Process 提供獨立的空間」。</blockquote><p>還記得自己第一次聽到 Container 這個詞是在轉職後的第一份工作的時候，那時候真的完全對 Container 這個詞一點概念也沒有，當然如果不懂其實好像對我當時的前端工作也不會太大的影響，但是還是很好奇這個是什麼東西，於是就問了後端同事「Container 是什麼？」，那時候得到的答案是：「Container 就是容器的意思，有點像是一個虛擬的電腦」，於是我對於 Container 的理解也就變成是 Container 是一個虛擬的主機，裡面會安裝一些我要用的程式。</p><p>上完這堂課之後，才發現自己當初的理解有些錯誤！</p><p>其實更正確的說法應該是，</p><blockquote>Container 是<strong>「一個被隔離的 Process」，而不單純只是一個空空的容器。</strong></blockquote><p>而在這之中會導出一個核心特性，那就是</p><blockquote><strong>「 Container 的生命週期與 Process 的生命週期一致，且每個 Container 都會有一個主要的 Process」</strong>。</blockquote><h3><strong>Process 指的是什麼？</strong></h3><p>Process 指的是進行中的程式，這樣說可能聽起來很抽象，再更具體一點，可以是執行一個 Bash Shell 讓自己可以在這個指令介面中下指令，也可以是一個 node server 的運作，這兩個都是會佔據記憶體的實體，而非靜態的程式碼。<br>這裡也透過兩個不同的小例子來看一下什麼是 Process。</p><h4>[持續在背景進行中的程式]</h4><p>先執行<br>docker container run -d nginx<br>執行後會印出這個 Container 的 ID</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*w-wBcbeSYVNDQGYL.png" /></figure><p>​再執行<br>docker container ls<br>可以看到一個運行中的 Container</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*eIHrnHtF4Uie4G-N.png" /></figure><h4>​<strong>[進行完成後結束的程式]</strong></h4><p>先執行<br>docker container run alpine echo &quot;hello my Container&quot;<br>執行之後會看到畫面上印出 hello my Container</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*wOGUuIk47HzSuZBe.png" /></figure><p>​<br>接下來執行<br>docker container ls<br>會發現沒有任何 Container</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*e6MRrWCIIjHM67NI.png" /></figure><p>​再透過執行<br>docker container ls -a<br>可以發現到其實剛剛真的有一個執行 hello my Container 的 Container，只是它的狀態是 Exited</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*mDf-5tBHzwQlzEsD.png" /></figure><p>​</p><p>從上面這兩個小例子，不難發現到我們前面提到過的特性，<strong>「<em>Container 的生命週期與 Process 的生命週期一致，且每個 Container 都會有一個主要的 Process</em>」</strong>。因為在第一個例子中，Container 一直存在著，而在第二個例子中，因為 Process 已經在指令完成後結束了，所以 Container 也就呈現 Exited 的狀態了，但透過加上 -a 查看 Container 清單，還是可以發現到他曾經存在過的痕跡。</p><h3><strong>被隔離的 Process 是什麼？</strong></h3><p>被隔離的部分指的是 「Process 被限制在一個受控的環境中，這個環境與主機（Host）以及其他容器相互隔離」。這樣說還是一樣很難懂，那就一樣直接用實際的案例來看看吧！</p><p>首先，先跑兩個指令，建立兩個 container<br>docker container run --name my-container-1 -it -d alpine<br>docker container run --name my-container-2 -it -d alpine</p><p>跑完指令，就可以看到有兩個 Container 正在運行中</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*IIxNkvMvivLBsDOt.png" /></figure><p>​接下來在 my-container-1 創建一個文件檔案，確認 my-container-1 內有 my-file 這個檔案。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*3TsDXhF-N-f_1fFI.png" /></figure><p>​</p><p>再前往 my-container-2 查看這個 container，可以發現沒有 my-container-1 中的檔案。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*IlDx6L1guPD82wNR.png" /></figure><p>​<br>雖然這兩個 container 都是運行在我的電腦上，但是從創建檔案這件事可以發現，兩個 Container 是各自獨立的存在。</p><p>也可以透過下面的指令，來檢查這兩個 Container 的 PID 資訊<br>docker ps --quiet|xargs -n 1 docker inspect --formate &#39;Container: {{.Name}}, PID: {{}.State.Pid}&#39;<br>會發現兩個的 PID 確實也不相同。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*8Rgw1wCIkoZtBBAW.png" /></figure><h3>​<strong>Container 不是虛擬主機(VM)？</strong></h3><p>前面有提到我原本把 Container 理解成是一個類似虛擬主機的東西，但實際理解之後才知道 Container 並不等同於 VM，只是在某些用途上有點相似。其實在使用的技術及特性上，Container 和 VM 都不一樣。<br>技術的部分 VM 是透過 Hypervisor 創建，Container 則是透過前面提到過的 Namespace 及 cgroups 創建。Container 是共用電腦主機本身的核心，VM 則是模擬整個完整的作業系統。另外， Container 的部分就如之前提到的一樣，一定會有一個 Process 存在，且生命週期與 Process 綁定在一起，VM 則是與 Process 是互相獨立，沒有關聯性。</p><h3><strong>重點回顧</strong></h3><p>最後讓我們回想一下今天說到了哪些重點：</p><ul><li>Container 並不是單純的空空的容器，而是一個被 Namespace 技術隔離的 Process。</li><li>Container 是各自獨立的空間。</li><li>一個 Container 一定會存在一個 Process。</li><li>Container 的生命週期和 Process 一致，當 Process 結束，Container 也會結束運行。</li><li>Process 指的是運行中的程式，例如：一個跑起來的 node server。</li><li>Container 不等於 VM。</li></ul><p>今天現大概複習了一下在課堂上學到的 Container 的部分，下次預計從 Docker Image 來複習在課堂上所學到的 Docker。<br>這次就先到這裡囉！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ab2a08a2cff5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【前端新手日記】網頁渲染模式 — CSR vs SSR]]></title>
            <link>https://pink-learn-frontend.medium.com/%E5%89%8D%E7%AB%AF%E6%96%B0%E6%89%8B%E6%97%A5%E8%A8%98-%E7%B6%B2%E9%A0%81%E6%B8%B2%E6%9F%93%E6%A8%A1%E5%BC%8F-csr-vs-ssr-cdebd12f48db?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/cdebd12f48db</guid>
            <category><![CDATA[ssrs]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[csr]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Sun, 03 Nov 2024 00:02:16 GMT</pubDate>
            <atom:updated>2024-11-03T00:02:16.568Z</atom:updated>
            <content:encoded><![CDATA[<h3>【前端新手日記】網頁渲染模式 — CSR vs SSR</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P5tWAQ_lIV1StQpZPwoB4w.png" /></figure><p>最近想要好好的來認識實現 SSR 的 next.js 這套框架，所以就想說先回歸根本，仔細來從 CSR 開始理解到 SSR。</p><h3><strong>什麼是 CSR ？</strong></h3><p>一樣從字面上粗淺的了解一下，所謂的 CSR 指的就是「客戶端渲染 (Client-Side Rendering)」。這裡的客戶端指的是瀏覽器，再更進一步的來看，也就是在瀏覽器中產生 HTML，然後將這個 HTML 動態插入到頁面中，將畫面顯示出來。想要檢查當前的網站是不是用 CSR 這種渲染方式的話，則可以透過檢視網站原始碼來判斷，當檢視原始碼時，發現是一個和當前顯示畫面不一致、不完整的 HTML 時，就可以知道這是一個 CSR 的網頁。<br>如同下面這張圖呈現的樣子</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*JCEO1BLnAuvFAa7a.png" /></figure><p>​<br>就如同前面提到的 CSR 是一種客戶端渲染的渲染模式，所以我們肉眼所看到的完整頁面，都不是一開始對伺服器發送 request，再從伺服器獲得 response 後，就獲得的內容，所以當我們檢視網頁原始碼時，才會發現原始的 HTML 是一個沒有呈現完整頁面內容的 HTML。</p><h3><strong>CSR 和 SPA 的關係是？</strong></h3><p>說到 CSR，大家會想到的應該還有 SPA，那這兩者的關係究竟是什麼呢？其實 CSR 並不等於 SPA，因為 CSR 是一種渲染模式，而 SPA 則是一種應用程式的類型，也就是所謂的「單頁式應用程式 (Single-page application)」，其他像是 MPA、PWA 都是一種應用程式類型。</p><blockquote>而 CSR 和 SPA 之間的關係則是「CSR 這個渲染模式，是實現 SPA 的常見方式」，也就是說這兩者彼此的確有關係，但並不等價。</blockquote><h3><strong>CSR 運作方式</strong></h3><p>大致上了解什麼是 CSR 後，接著來看看 CSR 的運作方式吧！</p><p>簡單來說就是</p><blockquote>「下載並執行從伺服器 response 回來的 HTML 檔案內引用的 javaScript 檔案，在瀏覽器產生完整的 HTML，以呈現完正的頁面」。</blockquote><p>從完整的流程來看的話，則是</p><blockquote>「輸入網址進入網頁 -&gt; 對伺服器發送 reques -&gt; 伺服器透過 response 把包含基礎內容的 HTML 檔案（只含有掛載完整內容的 &lt;div&gt;）和打包好的 JavaScript、CSS 等靜態資源 發送回來 -&gt; 瀏覽器下載並執行 JavaScript -&gt; JavaScript 在客戶端動態生成並渲染完整的 HTML 和頁面內容 -&gt; 用戶看到完整的網頁」 。</blockquote><p>看到這裡，你可能會有一個疑問，<strong>「response 回來的 HTML、CSS、JavaScript 是從哪產生的？」</strong>，我們可以再回到我們進行開發時的流程下去思考。當我們開發完成後，將網站部署出去前，一定會做的動作就是跑 build 的指令，通常會是 npm run build。可以觀察到，當我們執行這個 build 指令後，就會產出以下這些的檔案。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/542/0*XkgWyrTXFcFPxjrC.png" /></figure><p>​</p><p>這些檔案也就是我們從伺服器 response 拿到的打包後的檔案，完整的 HTML 內容則是會在下載執行裡面的 JavaScript 檔案後，才會被產生出來。</p><h3><strong>什麼是 SSR ？</strong></h3><p>前面已經看了什麼是 CSR了，接著也來看看什麼是 SSR 吧！一樣從字面上來看的話，也就是所謂的 SSR 也就是「伺服器端渲染 (Server-Side Rendering)」，意思也就是說會直接從伺服器的 response 拿到完整的 HTML，因為 HTML 是在伺服器上產生的。 所以有別於 CSR，當嘗試用檢視網頁原始碼來查看原始的 HTML 時，就可以看到和頁面呈現內容一樣完整的 HTML。<br>例如以下這張圖，可以看到實際肉眼看到的內容，出現在網頁原始碼裡面。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*rX3wkvFX1Wv2-iMs.png" /></figure><p>​</p><h3><strong>舊時代與新時代的 SSR</strong></h3><p>看到這裡有些人可能會突然跑出一個想法，那就是「JavaScript 跑去哪了？」。我想大家應該都知道要讓一個網頁可以達到互動的效果，一定會需要 JavaScript，但是整個靜態的 HTML 都是伺服器幫我們產生，那 JavaScript 會在哪個階段出現呢？這個就不得不提到新舊時代 SSR 的差異。</p><p>就如同前面所提到的內容一樣，伺服器會幫我們產出一個完整的靜態 HTML 檔案，所以畫面呈現的部分一定是沒有問題的，但是當我們需要互動時(例如送出表單)，一般的靜態的網站並沒辦法做到，那究竟是要怎麼達到這樣的效果呢？這裡就出現了新舊時代 SSR 的差異。</p><p><strong>在</strong><strong>舊時代的SSR模式中，如果想要進行送出表單之類的互動操作，會需要伺服器的幫忙</strong>，也就是說當我們送出表單時，也會向伺服器發送 request，進行相對應的處理，並且再次 response 一個新的完整 HTML，所以在做互動的動作時，整個網頁會被刷新，如果從 Dev Tools 觀察的話，就可以發現到當我們進行輸入表單的動作，或是切換頁面，都會看到有 response 回來一個完整新的 HTML，如同以下的畫面。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZyXerVnDI-Q6by5m.gif" /></figure><p><strong>​在</strong><strong>新時代的SSR模式中，SSR 模式則不只可以處理靜態頁面，也可以優雅的處理互動操作，因為在新時代的 SSR 模式中，會有一個 hydration 的動作，也就是將 JavaScript 綁定在 DOM 元素上</strong>，這就能讓 SSR 不用在做動作時都要請伺服器處理，切換頁面也不需要重新向伺服器取得新的 HTML，也就是說其實新時代的 SSR，是一種結合 CSR 的混合渲染模式。</p><p>如果觀察新時代的 SSR 網站也就可以發現雖然網頁原始碼一樣有顯示當前頁面的完整 HTML，但是當進行切換頁面或提交表單等的動作，不會再重新取得全新的 HTML，也就會像是以下這個狀況一樣：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YQirep7-bUdRdRHz.gif" /></figure><p>​</p><h3><strong>SSR 怎麼運作</strong></h3><p>大概認識 SSR 是什麼後，接著也來看看 SSR 的運作方式是什麼。</p><p>簡單來說就是</p><blockquote>「對伺服器發送 request，取得完整的 HTML 顯示在頁面上」。</blockquote><p>從完整的流程來看的話：</p><ul><li><strong>舊時代的 SSR：<br></strong>「輸入網址進入網頁 -&gt; 對伺服器發送 reques -&gt; 伺服器產生完整的 HTML 檔案 -&gt; 透過 response 把完整的 HTML 檔案和 CSS 等靜態資源發送回來(在 HTML 檔案中可能會包含處理特效的少量 JavaScript) -&gt; 用戶看到完整的網頁」 。</li><li><strong>新時代的 SSR：<br></strong>「輸入網址進入網頁 -&gt; 對伺服器發送 request -&gt; 伺服器產生完整的 HTML 檔案 -&gt; 透過 response 把完整的 HTML 檔案和 CSS 等靜態資源發送回來 -&gt; 瀏覽器下載 HTML 中用 script 引用的 JavaScript -&gt; 將 event Handlers 綁定到 DOM 上(Hydrate) -&gt; Hydrate 完成後，使用者就可以和頁面互動」。</li></ul><p>從上面新舊時代的 SSR 的運作流程來看的話，就可以發現從舊時代轉換到新時代的差異，在舊時代的 SSR 模式中，就是很單純的讓伺服器產生一個靜態的檔案來呈現畫面，如果要做一些互動操作，就必須還要透過伺服器進行處理，所以也就會需要伺服器再回傳一個新的 HTML 檔案；而新時代的 SSR 模式中，除了讓伺服器產生靜態檔案來呈現畫面話，還會透過 JavaScript 才讓頁面可以在瀏覽器中進行操做，也就是 Hydration 的部分，所以只有在第一次進入畫面時，會透過伺服器回傳一個完整的 HTML 檔案，之後切換頁面就不需要再讓伺服器提供完整的 HTML 檔案了。<br>而我們常常會聽到的 Next.js 和 Nuxt.js 就是新時代 SSR 模式的框架。</p><p>這裡我們再稍微深入一點看看 npm run build 的時候會出現什麼檔案，以下是使用 Next.js build 之後產生的檔案，</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/390/0*YIM1qVaR4nsIZ3JW.png" /></figure><p>​<br>可以發現到 SSR 模式的框架下 build 出來的檔案會比 CSR 來得複雜很多，這是因為在 SSR 模式下，不是單純使用 JavaScript 來產生完整的 HTML 內容，而是需要透過伺服器產生，產生出完整 HTML 的這個伺服器也並不是靜態伺服器(例如：Nginx)，而是由框架所建構的 node server，也就是說 build 出來的檔案，除了 CSS 等的靜態檔案外，還有關於伺服器渲染的邏輯及伺服器的設定內容。</p><h3><strong>從部署看 CSR 及 SSR 的差異</strong></h3><p>接著再讓我們更進一步地去從部署流程來思考。<br>當我們部署 CSR 專案的時候，我們就只是很單純把 build 出來的靜態檔案放到伺服器(例：Nginx)上，當瀏覽器對伺服器發送 request 時，就是把對應的檔案 response 回去，接下來就和前面所提到過的一樣，會去下載並執行 HTML 中 script 所引用的 JavaScript 檔案，接著在瀏覽器渲染出完整的 HTML。</p><p>而部署 SSR 的專案時，就不是單純的把靜態資源放到伺服器中，而是需要透過 Next 或 Nuxt 提供的指令，將 node server 給建立起來，因為產生出完整 HTML 的伺服器是 node server，即使是部署到 Nginx 中，也不是由 Nginx 來產生完整的 HTML，還是會需要透過 Nginx 將 request 轉送給 node server，才能產生出完整的 HTML 來回應給伺服器。</p><h3><strong>CSR 和 SSR 的優點和缺點</strong></h3><p>最後再來回顧一下前面說提到的 CSR 及 SSR 的特點，也思考一下它們各自的優缺點吧！</p><p>CSR 由於伺服器回傳的 HTML 是一個不含完整內容的 HTML ，需要在瀏覽器中透過下載並執行 javaScript 檔案，才會產生完整的畫面，所以第一次進入頁面的時候，會需要等待一些時間，才會看到完整的畫面，也就是說第一次進入畫面時，會看到白色的畫面一下子，才會出現完整的畫面，但是在之後，不論是對畫面進行操作或是切換頁面，都不需要再對伺服器發送 request，所以切換頁面和進行操作時，整體呈現會比較流暢。</p><p>SSR 由於是在伺服器中產生完整的 HTML 給瀏覽器呈現，所以首次進入頁面，不會顯示白色的畫面，但是在舊時代的 SSR 模式中，因為沒 JavaScript 的幫助，所以每次進行操作或是切換頁面時，都要重新和伺服器請求一個新的 HTML，整體的操作過程也就比較不流暢，因為會有刷新頁面的過程；但在新時代的 SSR 模式中，則是因為有 JavaScript 的幫助 (也就是前面所提到的 Hydration)，使得在首次拿到完整的 HTML 之後，接著在操作及切換頁面的動作中，就可以透過 JavaScript 讓整體流程更順暢，不用一定要藉由伺服器的幫助才能進行，也就是說<strong>新時代的 SSR 結合了 SSR 和 CSR 的優點</strong>。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*MAX9wK-mU5QzXIbq.png" /></figure><p>​</p><p>以上就是這次針對 CSR 及 SSR 稍微再深度一點的去了解的部分，打完收工！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cdebd12f48db" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[【亂亂寫筆記】VS Code — ESLin 應出現的警告提示未顯示]]></title>
            <link>https://pink-learn-frontend.medium.com/%E4%BA%82%E4%BA%82%E5%AF%AB%E7%AD%86%E8%A8%98-vs-code-eslin-%E6%87%89%E5%87%BA%E7%8F%BE%E7%9A%84%E8%AD%A6%E5%91%8A%E6%8F%90%E7%A4%BA%E6%9C%AA%E9%A1%AF%E7%A4%BA-6c2be3b4d681?source=rss-960694c636fc------2</link>
            <guid isPermaLink="false">https://medium.com/p/6c2be3b4d681</guid>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[vscode]]></category>
            <dc:creator><![CDATA[文科少女學程式]]></dc:creator>
            <pubDate>Sun, 04 Aug 2024 15:01:31 GMT</pubDate>
            <atom:updated>2024-08-04T15:01:31.732Z</atom:updated>
            <content:encoded><![CDATA[<h3>【亂亂寫筆記】VS Code — ESLin 應出現的警告提示未顯示</h3><blockquote><strong>​貼心小提醒！！</strong><br>因為是亂亂寫筆記，真的就是純紀錄之前實作時，如何解決曾經卡住過的問題，所以主要都是解決問題的方式為主，不會有太多詳細說明。</blockquote><p>最近在寫自己的 side project，但發現專案裡面的 VS Code 不會顯示我很習慣會用來提示自己沒寫好的警告黃底線，例如：useEffect dependency 沒寫完整的警告提示。這次也稍微記錄一下自己排除這個問題的過程，以及最後的解決方法。</p><h3><strong>VS Code 未顯示應該出現的 ESLint 警告提示</strong></h3><h4><strong>實際情境</strong></h4><p>以下是一段 React 專案中的程式碼，這個專案已經有 ESLint 的相關設定，預期在這個useEffect中，應該會有兩個相依值，所以當沒有把所有應該放入的相依值放入時，應該會有黃底線提示才對，但在這裡的實際情況卻是「沒有顯示任何提示 」(如下圖)。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*UYo62qZitmEkXalM.png" /></figure><p>​</p><h4><strong>除錯過程</strong></h4><p>逐步縮小問題發生的原因範圍：<br>1. 確認 VS Code 的 ESLint Extension 有無安裝。<br>2. 透過跑 ESLint 指令，確認 ESLint 規則是否有正常套用。<br>2. 確認 VS Code 有關 ESLint 的相關設定。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*qLMOqrOmX-P9W5-U.png" /></figure><p>​</p><p>- ESLint 有無設定 enable<br>- ESLint settings 內的 validate 設定有無加上</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*mlrmj8AN8FOIvdLo.png" /></figure><p>​</p><p>3. 確認 VS Code 版本和 ESLint 版本是否有不符的狀況。<br>從 VS Code output 將下拉選單切換到 ESLint 看結果。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*0843F4n09r5MZlKw.png" /></figure><p>​</p><h4><strong>最終問題排除方式</strong></h4><p>最後發現在這最後一個步驟，有出現以下的訊息，確認到是 VS Code 版本不符的狀況。<br>The language client requires VS Code version ^1.90.0 but received version 1.85.2</p><p>所以改使用版本較新的 VS Code 就能正常在 VS Code 視窗中確認到警告提示。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*d5uHG7iTNkPihdyB.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6c2be3b4d681" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>