🔍 搜尋結果:"

🔍 搜尋結果:"

一位資深軟體工程師的 VSCode 設定方式&外掛套件分享

資深開發者 Jatin Sharma 在國外論壇分享了個人使用的 VSCode 設定,內容豐富,跟大家分享! 原文出處:https://dev.to/j471n/my-vs-code-setup-971 ## 🎨主題 我使用 [**Andromeda**](https://marketplace.visualstudio.com/items?itemName=EliverLara.andromeda) 作為我的 VS Code 的主要主題 ![andromeda-截圖](https://res.cloudinary.com/practicaldev/image/fetch/s--bw_aagIQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/EliverLara/Andromeda/raw/master/images/andromeda.png) ## 🪟圖標 對於圖標,我會在 [**材質圖標主題**](https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme) 和 [**材質主題圖標**]( https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons) **找找看。** ### 材質圖標主題 ![材質圖標主題](https://i.imgur.com/xA90m2X.png) ### 材質主題圖標 ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675233057115/b8d1623c-a092-475e-a66a-91b4a42e5441.png) ## ⚒️外掛 最讚的部分來了,有很多擴展我只提到了我最喜歡的或我每天主要使用的擴展。 ### 自動重命名標籤 自動重命名配對的 HTML/XML 標記,與 Visual Studio IDE 的操作相同。 **下載:** [**自動重命名標籤**](https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag) ![](https://github.com/formulahendry/vscode-auto-rename-tag/raw/HEAD/images/usage.gif) ### 括號對著色切換器 VS Code 擴展,為您提供一個簡單的命令來快速切換全局“括號對著色” **下載:** [**括號對著色切換器**](https://marketplace.visualstudio.com/items?itemName=dzhavat.bracket-pair-toggler) ![](https://github.com/dzhavat/bracket-pair-toggler/raw/HEAD/assets/bracket-pair-toggler-demo.gif) ### C/C++ C/C++ 擴展向 Visual Studio Code 加入了對 C/C++ 的語言支持,包括編輯 (IntelliSense) 和除錯功能。 **下載:** [**C/C++**](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) ![](https://i.imgur.com/0syu1Ym.png) ### 程式碼執行器 執行多種語言的程式碼片段或程式碼文件 **下載:** [**程式碼執行器**](https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner) ![用法](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/usage.gif) ### 程式碼拼寫檢查器 一個基本的拼寫檢查器,可以很好地處理程式碼和文件。 該拼寫檢查器的目標是幫助發現常見的拼寫錯誤,同時保持較低的誤報數量。 **下載:** [**程式碼拼寫檢查器**](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) ![示例](https://raw.githubusercontent.com/streetsidesoftware/vscode-spell-checker/main/images/example.gif) ### DotENV VSCode `.env` 語法高亮。 **下載:** [**DotENV**](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) ![示例](https://github.com/mikestead/vscode-dotenv/raw/master/images/screenshot.png) ### 錯誤鏡頭 ErrorLens 通過使診斷更加突出來增強語言診斷功能,突出顯示語言生成的診斷的整行,並內聯打印訊息。 **下載:** [**錯誤鏡頭**](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) ![演示圖片](https://raw.githubusercontent.com/usernamehw/vscode-error-lens/master/img/demo.png) ### ES7+ React/Redux/React-Native 片段 ES7+ 中的 JavaScript 和 React/Redux 片段以及 [VS Code](https://code.visualstudio.com/) 的 Babel 插件功能 **下載:** [**ES7+ React/Redux/React-Native 片段**](https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets) ![](https://i.imgur.com/cYpm6cw.png) ### ESLint 該擴展使用安裝在打開的工作區文件夾中的 ESLint 庫。如果該文件夾未提供,則擴展程序將查找全局安裝版本。如果您尚未在本地或全局安裝 ESLint,請在工作區文件夾中執行“npm install eslint”進行本地安裝,或執行“npm install -g eslint”進行全局安裝。 **下載:** [**ESLint**](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) ![](https://i.imgur.com/R3o4517.png) ### Git 圖 查看存儲庫的 Git 圖表,並從圖表中輕鬆執行 Git 操作。可配置為您想要的外觀! **下載:** [Git Graph](https://marketplace.visualstudio.com/items?itemName=mhutchie.git-graph) ![Git Graph 記錄](https://github.com/mhutchie/vscode-git-graph/raw/master/resources/demo.gif) ### GitLens GitLens **增強了 VS Code 中的 Git,並解鎖每個存儲庫中**未開發的知識**。它可以幫助您通過 Git Blame 註釋和 CodeLens 一目了然地**可視化程式碼作者**,**無縫導航和探索** Git 存儲庫,**通過豐富的可視化和強大的比較命令**獲得有價值的見解**,等等更多的。 **下載:** [**GitLens**](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1675224552887/688896dd-cfff-41fc-aa2e-53716e5585c6.png) ### HTML 樣板 此擴展提供了所有 Web 應用程式中使用的標準 HTML 樣板程式碼。 **下載:** [**HTML 樣板**](https://marketplace.visualstudio.com/items?itemName=sidthesloth.html5-boilerplate) ![替代文本](https://s19.postimg.cc/3mig98d5v/html_boilerplate_1_0_3.gif) ### Import Cost 此擴展將在編輯器中內聯顯示導入包的大小。該擴展利用 webpack 來檢測導入的大小。 **下載:** [**Import Cost**](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) ![示例圖片](https://citw.dev/_next/image?url=%2fposts%2fimport-cost%2f1quov3TFpgG2ur7myCLGtsA.gif&w=1080&q=75) ### Live Server 它將啟用實時更改而不保存文件。 **下載:** [**Live Server**](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) ![實時伺服器演示 VSCode](https://github.com/ritwickdey/vscode-live-server/raw/HEAD/images/Screenshot/vscode-live-server-animated-demo.gif) ### Markdown 多合一 Markdown 所需的一切(鍵盤快捷鍵、目錄、自動預覽等)。 ***注意***:VS Code 具有開箱即用的基本 Markdown 支持(例如,**Markdown 預覽**),請參閱官方文件了解更多訊息。 **下載:** [**Markdown All in One**](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) ![切換粗體gif](https://github.com/yzhang-gh/vscode-markdown/raw/master/images/gifs/toggle-bold.gif) ### Markdown 預覽增強 它顯示了 Markdown 內容的增強預覽。 **下載:** [**Markdown 預覽增強版**](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) ![簡介](https://user-images.githubusercontent.com/1908863/28495106-30b3b15e-6f09-11e7-8eb6-ca4ca001ab15.png) ### 將 JSON 粘貼為程式碼 複製 JSON,粘貼為 Go、TypeScript、C#、C++ 等。 **下載 -** [**將 JSON 粘貼為程式碼**](https://marketplace.visualstudio.com/items?itemName=quicktype.quicktype) ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/llqdlpz0amo1vj7m5no5.png) ### 更漂亮 使用 Prettier 的程式碼格式化程序 **下載 -** [**Prettier**](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) ![更漂亮](https://i.imgur.com/wHlMe9e.png) ### Python IntelliSense (Pylance)、Linting、除錯(多線程、遠程)、Jupyter Notebooks、程式碼格式化、重構、單元測試等。 **下載 -** [**Python**](https://marketplace.visualstudio.com/items?itemName=ms-python.python) ![Python](https://i.imgur.com/cQ1ARrG.png) ### 設置同步 使用 GitHub Gist 跨多台計算機同步設置、程式碼片段、主題、文件圖標、啟動、按鍵綁定、工作區和擴展。 **下載 -** [**設置同步**](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync) ![設置同步](https://shanalikhan.github.io/img/login-with-github.png) ### Tailwind CSS IntelliSense 適用於 VS Code 的智能 Tailwind CSS 工具 **下載 -** [**Tailwind CSS IntelliSense**](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) ![Tailwind CSS IntelliSense](https://raw.githubusercontent.com/bradlc/vscode-tailwindcss/master/packages/vscode-tailwindcss/.github/banner.png) ### 所有亮點 突出顯示程式碼中的“TODO”、“FIXME”和其他註釋。 有時,在將程式碼發佈到生產環境之前,您會忘記檢查編碼時加入的 TODO。所以我長期以來一直想要一個擴展來突出顯示它們並提醒我還有註釋或尚未完成的事情。 希望這個擴展也能幫助您。 **下載 -** [**TODO 突出顯示**](https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight) ![TODO 突出顯示](https://github.com/wayou/vscode-todo-highlight/raw/master/assets/material-night.png) ### Turbo 控制台日誌 自動編寫有意義的日誌訊息的過程。 **下載 -** [**Turbo 控制台日誌**](https://marketplace.visualstudio.com/items?itemName=ChakrounAnas.turbo-console-log) ![Turbo 控制台日誌](https://image.ibb.co/dysw7p/insert_log_message.gif) ### 塔布寧人工智能 Tabnine 是一款 AI 程式碼助手,可讓您成為更好的開發人員。 Tabnine 將通過所有最流行的編碼語言和 IDE 中的實時程式碼完成來提高您的開發速度。 **下載 -** [**Tabnine AI**](https://marketplace.visualstudio.com/items?itemName=TabNine.tabnine-vscode) ![Tabnine AI](https://raw.githubusercontent.com/codota/tabnine-vscode/master/assets/completions-main.gif) ## ⚙️設置 以下“JSON”程式碼顯示了我的 VS Code 設置: ``` // user/settings.json { "files.autoSave": "afterDelay", "editor.mouseWheelZoom": true, "code-runner.clearPreviousOutput": true, "code-runner.ignoreSelection": true, "code-runner.runInTerminal": true, "code-runner.saveAllFilesBeforeRun": true, "code-runner.saveFileBeforeRun": true, "editor.wordWrap": "on", "C_Cpp.updateChannel": "Insiders", "editor.suggestSelection": "first", "python.jediEnabled": false, "editor.fontSize": 17, "emmet.includeLanguages": { "javascript": "javascriptreact" }, "editor.minimap.size": "fit", "editor.fontFamily": "Consolas, DejaVu Sans Mono, monospace", "editor.fontLigatures": false, "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "python.formatting.provider": "yapf", "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "git.autofetch": true, "git.enableSmartCommit": true, "html-css-class-completion.enableEmmetSupport": true, "editor.formatOnPaste": true, "liveServer.settings.donotShowInfoMsg": true, "[python]": { "editor.defaultFormatter": "ms-python.python" }, "diffEditor.ignoreTrimWhitespace": false, "[json]": { "editor.defaultFormatter": "vscode.json-language-features" }, "[c]": { "editor.defaultFormatter": "ms-vscode.cpptools" }, "editor.fontWeight": "300", "editor.fastScrollSensitivity": 6, "explorer.confirmDragAndDrop": false, "vsicons.dontShowNewVersionMessage": true, "workbench.iconTheme": "material-icon-theme", "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.renderWhitespace": "none", "workbench.startupEditor": "newUntitledFile", "liveServer.settings.multiRootWorkspaceName": "", "liveServer.settings.port": 5000, "liveServer.settings.donotVerifyTags": true, "editor.formatOnSave": true, "html.format.indentInnerHtml": true, "editor.formatOnType": true, "printcode.tabSize": 4, "terminal.integrated.confirmOnExit": "hasChildProcesses", "terminal.integrated.cursorBlinking": true, "terminal.integrated.rightClickBehavior": "default", "tailwindCSS.emmetCompletions": true, "sync.gist": "527c3e29660c53c3f17c32260188d66d", "gitlens.hovers.currentLine.over": "line", "terminal.integrated.profiles.windows": { "PowerShell": { "source": "PowerShell", "icon": "terminal-powershell" }, "Command Prompt": { "path": [ "${env:windir}\\Sysnative\\cmd.exe", "${env:windir}\\System32\\cmd.exe" ], "args": [], "icon": "terminal-cmd" }, "Git Bash": { "source": "Git Bash" }, "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe (migrated)": { "path": "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "args": [] }, "Windows PowerShell": { "path": "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, "Ubuntu (WSL)": { "path": "C:\\WINDOWS\\System32\\wsl.exe", "args": [ "-d", "Ubuntu" ] } }, "javascript.updateImportsOnFileMove.enabled": "always", "[dotenv]": { "editor.defaultFormatter": "foxundermoon.shell-format" }, "editor.tabSize": 2, "cSpell.customDictionaries": { "custom-dictionary-user": { "name": "custom-dictionary-user", "path": "~/.cspell/custom-dictionary-user.txt", "addWords": true, "scope": "user" } }, "window.restoreFullscreen": true, "tabnine.experimentalAutoImports": true, "files.defaultLanguage": "${activeEditorLanguage}", "bracket-pair-colorizer-2.depreciation-notice": false, "workbench.editor.wrapTabs": true, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[ignore]": { "editor.defaultFormatter": "foxundermoon.shell-format" }, "terminal.integrated.fontFamily": "courier new", "terminal.integrated.defaultProfile.windows": "pwsh.exe -nologo", "terminal.integrated.shellIntegration.enabled": true, "terminal.integrated.shellIntegration.showWelcome": false, "editor.accessibilitySupport": "off", "editor.bracketPairColorization.enabled": true, "todohighlight.isEnable": true, "terminal.integrated.shellIntegration.history": 1000, "turboConsoleLog.insertEnclosingClass": false, "turboConsoleLog.insertEnclosingFunction": false, "files.autoSaveDelay": 500, "liveServer.settings.CustomBrowser": "chrome", "liveServer.settings.host": "localhost", "liveServer.settings.fullReload": true, "workbench.editor.enablePreview": false, "workbench.colorTheme": "Andromeda Bordered" } ``` ## 總結 以上簡單分享,希望對您有幫助!

後端 JS 訓練二:第6課 ── 回應 http post request

