{"id":15097871,"url":"https://github.com/cj-yang0225/cj-newsapp","last_synced_at":"2026-01-07T11:15:39.957Z","repository":{"id":47301411,"uuid":"498622577","full_name":"CJ-Yang0225/CJ-NewsApp","owner":"CJ-Yang0225","description":"An app that collects news from various media in Taiwan","archived":false,"fork":false,"pushed_at":"2022-09-07T03:01:21.000Z","size":2650,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-01T17:44:44.927Z","etag":null,"topics":["axios","babel","es6","koa2","scss","vanilla-javascript","vanilla-js","vercel","webpack","webpack5"],"latest_commit_sha":null,"homepage":"https://cj-news.vercel.app/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CJ-Yang0225.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-06-01T06:48:16.000Z","updated_at":"2022-09-07T02:58:19.000Z","dependencies_parsed_at":"2023-01-17T22:31:22.928Z","dependency_job_id":null,"html_url":"https://github.com/CJ-Yang0225/CJ-NewsApp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJ-Yang0225%2FCJ-NewsApp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJ-Yang0225%2FCJ-NewsApp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJ-Yang0225%2FCJ-NewsApp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CJ-Yang0225%2FCJ-NewsApp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CJ-Yang0225","download_url":"https://codeload.github.com/CJ-Yang0225/CJ-NewsApp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245854875,"owners_count":20683428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axios","babel","es6","koa2","scss","vanilla-javascript","vanilla-js","vercel","webpack","webpack5"],"created_at":"2024-09-25T16:40:59.209Z","updated_at":"2026-01-07T11:15:39.913Z","avatar_url":"https://github.com/CJ-Yang0225.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CJ-NewsApp\n\n有時想看些新聞、時事，但不想被單一媒體的觀點所侷限，所以自製一個新聞的 Web App，藉由串接集成的 [Taiwan News API](https://newsapi.org/s/taiwan-news-api)，獲取各方新聞媒體的報導。\n\n## Client [連結](https://cj-news.vercel.app)\n\n原生 [Vanilla.js](http://vanilla-js.com/)🤣 撰寫前端 Client App，受到 React.js 和 Vue.js 的啟發，嘗試運用框架的核心理念，自訂 Webpack 環境，打造出專屬的專案架構。一方面可強化原生控制資料、事件、畫面三者的能力，另一方面能反思框架存在的意義、想解決什麼問題呢？\n\n### Features \u0026 Technologies\n\n#### 首頁（index.html）\n\n- 使用自己簡單封裝的 [XMLHttpRequest 工具](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/http.js)，發送 AJAX GET 請求，獲取類別（top, entertainment, sports, business, health, technology, science）對應的新聞，預設每頁為 10 筆報導（會根據裝置尺寸調整）。\n\n- 已獲取的資料會利用記憶體快取（memory cache）並附加一個能判斷資料是否過期的 timestamp（類似 Cookie 的 `Max-Age`），再將這個加工過的資料儲存至 `localStorage`，設定 5 分鐘後過期，需要重新請求資料，以確保新聞的即時性。（[程式碼 L67～L127](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/data.js#L67-L127)）\n\n- 標題區塊 [`Header`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/Header/index.js) 根據不同頁面顯示相應的訊息和連結。\n\n- 點擊上方導覽列 [`Navbar`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/Navbar/index.js)，更換新聞的類別，並即時更新 URL Search parameters（`?category=technology`），操控瀏覽器歷史紀錄（`history.pushState`）和監聽 `popstate` 事件。\n\n- 點擊新聞卡 [`NewsCard`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/NewsCard/index.js) 開啟有獨特 name 的分頁，讓點擊相同新聞卡時跳至同樣的分頁，不會重複開啟同一則新聞；另外用滑鼠中鍵或是右鍵（context menu）的「在新分頁中開啟連結」則可重複開啟。\n\n- 啟用圖片 lazy loading 以及增設圖片 `load` 事件，首次載入完成時觸發 fade-in 動畫；若瀏覽器有啟用快取（cache）機制則用 `HTMLImageElement.complete` 屬性判斷載入完成的時機來觸發 fade-in 動畫。\n\n- 行動裝置（Mobile device）裝置可透過左滑、右滑的手勢切換新聞類別。（[程式碼](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/NewsContainer/index.js)）\n\n- 當滾動至底部時觸發 [`loadMoreNews()`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/pages/index.js#L136-L162) 載入更多新聞，如果還有資料就加入提示 [`PullHint`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/PullHint/index.js)「載入更多新聞中」，更新頁數並填充下 10 筆報導。\n\n- 可對想追蹤、收藏的新聞報導點擊書籤圖示，使用 `localStorage` 儲存已收藏的新聞資料陣列，實現跨頁攜帶資料，加到**書籤收藏頁（collection.html）**。\n\n- 固定在左下角的部件組 [`Widgets`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/Widgets/index.js)，hover 後會展開內部組件，其一可進行主題模式（theme mode）的切換（預設採用使用者偏好的系統設定 `prefers-color-scheme`）；另一點擊後回到頁面頂部。\n\n- 手機、平板與電腦的 RWD（Responsive Web Design）。\n\n#### 書籤收藏頁（collections.html）\n\n- 取出 `localStorage` 的新聞資料陣列，展示出已收藏的全部新聞報導。\n\n- 點擊新聞卡 [`NewsCard`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/NewsCard/index.js) 開啟有獨特 name 的分頁，讓點擊相同新聞卡時跳至同樣的分頁，不會重複開啟同一則新聞；另外用滑鼠中鍵或是右鍵（context menu）的「在新分頁中開啟連結」則可重複開啟。\n\n- 可切換成管理模式進行整個 `localStorage` 新聞資料陣列的操作。\n\n- 固定在左下角的部件組 [`Widgets`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/components/Widgets/index.js)，hover 後會展開內部組件，其一可進行主題模式（theme mode）的切換（預設採用使用者偏好的系統設定 `prefers-color-scheme`）；其二點擊後回到頁面頂部。\n\n- 手機、平板與電腦的 RWD（Responsive Web Design）。\n\n#### 實用的工具函式們（utils）\n\n- 使用 RegExp 注入資料到類 html 格式的自訂 .tpl 檔案。（[`injectTpl()`](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/data.js#L7-L11)）\n\n- 幫助整理 object 的結構、URL 的參數、form data 的格式等等。（[程式碼 L13～L65](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/data.js#L13-L65)）\n\n- 操作卷軸和 DOM 相關屬性、新增 Web Components。（[程式碼](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/dom.js)）\n\n- 建立事件管理用的 class、實作 debounce 和 throttle。（[程式碼](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/src/utils/event.js)）\n\n### Module Bundler / Dependencies\n\n- Webpack5\n\n  - plugins\n\n    - [webpack.DefinePlugin](https://webpack.js.org/plugins/define-plugin/)\n\n      定義全域的環境變數 `NODE_ENV`，可判斷環境是 `\"development\"` 或 `\"production\"` 做出相應處理。\n\n    - [html-webpack-plugin](https://www.npmjs.com/package/html-webpack-plugin)\n\n      進入點（entry）主要分成 index.html 和 collections.html 兩頁面，共同的依賴包為 common.js。可以根據設定自動注入 title 名稱、SEO-friendly 標籤和解決需手動引入 JavaScript 與 CSS 的困擾。\n\n    - [mini-css-extract-plugin](https://www.npmjs.com/package/mini-css-extract-plugin)\n\n      `\"development\"` 環境下會以 CSS 注入 style 標籤的形式加到 HTML 中，但在 `\"production\"` 環境需要將 CSS 獨立抽離成靜態檔案，有利於提升效能與維護性，因此使用此 plugin。\n\n    - [webpack-bundle-analyzer](https://www.npmjs.com/package/webpack-bundle-analyzer)\n\n      視覺化分析工具，查看套件、模組的體積。\n\n  - loaders\n\n    - [babel-loader](https://www.npmjs.com/package/babel-loader)\n\n      搭配 Webpack 並與以下核心、preset 和 plugin 進行語法轉譯（transpiling）。\n\n      - [@babel/core](https://www.npmjs.com/package/@babel/core)\n\n        babel 的核心，負責讀取設定，提供執行轉譯的 API。\n\n      - [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env)\n\n        讓專案使用新的 JavaScript 語法，並根據瀏覽器環境（browserslist）添加需要的 polyfill，進而節省處理兼容所花費的時間。\n\n      - [@babel/plugin-transform-runtime](https://babeljs.io/docs/en/babel-plugin-transform-runtime)\n\n        進一步減少 bundle size。處理語法兼容經常會加上 helper function，此 plugin 可以在使用相同 helper function 時，預設以 import 的方式。\n\n    - [raw-loader（內建）](https://webpack.js.org/guides/asset-modules/)\n\n      載入自訂的 .tpl 檔案之原始內容（utf-8）\n\n    - [file-loader（內建）](https://webpack.js.org/guides/asset-modules/)\n\n      將特定檔案拷貝到目標資料夾，並返回對應的 URL\n\n    - [sass-loader](https://www.npmjs.com/package/sass-loader)\n    - [postcss-loader](https://www.npmjs.com/package/postcss-loader)\n    - [css-loader](https://www.npmjs.com/package/css-loader)\n    - [style-loader](https://www.npmjs.com/package/style-loader)\n\n  - Others\n    - [node-sass](https://www.npmjs.com/package/node-sass)\n    - [autoprefixer](https://www.npmjs.com/package/autoprefixer)\n    - [browserslist](https://www.npmjs.com/package/browserslist)\n    - [webpack-cli](https://www.npmjs.com/package/webpack-cli)\n    - [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server)\n    - [webpack-merge](https://www.npmjs.com/package/webpack-merge)\n    - [cross-env](https://www.npmjs.com/package/cross-env)\n\n## Proxy Server [連結](https://cj-news-server.vercel.app/)\n\n由於 News API 需要驗證 API Key 才能使用，為避免 API Key 在前端洩漏以及方便後續功能的擴充，所以用 Koa2 架設一個 Proxy Server，簡單路由後使用 Axios 帶上 API Key 到 Request Header 中，然後對目標 API 發送請求，最後獲得所需的新聞資料並回傳給前端。\n\n### Technologies \u0026 Features\n\n- 環境變數（Environment variables）\n\n  使用 `dotenv` 讀取 .env 和 .env.local 的環境變數。\n\n  - .env： 新增檔案命名為 .env，裡面放上本地開發用伺服器的 HOSTNAME 和 PORT。\n\n    ```plain\n    HOSTNAME=127.0.0.1\n    PORT=8080\n    ```\n\n  - .env.local： 放上 [Taiwan News API](https://newsapi.org/s/taiwan-news-api) Request Header 之 `X-Api-Key` 要驗的 `NEWS_API_KEY`，可放多支 key 增加請求次數（要用 NEWS_API_KEY 開頭）。\n\n    ```plain\n    NEWS_API_KEY0=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    NEWS_API_KEY1=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    NEWS_API_KEY2=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    NEWS_API_KEY3=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    NEWS_API_KEY4=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n    ```\n\n- [Axios 使用](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/server/src/config/index.js)\n\n  - 創建 `Axios` 的實例（instance），設定預設的選項，方便重複使用。\n  - 使用實例的 Interceptors，管理 Request 和 Response 過程發生的事情，例如處理 429 Too many requests 狀況（每支 API Key 有限制請求次數），更換成另一支 API Key。\n\n- [路由（routes）](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/server/src/routes/news.js)\n\n  `/api` 或 `/api/news`： 接收前端的 GET Request，調用中介層函式的 `getNews`\n\n- [控制器（controllers）](https://github.com/CJ-Yang0225/CJ-NewsApp/blob/main/server/src/controllers/news.js)\n\n  `getNews(ctx)`： koa2 會調用此中介層函式，放入包裝過、便於開發的 `ctx` 參數（HTTP `req` 和 `res` 等）。用於回傳新聞資料及處理例外事件。\n\n### Dependencies\n\n- [axios](https://www.npmjs.com/package/axios)\n- [dotenv](https://www.npmjs.com/package/dotenv)\n- [koa](https://www.npmjs.com/package/koa)\n- [koa-router](https://www.npmjs.com/package/koa-router)\n- [nodemon](https://www.npmjs.com/package/nodemon)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcj-yang0225%2Fcj-newsapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcj-yang0225%2Fcj-newsapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcj-yang0225%2Fcj-newsapp/lists"}