## 課程目標 - 使用 node 與 express 回應 http post request ## 課程內容 前面幾課我們學習了 http get 的寫法,這次來學 http post 的寫法 在「會更新主機資料狀態」的操作時,通常都會用 http post 例如,新增資料、更新資料、刪除資料,等等常見操作! 通常主機資料狀態會放在「資料庫軟體」例如 mysql 裡面,我們簡單起見,放在一個 json 檔案就好! --- 替這課建立 lesson6 資料夾,內容跟 lesson5 一樣即可 新增一個 new.ejs 檔案,放入以下內容 ``` <h1>新同學報到!</h1> <form method="post" action="/create"> name: <input type="text" name="name"> </form> ``` 然後在 index.js 加入這段程式碼 ``` const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: true })); app.get('/new', function(req, res){ res.render('new'); }); app.post('/create', (req, res) => { const fs = require('fs'); const name = req.body.name; const data = fs.readFileSync('./lesson6/users.json'); const users = JSON.parse(data); users.push({ name: name }); fs.writeFileSync('./lesson6/users.json', JSON.stringify(users)); res.send('<h1>新增成功!</h1>'); }) ``` 然後去終端機輸入 ``` node lesson6/index.js ``` 接著打開瀏覽器,在網址輸入 ``` http://localhost:3000/new ``` 你會看到新增同學的頁面與表單! 輸入姓名之後,送出表單就可以新增同學的資料了! --- 開頭跟 bodyParser 有關的兩行,要引入並設定好,才能使用 req.body 的參數 為什麼要加這兩行?這跟 express 的 middleware 機制設計有關,這邊不深入說明 反正就是在需要使用某些功能的時候,express 不會預設通通提供給你,要請你去設定啟用之後,才會支援 --- 之前處理 http get 的 url 參數時,我們使用 req.query 來取得參數 這次處理 http post 的表單參數時,我們使用 req.body 來取得參數 express 會這樣設計,是因為在 http 協定中,這兩種參數本來就放在不同地方喔! 看不太懂沒關係,先照做就對了! --- 檔案處理、新增資料放進 json 檔案的段落,跟之前的課程一樣,忘記的話就回頭翻閱一下吧! ## 課後作業 這次要開發「新增文章」的功能 並且練習用 node 回應 http post request 請把 hw5 的內容複製到 hw6,然後增加 new.ejs 檔案 ``` /repo /hw6 /index.js /homepage.ejs /view.ejs /new.ejs /posts.json /public /style.css ``` 接著修改主程式 index.js 的內容,讓網站能夠回應 `/new` 這個網址 這網址會把 new.ejs 的內容 render 出來,內容就放一個 form 表單 表單內可輸入 `標題` 以及 `文章內容`,點擊送出按鈕會以 http post 的形式發送到 `/create` 接著修改主程式 index.js 的內容,讓網站能夠回應 `/create` 這個網址 將接收到的資料,存進 `posts.json` 裡面 完成以上任務,你就完成這次的課程目標了! --- 在上傳到 github 時,你會注意到因為 `posts.json` 有改變,所以會帶著新資料一起上傳,好像有點怪? 其實,實務上會把資料存在 mysql 這類專門資料庫軟體,不會這樣存在 json 檔案中 本課程為了簡化,所以直接存在 json 檔案,這不是實務正常做法~ 為了方便起見,每次新增文章 `posts.json` 都會改變,就都一起上傳,沒關係~

後端 JS 訓練二:第5課 ── 處理 url 參數

## 課程目標 - 使用 node 與 express 處理 url 參數 ## 課程內容 在瀏覽網站的時候,點擊不同頁面時,有時需要透過 url(網址)附帶一些參數 這課來學習如何用 node 處理 url 參數 替這課建立 lesson5 資料夾,內容跟 lesson4 一樣即可 在 index.js 加入這段程式碼 ``` app.get('/view', (req, res) => { const data = fs.readFileSync('./lesson5/users.json'); const users = JSON.parse(data); const index = req.query.index; const user = users[index]; res.send('<h1>' + user.name + '同學,你好!</h1>'); }); ``` 然後把 hello.ejs 的內容修改成 ``` <div> <% for(var i = 0; i < users.length; i++) { %> <p> <%= users[i].name %>同學,你好! <a href="/view?index=<%= i %>">單獨打招呼頁面</a> </p> <% } %> </div> ``` 然後去終端機輸入 ``` node lesson5/index.js ``` 接著打開瀏覽器,在網址輸入 ``` http://localhost:3000/ ``` 你會看到每段訊息旁邊,多了一個單獨連結,點下去,就會到單獨打招呼頁面! 操作看看吧!試試看「透過網址傳參數」的感覺! --- 在 http 協定中,網址中出現問號 `?` 代表後面的部份是參數,參數的值應該可以單獨被抓出來 前面說過 `(req, res)` ,很多人會寫 `(request, response)`,兩者意思一樣,就是在 express 中代表請求&回應的物件 這邊是我們第一次使用「請求」物件!我們在請求物件中,使用 `req.query.index` 找出名為 `index` 的參數 然後在首頁,透過迴圈,加上每個單獨頁面的連結,通通使用 `index` 作為參數傳遞 --- 實務上,開發後端應用時,只要是 http get request,有時會透過網址本身代表參數,例如 ``` /view/1 /view/2 /view/3 ``` 有時會透過 url parameter 傳遞參數,例如 ``` /view?id=1 /view?id=2 /view?id=3 ``` 這是一個主觀偏好問題,其實都可以!效果也差不多 這兩種寫法,在各種程式語言中,處理起來有點不一樣 本課就以 url parameter 作為示範,先照做即可,別擔心! ## 課後作業 這次要開發「閱讀單篇文章」的功能 並且練習 url 參數的傳遞&取得 請把 hw4 的內容複製到 hw5,然後增加 view.ejs 檔案 ``` /repo /hw5 /index.js /homepage.ejs /view.ejs /posts.json /public /style.css ``` 修改部落格首頁,替每篇文章加上 <a> 超連結,點擊之後會前往單篇文章的網址 舉例來說,第一篇文章的網址會是 ``` <a href="/view?index=0"> ``` 第二篇的網址會是 ``` <a href="/view?index=1"> ``` 第三篇的網址會是 ``` <a href="/view?index=2"> ``` 接著修改主程式 index.js 的內容,讓網站能夠回應 `/view` 這種網址 然後根據從網址取得的索引參數,找出 posts.json 中對應的文章資料 接著把資料放進 view.ejs 模板中,回應顯示為網頁 請自行設計 view.ejs 的內容,簡單顯示一篇文章內容即可 請稍微加上一點樣式,弄得漂亮一點! 完成以上任務,你就完成這次的課程目標了!

後端 JS 訓練二:第4課 ── 提供靜態檔案

## 課程目標 - 使用 node 與 express 提供靜態檔案 ## 課程內容 目前為止的網頁,都是純 html,太陽春了! 這一課來學習,如何用 node 來提供 css 或圖片之類的「靜態檔案」! 替這課建立 lesson4 資料夾,內容跟 lesson3 同樣之外,再建立一個 public 資料夾,裡面新增 style.css 檔案 ``` /repo /lesson4 /index.js /hello.ejs /users.json /public /style.css ``` 在 index.js 裡面多加一行程式碼 ``` app.use(express.static('./lesson4/public')); ``` 在 style.css 裡面加入一些樣式 ``` p { color: green; } ``` 接著在 hello.ejs 加入樣式連結 ``` <link rel="stylesheet" href="/style.css"> ``` 然後去終端機輸入 ``` node lesson4/index.js ``` 接著打開瀏覽器,在網址輸入 ``` http://localhost:3000/ ``` 你會看到原本的打招呼訊息,文字變成了綠色! --- 後端程式設計,不論何種程式語言,在提供圖片、css 這種靜態檔案的時候,通常會開一個獨立的 public 資料夾來放檔案 所以本課使用的 `app.use(express.static('./lesson4/public'));` 程式碼 就只是設定一下,請 express 從某個資料夾提供靜態檔案,就這樣而已! ## 課後作業 這次要練習用 node 回應靜態檔案 請把 hw3 的內容複製到 hw4,然後增加 public 資料夾與 style.css 檔案 ``` /repo /hw4 /index.js /homepage.ejs /posts.json /public /style.css ``` 請在 style.css 中,稍微替你的日記首頁加一些樣式,弄得漂亮一點! 完成以上任務,你就完成這次的課程目標了!

後端 JS 訓練二:第3課 ── 使用 ejs 模板並讀取變數

## 課程目標 - 使用 ejs 模板並讀取變數 ## 課程內容 這一課來學習如何讀取變數之後,放進 ejs 模板呈現! 先來安裝之前用過的 readline-sync 套件 ``` npm install readline-sync ``` 替這課建立一個資料夾,然後建立 `index.js` `hello.ejs` `users.json` 這幾個檔案 此時資料夾會長類似這樣 ``` repo ├── package.json ├── package-lock.json ├── node_modules ├── lesson1 │   └── index.js ├── lesson2 │ ├── hello.ejs │   └── index.js └── lesson3 ├── users.json ├── hello.ejs    └── index.js ``` 在 users.json 放入以下內容 ``` [ { "name": "小明", }, { "name": "小華", }, { "name": "小王", } ] ``` 在 hello.ejs 放入以下內容 ``` <div> <% for(var i = 0; i < users.length; i++) { %> <p> <%= users[i].name %>同學,你好! </p> <% } %> </div> ``` 在 index.js 放入以下內容 ``` const express = require('express'); const app = express(); const engine = require('ejs-locals'); const fs = require('fs'); app.engine('ejs', engine); app.set('views', './lesson3'); app.set('view engine', 'ejs'); app.get('/', function(req, res){ const data = fs.readFileSync('./lesson3/users.json'); const users = JSON.parse(data); res.render('hello', { users: users }); }); app.listen(3000); ``` 然後去終端機輸入 ``` node lesson3/index.js ``` 接著打開瀏覽器,在網址輸入 ``` http://localhost:3000/ ``` 你會看到很多則訊息,分別跟多位同學打招呼! --- 這段程式不用我多加說明了吧!檔案讀取也是之前教過的內容! 這邊唯一多學的東西,就是 ejs 關於 for 迴圈的語法,反正就是 `<%` 跟 `%>` 之類的東西包起來而已 ejs 還有支援很多豐富的語法&功能,有興趣的話,自行研究一下吧! ## 課後作業 這次要練習匯入資料到 ejs 模板 請新建立 hw3 資料夾,採用以下結構 ``` /repo /hw3 /index.js /homepage.ejs /posts.json ``` 請修改 homepage.ejs 內容,將文章內容移出來,放在 posts.json 裡面 ``` [ { "title": "第一篇日記", "content": "今天天氣很好,夏天太陽很大,讓我心情很好" }, { "title": "第二篇日記", "content": "早起出門運動,回家沖個澡,整天心情很好" }, { "title": "第三篇日記", "content": "下大雨一整天,整天在家追劇,滿舒服的" } ] ``` 這樣 homepage.ejs 內就不會只是 html 了,你會練習到 ejs 獨有的模板語法! 完成以上任務,你就完成這次的課程目標了! --- 請更新你的 github 專案內容,此時應該變成這樣 ``` /repo /hw1 /index.js /hw2 /index.js /homepage.ejs /hw3 /index.js /homepage.ejs /posts.json ``` 依此類推,之後就這樣交作業!

用Github Page整理作品集

因為一直找不到工作(...汗)所以來整理作品 [成果展示](https://superyngo.github.io/to_do_list/) 基本上是參考[Let's Write](https://www.letswrite.tw/)做的[Demo](https://letswritetw.github.io/letswrite-food-check/) 在頁面下方有作品導覽以及Github連結 使用工具: 1.Github Page 2.[Google Tag Manager](https://tagmanager.google.com/#/home) 步驟: 1.申請GTM並取得GTM代碼 2.將GTM代碼插入所有專案的index.html中,並將所有專案上傳github,開啟page頁面 3.另外做一個myNavbat的github page,將所有專案連結放進去,可參考[我做的](https://github.com/superyngo/myNavbar) 4.用GTM的「自訂HTML」功能將myNavbar和github連結塞進所有專案中,參考代碼如下: ``` <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" /> <div class="myNav"></div> <div class="github"> <a class="githubHerf"> <i class="fa fa-github" aria-hidden="true"></i> </a> </div> <script> var githubHerf=document.querySelector(".githubHerf") githubHerf.setAttribute("href","https://github.com/superyngo"+{{Page Path}}) var myNav2 = document.querySelector(".myNav"); var iframe2 = document.createElement("iframe"); iframe2.setAttribute("src", "https://superyngo.github.io/myNavbar/"); iframe2.setAttribute("width", "100%"); iframe2.setAttribute("frameborder", "0"); myNav2.appendChild(iframe2) </script> <style> body{ background-color: #fff0df; } .github { position: fixed; left: 0; bottom: 0; display: flex; width: 100px; height: 100px; cursor: pointer; z-index: 2; :hover { position: absolute; left: 10px; bottom: 5px; font-size: 3rem; color: #fff; scale: 0.9; } } .github::before { content: ""; width: 0; height: 0; border-style: solid; border-width: 100px 0 0 100px; border-color: transparent transparent transparent #6e5494; } .github .fa { position: absolute; left: 10px; bottom: 5px; font-size: 3rem; color: #fff; } @media screen and (max-width: 600px) { :root { font-size: 12px; } .github { width: 70px; height: 70px; } .github::before { border-width: 70px 0 0 70px; } .github .fa { left: 8px; bottom: 4px; font-size: 2rem; } } </style> ``` 注意事項; 1.不知為何我GTM塞iframe一直不給過,所以用append的。 2.iframe內的超連結預設會在iframe內開啟,要避免只要在myNavbar的head裡加上<base target="_parent" />就可以了。 3當初做這些作業時完全沒考慮theme和RWD,花了一些時間但又不想花太多時間結果就勉勉強強用手機也都能開。 閒聊: 前陣子都在codeSignal上刷題,看別人解法真的很神,學很多... 有沒有北部公司缺人啊...投履歷都沒人回..中年轉行真的不行嗎...

改善重用性:Vue Composables 的推薦技巧&Design Patterns

本文將分為三個部分: 1. 通用設計模式 2. 我的建議 3. 進一步閱讀 享受並且讓我知道您在專案中使用的模式和實踐🚀 原文出處:https://dev.to/jacobandrewsky/good-practices-and-design-patterns-for-vue-composables-24lk ## 通用設計模式 我認為了解建置可組合項模式的最佳來源實際上是 Vue.js 文件,您可以在[此處](https://vuejs.org/guide/reusability/composables.html)查看 ### 基本可組合項 Vue 文件顯示了以下 useMouse 可組合項的示例: ``` // mouse.js import { ref, onMounted, onUnmounted } from 'vue' // by convention, composable function names start with "use" export function useMouse() { // state encapsulated and managed by the composable const x = ref(0) const y = ref(0) // a composable can update its managed state over time. function update(event) { x.value = event.pageX y.value = event.pageY } // a composable can also hook into its owner component's // lifecycle to setup and teardown side effects. onMounted(() => window.addEventListener('mousemove', update)) onUnmounted(() => window.removeEventListener('mousemove', update)) // expose managed state as return value return { x, y } } ``` 稍後可以在元件中使用它,如下所示: ``` <script setup> import { useMouse } from './mouse.js' const { x, y } = useMouse() </script> <template>Mouse position is at: {{ x }}, {{ y }}</template> ``` ### 異步可組合項 為了獲取資料,Vue 建議使用以下可組合結構: ``` import { ref, watchEffect, toValue } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) watchEffect(() => { // reset state before fetching.. data.value = null error.value = null // toValue() unwraps potential refs or getters fetch(toValue(url)) .then((res) => res.json()) .then((json) => (data.value = json)) .catch((err) => (error.value = err)) }) return { data, error } } ``` 然後可以在元件中使用它,如下所示: ``` <script setup> import { useFetch } from './fetch.js' const { data, error } = useFetch('...') </script> ``` ### 可組合合約 根據上面的示例,以下是所有可組合項都應遵循的約定: 1. 可組合文件名應以 use 開頭,例如 `useSomeAmazingFeature.ts` 2. 它可以接受輸入參數,這些參數可以是字串等基本類型,也可以接受 refs 和 getter,但需要使用 toValue 幫助器 3. Composable 應該返回一個 ref 值,該值可以在解構可組合項後存取,例如 `const { x, y } = useMouse()` 4. 可組合項可以保存可以在整個應用程式中存取和修改的全局狀態。 5. 可組合性可能會產生副作用,例如加入窗口事件偵聽器,但在卸載元件時應清除它們。 6. 可組合項只能在 `<script setup>` 或 `setup()` 掛鉤中呼叫。它們也應該在這些上下文中同步呼叫。在某些情況下,您還可以在生命週期掛鉤中呼叫它們,例如“onMounted()”。 7. 可組合項可以呼叫內部的其他可組合項。 8. 可組合項應在內部包裝某些邏輯,當過於復雜時,應將它們提取到單獨的可組合項中以便於測試。 ## 我的建議 我已經為我的工作專案和開源專案建置了多個可組合項 - NuxtAlgolia、NuxtCloudinary、NuxtMedusa,因此基於這些,我想根據我的經驗在上面的合同中加入一些要點。 ### 有狀態或/和純函數可組合項 在程式碼標準化的某個時刻,您可能會得出這樣的結論:您希望對可組合項中的狀態保留做出決定。 最容易測試的函數是那些不存儲任何狀態的函數(即它們是簡單的輸入/輸出函數),例如負責將字節轉換為人類可讀值的可組合函數。它接受一個值並返回一個不同的值 - 它不存儲任何狀態。 不要誤會我的意思,你不必做出“或”的決定。您可以完全保留有狀態和無狀態可組合項。但這應該是一個書面決定,以便以後更容易與他們合作 🙂 ### 可組合項的單元測試 我們希望使用 Vitest 為我們的前端應用程式實施單元測試。在後端工作時,進行單元測試程式碼覆蓋率非常有用,因為您主要關注邏輯。然而,在前端,您通常使用視覺效果。 因此,我們認為對整個元件進行單元測試可能不是最好的主意,因為我們基本上將對框架本身進行單元測試(如果按下按鈕,檢查狀態是否更改或模式是否打開)。 由於我們已將所有業務邏輯移至可組合項(基本上是 TypeScript 函數)內,因此它們很容易使用 Vitest 進行測試,並且允許我們擁有更穩定的系統。 ### 可組合項的範圍 不久前,在 VueStorefront 中,我們開發了自己的可組合方法(早在它們實際上像這樣被呼叫之前)。在我們的方法中,我們使用可組合項來映射電子商務的業務領域,如下所示: ``` const { cart, load, addItem, removeItem, remove, ... } = useCart() ``` 這種方法絕對有用,因為它允許將域包裝在一個函數中。在“useProduct”或“useCategory”等更簡單的示例中,實現和維護相對簡單。然而,正如您在此處的“useCart”示例中看到的那樣,當包裝一個包含更多邏輯而不僅僅是資料獲取的域時,這個可組合項正在發展成為一種非常難以開發和維護的形狀。 此時,我開始為 Nuxt 生態系統做出貢獻,其中引入了不同的方法。在這種新方法中,每個可組合項僅負責一件事。因此,我們的想法不是建置一個巨大的“useCart”可組合項,而是為每個功能建置可組合項,即“useAddToCart”、“useFetchCart”、“useRemovefromCart”等。 因此,維護和測試這些可組合項應該更容易 🙂 ## 進一步閱讀 這將是我的研究的全部內容。如果您想了解有關此主題的更多訊息,請務必查看以下文章: * https://vuejs.org/guide/reusability/composables.html * https://www.youtube.com/watch?v=bcZM3EogPJE * https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-composable/ * https://blog.logrocket.com/getting-started-vue-composables/ * https://macopedia.com/blog/news/how-can-vue-3-composables-make-your-life-easier

用 JavaScript 輕鬆做出深色模式 vs 淺色模式的方法

我曾經不同意淺色和深色模式切換。 “切換開關是用戶系統偏好設置!”我會天真地感嘆,選擇讓 [prefers-color-scheme CSS 媒體查詢](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) 控制我個人網站上的主題。沒有切換。沒有選擇。 🫠 自從黑暗模式出現以來,我一直是它的用戶。但最近,我更喜歡在輕型模式下使用_**一些**_網站和工具 - 包括我的個人網站 - 同時將我的系統設置牢牢地保留在黑暗中。我需要一個開關。我需要一個選擇!其他人也是如此。 在這篇文章中,我將向您展示如何使用 JavaScript 為我的網站建置終極主題 Toggle™️: 1. 在本地瀏覽器存儲中存儲和檢索主題首選項, 1. 退回到用戶系統首選項, 1. 如果未檢測到上述情況,則回退到默認主題。 TL;DR:[這是 CodePen 上的程式碼](https://codepen.io/whitep4nth3r/pen/VwEqrQL)。 原文出處:https://dev.to/whitep4nth3r/the-best-lightdark-mode-theme-toggle-in-javascript-368f ## 將資料屬性加入到 HTML 標記中 在 HTML 標記上,加入一個資料屬性,例如“data-theme”,並為其指定默認值“淺色”或“深色”。過去我使用自定義屬性“color-mode”而不是資料屬性(例如“color-mode=”light“”)。雖然這可行,但它沒有被歸類為有效的 HTML,而且我找不到任何相關文件!對此的任何見解都非常感激。 😅 ``` <html lang="en" data-theme="light"> <!-- all other HTML --> </html> ``` ## 通過 CSS 自定義屬性配置主題 在 CSS 中,通過“data-theme”每個值下的 [CSS 自定義屬性](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)(或變數)配置主題顏色` 屬性。請注意,您不一定需要將 `:root` 與 `data-theme` 結合使用,但它對於不隨主題變化的全局屬性很有用(如下例所示)。 [在 MDN 上了解有關 :root CSS 偽類的更多訊息。](https://developer.mozilla.org/en-US/docs/Web/CSS/:root) ``` :root { --grid-unit: 1rem; --border-radius-base: 0.5rem; } [data-theme="light"] { --color-bg: #ffffff; --color-fg: #000000; } [data-theme="dark"] { --color-bg: #000000; --color-fg: #ffffff; } /* example use of CSS custom properties */ body { background-color: var(--color-bg); color: var(--color-fg); } ``` 在 HTML 標籤上手動切換“data-theme”屬性,您就會看到主題已經發生變化(只要您使用這些 CSS 屬性來設置元素的樣式)! ## 在 HTML 中建置一個切換按鈕 將 HTML 按鈕加入到您的網站標題或任何需要主題切換的位置。加入一個 `data-theme-toggle` 屬性(稍後我們將使用它來定位 JavaScript 中的按鈕)和一個 [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label)如果您計劃在按鈕上使用圖標(例如太陽和月亮分別代表淺色和深色模式),以便螢幕閱讀器和輔助技術可以理解按鈕的用途互動按鈕。 ``` <button type="button" data-theme-toggle aria-label="Change to light theme" >Change to light theme (or icon here)</button> ``` ## 計算頁面加載時的主題設置 在這裡,我們將根據我所說的“_**偏好級聯**_”來計算主題設置。 ### 從本地存儲獲取主題首選項 我們可以使用 [JavaScript 中的 localStorage 屬性](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) 將用戶首選項保存在瀏覽器中,該首選項在會話之間持續存在(或直到它手動清除)。在 The Ultimate Theme Toggle™️ 中,存儲的用戶首選項是最重要的設置,因此我們將首先查找它。 頁面加載時,使用 localStorage.getItem("theme") 檢查之前存儲的首選項。在本文後面,我們將在每次按下切換按鈕時更新主題值。如果沒有本地存儲值,則該值為“null”。 ``` // get theme on page load localStorage.getItem("theme"); // set theme on button press localStorage.setItem("theme", newTheme); ``` ### 在 JavaScript 中檢測用戶系統設置 如果“localStorage”中沒有存儲的主題首選項,我們將使用 [window.matchMedia() 方法](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)通過傳入媒體查詢字串。您只需計算一項設置即可實現首選項級聯,但下面的程式碼顯示瞭如何檢測淺色或深色系統設置。 ``` const systemSettingDark = window.matchMedia("(prefers-color-scheme: dark)"); // or const systemSettingLight = window.matchMedia("(prefers-color-scheme: light)"); ``` `window.matchMedia()` 返回一個 `MediaQueryList`,其中包含您請求的媒體查詢字串,以及它是否與用戶系統設置“匹配”(true/false)。 ``` { matches: true, media: "(prefers-color-scheme: dark)", onchange: null } ``` ### 回退到默認主題 現在您可以通過“window.matchMedia()”存取“localStorage”值和系統設置,您可以使用首選項級聯(本地存儲,然後系統設置)計算首選主題設置,並回退到默認主題您的選擇(這應該是您之前在 HTML 標記上指定的默認主題)。 我們將在頁面加載時執行此程式碼來計算當前的主題設置。 ``` function calculateSettingAsThemeString({ localStorageTheme, systemSettingDark }) { if (localStorageTheme !== null) { return localStorageTheme; } if (systemSettingDark.matches) { return "dark"; } return "light"; } const localStorageTheme = localStorage.getItem("theme"); const systemSettingDark = window.matchMedia("(prefers-color-scheme: dark)"); let currentThemeSetting = calculateSettingAsThemeString({ localStorageTheme, systemSettingDark }); ``` ## 加入事件監聽器到切換按鈕 接下來,我們將設置一個事件監聽器,以便在按下按鈕時切換主題。使用我們之前加入的資料屬性(`data-theme-toggle`)定位 DOM 中的按鈕,並加入一個[事件監聽器](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) 加入到單擊按鈕。 - 將新主題計算為字串 - 計算並更新按鈕文本(如果您在按鈕上使用圖標,則可以在此處進行切換) - 更新按鈕上的aria標籤 - 切換HTML標籤上的data-theme屬性 - 將新的主題首選項保存在本地存儲中 - 更新內存中的currentThemeSetting ``` // target the button using the data attribute we added earlier const button = document.querySelector("[data-theme-toggle]"); button.addEventListener("click", () => { const newTheme = currentThemeSetting === "dark" ? "light" : "dark"; // update the button text const newCta = newTheme === "dark" ? "Change to light theme" : "Change to dark theme"; button.innerText = newCta; // use an aria-label if you are omitting text on the button // and using sun/moon icons, for example button.setAttribute("aria-label", newCta); // update theme attribute on HTML to switch theme in CSS document.querySelector("html").setAttribute("data-theme", newTheme); // update in local storage localStorage.setItem("theme", newTheme); // update the currentThemeSetting in memory currentThemeSetting = newTheme; }); ``` 要確認“localStorage”正在更新,請打開開發工具,導航到“Application”選項卡,展開“Local Storage”並選擇您的站點。你會看到一個鍵:值列表;查找“主題”並單擊按鈕即可觀看其實時更新。重新加載您的頁面,您將看到保留的主題首選項! ![帶有開發工具的瀏覽器窗口在應用程式選項卡上打開。選擇whitepanther dot com上的本地存儲,顯示主題light瀏覽器中存儲的鍵值對。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zosfts8gbby76vo1h40o.png) ## 把它們放在一起! 您現在可以通過以下方式建置您自己的終極主題切換™️: - 使用CSS自定義屬性來指定不同的主題顏色,通過HTML標籤上的資料屬性進行切換 - 使用 HTML 按鈕來啟動切換 - 使用首選項級聯計算頁面加載時的首選主題(本地存儲 > 系統設置 > 後備默認主題) - 單擊切換按鈕即可切換主題,並將用戶首選項存儲在瀏覽器中以供將來存取 這是完整的 CodePen,您可以在我的個人網站上查看工作版本。快樂切換! https://codepen.io/whitep4nth3r/pen/VwEqrQL

如何在微前端 Micro Frontends 架構中處理 CSS

如何在微前端中處理 CSS?畢竟,樣式始終是*任何* UI 片段所需要的東西,但是,它也是全局共享的東西,因此是潛在的衝突來源。 在這篇文章中,我想回顧一下現有的不同策略來馴服 CSS 並使其擴展以開發微前端。如果這裡的任何內容對您來說聽起來很合理,那麼也可以考慮研究一下[“微前端的藝術”](https://microfrontends.art/)。 **本文的程式碼可以在[github.com/piral-samples/css-in-mf](https://github.com/piral-samples/css-in-mf)找到。請務必查看示例實現。** CSS 的處理是否會影響每個微前端解決方案?讓我們檢查可用的類型來驗證這一點。 原文出處:https://dev.to/florianrappl/css-in-micro-frontends-4jai ## 微前端的類型 過去我寫了很多關於存在哪些類型的微前端、為什麼存在以及何時應該使用什麼類型的微前端架構的文章。採用 Web 方法意味著使用 iframe 來使用來自不同微前端的 UI 片段。在這種情況下,沒有任何限制,因為無論如何每個片段都是完全隔離的。 在任何其他情況下,無論您的解決方案使用客戶端還是伺服器端組合(或介於兩者之間的東西),您最終都會得到在瀏覽器中評估的樣式。因此,在所有其他情況下,您都會關心 CSS。讓我們看看這裡有哪些選項。 ## 無特殊處理 好吧,第一個 - 也許是最(或根據觀點,最不)明顯的解決方案是不進行任何特殊處理。相反,每個微前端都可以附帶額外的樣式表,然後在渲染微前端的元件時附加這些樣式表。 理想情況下,每個元件僅在首次渲染時加載所需的樣式,但是,由於這些樣式中的任何一個都可能與現有樣式衝突,我們也可以假裝在微前端的任何元件渲染時加載所有“有問題的”樣式。 ![衝突](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fewl99l6y04edyjnni2j.png) 這種方法的問題在於,當給出諸如“div”或“div a”之類的通用選擇器時,我們還將重新設置其他元素的樣式,而不僅僅是原始微前端的片段。更糟糕的是,類和屬性也不是故障保護措施。像“.foobar”這樣的類也可以在另一個微前端中使用。 **您將在引用的演示存儲庫中找到兩個衝突的微前端的示例,網址為 [solutions/default](https://github.com/piral-samples/css-in-mf/tree/main/solutions)。** 擺脫這種痛苦的一個好方法是進一步隔離元件 - 就像 Web 元件一樣。 ## 影子 DOM 在自定義元素中,我們可以打開一個影子根來將元素附加到專用的迷你文件,該迷你文件實際上與其父文件屏蔽。總的來說,這聽起來是一個好主意,但與這裡介紹的所有其他解決方案一樣,沒有硬性要求。 ![Shadow DOM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xp18uw9sxffq1l7oj6mc.png) 理想情況下,微前端可以自由決定“如何”實現元件。因此,實際的 Shadow DOM 集成必須由微前端完成。 使用 Shadow DOM 有一些缺點。最重要的是,雖然 Shadow DOM 內部的樣式保留在內部,但全局樣式也不會影響 Shadow DOM。乍一看,這似乎是一個優勢,但是,由於整篇文章的主要目標只是隔離微前端的樣式,因此您可能會錯過諸如應用某些全局設計系統(例如 Bootstrap)之類的要求。 要使用 Shadow DOM 進行樣式設置,我們可以通過“link”引用或“style”標籤將樣式放入 Shadow DOM 中。由於 Shadow DOM 是無樣式的,並且外部的樣式不會傳播到其中,因此我們實際上需要它。除了編寫一些內聯樣式之外,我們還可以使用捆綁器將“.css”(或者類似“.shadow.css”的內容)視為原始文本。這樣,我們只會得到一些文本。 對於 esbuild,我們可以配置 `piral-cli-esbuild` 的預製配置,如下所示: ``` module.exports = function(options) { options.loader['.css'] = 'text'; options.plugins.splice(0, 1); return options; }; ``` 這會刪除初始 CSS 處理器 (SASS) 並為“.css”文件配置標準加載器。現在,shadow DOM 中的某些樣式的工作方式如下: ``` import css from "./style.css"; customElements.define(name, class extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } connectedCallback() { this.style.display = "contents"; const style = this.shadowRoot.appendChild(document.createElement('style')); style.textContent = css; } }); ``` 上面的程式碼是一個有效的自定義元素,從樣式角度來看它是透明的(“display:contents”),即只有其內容會反映在渲染樹中。它託管一個包含單個“style”元素的影子 DOM。 `style` 的內容設置為 `style.css` 文件的文本。 **您將在 [`solutions/shadow-dom`](https://github.com/piral-samples/css-in-mf/tree/main) 引用的演示存儲庫中找到兩個衝突的微前端的示例/解決方案/shadow-dom)。** 域元件避免使用影子 DOM 的另一個原因是,並非每個 UI 框架都能夠處理影子 DOM 中的元素。因此,無論如何都必須尋找替代解決方案。一種方法是轉而使用一些 CSS 約定。 ## 使用命名約定 如果每個微前端都遵循全局 CSS 約定,那麼就可以在元級別上避免衝突。最簡單的約定是在每個類前面加上微前端的名稱。因此,舉例來說,如果一個微前端稱為“shopping”,另一個微前端稱為“checkout”,那麼兩者都會將其“active”類分別重命名為“shopping-active”/“checkout-active”。 ![約定](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6l8r1s59v1dcu3yp022u.png) 這同樣適用於其他可能存在衝突的名稱。舉個例子,在微前端稱為“shopping”的情況下,我們將其稱為“shopping-primary-button”,而不是像“primary-button”這樣的ID。如果由於某種原因,我們需要設置元素的樣式,我們應該使用後代選擇器(例如“.shopping img”)來設置“img”標籤的樣式。現在,這適用於具有“shopping”類的 *some* 元素中的“img”元素。這種方法的問題是購物微前端也可能使用其他微前端的元素。如果我們看到“div.shopping > div.checkout img”怎麼辦?儘管“img”現在由通過“checkout”微前端帶來的元件託管/集成,但它的樣式將由“shopping”微前端 CSS 設計。這並不理想。 儘管命名約定在一定程度上解決了問題,但它們仍然容易出錯並且使用起來很麻煩。如果我們重命名微前端會怎樣?如果微前端在不同的應用程式中獲得不同的名稱怎麼辦?如果我們在某些時候忘記應用命名約定怎麼辦?這就是工具幫助我們的地方。 ## CSS 模塊 自動引入一些前綴並避免命名衝突的最簡單方法之一是使用 CSS 模塊。根據您選擇的捆綁器,這可以是開箱即用的,也可以通過一些配置更改來實現。 ![CSS 模塊](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o49rdmokn5kvn8ncjjrt.png) ``` // Import "default export" from CSS import styles from './style.modules.css'; // Apply <div className={styles.active}>Active</div> ``` 導入的模塊是一個生成的模塊,保存將其原始類名(例如“active”)映射到生成的類名的值。生成的類名通常是 CSS 規則內容與原始類名混合的哈希值。這樣,名稱應該盡可能唯一。 作為示例,讓我們考慮使用“esbuild”建置的微前端。對於“esbuild”,您需要一個插件(“esbuild-css-modules-plugin”)和相應的配置更改以包含 CSS 模塊。 使用 Piral 我們只需要調整 `piral-cli-esbuild` 已經帶來的配置。我們刪除標準 CSS 處理(使用 SASS)並用插件替換: ``` const cssModulesPlugin = require('esbuild-css-modules-plugin'); module.exports = function(options) { options.plugins.splice(0, 1, cssModulesPlugin()); return options; }; ``` 現在我們可以在程式碼中使用 CSS 模塊,如上所示。 CSS 模塊有一些缺點。首先,它附帶了一些標準 CSS 的語法擴展。這對於區分我們想要導入的樣式(因此要進行預處理/哈希)和應保持原樣的樣式(即稍後在不導入的情況下使用)是必要的。另一種方法是將CSS直接帶入JS文件中。 ## CSS-in-JS CSS-in-JS 最近的名聲很差,但是,我認為這是一個誤解。我也更喜歡將其稱為“CSS-in-Components”,因為它為元件本身帶來了樣式。一些框架(Astro、Svelte 等)甚至允許通過其他方式直接執行此操作。經常被提及的缺點是性能 - 這通常是由於在瀏覽器中編寫 CSS 造成的。然而,這並不總是必要的,在最好的情況下,CSS-in-JS 庫實際上是建置時間驅動的,即沒有任何性能缺陷。 ![CSS-in-JS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36w7yuksmdzgstastjxv.png) 然而,當我們談論 CSS-in-JS(或 CSS-in-Components)時,我們需要考慮現有的各種選項。為簡單起見,我只包含三個:情感、樣式元件和香草提取物。讓我們看看它們如何幫助我們在將微前端整合到一個應用程式中時避免衝突。 ### Emotion Emotion 是一個非常酷的庫,它附帶了 React 等框架的幫助程序,但沒有將這些框架設置為先決條件。情感可以很好地優化和預先計算,並允許我們使用可用的 CSS 技術的完整庫。 使用“純粹”情感相當容易;首先安裝包: ``` npm i @emotion/css ``` 現在您可以在程式碼中使用它,如下所示: ``` import { css } from '@emotion/css'; const tile = css` background: blue; color: yellow; flex: 1; display: flex; justify-content: center; align-items: center; `; // later <div className={tile}>Hello from Blue!</div> ``` `css` 幫助器允許我們編寫被解析並放置在樣式表中的 CSS。返回值是生成的類的名稱。 如果我們特別想使用 React,我們還可以使用 Emotion 中的 jsx 工廠(引入了一個名為 css 的新標準 prop)或 styled 幫助器: ``` npm i @emotion/react @emotion/styled ``` 現在感覺很像樣式是 React 本身的一部分。例如,“styled”幫助器允許我們定義新元件: ``` const Output = styled.output` border: 1px dashed red; padding: 1rem; font-weight: bold; `; // later <Output>I am groot (from red)</Output> ``` 相比之下,“css”輔助屬性使我們能夠稍微縮短符號: ``` <div css={` background: red; color: white; flex: 1; display: flex; justify-content: center; align-items: center; `}> Hello from Red! </div> ``` 總而言之,這會生成不會衝突的類名,並提供避免樣式混合的穩健性。 “styled”助手尤其受到流行的“styled-components”庫的啟發。 ### 樣式元件 “styled-components”庫可以說是最流行的 CSS-in-JS 解決方案,並且常常是此類解決方案聲譽不佳的原因。從歷史上看,這實際上是在瀏覽器中編寫 CSS 的全部內容,但在過去幾年中,他們確實極大地推進了這一點。今天,您也可以對所使用的樣式進行一些非常好的伺服器端組合。 與“emotion”相比,安裝(針對 React)需要更少的軟體包。唯一的缺點是打字是事後才想到的 - 所以你需要安裝兩個包才能完全喜歡 TypeScript: ``` npm i styled-components --save npm i @types/styled-components --save-dev ``` 安裝後,該庫就已經完全可用: ``` import styled from 'styled-components'; const Tile = styled.div` background: blue; color: yellow; flex: 1; display: flex; justify-content: center; align-items: center; `; // later <Tile>Hello from Blue!</Tile> ``` 其原理與“情感”相同。因此,讓我們探索另一種選擇,嘗試從一開始就實現零成本,而不是事後的想法。 ### Vanilla Extract 我之前寫的關於利用類型更接近元件(並避免不必要的執行時成本)的內容正是最新一代 CSS-in-JS 庫所涵蓋的內容。最有前途的庫之一是“@vanilla-extract/css”。 使用該庫有兩種主要方式: - 與您的捆綁器/框架集成 - 直接使用 CLI 在此示例中,我們選擇前者 - 並將其集成到“esbuild”。為了使集成正常工作,我們需要使用“@vanilla-extract/esbuild-plugin”包。 現在我們將其集成到建置過程中。使用 `piral-cli-esbuild` 配置,我們只需將其加入到配置的插件中: ``` const { vanillaExtractPlugin } = require("@vanilla-extract/esbuild-plugin"); module.exports = function (options) { options.plugins.push(vanillaExtractPlugin()); return options; }; ``` 為了使 Vanilla Extract 正常工作,我們需要編寫 `.css.ts` 文件,而不是普通的 `.css` 或 `.sass` 文件。這樣的文件可能如下所示: ``` import { style } from "@vanilla-extract/css"; export const heading = style({ color: "blue", }); ``` 這都是有效的 TypeScript。我們最終會得到一個類名的導出 - 就像我們從 CSS 模塊、Emotion 中得到的一樣 - 你明白了。 所以最後,上面的樣式將像這樣應用: ``` import { heading } from "./Page.css.ts"; // later <h2 className={heading}>Blue Title (should be blue)</h2> ``` 這將在建置時完全處理——而不是執行時成本。 您可能會感興趣的另一種方法是使用 CSS 實用程序庫,例如 Tailwind。 ## CSS 實用程序,例如 Tailwind 這是一個獨立的類別,但我認為既然 Tailwind 是這個類別中的主要工具,我將只介紹 Tailwind。 Tailwind 的主導地位甚至達到了甚至有人問“你寫 CSS 還是 Tailwind?”之類的問題。這與 jQuery 在 DOM 操作領域的統治地位非常相似。 2010 年,人們問“這是 JavaScript 還是 jQuery?”。 無論如何,使用 CSS 實用程序庫的優點是根據使用情況生成樣式。這些樣式不會衝突,因為實用程序庫始終以相同的方式定義它們。因此,每個微前端將僅附帶實用程序庫中根據需要顯示微前端所需的部分。 ![Tailwind](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8waj7k3823oq77qhkzmb.png) 如果使用 Tailwind 和 esbuild,我們還需要安裝以下軟體包: ``` npm i autoprefixer tailwindcss esbuild-style-plugin ``` esbuild的配置比之前複雜一點。 `esbuild-style-plugin` 本質上是 esbuild 的 PostCSS 插件;所以必須正確配置: ``` const postCssPlugin = require("esbuild-style-plugin"); module.exports = function (options) { const postCss = postCssPlugin({ postcss: { plugins: [require("tailwindcss"), require("autoprefixer")], }, }); options.plugins.splice(0, 1, postCss); return options; }; ``` 在這裡,我們刪除了默認的 CSS 處理插件 (SASS),並將其替換為 PostCSS 插件 - 使用 PostCSS 的“autoprefixer”和“tailwindcss”擴展。 現在我們需要加入一個有效的 *tailwind.config.js* 文件: ``` module.exports = { content: ["./src/**/*.tsx"], theme: { extend: {}, }, plugins: [], }; ``` 這本質上是配置 Tailwind 的最低要求。它只是提到應該掃描 `tsx` 文件以了解 Tailwind 實用程序類的使用情況。然後找到的類將被放入 CSS 文件中。 因此,CSS 文件還需要知道生成/使用的聲明應包含在哪裡。至少我們只有以下 CSS: ``` @tailwind utilities; ``` 還有其他“@tailwind”指令。例如,Tailwind 帶有重置和基礎層。然而,在微前端中,我們通常不關心這些層。這屬於應用程式 shell 或編排應用程式的關注範圍 - 而不是域應用程式。 然後,CSS 將被替換為 Tailwind 中已指定的類: ``` <div className="bg-red-600 text-white flex flex-1 justify-center items-center">Hello from Red!</div> ``` ## 比較 迄今為止提出的幾乎每種方法都是微前端的可行競爭者。一般來說,這些溶液也可以混合。一個微前端可以採用影子 DOM 方法,而另一個微前端則對 Emotion 感到滿意。第三個圖書館可能會選擇使用香草精。 最後,唯一重要的是所選擇的解決方案是無碰撞的,並且不會帶來(巨大的)執行時成本。雖然某些方法比其他方法更有效,但它們都提供了所需的樣式隔離。 |方法|遷移工作|可讀性|穩健性|性能影響| | ----------- | -------------- | -------------- | ---------- | ------------------ | |大會 |中等|高|低|無 | | CSS 模塊 |低|高|中等|從無到低| |影子 DOM |低到中|高|高|低| | JS 中的 CSS |高|中到高|高|從無到高| |順風|高|中等|高|無 | 性能影響很大程度上取決於實施。例如,對於 CSS-in-JS,如果解析和組合在執行時完全完成,您可能會產生很大的影響。如果樣式已經預先解析但僅在執行時組合,則影響可能很小。如果使用像香草精這樣的解決方案,您基本上不會產生任何影響。 對於 Shadow DOM,主要的性能影響可能是 Shadow DOM 內部元素的投影或移動(本質上為零)以及“style”標籤的重新評估。然而,這是相當低的,甚至可能會產生一些性能優勢,給定的樣式總是切中要害,並且僅專用於要在影子 DOM 中顯示的某個元件。 在示例中,我們有以下捆綁包大小: |方法|索引 [kB] |頁碼 [kB] |表 [kB] |總體 [kB] |尺寸 [%] | | ----------------- | ---------- | --------- | ----------- | ------------ | ------ | |默認| 1.719 | 1.719 1.203 | 1.203 0.245 | 0.245 3.167 | 3.167 100% | |大會 | 1.761 | 1.761 1.241 | 1.241 0.269 | 0.269 3.271 | 3.271 103% | | CSS 模塊 | 2.149 | 2.149 2.394 | 2.394 0 | 4.543 | 4.543 143% | |影子 DOM | 10.044 | 10.044 1.264 | 1.264 0 | 11.308 | 11.308 357% | |情感| 1.670 | 1.670 1.632 | 1.632 25.785 | 25.785 29.087 | 29.087 918% | |樣式元件 | 1.618 | 1.618 1.612 | 1.612 63.073 | 63.073 66.303 | 66.303 2093% | |香草精 | 1.800 | 1.800 1.257 | 1.257 0.314 | 0.314 3.371 | 3.371 106% | |順風| 1.853 | 1.853 1.247 | 1.247 0.714 | 0.714 3.814 | 3.814 120% | 對這些數字持保留態度,因為在情感和样式元件的情況下,執行時可以(並且可能甚至應該)共享。另外,給定的示例微前端確實很小(所有 UI 片段的總體大小為 3kB)。對於更大的微前端,增長肯定不會像這裡描述的那麼問題。 Shadow DOM 解決方案的大小增加可以通過我們提供的簡單實用腳本來解釋,該腳本可以輕鬆地將現有的 React 渲染包裝到 Shadow DOM 中(無需生成新樹)。如果這樣的實用程序是集中共享的,那麼其大小將更接近其他更輕量級的解決方案。 ## 結論 在微前端解決方案中處理 CSS 並不困難 - 只需從一開始就以結構化和有序的方式完成,否則就會出現衝突和問題。一般來說,建議選擇 CSS 模塊、Tailwind 或可擴展的 CSS-in-JS 實現等解決方案。

簡單介紹幾個基本 Git 指令

## 介紹 在軟體開發的世界裡,Git 已經成為版本控制必不可少的工具。它允許開發人員有效地協作、跟踪更改和有效地管理程式碼存儲庫。無論您是初學者還是經驗豐富的開發人員,理解和掌握 Git 命令對於最大限度地提高工作效率至關重要。在這篇博文中,我們將探索每個開發人員都應該知道的最重要的 Git 命令。 原文出處:https://dev.to/syedsadiqali/mastering-git-top-commands-every-developer-should-know-5hkn ### git init: 使用 Git 的第一步是初始化存儲庫。通過在您的專案目錄中執行 git init,您可以建立一個空的 Git 存儲庫,從而為您的專案啟用版本控制。此命令為 Git 設置必要的基礎結構以開始跟踪更改。 ``` $ git init ``` ### git clone: 要開始處理現有專案,您通常需要製作存儲庫的本地副本。 git clone 允許您將整個存儲庫及其歷史下載到本地計算機。它在您的本地副本和遠程存儲庫之間建立連接,使您能夠獲取更新並為專案做出貢獻。 ``` $ git clone https://github.com/example/repository.git ``` ### git add: 在將更改提交到存儲庫之前,您需要暫存要包含在下一次提交中的文件。 git add 允許您有選擇地將文件或整個目錄加入到暫存區。它為即將到來的提交做好準備,表明您希望將這些更改包含在存儲庫中。 ``` $ git add file.txt $ git add folder/ $ git add . ``` ### git commit: 暫存更改後,您可以使用 git commit 建立新的提交。提交充當專案在特定時間點的快照,保留您所做的更改。每個提交都有一個唯一的標識符、提交訊息,並引用以前的提交,從而形成專案的時間順序歷史記錄。 ``` $ git commit -m "Initial commit" ``` ### git pull: 協作專案通常涉及在同一個存儲庫上工作的多個開發人員。 git pull 允許您從遠程存儲庫獲取最新更改並將其合併到本地分支。它確保您的本地副本是最新的,在您開始工作之前包含其他人所做的任何新提交。 ``` $ git pull origin master ``` ### git push: 一旦您對本地分支進行了更改並想與其他人共享它們,您可以使用 git push 將您的提交上傳到遠程存儲庫。它將您的本地更改與中央存儲庫同步,使它們可供從事該專案的其他人使用。 ``` $ git push origin master ``` ### git branch: 分支是 Git 中的一項強大功能,可實現並行開發和實驗。 git branch 允許你建立新分支或列出現有分支。您可以通過使用 git checkout 在分支之間切換來獨立處理不同的功能或錯誤修復。 ``` $ git branch $ git branch new-feature ``` ### git merge: 當你完成一個特性分支的工作或者想要將一個分支的更改合併到另一個分支時,你可以使用 git merge。它將源分支中的更改合併到目標分支中,建立一個包含兩組更改的新提交。 ``` $ git merge new-feature ``` ### git stash: 有時,您可能需要在處理未完成的更改時切換到不同的分支。 git stash 允許您將修改臨時保存在類似堆棧的結構中,使您能夠在不提交或丟棄更改的情況下切換分支。稍後,您可以將隱藏的更改應用到適當的分支。 ``` $ git stash save "Work in progress" $ git stash apply ``` ### git log: 要檢查存儲庫的提交歷史,git log 是一個方便的命令。它顯示按時間順序排列的提交列表,包括作者、時間戳和提交訊息。您可以使用此命令的各種選項來根據您的要求過濾和格式化輸出。 ``` $ git log $ git log --author="John Doe" ``` ## 結論: Git 是一個強大的版本控制系統,它使開發人員能夠有效協作並跟踪專案中的更改。通過掌握這些頂級 Git 命令,您將能夠輕鬆導航存儲庫、管理分支和跟踪更改。無論您是從事個人專案還是參與大型軟體開發,理解和使用這些命令

簡單說明 JavaScript 中常看到的 Strict Mode 字串

在 JavaScript 中,提供了一種稱為“嚴格模式”的功能,該功能在 ECMAScript 5 (ES5) 中引入,可幫助開發人員避免常見的 JavaScript 陷阱。在本文中,我們將深入探討什麼是嚴格模式、如何啟用它以及它提供的好處。 原文出處:https://dev.to/accreditly/understanding-javascript-strict-mode-4e3j ## 什麼是嚴格模式? 嚴格模式是一種選擇受限的 JavaScript 變體的方式。在嚴格模式下,JavaScript 通過將它們更改為拋出錯誤來消除一些 JavaScript 靜默錯誤。它修復了使 JavaScript 引擎難以執行優化的錯誤,並禁止了一些可能在未來版本的 ECMAScript 中定義的語法。 ## 啟用嚴格模式 要在 JavaScript 中啟用嚴格模式,您可以使用字串“use strict”。這可以針對整個腳本或在單個函數中完成。 對於整個腳本: ``` "use strict"; var v = "Hello, I'm in strict mode!"; ``` 對於單個函數: ``` function strictFunc() { "use strict"; var v = "Hello, I'm in strict mode inside a function!"; } ``` `"use strict"` 指令只能在腳本或函數的開頭辨識。 ## 使用嚴格模式的好處 嚴格模式以兩種方式提供幫助: 1. 它捕捉常見的編碼錯誤和“不安全”的行為。 例如,變數必須用 `var`、`let` 或 `const` 聲明。未聲明的變數將導致錯誤。 ``` "use strict"; x = 3.14; // This will cause an error because x is not declared ``` 2. 它防止使用未來的 ECMAScript 保留字。例如: ``` "use strict"; var let = "Hello"; // This will cause an error because "let" is a reserved word in ES6 ``` 3. 它簡化了 eval() 和參數。 在嚴格模式下,在 eval() 語句中聲明的變數不會在周圍範圍內建立變數。 ``` "use strict"; eval("var x = 10;"); // This will cause an error because x is not defined outside the scope of eval() console.log(x); ``` 4. 對於不是方法或構造函數的函數,它將 this 限制為未定義。 在非嚴格模式下,`this` 將默認為全局物件,瀏覽器上下文中的`window`。 ``` "use strict"; function myFunction() { console.log(this); // Will output: undefined } myFunction(); ``` 使用嚴格模式可以幫助您捕獲否則會被靜默忽略的錯誤。它還有助於防止您使用可能有問題的語法和做出低效的編碼決策。嚴格模式可以使您的 JavaScript 程式碼更加健壯和可維護,最好的做法是使用“use strict”指令啟動您的腳本。

7 個進階、好用的 TypeScript 技巧用法分享

**TypeScript** 是一種出色的工具,可以讓我們的生活更輕鬆並避免 **bug**,但有時使用起來感覺不知所措。 本文概述了專業人士使用的 7 個 **TypeScript** 技巧,它們將使您的生活更輕鬆。 原文出處:https://dev.to/ruppysuppy/7-secret-typescript-tricks-pros-use-3ckg ## 1. 型別推理 **Typescript** 足夠聰明,可以在您幫助縮小資料類型時**推斷資料類型**。 ``` enum CounterActionType { Increment = "INCREMENT", IncrementBy = "INCREMENT_BY", } interface IncrementAction { type: CounterActionType.Increment; } interface IncrementByAction { type: CounterActionType.IncrementBy; payload: number; } type CounterAction = | IncrementAction | IncrementByAction; function reducer(state: number, action: CounterAction) { switch (action.type) { case CounterActionType.Increment: // TS infers that the action is IncrementAction // & has no payload return state + 1; case CounterActionType.IncrementBy: // TS infers that the action is IncrementByAction // & has a number as a payload return state + action.payload; default: return state; } } ``` 如上所示,**TypeScript** 根據 `type` 屬性推斷操作的類型,因此您無需檢查 `payload` 是否存在。 ## 2. 字串類型 通常你需要一個變數的特定值,這就是**文字類型**派上用場的地方。 ``` type Status = "idle" | "loading" | "success" | "error"; ``` 它也適用於數字: ``` type Review = 1 | 2 | 3 | 4 | 5; // or better yet: const reviewMap = { terrible: 1, average: 2, good: 3, great: 4, incredible: 5, } as const; // This will generate the same type as above, // but it's much more maintainable type Review = typeof reviewMap[keyof typeof reviewMap]; ``` ## 3.類型守衛 **類型保護** 是另一種縮小變數類型的方法: ``` function isNumber(value: any): value is number { return typeof value === "number"; } const validateAge = (age: any) => { if (isNumber(age)) { // validation logic // ... } else { console.error("The age must be a number"); } }; ``` 注意:在上面的示例中,最好使用: ``` const validateAge = (age: number) => { // ... }; ``` 這個例子是為了展示 **type guards** 是如何工作的一個簡化。 ## 4.索引簽名 當對像中有**動態鍵**時,可以使用**索引簽名**來定義其類型: ``` enum PaticipationStatus { Joined = "JOINED", Left = "LEFT", Pending = "PENDING", } interface ParticipantData { [id: string]: PaticipationStatus; } const participants: ParticipantData = { id1: PaticipationStatus.Joined, id2: PaticipationStatus.Left, id3: PaticipationStatus.Pending, // ... }; ``` ## 5.泛型 **泛型** 是一個強大的工具,可以讓您的程式碼更易於重用。它允許您**定義一個類型,該類型將由您的函數的使用決定**。 在以下示例中,`T` 是通用類型: ``` const clone = <T>(object: T) => { const clonedObject: T = JSON.parse(JSON.stringify(object)); return clonedObject; }; const obj = { a: 1, b: { c: 3, }, }; const obj2 = clone(obj); ``` ## 6. 不可變類型 您可以通過加入 `as const` 使您的類型**不可變**。這確保您不會意外更改值。 ``` const ErrorMessages = { InvalidEmail: "Invalid email", InvalidPassword: "Invalid password", // ... } as const; // This will throw an error ErrorMessages.InvalidEmail = "New error message"; ``` ## 7. 部分、選擇、省略和必需類型 通常在使用 **server** 和 **local data** 時,您需要將一些屬性設置為 **optional** 或 **required**。 而不是使用相同資料的略微更改版本定義數百個接口。您可以使用 `Partial`、`Pick`、`Omit` 和 `Required` 類型來做到這一點。 ``` interface User { name: string; age?: number; email: string; } type PartialUser = Partial<User>; type PickUser = Pick<User, "name" | "age">; type OmitUser = Omit<User, "age">; type RequiredUser = Required<User>; // PartialUser is equivalent to: // interface PartialUser { // name?: string; // age?: number; // email?: string; // } // PickUser is equivalent to: // interface PickUser { // name: string; // age?: number; // } // OmitUser is equivalent to: // interface OmitUser { // name: string; // email: string; // } // RequiredUser is equivalent to: // interface RequiredUser { // name: string; // age: number; // email: string; // } ``` 當然,你可以使用 **intersection** 來組合它們: ``` type A = B & C; ``` 其中 `B` 和 `C` 是任何類型。 --- 謝謝閱讀,希望對您有幫助!

給軟體工程師:50 個好用的 ChatGPT 咒語指令

下列 ChatGPT-4 咒語,對開發者很有幫助。附上原文與中文指令,供您參考。 原文出處:https://dev.to/hackertab_org/50-chat-gpt-prompts-every-software-developer-should-know-tested-9al ### **程式碼生成** - Generate a boilerplate `[language]` code for a `[class/module/component]` named [name] with the following functionality: `[functionality description].` - 為名為 [name] 的 `[class/module/component]` 生成樣板 `[language]` 程式碼,具有以下功能:`[functionality description]。 - Create a [language] function to perform `[operation]` on `[data structure]` with the following inputs: [input variables] and expected output: `[output description]`. - 建立一個 [語言] 函數以使用以下輸入對 `[資料結構]` 執行 `[操作]`:[輸入變數] 和預期輸出:`[輸出描述]`。 - Generate a `[language]` class for a `[domain]` application that includes methods for `[methods list]` and properties `[properties list]`. - 為包含“[方法列表]”的方法和屬性“[屬性列表]”的“[域]”應用程式生成一個“[語言]”類。 - Based on the [design pattern], create a code snippet in [language] that demonstrates its implementation for a [use case]. - 基於[設計模式],用[語言]建立一個程式碼片段,演示其對[用例]的實現。 **例子:** ``` Generate a boilerplate Python code for a shopping cart module named "ShoppingCart" with the following functionality: - A constructor that initializes an empty list to store cart items. - A method called "add_item" that takes in an item object and adds it to the cart. - A method called "remove_item" that takes in an item object and removes it from the cart if it exists. - A method called "get_items" that returns the list of items in the cart. - A method called "get_total" that calculates and returns the total price of all items in the cart. ``` ### **程式碼完成** - In `[language]`, complete the following code snippet that initializes a [data structure] with `[values]`: `[code snippet]`. - 在“[語言]”中,完成以下使用“[值]”初始化[資料結構]的程式碼片段:“[程式碼片段]”。 - Finish the `[language]` function that calculates [desired output] given the following input parameters: `[function signature]`. - 在給定以下輸入參數的情況下完成計算[期望輸出]的[語言]函數:[函數簽名]。 - Complete the `[language]` code to make an API call to `[API endpoint]` with [parameters] and process the response: `[code snippet]`. - 完成“[語言]”程式碼以使用[參數]對“[API 端點]”進行 API 呼叫並處理響應:“[程式碼片段]”。 **Example** : Finish the Python function that calculates the average of a list of numbers given the following input parameters: **示例**:完成計算給定以下輸入參數的數字列表的平均值的 Python 函數: ``` def calculate_average(num_list) ``` ### **錯誤檢測** - Identify any potential bugs in the following [language] code snippet: `[code snippet]`. - 確定以下 [語言] 程式碼片段中的任何潛在錯誤:`[程式碼片段]`。 - Analyze the given [language] code and suggest improvements to prevent [error type]: `[code snippet]`. - 分析給定的[語言]程式碼並提出改進建議以防止[錯誤類型]:`[程式碼片段]`。 - Find any memory leaks in the following [language] code and suggest fixes: `[code snippet]`. - 在以下 [語言] 程式碼中查找任何內存洩漏並提出修復建議:`[程式碼片段]`。 **Example** : Identify any potential bugs in the following Python code snippet: **示例**:辨識以下 Python 程式碼片段中的任何潛在錯誤: ``` def calculate_sum(num_list): sum = 0 for i in range(len(num_list)): sum += num_list[i] return sum ``` ### **程式碼審查** - Review the following `[language]` code for best practices and suggest improvements: `[code snippet]`. - 查看以下“[語言]”程式碼以獲得最佳實踐並提出改進建議:“[程式碼片段]”。 - Analyze the given `[language]` code for adherence to `[coding style guidelines]`: `[code snippet]`. - 分析給定的“[語言]”程式碼是否符合“[編碼風格指南]”:“[程式碼片段]”。 - Check the following [language] code for proper error handling and suggest enhancements: `[code snippet]`. - 檢查以下 [語言] 程式碼以正確處理錯誤並提出改進建議:`[程式碼片段]`。 - Evaluate the modularity and maintainability of the given `[language]` code: `[code snippet]`. - 評估給定“[語言]”程式碼的模塊化和可維護性:“[程式碼片段]”。 **Example** : Review the following Python code for best practices and suggest improvements: **示例**:查看以下 Python 程式碼以獲得最佳實踐並提出改進建議: ``` def multiply_list(lst): result = 1 for num in lst: result *= num return result ``` ### **API 文件生成** - Generate API documentation for the following `[language]` code: `[code snippet]`. - 為以下“[語言]”程式碼生成 API 文件:“[程式碼片段]”。 - Create a concise API reference for the given `[language]` class: `[code snippet]`. - 為給定的“[語言]”類建立簡明的 API 參考:“[程式碼片段]”。 - Generate usage examples for the following `[language]` API: `[code snippet]`. - 為以下“[語言]”API 生成用法示例:“[程式碼片段]”。 **Example** : Generate API documentation for the following JavaScript code: **示例**:為以下 JavaScript 程式碼生成 API 文件: ``` /** * Returns the sum of two numbers. * @param {number} a - The first number to add. * @param {number} b - The second number to add. * @returns {number} The sum of a and b. */ function sum(a, b) { return a + b; } ``` ### **查詢優化** - Optimize the following SQL query for better performance: `[SQL query]`. - 優化以下 SQL 查詢以獲得更好的性能:`[SQL 查詢]`。 - Analyze the given SQL query for any potential bottlenecks: `[SQL query]`. - 分析給定的 SQL 查詢是否存在任何潛在瓶頸:`[SQL 查詢]`。 - Suggest indexing strategies for the following SQL query: `[SQL query]`. - 為以下 SQL 查詢建議索引策略:`[SQL 查詢]`。 - Optimize the following NoSQL query for better performance and resource usage: `[NoSQL query]`. - 優化以下 NoSQL 查詢以獲得更好的性能和資源使用:`[NoSQL 查詢]`。 **Example** : Optimize the following SQL query for better performance: **示例**:優化以下 SQL 查詢以獲得更好的性能: ``` SELECT * FROM orders WHERE order_date BETWEEN '2022-01-01' AND '2022-12-31' ORDER BY order_date DESC LIMIT 100; ``` ### **用戶界面設計** - Generate a UI mockup for a `[web/mobile]` application that focuses on [`user goal or task]`. - 為專注於 [`用戶目標或任務]` 的`[web/mobile]` 應用程式生成 UI 模型。 - Suggest improvements to the existing user interface of `[app or website]` to enhance `[usability, accessibility, or aesthetics]`. - 建議改進“[應用程式或網站]”的現有用戶界面,以增強“[可用性、可存取性或美學]”。 - Design a responsive user interface for a `[web/mobile]` app that adapts to different screen sizes and orientations. - 為適應不同螢幕尺寸和方向的“[web/mobile]”應用程式設計響應式用戶界面。 **Example** : Generate a UI mockup for a mobile application that focuses on managing personal finances. **示例**:為專注於管理個人財務的移動應用程式生成 UI 模型。 ### **自動化測試** - Generate test cases for the following [language] function based on the input parameters and expected output: `[function signature]`. - 根據輸入參數和預期輸出為以下 [語言] 函數生成測試用例:`[函數簽名]`。 - Create a test script for the given [language] code that covers [unit/integration/system] testing: `[code snippet]`. - 為涵蓋[單元/集成/系統]測試的給定[語言]程式碼建立測試腳本:`[程式碼片段]`。 - Generate test data for the following [language] function that tests various edge cases: `[function signature]`. - 為以下測試各種邊緣情況的[語言]函數生成測試資料:`[函數簽名]`。 - Design a testing strategy for a [web/mobile] app that includes [unit, integration, system, and/or performance] testing. - 為 [網絡/移動] 應用程式設計測試策略,包括 [單元、集成、系統和/或性能] 測試。 **Example:** Generate test cases for the following Python function based on the input parameters and expected output: **示例:** 根據輸入參數和預期輸出為以下 Python 函數生成測試用例: ``` def divide(a: float, b: float) -> float: if b == 0: raise ZeroDivisionError('division by zero') return a / b ``` ### **程式碼重構** - Suggest refactoring improvements for the following [language] code to enhance readability and maintainability: `[code snippet]`. - 建議對以下 [語言] 程式碼進行重構改進,以增強可讀性和可維護性:`[程式碼片段]`。 - Identify opportunities to apply [design pattern] in the given [language] code: `[code snippet]`. - 確定在給定的[語言]程式碼中應用[設計模式]的機會:`[程式碼片段]`。 - Optimize the following [language] code for better performance: `[code snippet]`. - 優化以下 [語言] 程式碼以獲得更好的性能:`[程式碼片段]`。 **Example** : Optimize the following Python code for better performance: **示例**:優化以下 Python 程式碼以獲得更好的性能: ``` def find_max(numbers): max_num = numbers[0] for num in numbers: if num > max_num: max_num = num return max_num ``` ### **設計模式建議** - Based on the given [language] code, recommend a suitable design pattern to improve its structure: `[code snippet]`. - 根據給定的[語言]程式碼,推薦合適的設計模式來改進其結構:`[程式碼片段]`。 - Identify opportunities to apply the [design pattern] in the following [language] codebase: `[repository URL or codebase description]`. - 確定在以下 [語言] 程式碼庫中應用 [設計模式] 的機會:`[存儲庫 URL 或程式碼庫描述]`。 - Suggest an alternative design pattern for the given [language] code that may provide additional benefits: `[code snippet]`. - 為可能提供額外好處的給定 [語言] 程式碼建議替代設計模式:`[程式碼片段]`。 **Example:** Based on the given Python code, recommend a suitable design pattern to improve its structure: **例子:** 根據給定的Python程式碼,推薦合適的設計模式來改進其結構: ``` class TotalPriceCalculator: def calculate_total(self, items): pass class NormalTotalPriceCalculator(TotalPriceCalculator): def calculate_total(self, items): total = 0 for item in items: total += item.price * item.quantity return total class DiscountedTotalPriceCalculator(TotalPriceCalculator): def calculate_total(self, items): total = 0 for item in items: total += item.price * item.quantity * 0.9 # apply 10% discount return total class Order: def __init__ (self, items, total_price_calculator): self.items = items self.total_price_calculator = total_price_calculator def calculate_total(self): return self.total_price_calculator.calculate_total(self.items) class Item: def __init__ (self, name, price, quantity): self.name = name self.price = price self.quantity = quantity ``` ### **算法開發** - Suggest an optimal algorithm to solve the following problem: `[problem description]`. - 建議解決以下問題的最佳算法:`[問題描述]`。 - Improve the efficiency of the given algorithm for `[specific use case]`: `[algorithm or pseudocode]`. - 為“[特定用例]”提高給定算法的效率:“[算法或偽程式碼]”。 - Design an algorithm that can handle `[large-scale data or high-throughput]` for `[specific task or operation]`. - 為“[特定任務或操作]”設計一種可以處理“[大規模資料或高吞吐量]”的算法。 - Propose a parallel or distributed version of the following algorithm to improve performance: `[algorithm or pseudocode]`. - 提出以下算法的並行或分佈式版本以提高性能:`[算法或偽程式碼]`。 ### **程式碼翻譯** - Translate the following `[source language]` code to `[target language]`: `[code snippet]`. - 將以下“[源語言]”程式碼翻譯成“[目標語言]”:“[程式碼片段]”。 - Convert the given `[source language]` class or module to `[target language]` while preserving its functionality and structure: `[code snippet]`. - 將給定的“[源語言]”類或模塊轉換為“[目標語言]”,同時保留其功能和結構:“[程式碼片段]”。 - Migrate the following `[source language]` code that uses `[library or framework]` to [target language] with a similar library or framework: `[code snippet]`. - 將以下使用“[庫或框架]”的“[源語言]”程式碼遷移到具有類似庫或框架的“目標語言”:“[程式碼片段]”。 **Example:** Translate the following Python code to JavaScript: **示例:**將以下 Python 程式碼轉換為 JavaScript: ``` def factorial(n): if n == 0: return 1 else: return n * factorial(n-1) ``` ### **個性化學習** - Curate a list of resources to learn `[programming language or technology]` based on my current skill level: `[beginner/intermediate/advanced]`. - 根據我目前的技能水平,策劃學習`[編程語言或技術]`的資源列表:`[初學者/中級/高級]`。 - Recommend a learning path to become proficient in `[specific programming domain or technology]` considering my background in `[existing skills or experience]`. - 考慮到我在“[現有技能或經驗]”方面的背景,推薦精通“[特定編程領域或技術]”的學習路徑。 - Suggest project ideas or coding exercises to practice and improve my skills in `[programming language or technology]`. - 建議專案想法或編碼練習,以練習和提高我在“[編程語言或技術]”方面的技能。 ### **程式碼可視化** - Generate a UML diagram for the following `[language]` code: `[code snippet]`. - 為以下“[語言]”程式碼生成一個 UML 圖:“[程式碼片段]”。 - Create a flowchart or visual representation of the given `[language]` algorithm: `[algorithm or pseudocode]`. - 建立給定“[語言]”算法的流程圖或可視化表示:“[算法或偽程式碼]”。 - Visualize the call graph or dependencies of the following `[language]` code: `[code snippet]`. - 可視化以下“[語言]”程式碼的呼叫圖或依賴關係:“[程式碼片段]”。 **Example** : Generate a UML diagram for the following Java code: **示例**:為以下 Java 程式碼生成 UML 圖: ``` public abstract class Vehicle { private String model; public Vehicle(String model) { this.model = model; } public String getModel() { return model; } public abstract void start(); public abstract void stop(); } public class Car extends Vehicle { public Car(String model) { super(model); } @Override public void start() { System.out.println("Starting car engine"); } @Override public void stop() { System.out.println("Stopping car engine"); } } public class Motorcycle extends Vehicle { public Motorcycle(String model) { super(model); } @Override public void start() { System.out.println("Starting motorcycle engine"); } @Override public void stop() { System.out.println("Stopping motorcycle engine"); } } ``` ### **資料可視化** - Generate a bar chart that represents the following data: `[data or dataset description]`. - 生成代表以下資料的條形圖:`[資料或資料集描述]`。 - Create a line chart that visualizes the trend in the following time series data: `[data or dataset description]`. - 建立一個折線圖,將以下時間序列資料的趨勢可視化:`[資料或資料集描述]`。 - Design a heatmap that represents the correlation between the following variables: `[variable list]`. - 設計一個表示以下變數之間相關性的熱圖:`[變數列表]`。 --- 以上,簡單分享,希望對您有幫助!

Composer 進階原理:PHP命名空間與PSR-0

上次的說明了PHP套件管理的歷史與社群提出的解決之道,本篇文章接著談類別管理的進階議題。 # 當類別名稱一樣... 當專案大了起來,有時候會有類別名稱重複的問題。 假設今天要撰寫一個論壇模組,提供討論區與留言板功能。 你一定很想將討論區的文章與留言板的文章都命名類別為Article: ``` // BoardArticle.php <?php class Article{ //... } ?> // ForumArticle.php <?php class Article{ //... } ?> ``` 當然了,這麼做會得到一個結果: ``` Fatal error: Cannot redeclare class Article ``` 這種問題有一個簡單的解決辦法,就是加上前綴字。 類別分別命名為ForumArticle與BoardArticle就可以了。 *Q1: 等一下!這個解法好陽春!我看到至少4個問題:* 1. 類別名稱容易變得冗長。 2. 有些類別一開始你以為不會跟人重複,結果之後真的重複了。難道永遠替類別加前綴字? 3. 類別名稱寫Article俐落多了!文章就是概念上的文章,不要逼我告訴你是討論區還是留言板!如果專案用到兩種留言板模組,分別由以前的兩個前輩寫好,難道還要逼我把作者名稱寫進去? ``` class TonyBoardArticle{ //... } class JackBoardArticle{ //... } ``` 4. 如果我在打造框架(framework)呢?幾乎會把所有常見名詞用過一次(像是Request、Loader、Response、Controller、Model等類別)!難道前面全部前綴? 看看 Codeigniter 的原始碼,全部用CI_當作前綴。超醜的。 # 命名空間(namespace)登場 於是PHP從5.3版之後支援了命名空間(namespace)。 所以可以用Article替類別定義了: ``` // BoardArticle.php <?php namespace Board; class Article{ //... } ?> // ForumArticle.php <?php namespace Forum; class Article{ //... } ?> ``` 使用類別時只要加上命名空間即可: ``` //index.php <?php include 'BoardArticle.php'; include 'ForumArticle.php'; $article = new Forum\Article(); $post = new Board\Article(); ``` 如果當前的php檔只用到其中一個Article類別,可以指定當前的php檔只用哪個命名空間+類別的組合: ``` <?php include 'BoardArticle.php'; include 'ForumArticle.php'; use Forum\Article; $article = new Article(); $fArticle = new Forum\Article(); $bArticle = new Board\Article(); ``` 如此一來,當php找不到Article類別時,便會去使用use關鍵字宣告的組合。 當然了,就算用use指定過,原本的宣告方式還是可以用的。(如最下方兩行所示) # 當東西多了起來... OK,可以繼續完成我們的論壇模組了! 討論區跟留言板有各自的文章,再來還需要「推文」: ``` <?php namespace Board; class Comment{ // ... } ?> <?php namespace Forum; class Comment{ // ... } ?> ``` 使用剛剛學到的命名空間去載入他們: ``` <?php // index.php include 'BoardArticle.php'; include 'ForumArticle.php'; include 'BoardComment.php'; include 'ForumComment.php'; $article = new Forum\Article(); $comment = new Forum\Comment(); // ... ``` 果然是漂亮的各種命名阿! *Q2: include有好多行!上次提到了Composer可以解決這種問題,當引入命名空間之後,Composer也能發揮作用嗎?* **是的。** # Composer登場 跟上次初學Composer一樣,建立一個composer.json檔: ``` { "autoload": { "files": [ "ForumArticle.php", "ForumComment.php", "BoardArticle.php", "BoardComment.php" ] } } ``` 注意,上次我們用"classmap"指定資料夾、把資料夾內檔案全掃一次,這次我們用"files"分別設定各個檔案。 再來,在terminal輸入 ``` composer install ``` 執行完畢之後,跟上次一樣,只要載入一個檔案: ``` <?php require 'vendor/autoload.php'; $article = new Forum\Article(); $comment = new Forum\Comment(); ``` 就可以使用各個類別囉! *Q3: 等一下!看起來跟沒有命名空間的時候差不多啊!一樣是把php檔自動require進去而已?* 對啊,你最上面的原始寫法,也只是手動載入好幾個檔案,在載入的時候本來就沒有特別之處: ``` <?php // index.php include 'BoardArticle.php'; include 'ForumArticle.php'; include 'BoardComment.php'; include 'ForumComment.php'; $article = new Forum\Article(); $comment = new Forum\Comment(); // ... ``` 載入php檔就只是載入,跟命名空間是兩回事。 *Q4: 還是不太對啊!上次我用classmap一次把好幾個資料夾內容掃過,這次我用files分別指定每個檔案幹嘛?Composer不是厲害在能指定資料夾去自動掃過?* ......你說的沒錯。 開個my_lib資料夾,把4個php檔都丟進去吧。composer.json這樣寫就好了: ``` { "autoload": { "classmap": [ "my_lib" ] } } ``` 再來,在terminal輸入 ``` composer install ``` 這樣就搞定了! 其實我只是想示範,除了用classmap設定資料夾之外,也可以用files直接指定檔案。 *Q5: OK,原諒你。不過,我必須說,我今天什麼都沒學到。最後還是在composer.json寫classmap而已,跟上次一模一樣。* 是的...我剛說了,載入php檔就只是載入,跟命名空間是兩回事。 我今天介紹的namespace功能是PHP本身提供的。而Composer只是協助你載入的工具、當然不可能改變程式語言本身。 Composer只是幫助你少打那一串require而已。 *Q6: ㄟ等等...有個地方我覺得很醜。我們現在的my_lib資料夾裏面有4個檔案,檔名很醜:* ``` "ForumArticle.php" "ForumComment.php" "BoardArticle.php" "BoardComment.php" ``` 類別名稱本身都是俐落的Article跟Comment了,檔案名稱還是有前綴字。但也不可能有兩個Article.php、兩個Comment.php。你有沒有辦法解決這個美感問題? 這個問題簡單,把那個my_lib資料夾刪掉,開一個Forum資料夾、一個Board資料夾。本來的ForumArticle.php改成Article.php丟進Forum資料夾、本來的BoardArticle.php改成Article.php丟進Board資料夾。composer.json改寫成這樣: ``` { "autoload": { "classmap": [ "Board", "Forum" ] } } ``` 再來,在terminal輸入 ``` composer install ``` 這招不錯吧!檔案名稱就是類別名稱,乾淨俐落! 而且資料夾的名字本身就是namespace的名稱! 以後都這樣做啦!一用到命名空間就開個同名資料夾把檔案丟進去! *Q7: 這招我覺得還好耶...。本來檔案都放在my_lib,我在composer.json只要填my_lib一行就好,現在變成要填兩行。要是我這個論壇模組有一大堆類別、用了一大堆命名空間呢?那我classmap底下不就要填入好幾行?那我寧可全部丟進my_lib,只填my_lib一行!* 唔,這樣說也是有道理。那重新建立my_lib資料夾,把Board跟Forum資料夾丟進my_lib資料夾。 composer.json改回這樣寫: ``` { "autoload": { "classmap": [ "my_lib" ] } } ``` 再來,在terminal輸入 ``` composer install ``` classmap不只是告訴Composer去載入哪幾個資料夾內的檔案,還會把資料夾內的資料夾也全部掃過一次。 怎麼樣,Composer夠神吧。 *Q8: 原來classmap底下會遞迴掃描下去...。我決定了,我的Forum跟Board都是在提供線上討論功能,我決定替我這個模組命名為Discussion。我要在my_lib底下開Discussion資料夾,然後把原本的Forum跟Board資料夾都丟進去。你覺得這個想法如何?* 還不錯。一個Discussion資料夾就是你的整個Discussion模組。 提供了討論區、留言板功能的Discussion模組,我喜歡。 *Q9: 好像忘了什麼...。啊,剛剛說命名空間跟檔案結構符合會最漂亮。那我要把那4個檔案的namespace改成這樣:* ``` <?php namespace Dicussion/Forum; class Article{ //... } ``` 剛剛說了,載入檔案就只是載入檔案,跟命名空間無關。 現在檔案結構沒變,所以我應該不用重新輸入composer install。 讓我試試...。 靠!怎麼噴error了!你騙我? ``` Fatal error: Class 'Discussion\Forum\Comment' not found ``` 呃...,我前面的說法確實有點誤導。 PHP自動載入的基本函式長這樣: ``` void __autoload ( string $class ) ``` 如你所見,PHP至少需要Composer提供資訊指出$class該去哪個檔案找。 namespace改變之後,PHP會找不到對應的$class在哪。 所以還是輸入一下composer install吧!Composer會把需要的資訊整理好的。 *Q10: OKOK,我知道了,我駕馭這一切了。我覺得這個Dicsussion模組真的超屌的,不但命名空間漂亮,連檔案結構都漂亮。我要把這個Discussion資料夾整個丟給我朋友,他們公司最近需要論壇模組。* 讓我打電話給他...。 「什麼?你們已經做好半個論壇模組了?你只需要我模組的其中幾個功能?你們的模組也是放在Discussion資料夾?」 糟糕,資料夾名稱重複了!所以我的模組拿給別人還是有不相容的可能,怎麼辦? 沒有錯..還記得你Q1提到的第3個狀況嗎?確實有把作者名稱加進去的必要! 別怕,我教你。你開一個Tony資料夾,把整個Discussion資料夾丟進去。 接著所有檔案namespace改成像這樣: ``` <?php namespace Tony/Dicussion/Forum; class Article{ //... } ``` 要用的時候就這樣喔: ``` <?php require 'vendor/autoload.php'; $article = new Tony\Discussion\Forum\Article(); $comment = new Tony\Discussion\Forum\Comment(); ``` 是變得有點長啦。 但這下搞定了吧!作者名稱再撞到的話,就改個獨特的名稱就是了! # 終於。讓我們談談PSR-0 你一定常聽到PSR-0對吧! PSR-0是PHP跨框架相容性統一標準組織訂出來的自動載入慣例。 來談談PSR-0幾個最重要的要求吧! * 命名空間加上類別名稱一定要長這樣: ``` {vendor name}\{namespace}\{class name} ``` * 前面一定要是作者名稱 * 中間可以有任意層次的命名空間、最後是類別名稱。 * 中間任意層次的命名空間直接對應到檔案結構。 發現了嗎?在剛剛Q1~Q10的過程中,其實你已經把PSR-0學完了,連設計原理都一起搞懂了。 懂這些之後,你也可以做好自己的模組、發佈到 Packagist 給全世界透過composer下載、使用了! 最後,如果遵守psr-0的話,composer.json可以這樣寫: ``` { "autoload": { "psr-0": { "Tony\\Discussion\\": "my_lib/" } } } ``` 注意,雙引號、兩次反斜線並沒有特別意思,只是 json 規定的格式。 跟classmap一樣都可以完成任務。兩者其實是有差別的...,我們下次再談。 # 結語 Composer工具以及PHP-FIG組織的出現,讓一直以來散落各地的PHP社群開始有集中的趨勢。 換句話說,各社群終於能共享彼此的library了。 然而,如你所見,psr-0不但導致檔案結構容易變得深層,還要求檔案結構必須配合程式碼,這也招致了不少批評。 https://r.je/php-psr-0-pretty-shortsighted-really.html 除此之外,composer autoload內的classmap跟psr-0到底如何分工?效能差異又為何?這些問題也都還在爭論與驗證當中。 http://stackoverflow.com/questions/22803419/why-use-a-psr-0-or-psr-4-autoload-in-composer-if-classmap-is-actually-faster 不過,PSR-0在各框架已被廣泛支援,因此建議你還是需要有所瞭解。 # 最後... 現在已經出現psr-0的替代方案,稱為psr-4。 PSR-0從2014-10-21開始被註明為不建議使用。 至於PSR-4..我們下次再談。

COMPOSER 設計原理與基本用法

相信有在用PHP的朋友近年來常聽到composer這個套件管理工具。 它到底是做什麼用的?又是為了解決什麼問題而存在呢? 要瞭解這個,得先從歷史開始說起...。 # PHP最早讀取套件的方法 初學PHP時,最早會面對的問題之一就是require與include差別何在? require_once與include_once又是什麼? 弄懂這些問題之後,如果不使用framework,直接開發,便常出現類似這樣的code: ``` // whatever.php // 這檔案需要用到幾個類別 require 'xxx_class.php'; require 'yyy_class.php'; require 'zzz_class.php'; // ... ``` 然後在其他檔案會出現: ``` // another.php // 這檔案需要用到幾個類別 require 'yyy_class.php'; require 'zzz_class.php'; // ... ``` 這樣的結果,會產生至少兩個問題: 1. 許多檔案用到同樣幾個class,於是在不同地方都需要載入一次。 2. 當類別多了起來,會顯得很亂、忘記載入時還會出現error。 那麼,不如試試一種懶惰的作法? 寫一個php,負責載入所有類別: ``` // load_everything.php require 'xxx_class.php'; require 'yyy_class.php'; require 'zzz_class.php'; require 'aaa_class.php'; require 'bbb_class.php'; require 'ccc_class.php'; ``` 然後在其他檔案都載入這支檔案即可: ``` require 'load_everything.php' ``` 結果新問題又來了:當類別很多的時候,隨便一個web page都會載入一堆code,吃爆記憶體,怎麼辦呢? # __autoload 為了解決這個問題,PHP 5開始提供__autoload這種俗稱「magic method」的函式。 當你要使用的類別PHP找不到時,它會將類別名稱當成字串丟進這個函式,在PHP噴error投降之前,做最後的嘗試: ```// autoload.php function __autoload($classname) { if ($classname === 'xxx.php'){ $filename = "./". $classname .".php"; include_once($filename); } else if ($classname === 'yyy.php'){ $filename = "./other_library/". $classname .".php"; include_once($filename); } else if ($classname === 'zzz.php'){ $filename = "./my_library/". $classname .".php"; include_once($filename); } // blah } ``` 也因為PHP這種「投降前最後一次嘗試」的行為,有時會讓沒注意到的人困惑「奇怪我的code怎麼跑得動?我根本沒有require啊..」,所以被稱為「magic method」。 如此一來,問題似乎解決了? 可惜還是有小缺點..,就是這個__autoload函式內容會變得很巨大。以上面的例子來說,一下會去根目錄找、一下會去other_library資料夾、一下會去my_library資料夾尋找。在整理檔案的時候,顯得有些混亂。 # spl_autoload_register 於是PHP從5.1.2開始,多提供了一個函式。 可以多寫幾個autoload函式,然後註冊起來,效果跟直接使用__autoload相同。 現在可以針對不同用途的類別,分批autoload了。 ```spl_autoload_register('my_library_loader'); spl_autoload_register('other_library_loader'); spl_autoload_register('basic_loader'); function my_library_loader($classname) { $filename = "./my_library/". $classname .".php"; include_once($filename); } function other_library_loader($classname) { $filename = "./other_library/". $classname .".php"; include_once($filename); } function basic_loader($classname) { $filename = "./". $classname .".php"; include_once($filename); } ``` 每個loader內容可以做很多變化。可以多寫判斷式讓它更智慧、可以進行字串處理...。 自動載入類別的問題終於解決了...。 *但是光上面的code也有15行,而且在每個project一定都會寫類似的東西。有沒有辦法自動產生這15行呢? 我的願望很簡單,我告訴你,反正我有my_library資料夾跟other_library資料夾,你自己進去看到什麼類別就全部載入好不好...?* *阿不對,全部載入剛又說效能不好,那你進去看到什麼就全部想辦法用spl_autoload_register記起來好不好...?* *我懶得打15行了,我只想打這幾個字:* ``` $please_autoload = array( 'my_library', 'other_library'); ``` *可不可以發明一個工具,去吃$please_autoload這個變數,然後自己想辦法載入一切啊...?* *ㄟ等等,我連php程式碼都懶得打了,在web領域JSON格式更簡潔。允許我這樣打,好嗎?* ``` { "autoload": [ "my_library", "other_library" ] } ``` *然後誰來個工具幫我產生一大串autoload相關的php程式碼吧...,可以嗎?* **可以。** # Composer登場 首先,裝好composer(本文不介紹如何安裝。) 再來,建立一個composer.json檔,裏面輸入這些: ``` { "autoload": { "classmap": [ "my_library", "other_library" ] } } ``` 比原本希望的多打了一些字,不過差不多。 再來,在terminal輸入 ``` composer install ``` 執行成功之後,你會看到一個vendor資料夾,內含一個autoload.php。 沒錯,跟你夢想的一樣。你只要載入這個檔案: ``` require 'vendor/autoload.php'; ``` 你需要的所有類別,都會在適當的時候、以適當的方式自動載入。 php再也不會噴error說你「類別尚未定義」了! 這vendor資料夾裏面的一切,都只是php code而已,並沒有特別神奇的地方。只要去看autoload.php的原始碼,就能知道composer到底寫了哪些php code給你。 *ㄟ等等,我寫的類別都放在my_library裏面了,other_library都是網路上copy下來的現成類別。我想要用Google API的Client類別、Doctrine資料庫管理抽象層類別、還有guzzlehttp的發送request類別。* *我連去下載這些檔案、然後丟進這個資料夾都懶得做了,我根本不想手動建立other_library這個資料夾。composer真那麼神...不如連下載都幫我自動下載?可以嗎?* **可以。** 查詢一下那幾個套件在 https://packagist.org/ 的名稱、還有你需要的版本號。 把剛剛的composer.json改成這樣: ``` { "require": { "google/apiclient": "1.0.*@beta", "guzzlehttp/guzzle": "~4.0", "doctrine/dbal": "~2.4" }, "autoload": { "classmap": [ "my_library" ] } } ``` 然後'composer install'指令除了自動載入你的類別之外、還會自動下載你需要的類別、然後自動載入它們。 一樣require 'vendor/autoload.php'就可以了。composer實在是太棒了。 其實composer解決的問題不只這樣。 類別多了起來之後,各種程式語言都提供namespace功能協助分類。 在有namespace的情況下,PHP社群與composer是如何解決自動載入的問題呢? 這些比較進階的內容,下回分曉。

後端 JS 訓練一:第4課 ── 把 node 非同步設計改寫為同步設計

## 課程目標 - 把 node 非同步設計改寫為同步設計 ## 課程內容 前面我說過,node 有些地方的寫法設計,看起來很怪、很醜 在瀏覽器 js 環境,我們的確不知道用戶何時會點擊按鈕、不知道 AJAX 回應何時會拿到 不能讓網頁整個停下來卡住、等待上述事件發生,所以必須用「非同步程式設計」處理 可是檔案系統模組,絕大多數情況下,你就是要現在就讀取好檔案、讀取完才讓程式接著繼續跑,才有意義 實在沒必要用「非同步程式設計」處理! 取得用戶輸入就更不用說了,程式就是該停在這裡、等用戶輸入完再接著繼續執行,才有意義 何必用「非同步程式設計」處理! 其他程式語言,大多都是「同步程式設計」,例如用 Python 寫同樣程式時 就是一行一行寫下來即可,不用像這邊這樣把函式當參數傳進去 簡單講,javascript 的非同步設計,在網頁畫面上運作時,有其優勢與必要 但是在用來寫腳本時,顯得多餘而不必要 --- 來學習怎麼改寫吧,打開 `read-my-name.js`,改寫成以下內容 ``` var fs = require('fs'); var data = fs.readFileSync('my-name.txt'); console.log("您好," + data); process.exit(0); ``` 執行看看! ``` node read-my-name.js ``` 跑起來一模一樣,但是不是好讀多了? 注意 `fs.readFileSync` 後面的 sync,那就是同步的意思 --- 接著來改寫第二個程式吧,打開 `write-my-name.js`,改寫成以下內容 ``` var fs = require('fs'); const readlineSync = require('readline-sync'); var answer = readlineSync.question('請問您的大名?\n'); fs.writeFileSync('my-name.txt', answer); console.log('儲存成功。'); process.exit(0); ``` 是不是好讀多了? 新函式名稱,就是後面加上 sync 而已,node 官方知道大家的抱怨,所以有提供這些同步函式 但是,這段 code 不能直接執行,因為 `readline-sync` 不是官方內建模組,是社群開發的,需要透過 npm 安裝 怎麼辦? --- NPM 全稱為 Node Package Manager,也就是 Node 套件管理工具 關於 npm 的安裝,網路上有非常多教學,請根據你的作業系統,自己找一套安裝方法 安裝好之後,你就可以使用「終端機」運行 npm 指令 --- 請在終端機輸入 `npm install readline-sync` 即可安裝 `readline-sync` 套件 會多出兩個檔案 `package-lock.json` `package.json` 用來記錄目前專案用到的 npm 套件,你可以打開這兩個檔案看一看 還會多出一個資料夾 `node_modules`,套件本身會下載到這個地方,你可以打開資料夾逛一逛 接著執行看看! ``` node write-my-name.js ``` 跑起來一模一樣,但是好讀多了! ## 課後作業 接續前一課的作業,現在來嘗試「同步」的語法 請把「非同步」的語法改寫,通通改用「同步」的語法 改完之後,程式碼中,應該就不會出現 callback function 了 也就是不會出現「把一段函式定義當作參數傳進另一個函式」這種情況 完成以上任務,你就完成這次的課程目標了!

後端 JS 訓練一:第2課 ── 用 node 讀取檔案內容

## 課程目標 - 用 node 讀取檔案內容 ## 課程內容 來學習一下用 node 讀取檔案的方法 學會這方法,可以用 node 寫出簡易的檔案內容分析程式 --- 建立一個 `my-name.txt` 文字檔,在裡面輸入你的名字 然後建立 `read-my-name.js` 程式,裡面輸入 ``` var fs = require('fs'); fs.readFile('my-name.txt', function(err, data) { console.log("您好," + data); process.exit(0); }); ``` 然後去終端機輸入 ``` node read-my-name.js ``` 你會看到一段打招呼的訊息! --- 讓我們逐行說明一下 ``` var fs = require('fs'); ``` 這是載入「檔案系統模組」的意思,`fs` 是 `file system` 的縮寫 `readFile` 函式,第一個參數是檔案名稱,第二個參數是「一個函式定義」 看起來有點怪,其實是「非同步程式設計」的關係 之前在學網頁元素的事件處理時,就是用「非同步」程式設計處理 也就是我不確定「點擊」事件何時會發生,但我先「綁定」好事件發生時要做的任務 還有在網頁呼叫 AJAX 時也是,我不確定「主機回應」何時會拿到,但我先「綁定」好拿到之後要做的任務 這邊 `fs` 的意思就是:我不確定何時會「檔案讀取完畢」,但我先「綁定」好讀取完畢之後,要做的任務 看起來很怪、很醜,這種設計的意義不明,對嗎?我也覺得!後面會教你如何改寫成「同步程式設計」,先照做就好 -- `console.log` 是印出訊息到終端機(語法就跟在瀏覽器環境一樣!) `process.exit(0);` 是結束程式的意思。那個 `process` 是代表當前 node 程式的全域變數 --- 在瀏覽器裡面,無法去操作「檔案系統模組」,否則也太可怕,每個人上網時,都能用 js 亂改電腦上的檔案,資安大問題! 在瀏覽器裡面,也沒有 `process` 這個全域變數 用 node 跑完這段程式,你應該會發現,這段程式拿到網頁環境,是無法執行的! 在瀏覽器、node 跑 js 程式,有很多相似,也有很多相異之處,希望你慢慢抓到兩者的感覺! ## 課後作業 讓我們嘗試開發一個 CLI 版本的待辦管理工具 請建立一個檔案 `todos.json` 並放入以下內容 ``` [ { "title": "去操場跑步" }, { "title": "去市場採購" }, { "title": "找朋友吃飯" } ] ``` 然後建立一個 `read.js` 檔案 使用者輸入 `node read.js` 之後,終端機會去讀取 `todos.json` 並且顯示 ``` 您的待辦事項: #0 去操場跑步 #1 去市場採購 #2 找朋友吃飯 ``` 完成以上任務,你就完成這次的課程目標了! --- 交作業的方法: **方法一** 直接截圖視窗內容,上傳到留言區 **方法二** 開一個新資料夾,用 git 初始化這個資料夾 接下來的作業,都放在這個資料夾,然後上傳 github 然後把 github 專案連結,貼到留言區即可