<!-- more -->
在研究的過程中,有些不懂的名詞或是疑問浮出,因此我自己整理了問答列於文章下方,有興趣的人可以跳到那邊先看看。以下我想聊聊對於 Passkeys 的應用場景。
這情境我想很常發生在你我之間,我們的長輩可能是 50~60 歲,甚至更高年紀,他們對於資安風險可能沒什麼概念,常用的帳號密碼的管理也可能做得很粗糙甚至是完全沒有管理,每次要輸入密碼的時候都要想半天。
Passkeys 的到來會是怎麼樣的情境?在 Google 正式支援 Passkeys 的到來,我們可以利用長輩的手機為長輩的 Google 帳號建立 Passkeys,未來在他們需要登入 Google 的地方,只要手機在身邊,就可以利用 iOS 的 FaceID 或是 TouchID 或是其他 Android 生物解鎖的方式,讓他們登入 Google 帳號。對長輩來說這種生物辨識的解鎖行之有年,因此他們會覺得都一樣,只要刷臉或是指紋就可以登入,所以使用起來一樣方便。
這需要時間,因為在此時不是每一家公司都支援利用 Passkeys 去「創建」帳號,就連 Google 也不是,Google 只是多提供一個讓使用者更安全且更方便的「登入」方式。有支援 Passkeys 的服務可以到 1Password 整理的Passkeys .directory去看。
以後我們要登入一些服務,只要確定你的裝置擁有 Passkeys 的私鑰(這邊不談太多細節,有興趣的人推薦參考這篇)或者是該私鑰在可取得的行動裝置或其他地方,就可以認證成功然後登入該服務,再也不用記得密碼。 這個體驗其實跟目前有在使用密碼管理服務差不多,但是 Passkeys 的特性讓這樣的過程更安全。 由於 Apple 和 Google 都有實作密碼管理的工具,因此回到上述的場景一,如果是用手機建立 Passkeys,則可以直接利用內建的密碼管理工具(例如: keychain)就可以跨裝置同步 Passkeys 私鑰,而能夠跨裝置同步這點也是 Passkeys 的一個重要特性,就可以在 iPhone 和 iPad 之間同步這些登入資訊了。
這點我目前是保持疑問態度,原因是我認為帳號需要分成主要服務帳號以及非主要服務帳號。所謂的主要服務帳號就是指 Google、Apple 這些會直接跟手機有關連的帳號,或者說那些你會使用的 email 服務。
今天如果你需要創建 Google 帳號,其實是不能使用 Passkeys 的方式來創建帳號的。要在手機上登入你的 Google 或是 Apple 帳號,目前也都還是需要密碼才能登入。而這些主要服務帳號都擁有不同種的「忘記密碼」的服務,因為你會將手機號碼給他們,或是給不同的 email 帳號,讓真正需要忘記密碼的時候,Google 和 Apple 能有一個方式聯絡你。
這邊我還沒有想通的是,如果有一個服務支援了 Passkeys,你在建立帳號的時候根本沒有輸入過密碼,但你當時是利用手機申請的,然而已經換手機沒有備份,也就是 Passkeys 私鑰已經不在手上狀態,那不就永遠都沒辦法登入該服務了嗎?這件事類似加密貨幣錢包的 12 個字串不見,因為其實這就是私鑰的一種。
目前看到的一個解法是,註冊的時候就是需要利用 email 當做 account,今天你要登入的時候是輸入 email,然後它會寄 passcode 到你的 email(就像是 OTP),也就是你還是需要登入該 email 才能登入該服務。
我是 1Password 的愛好者,目前 1Password 也在官方部落格提到他們是大力擁抱 Passkeys,目前 extension 在 beta 階段已經可以使用 Passkeys,且預期 2023 年 7 月就可以將這些功能上線到 mobile。我會持續關注 1Password 提出的解決方案,因為他們跟 Google 和 Apple 的解決方案不一樣,是跨平台不會限制在單一的平台上。而未來也可以利用 Passkeys 來解鎖 1Password,這也是很令人期待的一個功能。
Passkeys 的普及絕對是好處大於壞處,由各家科技大廠紛紛加入所制訂出來的協定,讓更多人可以擁抱無密碼登入的體驗,再也不用去思考該網站要用什麼密碼,以及每次註冊都要在那邊思考要用什麼密碼甚至是網站還會限制密碼長度或是格式這種問題了。
<!-- more -->
對於 GraphQL 熟悉的朋友,對於 fragment 應該不陌生。如果不太熟的朋友,讓我們一起研究下去。
Fragment 最大的特點就是 reusable,只要在一個檔案定義好對應的 fields,其他會用到的地方就可以 import 該檔案拿去用。有了 fragment 的幫助,我們可以在 Query 和 Mutation 共用同樣的部分,不僅好管理,也不用重複的撰寫那些 fields。
基於這樣的特性,fragment 本身就帶有點 component 的概念,因為 component 也是在一個檔案先寫好,其他需要用到的地方再 import 去用。
這邊直接引用官方的 sample code 稍微感受一下:
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
有興趣的人可以到官方網站了解更多。
Component 化是現代前端框架、套件都不陌生的管理方式,一個個 component 不僅在 codebase 好管理,也可以針對單一檔案做測試,可以說是優點不少。
當使用 React component 搭配 GraphQL 時候,在每一個 component 需要資料去 render 畫面時,就可以善用 GraphQL 的特性,將底下的子 component 需要用到的 fields 都收集組裝出一個大的 query,再由上層的 component 去取得後端資料後,往下利用 props 傳遞讓子 component 去做 render 的動作。
根據 Apollo 文件,colocated fragments 是指該 fragment 是針對某特定 component 而生的,因此最好的管理方式就是將該 fragment 直接擺在此 component 附近,這個動作就叫做 colocated。
你可以選擇直接將 fragment 寫在該 component 的 tsx 檔裡面(Apollo 官方也是如此),而我個人習慣的管理方式是,創一個同名且帶有 fragments.ts
的後綴檔案跟 component 放在一起。如下面表示:
- components
- post.tsx
- post.fragments.ts
這樣的管理方式有幾個好處,
然而這樣的方式會有一些不方便的地方,就是在 component 的 props 的 type 定義,每次如果有修正相關的 fragment fields,都需要再次手動去改動該 component 的 type,會有點惱人。
基於都是利用型別推斷的方式,GraphQL Code Generator 就可以來協助我們自動化的做到這件事情。
接下來會用官方範例的縮寫 codegen
來稱呼這套 CLI。
上面段落提到,今天如果改了一個 colocated fragment 的 fields,而每次都要手動去修改 component props 的 type 會有點麻煩,而 codegen 就可以自動化做到這件事情。
我們要用到的 plugin 就是這套 near-operation-file-preset。
以下直接分享我習慣的設定,與官方範例略有不同,各位可以參考然後調整適合自己的版本。
overwrite: true
schema: 'schema.graphql'
generates:
types/graphql.ts:
plugins:
- typescript
config:
namingConvention: keep
./:
plugins:
- typescript-operations
- typescript-react-apollo
preset: near-operation-file
presetConfig:
baseTypesPath: types/graphql.ts
extension: .generated.tsx
config:
namingConvention: keep
documents:
- '**/*.fragments.ts'
- '**/*.query.ts'
- '**/*.mutation.ts'
- '**/*.subscription.ts'
這邊簡單說明一下原理,codegen 第一件事需要拿我們 GraphQL 的 schema.graphql
檔去產生出對應的 type 檔,這邊我們指定 build 出來的檔案放在 types/graphql.ts
。
接著 codegen 會根據這個 baseTypesPath 搭配相關的 plugin,找到 documents 所列的檔案,自動的去產生相對應的 generated 檔。然而因為我們有用到 typescript-react-apollo 的 plugin,因此 apollo react 的 useQuery 和 useMutation 它都會自動的 build 出來,我們就可以直接拿來使用,而不用自己再重新寫一次,非常方便。
今天假設我們有一個 Comment.tsx
的 component,且有定義好 Comment.fragments.ts
和 Comment.mutation.ts
檔案,當跑完 codegen 後的用法會類似以下:
import { CommentFragment } from './Comment.fragments.generated';
import { useDeleteCommentMutation } from './Comment.mutation.generated';
type Props = {
comment: CommentFragment;
};
const Comment = ({ comment }: Props) => {
const [deleteComment] = useDeleteCommentMutation({
onError,
onCompleted,
});
// you can use `comment` or call `deleteComment` to mutate the data
};
export default Comment;
上層有用到這個 <Comment />
的地方,只要確定有將 Comment.fragments 正確的 import 到,然後跟後端拿到資料後,利用 <Comment comment={comment} />
往下傳遞就可以善用到 GraphQL colocated 的特性。
React Component 與 GraphQL 的搭配寫起來很舒服,在這個 TypeScript 盛行的年代,搭配 GraphQL SDL 的特性,利用 colocated pattern 來管理對應的 fragments,再加上 codegen 的自動化幫忙,讓開發體驗更上一層樓。
內文若有錯誤,還請不吝讓我知道,我有看到的話會馬上更正。如果你也有相關的 GraphQL 管理經驗,也歡迎一同分享交流。
<!-- more -->
以下有些名詞我選擇直接用縮寫,因此在這邊先做介紹:
有鑑於此部落格太久沒有更新,上一篇文章是從 Hexo 搬到 Gatsby,這兩套框架都是非常優異,也都有持續維護的狀態。
這幾年更加吸引大眾目光的是 Next.js,其背後團隊 Vercel 來頭不小。目前的 CEO rauchg 是 socket.io 作者,看貢獻度目前也是排在第一位。夾帶這些名氣,以及 Next.js 推出的解決方案,不論是 CSR, SSR, 和 SSG 都可以處理。
因此,就選擇將本部落格再度搬移到 Next.js 了。XD
Next.js 沒有原生支援 RSS feed 產生,所以必須要根據 Next.js 的架構去研究,在哪一個時間點產生出 RSS feed 會是最可行的作法。
在研究了 Next.js 的流程後,有幾種作法:
如果是 SSR 處理方式,尤其是直接將 project host 在 vercel 上面。可以直接在 pages/rss.js
裡面,利用 Next.js 的架構,直接在這支 JS 檔裡面處理回傳 RSS feed 的動作。
要注意的是 RSS 需要回傳 XML 的格式,因此 在 pages/rss.js
的 getServerSideProps 就必須要指定回傳的 header。詳細的實作可以參考這篇文章。
然而因為我的架構是擁有自己的 markdown file,不論用哪些框架只是幫我把這些 markdown file 轉成 HTML render 出來即可,因此 SSR 就不是我選擇的方向,也就往 SSG 解決方案去思考了。
SSG 就是將所有的檔案都在 build time 轉成 static file,所以自然沒有 server 端可以幫忙處理 response XML 的動作,因此就必須思考將 RSS feed 直接 build 成一個獨立的 public 檔案,讓想要訂閱 RSS feed 的人可以利用該連結直接訂閱。
在程式端具體的實作流程有許多種,以下舉我認為比較可行的兩種:
"postbuild": "node lib/rss.js"
,基於 npm script 的定義,只要在 build script 跑完後,就去直接執行這個 npm script 在 public
(Next.js 定義的公開資料夾路徑)資料夾產生對應的 RSS file。public
資料夾即可。這兩種真正執行的 function 內容幾乎一樣,就是包裝起來執行的方式和時間點不一樣而已。
我自己是選擇第二種,在 Next.js build 階段就有去執行產生 RSS file,可以在開發的時候比較好 debug。
這次我選擇 feed 這個套件來幫忙產生 RSS feed。
yarn add feed
Feed
instance。import { Feed } from 'feed';
import config from '../config';
export const generateRSS = async () => {
const feed = new Feed({
title: config.title,
description: config.subtitle,
id: config.siteUrl,
link: config.siteUrl,
image: `${config.siteUrl}/image.png`,
favicon: `${config.siteUrl}/favicon.ico`,
copyright: `© ${new Date().getFullYear()} ${
config.title
}. All rights reserved.`,
feedLinks: {
atom: `${config.siteUrl}/atom.xml`,
},
author: {
name: 'Daniel Tseng',
email: 's92f002@gmail.com',
link: config.siteUrl,
},
});
更多參數可以參考 官方 Example
import { Feed } from 'feed';
import config from '../config';
import markdownToHtml from './markdown';
import { getAllPosts } from './blog';
export const generateRSS = async () => {
const feed = new Feed({
// 略,請見上方
});
const posts = getAllPosts();
await Promise.all(
posts.map(async (post) => {
const validURI = `${config.siteUrl}${encodeURI(post.slug)}`;
feed.addItem({
id: validURI,
link: validURI,
title: post.frontmatter.title,
description: post.excerpt,
date: new Date(post.date),
image: post.ogImageUrl,
content: await markdownToHtml(post.content || ''),
author: [
{
name: 'Daniel Tseng',
email: 's92f002@gmail.com',
link: config.siteUrl,
},
],
});
})
);
};
在輸出的時候,需要注意的是,那些會訂閱 RSS 的 service 會 default 找哪些路徑?
靜態檔案解決方案,根據參考大家常放的檔案路徑,做了以下歸納:
/atom.xml
/rss.xml
/rss/atom.xml
因此我選擇這樣做:
import fs from 'fs';
import { Feed } from 'feed';
import config from '../config';
import { getAllPosts } from './blog';
export const generateRSS = async () => {
const feed = new Feed({
// 略
});
// 略
const invalidCharInXMLSpecRegexp =
// eslint-disable-next-line no-control-regex
/((?:[\0-\x08\x0B\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/g;
fs.writeFileSync(
'./public/rss.xml',
feed.atom1().replace(invalidCharInXMLSpecRegexp, '')
);
fs.writeFileSync(
'./public/atom.xml',
feed.atom1().replace(invalidCharInXMLSpecRegexp, '')
);
};
至於為什麼選擇 Atom 1.0 格式而不是 RSS 2.0 格式, 主要是因為 RSS 2.0 有比較多的限制,相對之下,比較晚誕生的 Atom 1.0 格式是主流,(這點如果有講錯歡迎指教,我有看到我會馬上修正)。
因此,這邊直接選擇只支援 Atom 1.0 格式。
在 Next.js 的流程中,有幾個基本但是重要的 function:
在官方的文件也清楚的表明了:
getStaticProps
(Static Generation): Fetch data at build time.
也就是在 build time 的時候,Next.js 一定會執行這個 function。我們便可以把產生 RSS 的動作放進這個 function 裡面,如下:
// pages/index.js
import { generateRSS } from '../utils/rss';
export async function getStaticProps() {
const posts = getAllPosts();
const postPromises = posts.map(async (post) => ({
...post,
html: await markdownToHtml(post.content || ''),
}));
await generateRSS(); // 主要是這一行
return {
props: {
posts: await Promise.all(postPromises),
},
};
}
如此一來就可以在開發階段看到 RSS feed 產生了。
搬移到 Next.js 之後,寫起來的體驗,更接近原生開發 React 的感覺,想要什麼功能自己實作,彈性很大很自由,而不像是 Gatsby 整套就是為了 SSG 而生的解決方案。在 Gatsby 遇到很多問題都有現成的套件,裝上去馬上就可以用。
然而有了彈性就代表需要選擇,為什麼選擇 A 而不是選擇 B,每一步都需要思考。坦白說實作這短短幾行 code 花不了多少時間,但是背後的選擇我想才是關鍵,藉由這篇文章記錄思考的過程。
<!-- more -->
本篇文章並非詳細介紹 Gatsby,如想了解更多,請至官方網站。
Hexo 是一套歷史已久的 static site generator,此部落格原本就是利用 Hexo 架起來的。然而,後起之秀 Gatsby 採用著不同的資料流處理方式,加上優異的架構,最重要的是該團隊獲得資金並成立公司的新聞一出,我對於這個專案後續維護更加看好,就決定搬家到 Gatsby。
此處列出一些在搬部落格過程中需要注意的點,並作大綱的列點,可以選擇自己喜歡的部份研究。
Hexo 原生就提供產生文章的 CLI API,因此使用上蠻方便,只要一個指令就可以自動產生 Markdown 檔。而 Gatsby 就得要自行選擇文檔的類型,因為當初選用 Hexo 就是它可以利用 Markdown 寫部落格,所以轉換到 Gatsby 自然就沿用 Markdown 了。
在部落格架構設計上,我把所有原始檔放在 ./content/_posts
底下。但想讓 Gatsby 知道文件位置,就得利用 gatsby-source-filesystem
這個 plugin,如下設定:
// gatsby-config.js
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'posts', // Name this source
path: path.resolve('./content/_posts'),
},
},
];
ps. Gatsby 的範例都利用 ``(backtick)
包住字串,但我還是習慣使用 single quote。
把原始 file 讀進來之後,Gatsby 也提供了專為 Markdown 檔處理的 plugin,名為 gatsby-transformer-remark
。
經過這個 plugin 處理過的資料,會被轉成 Gatsby 架構下的 node
,此 node 的 type 為 MarkdownRemark
,該 node 會增加許多有用資訊,例如:html
, headings
, excerpt
等等,省下許多 parse Markdown 檔的功夫。
// gatsby-config.js
plugins: ['gatsby-transformer-remark'];
後續還會介紹更多這個 plugin 的設定方式,也可直接參考 repo
當初利用 Hexo 建立出來的 URL 為類似 /2017/07/03/利用-Hub-來自動發-GitHub-PR/
這樣的 pattern,
為了不 breaking 既有 URL,我選擇將新版的 URL 設計和當初一樣。
此時就得讓 Gatsby 讀懂日期,這邊當初猶豫是要從 Markdown 裡面拿或者是從檔名拿日期。
將日期寫在檔名有多一個好處,就是可以利用檔名來做排序,基於這樣就決定將原本的所有 Markdown 檔都加上日期,修改過後的檔名為 2017-07-03-利用-Hub-來自動發-GitHub-PR.md
。
Gatsby 提供 onCreateNode
此 API 操作,原理上是在 Gatsby 要 createNode 時,在該 node 上面加上一些我們用的到的資訊。
我在此處便是利用 RegExp 將檔名給 parse 出來,並在 node 上面新增一個 field 叫做 slug
,也就是 URL 上面除了 domain 後面的 pathname
部分。
// gatsby-node.js
const BLOG_POST_FILENAME_REGEX = /([0-9]+)-([0-9]+)-([0-9]+)-(.+)\.md$/;
exports.onCreateNode = ({ node, getNode, boundActionCreators }) => {
const { createNodeField } = boundActionCreators;
// 確定是 Markdown 檔再做操作
if (node.internal.type === 'MarkdownRemark') {
const { relativePath } = getNode(node.parent);
const match = BLOG_POST_FILENAME_REGEX.exec(relativePath);
const year = match[1];
const month = match[2];
const day = match[3];
const filename = match[4];
// 組出我們要的 slug pattern
const slug = `/${year}/${month}/${day}/${filename}/`;
// 在該 node 上面多增加一個欄位,未來可以 Query
createNodeField({
node,
name: 'slug',
value: slug,
});
}
};
有了前述的 node 資料之後,我們在 Gatsby 創造這些 page,讓使用者在進入 /YYYY/MM/DD/<blog-title>/
這樣的 URL 可以順利取得文章內容。
Gatsby 提供的另一個 API 叫做 createPages
,此 API 就是你所有的 page 都需要透過該 API 來創造,如此一來 Gatsby 才可以正確的回傳該頁面。
// gatsby-node.js
exports.createPages = async ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
const allMarkdown = await graphql(`
{
allMarkdownRemark {
edges {
node {
fields {
slug # 此處的 slug 就是上述 onCreateNode 時候加上去的
}
}
}
}
}
`);
allMarkdown.data.allMarkdownRemark.edges.map(({ node }) => {
const { slug } = node.fields;
createPage({
path: slug, // 告訴 Gatsby 這個 URL path
// 這邊選用的 component,就可以在 pageQuery 使用 context 所傳進去的 slug
component: path.resolve('./src/templates/post.js'),
layout: 'index',
context: {
// Data passed to context is available in page queries as GraphQL variables.
slug,
},
});
});
};
在 Gatsby 的架構內,每一個需要不同 URL 的頁面都需要自己創造一個 page,例如:首頁、每一篇文章、archive 頁面等等,底下列出此部落格需要的 URL:
/
: index page/YYYY/MM/DD/<blog-title>/
: article page/archives
: archives page/tags/<tag>
: tags page因為 Gatsby 底下會自動的把 ./src/pages/*.js
自動 create page,所以我們在設計首頁的時候,就是去修改 ./src/pages/index.js
此頁即可。
該頁最主要就是把所有的文章時間、title 等資料拿出來,GraphQL 的 query 如下:
// src/pages/index.js
export const pageQuery = graphql`
query BlogIndexQuery {
# query 依照 date 排序
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
edges {
node {
fields {
slug
}
html # 需要把 html 拿回來 render
frontmatter {
title
# 可以在此處就把 date format 成我們要的格式
date(formatString: "MMM DD, YYYY")
tags
}
}
}
}
}
`;
上述的 html
也可以用 excerpt
去拿,但我選擇拿 html 再選擇我要的顯示在首頁部分內容。
該頁面與 index page 大同小異,該 page 位於 ./src/pages/archives.js
,唯一不同的是因為 archives page 有一種整理的效果,因此我們只要拿 title、date、tags 即可。
tags page 顧名思義就是每篇文章我會給一些 tag,寫在 Markdown 最上方,所以可以利用套件幫我們產生的 frontmatter 拿到每一篇的 tags 資料,但要怎樣擁有不同頁的 tag 呢?
此時就必須在 createPage 的時候就將所有的 tag 拿出來,然後產生每一個 tag 的 page。
//
exports.createPages = async ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators;
const allMarkdown = await graphql(`
{
allMarkdownRemark {
edges {
node {
frontmatter {
tags
}
}
}
}
}
`);
allMarkdown.data.allMarkdownRemark.edges.map(({ node }) => {
const { tags } = node.frontmatter;
tags.map((tag) => {
createPage({
path: `/tags/${tag}`, // 此處創造 tag URL
component: path.resolve('./src/templates/tags.js'), // 選擇用 tags template
layout: 'index',
context: {
tag, // 傳進去讓 tags template 的 pageQuery 可以使用該 tag 去 query
},
});
});
});
};
創造完每一頁 tag 的 page 之後,我們來看看 tags template 應該怎樣寫。
// ./src/templates/tags.js
export const pageQuery = graphql`
query PostByTag($tag: String!) {
allMarkdownRemark(
filter: { frontmatter: { tags: { in: [$tag] } } }
sort: { fields: [frontmatter___date], order: DESC }
) {
edges {
node {
id
html
fields {
slug
}
frontmatter {
title
date(formatString: "MMM DD, YYYY")
tags
}
}
}
}
}
`;
最關鍵的就是 Query 的參數了,利用 createPage 傳進來的 $tag
,我們在 allMarkdownRemark
的參數就利用到了 filter
、sort
等使用情境,拿回所有擁有這個 tag 的文章資料再去做 render。
當初設定 Favicon 也遇到一些問題,本來以為放在 /static/favicon.ico
就可以讓瀏覽器抓到,可是怎麼嘗試都不成功。
後來請教同事 @jigsawye 才發現原來是要利用 loader,也就是直接將 favicon file 給 import
到環境內,再利用 react-helmet
做操作。
// ./src/layouts/Head.js
import Helmet from 'react-helmet';
import Favicon from '../assets/favicon.png';
const Head = ({ data }) => (
<Helmet>
<link rel="icon" type="image/png" sizes="30x30" href={Favicon} />
</Helmet>
);
export default Head;
推薦幾個在架設這個部落格用到的 Gatsby plugin
gatsby-plugin-react-next
: v1 還是內建 react v15,所以需要裝這套gatsby-plugin-react-helmet
: 操作該頁面的 HTML meta 資料gatsby-remark-prismjs
: 可以將 Markdown 內的程式碼做上色gatsby-remark-autolink-headers
: 可以讓 Markdown 的 Heading 都自動加上 id,瀏覽更方便gatsby-plugin-sitemap
:自動產生 Sitemapgatsby-plugin-feed
:自動產生 RSS feed,production 才會輸出,輸出檔名為 /rss.xml
gatsby-plugin-google-analytics
: 可以載入 GA 使用disqus-react
: Disqus 官方的 React 套件這次的搬家花了不少心力,其中最多的時間其實是在重新架構整個 layout,而搬家過後因為 client side render,整體速度提昇不少,也對於 Gatsby 的生態圈蠻看好,希望可以持續發展。
雖然現在市面上有很多撰寫文章的平台(例如:Medium),不過這種一手打造的部落格,擁有自己 content 的感覺,或許就是工程師的浪漫吧。
]]>因為工作和平時習慣的 Git 協作平台是 GitHub,因此常常發 pull request (以下簡稱 PR) 到 GitHub 上面,而這個流程對我來說不太順手,因此便想要利用更自動化的方式來做掉。
<!-- more -->
一般來說發 GitHub 的 PR 流程如下:
這樣的流程除了需要在瀏覽器 和 terminal 間切換外,最麻煩的是需要操作滑鼠多點好幾下,整個流程會將思緒打斷。
這是一套 GitHub 官方所維護的套件,用來加強原生 git 的功能,而這個套件剛好提供了
$ hub pull-request
的功能,可以在該 local branch 發 PR。
使用 Hub 套件發 PR 流程如下:
使用 Hub 後流程已改善,把在瀏覽器上的操作帶回到 terminal,但整體使用卻依舊不便,因此我便思考能不能一鍵發 PR 到 GitHub 上面,而最後找到了下面這個解決方案,雖非最完善,但方便不少。
$ vim ~/.oh-my-zsh/lib/aliases.zsh (in my case)
$ alias pr='hub pull-request -m "$(git reflog -1 | sed '\''s/^.*: //'\'')" | xargs open'
$ source ~/.zshrc (in my case)
# local commit
$ git push origin <branch-name> && pr
藉由 Hub 的 pull-request 加上自己所寫的 alias, 此 alias 會將 commit message 的第一行拿出來自動當做 PR title, 且會自動打開瀏覽器,可以再度進行檢視或做最後的修正。
能夠利用小技巧將繁瑣重複的事情給自動化,是每個人都很樂見的,在此分享,希望能幫助到可能也需要的你。
]]>然而一些新接觸的開發者,在流程控制上會聽聞 Promise 的好處進而開始使用,但較早期的套件會遵循著 node.js style 的 Callback;因此本篇文章將會介紹如何將這類 Callback 利用套件轉成 Promise。
<!-- more -->
在 JS 的流程處理上,有許多人一定會遇見 Callback function,而 node.js 有獨特的規範,遵循著這類規範會讓更多開發者受益,這類的 Callback 被稱為 Error-First Callbacks
。
fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err; // 此處為 Node.js Style Callback
console.log(data);
});
將會是把第三方套件 Node.js Style Callback 轉成 Promise,接著就可以 .then()
或者是利用 async
await
去處理流程了。值得一提此套件利用的是 any-promise
,理論上你可以隨時的設置你想要的 Promise 實作。
因為 README 上面寫的蠻不清楚,以下列出幾種狀況,也推薦看套件的測試檔案範例:
範例一:非同步 function 直接接收 cb
function fn(cb) {
// 處理複雜流程
cb(null, true);
}
const p = thenify(fn); // p is a promise now.
p().then(val => console.log(val)); // true
範例二:非同步 function 直接接收 cb,該 cb 回傳多個參數
function fn(cb) {
// 處理複雜流程
cb(null, 1, 2, 3); // 除了第一個 error msg 外,回傳多個值
}
const p = thenify(fn); // p is a promise now.
p().then(val => console.log(val)); // [1, 2, 3],會轉成 array
範例三:非同步 function 直接接收多個參數及 cb,該 cb 亦接受多個參數
此範例為最常被使用功能,關鍵點為 cb 位在 fn 的最後一個參數,且此 cb 遵循著上述提到的 Node.js Style Callback。
function fn(a, b, c, cb) {
// 處理複雜流程
cb(null, a, b); // 最後回傳 error msg(null) 和 a, b
}
const p = thenify(fn); // p is a promise now.
p(1, 2, 3).then(values => console.log(values)); // [1, 2],callback 回傳的值,一樣會包成 array
隨著 node 核心原生支援的語法越來越多,選擇利用 babel 搭配就可以在此時享受到 Promise 甚至是 async, await 語法帶來的好處,然而較早期的套件甚至不預設回傳 Promise,此時便需要一些工具的幫助。
希望這篇文章能幫助到那些一接觸學習 JS 流程控制就享受到 Promise 好處,但實務上卻不太理解如何處理原生或第三方套件這類 Callback function 的開發者。
let
和 const
,這篇文章將會談談 var
、let
和 const
之間的差異。
<!-- more -->
如圖所見,JS 內變數的宣告總共會經歷三個階段
var x; // Declaration, Initialization
x = 'Hello World'; // Assignment
// all in one line
var y = 'Hello World';
var
廣泛被使用,直到 ES6 推出後才有了其他的替代方式,而如果 var 被宣告在函數內,則沒有辦法在函數外部取得該變數。
function someFunc() {
var innerVar = 'hi';
return innerVar;
}
console.log(innerVar); // ReferenceError: innerVar is not defined
在 ES6 後,出現了 let
的方式宣告變數,它和 var
有共同的作用,便是在函數中封閉,額外的效果是在 block statement 內也是封閉,作用域變得更小範圍,可以減少錯誤的發生。
let x; // Declaration, Initialization
x = 'Hello World'; // Assignment
// all in one line
let y = 'Hello World';
if (true) {
let innerLet = 'hi';
} else {
// do nothing
}
console.log(innerLet); // ReferenceError: innerLet is not defined
上述例子代表 innerLet 只能在 block statement 內存活,跳出這個 block statement 就無法取得,我們將 let 換成 var 看看:
if (true) {
var innerLet = 'hi';
} else {
// do nothing
}
console.log(innerLet); // hi
由上述可以清楚看到將 let
宣告的方式改成 var
則可以在 block statement 之外取得該變數,這就是 let
相對於 var
作用域更小的關係。
const
顧名思義就是 constant 的縮寫,ES6 新的語法,代表說只要當宣告過後,該賦予的值就不能被更動,更嚴格來說,是該變數指向的記憶體位址不能被更動。因此,當利用 const 宣告了 Array
或是 Object
,只要不重新賦予值,而是更動 array 或 object 內部狀況,是可以被接受的操作方式,參考以下例子:
const PI = 3.14159;
PI = 3.1415926; // TypeError: Assignment to constant variable.
const arr = [];
arr.push(PI); // [ 3.14159 ];
const obj = {};
obj.pi = PI; // { pi: 3.14159 };
arr = new Array(); // TypeError: Assignment to constant variable.
obj = new Object(); // TypeError: Assignment to constant variable.
在利用 var
, let
, const
宣告變數的時候,變數都會自動的做 Hoisting
,也就是被抬升到該作用域的最高處,但是抬升過後,var
會對於變數賦予值 undefined
,而 let
, const
一樣也會被抬升,但是沒有初始值,因此會有 ReferenceError 發生。
console.log(hello); // undefined
var hello;
hello = "I'm a variable";
console.log(world); // ReferenceError: world is not defined
let world;
world = "I'm a variable";
在 ES6 推出後,應該盡可能的利用 let
和 const
去宣告變數,因為它們的限制更高,會減少出錯的機會。若有使用 ESLint,甚至有條件限制使用 var
。若是情況一定需要使用 var
,也應該在作用域的最一開始就宣告變數,以免發生 hoisting 導致無法預期的結果出現。
babel 是現代前端工程師都不陌生的工具,它可以將 ES6 (+) 的語法轉換為 ES5 甚至是更低的版本,利用 babel 可以讓開發者即刻享受到 ES6 語法所帶來的便利性。 然而許多 babel 相關的名詞卻常常困擾想要入門的新手們,因此利用本文簡介那些與 babel 相關的名詞,以 babel 6 為主。
本篇將會會介紹到:
<!-- more -->
本文用意並非完善的介紹整個 babel 生態系,而是作者整理自己常見且容易搞錯的 babel 相關名詞。
這是 babel
這個工具需要用到的設定檔,以下所介紹到的各項都需要該設定檔讓 babel 有轉換的依據。
方法一:
直接寫獨立的 .babelrc
檔,一個基本的格式如下:
{
"presets": ["es2015"]
}
方法二:
直接寫進 package.json
檔
{
"name": "my-package",
"version": "1.0.0",
"babel": {
// your babel config here
}
}
註:方法二雖然可以這樣寫,但不建議使用,因為在 react-native 會出現已知的問題。
利用 npm 安裝 babel-cli 將會同時註冊 babel
和 babel-node
兩個最常使用的指令。
$ npm install babel-cli --save-dev
利用上述指令安裝在 project 的目錄底下。
可以直接在終端機利用 babel
指令做 ES6 的語法轉換,
常用的情景為將 src
資料夾 build 成 lib
資料夾,
身為 library 開發者,以不要預設使用者有 ES6 的環境為佳,
上述簡單的指令如下:
$ babel src -d lib
若需更多參數,請參考官方教學。
可以利用 babel-node myEs6.js
直接運行 ES6 的 code,
當然需要 .babelrc 檔還有相關的 presets 或是 plugins 做為 babel 轉換的依據。
而 babel-node
執行的時候會預設載入 babel-polyfill
使用,
因此會佔大量的記憶體空間,官方不建議在 production 環境使用。
presets 和 plugins 這兩個 key 在 .babelrc
檔內會很常看到,
其中的差異便是一個 preset 可以包含其他不同 presets 或是不同的 plugins。
例如 babel-preset-es2015
當中包含了
transform-es2015-arrow-functions
transform-es2015-block-scoped-functions
等 21 個不同的 plugins。
順序問題
babel 在執行 transform 的過程,會 plugins 先載入,且按照由上往下(由左向右)的順序載入, 但是要注意的事情,presets 會在 plugins 之後,然後載入的順序是由下往上(由右向左)的反向順序。
babel 針對 stage 有實作幾個不同的 presets,包含了
stage 數字越大的 preset 所包含的 plugins 代表即將進入 ECMA262 standard, TC39 Process, 官方預設的範例 stage 0 的 preset 使用就是因為其包含了 stage 1, 2, 3 的 presets, 而許多開發者直接用 stage 0,會把全部 stage 都載入,建議花時間了解各個 preset-stage 分別載入哪些 plugins 為佳。
stage 是會隨著時間演進,在不同的階段所看到的 stage 內容可能都不一樣。
當載入 babel-register
後,其接下來的 es6 語法都可以被設定的 .babelrc 做轉換,
載入的方式有兩種:
方法一:
額外建立一個進入點檔案,由於在這個進入點 node 並不知道 es6 語法,
因此於此需要利用 require('babel-register')
的方式載入
進入點檔名以 entry.js
為例:
require('babel-register');
require('./yourEs6Index');
因此未來執行 node entry.js
就可以利用 babel-register
動態載入的方式進行轉換語法。
方法二:
在終端機執行 node
的時候,直接利用 -r
參數帶入 babel-register
,-r
等於 --require
代表 module to preload
$ node -r babel-register yourEs6Index.js
什麼是 polyfill
?
wiki: In web development, a polyfill is code that implements a feature on web browsers that do not support the feature.
因此 babel-polyfill
顧名思義就是 babel 幫我們做了一些現階段還沒有被各家瀏覽器通用支援的 feature,好讓我們在現階段就可以利用一些未來原生的語法,例如:Promise
, Array.from
, Object.assign
, Array.includes
等。
像是 Chrome 對於 es6 的支援度一直以來都蠻高的,但是並非每家瀏覽器廠商都能支援,因此需要有 polyfill。
babel-node
當利用 babel-node 去運行 js 檔的時候,會預設載入 babel-polyfill
,
因此你即可利用 babel-node run 一個帶有 promise 的 js 檔,
而不需再另外 require bluebird
等套件。
babel-polyfill
主要 includes 了 regenerator runtime 和 core-js。
regenerator runtime
就是將 generator/async 轉換成 es5 語法,而 core-js
是 Modular standard library for JavaScript 集合,詳細請參閱連結。
在做轉換的時候,若利用 babel-polyfill
會做 global scope,所以當你今天是要做 lib/tool 模式,沒辦法控制你的運行環境,則不適合利用 babel-polyfill,需要用 babel-plugin-transform-runtime
為佳。
babel-plugin-transform-runtime
會把多個檔案 reference 到 babel-runtime
這個 package,因此當你使用 transform-runtime
就一定要裝 babel-runtime
babel-plugin-transform-runtime
的轉換機制也是 alias 到 core-js
,就和 babel-polyfill
一樣,所以不用再 require babel-polyfill
$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save babel-runtime
loader
是 webpack
用來載入各種不同類型檔案的套件,而 babel-loader
讓 webpack 可以用來執行 babel 轉換的的一種套件。
利用 babel-loader
可以利用 webpack 打包時候同時進行 babel 的轉換,以下是簡單範例檔:
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel'],
exclude: /node_modules/,
},
],
},
因為 babel-loader
的速度很慢,官方建議把 node_modules
exclude 掉。
ESLint
堪稱是近代偉大的 linter 發明之一,它可以讓使用者高度客製化的 parser 語法,而目前原生的 ESLint 支援的語法有 ES6/ES7
, JSX
, and object rest/spread
,如果你用到的更多 babel 語法則需要 babel-eslint 來幫忙。
$ npm install eslint@3.x babel-eslint@6 --save-dev
.eslintrc 範例檔
{
"parser": "babel-eslint",
"rules": {
"strict": 0
}
}
當然 babel 的套件不僅僅如此,還有 babelify, babili 等許多相關工具尚未有時間介紹,本篇所提及的介紹希望能對於部分開發者有幫助。 若有不清楚或者會誤導讀者的方向,還請不吝指教。
]]><!-- more -->
本文並非會有教學範例檔,僅會針對 FB 提供的 Graph API 做簡單的範例。
此處的範例會利用 ES6 的 template strings
語法。
最近 FB 推出了可以回覆他人的功能,因此留言有可能會有巢狀情形,但可以觀察到的是,目前 FB 的機制就是至多一層的回覆。因此簡單的架構如下:
comment 1
- reply_comment 1
- reply_comment 2
comment 2
- reply_comment 1
_ reply_comment 2
...
...
`http://graph.facebook.com/?id=${URL}`;
GET 'http://graph.facebook.com/?id=http://www.google.com'
{
id: "http://www.google.com",
shares: 31205003,
comments: 1323
}
因此可以知道 www.google.com 在 FB Graph API 有 1323 筆留言數。
Graph API 在留言版其實是公開,只要給定 URL 就可以拿到該網址的留言, 這邊要注意的是需要確定該 URL 是 OG:URL 的參數給 FB 才拿的到。
`http://graph.facebook.com/comments?id=${URL}&limit=${comments}&filter=stream`;
底下是 return sample json
{
created_time: "2012-04-16T12:45:03+0000",
from: {
name: "Sunil Maheshwari",
id: "100000525493028"
},
message: "hello",
can_remove: false,
like_count: 0,
user_likes: false,
id: "381702034999_21746175"
}
若此處沒有加上 &limit
的話,會拿到比較少的數量。
加上 &filter=stream
參數,則會一併將回覆狀態的留言拿回來。
因為在此記錄部落格使用,在實作上並不會加上 &filter=stream
這個參數,以免拿第二層的時候重複還需要做額外處理。
在拿第一層的時候,因為是 public API 狀態,因此不需要 token,且網址利用 http 就可以。 但在拿第二層留言,就需要 https + access_token 狀態。
最簡單取得 access_token 方式是到 FB graph explorer 申請 access_token 來實作。
接著需要串接的 API 格式為:
`https://graph.facebook.com/${id}/comments?access_token=${token}`;
上述 id 就是第一層拿回來的 json 格式內的 id, token 則是 access_token。
在實作上因為無法透過第一層的 API 得到是否有無第二層留言,因此若要完整的拿取全部的留言,則需要將全部的 id 跑過一次才可以得到完整的結果。
<!-- more -->
先把會用到的全部指令列在這邊,下面會分項目做解釋
npm install -g hubot coffee-script yo generator-hubot
mkdir hubot
cd hubot
yo hubot
npm install hubot-slack --save
git init
git add .
git commit -m "Initial commit"
GET HUBOT_SLACK_TOKEN // https://my.slack.com/services/new/hubot
Install the Heroku Toolbelt // https://toolbelt.heroku.com/
heroku create "project-name"
heroku config:add HEROKU_URL=https://"project-name".herokuapp.com
heroku config:add HUBOT_SLACK_TOKEN="xoxb-********-********"
git push heroku master
npm install -g hubot coffee-script yo generator-hubot
hubot 會用到 coffee-script 和 yo 去產生整個專案,所以需要安裝在全域 -g
mkdir hubot
cd hubot
此處創建資料夾可建立自己的名字
yo hubot
這邊會問你一些問題,記得在 adapter
打 slack
此舉會讓官方產生預設 heroku 的 Procfile
裡面多了這一行
web: bin/hubot -a slack
這是為了讓 heroku 啟動時候知道怎樣運作的指令
這是 slack 官方維護的套件,穩定度應該頗高,安裝後一併做一個專案 git 初始化並 commit
npm install hubot-slack --save
git init
git add .
git commit -m "Initial commit"
到此處建立新的 hubot service 若有多個 team 帳號,請記得確定你登入的帳號是在哪一個 team 底下
取一個 hubot 要在 slack 內的名字,下圖用 hubot
做示範
接著下一步就可以取得 HUBOT_SLACK_TOKEN,記得把這個 TOKEN 記下來
HUBOT_SLACK_TOKEN=xoxb-********-******** ./bin/hubot --adapter slack
本機端記得先安裝 redis,hubot 會用到, 順利的話就可以在 slack 啟動 hubot 囉!
可以打開 hubot 跟它對話,
如果看到 PONG
則代表成功
slack 官方推薦的平台是 heroku,這邊介紹如何運作, 要記得的原理就是其實上述已經在本機端可以運行了, 這個步驟就是將 server 放到 heroku 上面去跑而已。
首先安裝 Heroku Toolbelt,這部份請看 heroku 官方教學
heroku create "project-name"
heroku config:add HEROKU_URL=https://"project-name".herokuapp.com
heroku config:add HUBOT_SLACK_TOKEN="xoxb-********-********"
git push heroku master
這個 project-name 其實就是未來你的 herokuapp 的 URL, 不能和其他人重複,因此名字可以想自己容易記得即可。
heroku config:add
是將一些變數丟給遠端的 heroku 知道,
讓他可以抓到 HEROKU_URL
HUBOT_SLACK_TOKEN
等,
接著就是將 local 這個 git repo push 到 heroku 上面。
等到 heroku 跑完後,hubot 的 server 已經跑在 heroku 上面了, 而免費的 heroku dyno 有每 24 小時一定要停機 6 小時的規定, 因此若要拿來當正式的 bot 服務, 建議自己架 server 或者就付費買 heroku 的服務。
另外,在 hubot 專案底下的 hubot-heroku-keepalive
就是會固定戳 heroku,避免 30 分鐘後這個 dyno 就休息了。
在 heroku 上面有免費的 redis add-on 可以用, 每個月有 30mb 的免費使用量。
有任何問題,歡迎留言討論。
]]><!-- more -->
以下的 localhost 環境皆為 Mac 10.10,express.js, 而 deploy 的環境皆為 Linux 14.04 環境。
NODE_ENV 是運行 Node.js 重要的變數,在本機開發的時候預設為 NODE_ENV=development
。
在執行 app.js
(aka bin/www
) 時,選擇需要的變數 (development || production),若要運行為 production 環境指令為
$ NODE_ENV=production node bin/www
當然這個 NODE_ENV
值可以直接 export 在你所運行的環境當中,
$ export NODE_ENV=production
$ node bin/www
而若不想每次開啟 shell 都要重新 export 一次,可以將 export 指令寫進 ~/.bashrc
內,之後開啟 shell 就會設定 NODE_ENV=production 了!
Q:那如何在 express.js 框架下的 app.js 拿到環境變數呢? A:只要利用 express 框架為我們做好的 API 如下:
// app.js
app.get('env');
即可得到 NODE_ENV
值。
關於 config 檔的設定,每個人有不同的習慣,我介紹我常用的 config 檔設定方式。
// config.js
var config = {
development: {
port: 3000,
// anything else
},
production: {
port: 3001,
// anything else
},
};
module.exports = config;
// app.js
var config = require('./config.js')[app.get('env')];
var port = config.port; // production mode will return 3001
這樣設定 config 檔後,未來就可以利用 NODE_ENV 的不同來判斷應該要連接的資料,例如在 dev DB 和 production DB 的分開等等情況。
在樣板引擎方面我習慣 ejs,而 ejs 會在 production
的狀態下把 view template 快取起來,加速 render 的時間,因此需要做 restart node server 的情況才可以解決快取問題。
ps. 或許這個問題有其他更好解法,非常歡迎協助補充。
因為曾經撞過這些雷,單純就是經驗不足,以致於值得記錄一下 XD
直接提供 debug 經驗談:
相對路徑
和絕對路徑
在環境的問題#!/usr/bin/env node
// 讓環境找得到 node 去執行它Q:在本機端匯出和匯入都好好的,不知道為什麼到遠端的環境就沒有辦法匯入? A:原因是語系問題,記得在 DB 匯入前先執行 export 或寫入 bashrc 檔
export LC_ALL="en_US.UTF-8"
花時間經歷過的才會印象深刻,上述這幾點都是我利用時間所換來的,將此記錄在這邊,也希望能或多或少幫助到一些人:)
]]><!-- more -->
Pocket 的閱讀介面是它的一大優勢,而 Evernote 的搜尋功能是有目共睹的準確,我們將利用 IFTTT 這個自動化工具來實作「當我從 Pocket 封存項目後,自動儲存全文到 Evernote」。
有使用 IFTTT 的朋友應該知道說其實它有內建 Pocket 的選項,但是由於它提供的 Pocket 儲存只有所謂的 Excerpt 的功能,也就是只有部分的內容,並沒有辦法全文儲存到 Evernote 的 note 當中。
因此我們的解決步驟為:
未來就可以利用 Evernote 強大的搜尋功能來做到找到曾經閱讀封存的文章。
Pocket > Options > Privacy
把 RSS feed 設為 publicArchive feed
取得 Pocket 帳戶底下封存項目的 RSS feed link,連結應該為 http://getpocket.com/users/<你的帳號>/feed/read
fivefilters
建立 full text RSS,貼上你的 feed url 後,按下 Create feedrecipe
創建你自己的版本完成上述步驟後,即可在 Evernote 你所命名的筆記本內看到你在 pocket 所封存的項目囉。
其實在 pocket mobile app 上面,有直接儲存到 Evernote 的選項,但我閱讀文章完如果值得存下來的,我習慣直接 archive 起來。 實際使用這個 recipe 後,發現有一些網站的 full text RSS 抓的並不是很準確,速度也沒有很快,通常都要半個小時後才會在 Evernote 出現,但在網頁版並沒有存到 Evernote 的選項,在權衡下,我還是選擇使用此 recipe 來做為未來可以在 Evernote 搜尋的自動化工具。
Automate Full Text of Pocket Backup to Evernote with IFTTT and FiveFilters
]]>這篇 blog 記錄網站開發超過兩年半經驗的我,最常用到的終端機指令 (command line)。
<!-- more -->
本篇針對的讀者是 mac 新手。
建議下載 iterm2 來使用,有興趣可參考設定
前面加上錢字號($)代表此行為 command line 開始,真正在打的時候不用加入$
$ cd
: 移動 root 位置
$ cd ..
: 移動到目前所在位置的上一層
$ cd ../..
: 移動到目前所在位置的上兩層
$ pwd
: 列出目前完整路徑 --> 可以知道自己現在在哪邊,再決定要如何利用 cd
移動
$ ls
: 列出所在目錄的檔案
$ ls -a
: 列出的目錄檔案包含隱藏檔
$ ls -al
: 列出的目錄檔案包含隱藏檔 & 檔案屬性和權限
$ vi(m) **.xx
: 創建檔名為**
,附檔名為xx
的檔案 --> 之後會進入 vi(m) 文字編輯模式,推薦查閱鳥哥 vim 教學
$ mkdir ***
: 創建名稱為***
的資料夾
$ rmdir ***
: 移除名稱為***
的資料夾 --> 需確定資料夾為空
$ cp dest1 dest2
: 把 dest1 檔案複製到 dest2 的位置
$ mv dest1 dest2
: 把 dest1 檔案移動到 dest2 的位置,亦可作為變更檔名使用,例如 $ mv test.txt no-test.txt
,就可以把檔名 test 的文字檔改變成為 no-test 檔名。
$ sudo su
: 取得 root 權限
以上列出我最常用的指令,許多指令都可以帶有特殊的參數,unix base 底下的 command line 也不只這些,想要更進一步,可以再多去參考書籍或是教學。
<!-- more -->
crontab 是 Linux 內建的機制,可以根據設置的時間參數來執行例行性的工作排程。
上述這張圖可以清楚的顯示出前五項參數應該要帶進去的數字。
依序是分鐘
, 小時
, 日期
, 月份
, 星期
, command
參數為0-59
, 0-23
, 1-31
, 1-12
, 0-6
, 需要執行的command
※ 星期參數為0
代表星期日
【*】
:星號,代表任何時刻都接受的意思
【,】
:逗號,代表分隔時段。例如:30 9,17 * * * command
,代表早上 9 點半和下午五點半都執行 command。
【-】
:減號,代表一段時間範圍。例如:15 9-12 * * * command
,代表從 9 點到 12 點的每個 15 分都執行 command。
【/n】
:斜線,n 代表數字,表示每個 n 單位間隔。例如:*/5 * * * * command
,代表每隔 5 分鐘執行一次 command。
還有一些人性化的參數,一次取代全部五個數字參數
【@reboot】
:僅在開機的時候執行一次。
【@yearly】
:一年執行一次,和0 0 1 1 * command
效果一樣。
【@annually】
:(和@yearly
一樣)
【@monthly】
:一個月執行一次,和0 0 1 * * command
效果一樣。
【@weekly】
:一個星期執行一次,和0 0 * * 0 command
效果一樣。
【@daily】
:每天執行,和0 0 * * * command
效果一樣。
【@midnight】
:(和@daily
一樣)
【@hourly】
:每小時執行,和0 * * * * command
效果一樣。
crontab 是會根據不同的使用者去判定可以操作的範圍。
$ crontab -l
: 列出該使用者擁有的 crontab 指令
$ crontab -e
: 編輯該使用者的 crontab 指令
$ crontab -r
: 將使用者的 crontab 全部清除!( 小心使用 )
編輯完後就可以存檔離開,Linux 系統便會依照你設定的排程固定做事,非常方便。
※ 下達指令請用 絕對路徑 避免錯誤
$ */5 * * * * /home/ubuntu/test.sh
:每五分鐘執行一次測試 shell script
$ 0 9 * * 1-6 node /home/ubuntu/workspace/report.js
:每天早上九點(除了星期日)執行 report.js 這隻檔案
Schedule Tasks on Linux Using Crontab 鳥哥的 Linux 私房菜 例行性工作排程 (crontab)
]]><!-- more -->
一般在 Sublime 和 iTerm 之間切換,我都是利用 cmd+tab
來做切換,但是這樣的使用情境,如果在筆電上開發,則會在執行 iTerm 的時候遮到 Sublime 的內容。如下圖所示:
讓我們更改 iTerm 的視窗配置,來改善這樣的情況!
根據上圖,依序找到 Profiles --> Window --> Style: Bottom of screen 調整完後視窗的高度會根據設定的 Rows 高度來決定。
根據個人習慣,在不與 Sublime 相關的快捷鍵衝突,我建議採用 cmd+.
來啟動 iTerm。
※ 記得重新啟動 iTerms 來檢視設定的效果。
做完上面的設定就大功告成了!
未來在編輯的時候,就可以利用 cmd+.
來啟動&關閉終端機,操作感覺接近是內建在 Sublime 的環境。
如下圖所示:
Enjoy!
]]><!-- more -->
左邊為本篇所採用的縮寫,右邊則為鍵盤上面的標示
「基礎模式」介紹非 sublime 專用的快捷鍵,是一般使用者都可以快速上手的部分,想要看進階的可以跳過這部份。
快速開啟整個資料夾(專案)
關閉視窗分頁
開新分頁
重新開啟剛剛關閉的分頁
貼上時,符合縮排
以下介紹 sublime 的畫面配置,常常因為編輯情境的所需,利用快捷鍵讓自己的畫面配置更加有彈性。
分割視窗,讓你的編輯範圍有多個 panel。
常用為cmd + option + 1
和cmd + option + 2
之間切換。
使用情境:左邊.html 右邊.css,編輯起來快速又方便。
建議:利用空白鍵右邊的兩個連續按鈕搭配數字。
關閉左側資料夾目錄,讓畫面變得更寬敞。 這是我非常使用的一個快捷鍵,可以讓編輯的區域變得更大。
進入 zen 狀態,單份文件變成全螢幕,且左邊會自動縮排。
使用情境:當不常需要切換檔案時,此模式可以專注在單一檔案上,打這篇 blog 時我便這樣使用。
建議:快捷鍵不好記,可以點選View --> Enter Distraction Free Mode
底下介紹的部份,回到 sublime text 編輯器本身,因為重點在編輯部分,因此在此將「選取」特別整理成一區。
快速選取一範圍內的字串,連按d
的話會選取整份文件內相同的字串。
當選取完後,可以直接打字,因此就可以將整份文件的字串全部改成新字串。
選取游標在內的一行,連按l
的話會往下選取下面的行數。
此功能常與上述cmd + l
配合,當選取多行後,按下cmd + shift + l
,則會在多行的情況結尾出現游標,可以做多行編輯。
當按住option
後,搭配滑鼠拖拉
便可以一次選取多行,並且產生游標。
注意:拖曳的時候,滑鼠必須是由上到下垂直的選取狀態
按住cmd
後,利用滑鼠在文件內點選,便可以在任何位置新增游標,產生多選狀態做編輯。
讓你的游標可以快速的回到該行的最前面或是最後面。
每按一次會選擇一個字元,可以更加精準的選取自己要的部份。
從游標所在處,往前選取或者往後選取該行到底。
在 sublime 裡面尋找的功能做的非常強大,不論是文件內、或是文件名稱都可以快速找到。 底下將會利用 GoTo Anything 這個強大的內建功能來實作。
利用cmd + p
,之後等視窗出現後,即可輸入你要找的檔名,按下 enter 即可開啟。
此功能相同於control + g
,可以快速的跳到你指定的行數。
此功能相同於cmd + r
,可以快速跳到定義的 function
建議:若是知道要找 function,建議使用這個而非使用cmd + f
此功能可以快速找到文件內的關鍵字。
個人比較少用這個功能,利用cmd + f
時,可以持續按 enter 找到目標。
全文搜尋,可以找出「整個 project」內的關鍵字。 在 Find Result 內,點選兩下,便可以跳到該文件,這是我覺得最實用的部份。
將選取起來的行,整段往上或往下移動。 使用情境:當幾行 code 需要移動不算太大範圍的時候,可以使用這個快捷鍵,而不用剪下再貼上。
將該行註解。
個人建議:搭配cmd + l(連按)
可以選取多行,一次註解起來。
<!-- more -->
本篇要做 nginx 這套 web server 的設定檔更改,來達到雖然存取靜態頁面,卻可以利用 mydomain.com/user 的 URL 來拿到所要的靜態頁面。
其實是我單純是因為不想要看到.html 這樣的附檔名,這看起來不專業!
本篇環境為 ubuntu14.04 下執行。
在 nginx 下,你要將靜態檔案放在 server 哪邊都可以,在此我根據之前 apache 習慣的設定,放在/var/www
cd /var
sudo mkdir www
sudo chown -R www-data:www-data /var/www/mydomain.com
sudo chmod 755 /var/www
如此一來你便可以將整個靜態網站檔案放在/var/www/mydomain.com 目錄底下。
cd /etc/nginx/sites-enabled
sudo vim mydomain.com
修改底下內容為你要的設定
server {
listen 80;
root /var/www/mydomain;
index index.html index.htm;
server_name mydomain;
location / {
try_files $uri $uri/ $uri.html;
}
}
此設定將會當抓到$uri 時,nginx 會自動帶入$uri/
或是$uri.html
因此我們送出mydomain.com/user
,nginx 會試著搜尋mydomain.com/user/
或mydomain.com/user.html
。
sudo rm /etc/nginx/sites-enabled/default
在我設定的時候,需把 default 刪除後,才可以正常的讀取到新設定的 mydomain.com 檔,歡迎各位先進補充這點。
/etc/init.d/nginx restart
|| sudo service nginx restart
如此一來你便可以利用mydomain.com/user
純取到相關的靜態頁面了!
reference
How To Set Up nginx Virtual Hosts (Server Blocks) on Ubuntu 12.04 LTS
]]><!-- more -->
顧名思義就是別名,其語法如下,舉 mac 的 apache server 所在為 example:
alias goproject='cd /Library/WebServer/Documents/yourproject'
因此在 iTerm 打上這串後,未來便可以使用goproject
直接執行後面那串,快速又方便。
在設定完 alias,原本以為就這樣,結果發現如果重開 iTerm 後,之前設定的 alias 都不見了,原因是因為沒有真正寫入 bash 檔,因此在開啟 iTerm 的時候,並沒有被載入,所以我們要將
sudo vim /etc/bashrc
// 在bashrc檔裡面加上新的一行
alias goproject='cd /Library/WebServer/Documents/yourproject'
ps. 因為是 root 權限,存檔的時候記得要用:wq!
強制寫入。
重新開啟 iTerm 後,便可以使用 goproject 來快速進到你要的路徑,當然你也可以自行設定你要的指令。
alias // 列出所有的alias檔
unalias goproject // 把goproject這個alias刪掉
reference
]]><!-- more -->
利用顏色、深度、對比來讓使用者確切的知道他們現在處於網站的哪一個地方,了解什麼地方可以點選,以便讓他們往下繼續逛他們趕興趣的頁面或內容。頁面上可見的文字區塊大致分成三種情況,分別是可以點(clickable)連結或按鈕、被選取(chosen)項目以及其他文字(plain text)。
底下這張圖示指出,藍色的字代表可以點擊(clickable),而黑色的字代表你現在正在這個項目(chosen)內,簡單清楚的表達出區分的效果。
當你有多重選項的時候,有一個強調的選項或許對於使用者來說是一個不錯的刺激。
在這篇心理學研究指出,越少的選項可以讓使用者決定得更快,因此,試著強調某個特定的訴求吧!
在你要刪除一個動作的時候,如果視窗一直跳出「您確定要刪除嗎?」的訊息,還要你多按一個按鍵才能確定刪除,是不是有點惱人?
原作者提到他相信大多情況我們都不會誤按功能按鈕,有的話也是少數,因此利用重作(undo)的功能而非要使用者一直確認,如此一來能讓使用者更加有掌握感,當他們要進行大量的刪除動作,便可以提高效率,不小心誤刪的情況發生,還有重作的選項可以復原。
這是一個決策,純看你要針對某特定的族群或是針對全部人,有利有弊是一定的。當你針對某特定族群打廣告,勢必會壓縮到其他的群眾,進而產生排他性。這種策略的風險是你可能會削減自己短期和限制潛在的客戶。
給使用者明確的指示,讓他們知道點下這一個按鍵或已經滑動到這個頁面的最底端,接下來他該何去何從?千萬不要用那種「或許」、「應該」的字眼造成不確定感。要讓你的使用者知道下一步該怎麼走,至於要不要走,就交給使用者自己去決定了。
]]>由於近期想要把不同的 node.js 程式放在同一個 server,因此開始研究 nginx 用法,記錄下來我的實作方式。
<!-- more -->
將不同的 domain 都指向你的主機 ip,此時都會指向 HTTP 預設的 80 port,後面再用 nginx 設定由不同的 port 去處理不同的 node.js 程式。
sudo apt-get update
sudo apt-get upgrade // 確定抓到套件
sudo apt-get install nginx
sudo service nginx start
在安裝的時候,記得你如果有其他 server 在 run 必須要停掉,不然佔住 80 port 是沒有辦法裝成功 nginx 的。
會寫 node.js 應該會將 port listen 在不同的 port,注意不要用常用的那些 port 即可。例如:80(HTTP)、22(SSH)。
進到/etc/nginx/sites-enabled
,然後創建跟你 domain 一樣的檔案,記得權限要用sudo
去創
sudo vim domain1.com
檔案內容
server {
listen 80;
server_name domain1.com;
access_log /var/log/nginx/domain1.access.log;
location / {
proxy_pass http://127.0.0.1:4000/;
}
}
同理創建 domain2.com,記得 4000 port 要改成你設定的 port。
/etc/init.d/nginx restart
記得做過更動後,要重新啟動 nginx 才有用。 如此一來,不同的 domain 就可以連到同一台 server 的不同支 node.js 去執行了。
reference
]]><!-- more -->
Mac 內建 Apache,只要打開即可。
sudo apachectl start
/Library/WebServer/Documents/
此處新增名為 page.php 的檔案 application -> view -> cep(optional dir) -> page.php
如 CSS、JS 檔,習慣性會創建 assets 資料夾將他們放進去,並且放在跟 application 同階層的 dir 內,如圖。
application -> config -> routes.php
新增如下:
$route['(:any)'] = 'cep/$1';
$route['default_controller'] = "cep";
$route['404_override'] = '';
在 controllers 資料夾內,新增 php 檔,內定新的 public function,參照 welcome.php 修改即可。
新增一個 class extends CI_Controller,裡面加上 page()這個 public function。
class Cep extends CI_Controller {
public function index()
{
$this->load->view('cep/index');
}
public function page()
{
$this->load->view('cep/page');
}
}
$config['base_url'] = '/專案資料夾名字/';
$autoload['helper'] = array('url');
原本是沒有 url,加上 url。<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/bootstrap.min.css">
<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/main.css">
<script src="<?php echo base_url(); ?>assets/js/vendor/modernizr-2.6.2.min.js"></script>
如此一來便可以在http://localhost/專案資料夾/index.php/page
看到靜態頁面了。
因為每次網址上面都需要有 index.php,覺得不好看,因此兩個步驟把它改掉。
.htaccess
檔,內容如下RewriteEngine on
RewriteCond $1 !^(index\.php)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
<Directory /Library/WebServer/Documents>
Options FollowSymLinks
AllowOverride All
</Directory>
sudo apachectl restart
後記
因尚有其他專案,故本專案檔是全部在一個資料夾內,非直接在 web server 的 root 實作。
]]><!-- more -->
採用單欄式的好處是可以讓讀者自然而然的了解閱讀方向為由上而下,採用多欄式的頁面設計,會有額外增加的 risk,會讓讀者容易分心。在文章(頁面)的最後採用 call to action,引導讀者點進你想要的導向頁面。
好朋友間互相送禮,是很正常不過的對吧?在面對你的使用者也是一樣的,根據互惠原則,給禮物是一個激勵使用者的手法,讓使用者會更願意回來你的網站。
在過去,我們很容易將功能差不多的介面,在頁面上分成好多部分。當你的 UI 越分散,那麼該網站的使用者其學習曲線便越高,試著重新設計你的 UI 吧,把那些功能相近的按鈕、區塊放在一起。
列出那些曾經在 social media 討論本身產品的人,利用他們的見證會比自己在網站上面寫還來的有用。「Our customers say」會比「We are awesome」還更具有說服力。
你的 call to action 是網頁中重要的項目,尤其在很長的頁面時,不要吝嗇讓它出現超過一次。當使用者滑到頁面底端時,是該他們做決定的時候了,離開或是完成你的主要訴求。
]]>Sublime Text 2 是網頁開發者都不陌生的一套編輯器,除了單純的文字編輯外,它還有很多實用的套件,這篇來介紹我平常常用的 Sublime Text 2 套件。
<!-- more -->
Mac 環境,所以快捷鍵會介紹 command 的配置,若 windows 版本請自行查閱。
所謂 Package Control 就是 Sublime Text 2 用來裝套件的,因此在裝其他的套件之前,我們必須先來安裝 Package Control。
開啟Sublime Text 2
開啟console,快捷鍵ctrl+`
貼上以下程式碼
import urllib2,os; pf='Package Control.sublime-package'; ipp = sublime.installed_packages_path(); os.makedirs( ipp ) if not os.path.exists(ipp) else None; urllib2.install_opener( urllib2.build_opener( urllib2.ProxyHandler( ))); open( os.path.join( ipp, pf), 'wb' ).write( urllib2.urlopen( 'http://sublime.wbond.net/' +pf.replace( ' ','%20' )).read()); print( 'Please restart Sublime Text to finish installation')
程式碼可以參照官網
安裝完後,未來我們就可以使用cmd+shift+p
,打入install package
,即可啟用 Package Control,如下圖
cmd+shift+p
install package
package名稱
這是一套超過 196K 人裝的套件,如圖所示,寫 html 常常遇到不知道 close tag 在哪邊,裡用它可以清楚的將 close tag 標示出來。
另外它有一個很好的地方,就是會在每一行的前面列出來目前的 tag,不同的語言還有不同的 icon,因此可以更快的知道自己的位置。
以前它叫做 Zen coding,現在則改為 Emmet。
可以將需要重複的 html 用很簡短的方式寫出來,例如:
.container>.col-lg-4*3
若你發現按下 tab 後竟然沒有用,記得確定自己是不是在 html 文件內。
view --> syntax --> HTML(5)
在 HTML5 的文件下,!+tab
會有出現 HTML5 的 snippet 出現,非常好用!
有利用到 JSON 的人,想必一定會為了格式上面的問題而煩惱,只要裝上這個,JSON 立刻變得很好看。
裝完之後,把你要修改的 JSON 選取起來,按下快捷鍵
cmd+ctrl+j
立刻就可以把 JSON 變得很漂亮,也可以自行進去定義縮排大小。
其實這個套件全名是 flatland 才對,可以把 Sublime Text 2 的整體環境變得扁平化。
安裝方法:
以上介紹了一些我常用的 Sublime Text 2 套件,但一直沒有找到好看的主題,如果你有推薦的主題,非常歡迎交流!
]]>主目錄:/var/www
wordpress資料夾:/var/www/blog
<!-- more -->
wordpress 會利用.htaccess 去更改固定連結,是位在根目錄,也就是/var/www
底下,wordpress 會根據你的網站位置URL
去設定.htaccess
利用終端機產生.htaccess
vi .htaccess
chmod 777 .htaccess
這邊我選擇文章名稱的格式
因為剛剛上面有設定.htaccess 權限打開到最大,因此進後台設定完,記得把 chmod 改為 644,要注意安全性。
我的 wordpress 是架在 Amazon EC2 上的 ubuntu,server 是 apache2,因此上網搜尋相關資料,解決辦法如下:
sudo a2enmod rewrite
sudo /etc/init.d/apache2 restart
只要兩行就可以解決!
在解決這個問題的時候,找了很多資料,一來是不明白.htacces 真正的目錄,二來是不知道 ubuntu 的 rewrite 要打開,因此在這邊記錄下來,讓有相同困擾的人可以了解。
]]><!-- more -->
關於開啟 AWS 的過程,實際走過一遍之後,阿正老師的這篇,其內寫的不錯,推薦跟著走一遍,就會了解很多。
在實際走過後,會發現阿正老師這篇真的超用心,因此接下來主要會利用這篇,再加上些我的補充。
提到將主機開的位置,現在已經有 tokyo 的據點,離台灣更近,所以建議將 instance 位置設在 tokyo
建議將 instance 位置設在 tokyo
利用 VISA 卡,選擇免費方案(圖中有星星的都是免費方案),在這邊我選擇 ubuntu 來做為我的系統。
在阿正老師的文章內看到關於 key pair 介紹,很重要,一定要記住要把下載下來的 pem 給管理好,未來是需要利用它來做 ssh 登入主機。
.pem 檔需要存好,一台主機配對一個 key pair,且不能做更改,
在沒有設定 security group 的時候,新開的 instance 可能是鎖起來的,會有 SSH 連線上的問題。
Inbound
SSH
&HTTP
,Source 部分都維持 0.0.0.0/0 即可,加入後記得要按Apply Rule Change
才生效記得開啟 SSH(20)、HTTP(80)
每一個 instance 都應該要綁定一個 elastice ip,未來可以作為連線使用。
申請完 elastic ip 後,原本的 Public domain 前半部分會改變為新的 ip
*_ 其實我在實作時,是先做了 SSH 連線,後來在去申請 elastic ip,結果 associate 完後,我又要 ssh 連線,發現沒有辦法登入,之後才瞭解是做了 elastic ip 後,連線的 ip 也需要一並跟著改變。_
chmod 600 ~/.pem
ssh -i ~/.pem ubuntu@ip
_ ~/.pem
是此 instance 的 key pair .pem 檔的路徑
_ ubuntu
是因為我用 ubuntu 當做 OS,如果當初選擇 Amazon linux 的話,則需要輸入ec2-user
取代 ubuntu * ip
則為 instance 的 ip,進到 console,左側選 instance,拉到底下看見Public DNS: ec2-xx-xxx-xxx-xx.ap-northeast-1.compute.amazonaws.com
xx.xxx.xxx.xx
即為你的連線 ipubuntu@ip-xxx-xxx-xxx-xxx:~$
字眼!那就恭喜了!pem 檔的權限要更改為 600
剛進到 instance,記得將環境設定一下
sudo apt-get update
+ sudo apt-get upgrade
sudo su
取得 root 權限sudo apt-get install tasksel
sudo tasksel install lamp-server
sudo apt-get install phpmyadmin
wget http://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz
mysql -u root -p
當你安裝完後,事實上可以利用 public domain 來連線看看
直接在 console 裡面找到 instance 的 public domian,連線看看是否成功
cd /var/www
sudo vi test.php
創新 php 檔案,並進到 vim 模式i
進入編輯模式,記得看下面是否出現-- INSERT --
<?php phpinfo(); ?>
:wq
-> enter 存檔(記得是看 iterm 下面)如果你有自己的 ip,想要指到 EC2 的話,按照下面作法。
A record
Elastic IP
即可reference
]]>如果有問題,或者我有寫錯的地方,歡迎留言讓我知道!
Sublime Text 2 是一套越來越火紅的編輯器,如果你是接觸網頁開發,想必對於這套軟體不陌生,以下分享幾個好用的技巧,都是我自己平常比較常使用的技巧,因為我本身是一個懶得看文件的人,所以就整理這篇與大家分享。
<!-- more -->
我是使用 mac,所以快捷鍵就會是 command 的配置。
有發現你的 Sublime Text 2 右下角有你正在編輯的環境語言嗎?舉凡 JAVA、CSS、HTML5 等
比如說我現在要從 HTML5 切到 CSS 介面,除了由上方的 View->Sytax 切換外,可以利用快捷鍵
切換到 CSS 範例:
shift+command+p
鍵入sscss
每一個 Color Scheme 都會針對不同的語言去做優化,因此值得學習。
貼心的 Sublime Text 2 有內建 HTML5 的 snippet,方法如下:
! + tab
html:5 + tab
都可以達成 HTML5 快速生成已經預定的 snippet.
按住command+點選你要的位置
推薦用在處理 Array 等結構重複性高的資料型態。
按住option+按著滑鼠左鍵直行往下拖曳選取
推薦用在處理 html 等修改固定 class 或其他部分。
通常你都怎樣選取雙引號內的字串呢?"string"
利用滑鼠從第一個引號拉到後面那個。
輸入:command + d
即可完成
有時候我時常左邊放 HTML 檔,右邊放 SCSS 檔,一邊看一邊編輯,這時候就要分割畫面。
輸入:command+option+數量
就可以把視窗分割成你要的數量。
有時候從網站上複製一段 code,常常貼上的部份本身就有縮排,貼完卻只有第一行有縮排,其他跑到前面。
複製完後,輸入:shift+command+v
也就是在原本的貼上加上 shift 就可以解決!
這是第二屆的 JSDC,由 TonyQ 及三大社群舉辦的聚會,非常幸運的我能順利的利用社群票的機制搶到票,在眾多非常有經驗的強者面前,聽完他們的分享,越感覺自己對於網頁開發充滿了熱情!
<!-- more -->
『我們不叫前端工程師,以後請叫我們前端設計師!』
這是聽完今年的 JSDC 感覺最有趣的一句話了。這句話帶出了他們的幽默風趣,在 Web Develope 這一個圈子,能結合設計還有程式的,就是前端工程師,也就是所謂的 F2E。
雖然對於網頁開發我自己算是沒有什麼經驗,要談設計我也沒有什麼基礎知識,但我知道這條路是我喜歡的,我會繼續走下去。給自己的目標是十年,我希望十年後我能跟現在我敬佩的前輩們一起討論,一起努力。
【AWS】介紹了他們的服務現在已經針對 node.js 有放出 SDK - 有用過的人歡迎一起分享討論。
【保哥】介紹了他的開發經驗,針對 code 要怎樣去調教,他說了一句話讓我印象深刻:
通常你的網站會慢,第一個要查的就是你自己所寫的 code,不是你套用的那些 framework。
保哥也推薦利用jsperf.com去測試自己的 code,雖然執行上面的效果一模一樣,有可能只差一個new
效率就差了 200 倍之多。
在技術上有卓越表現,是工程師的浪漫。
在設計上有卓越表現,是設計師的浪漫。
在產品上有卓越表現,是企劃與 PM 的浪漫。
【Even Wu】Even 介紹到了如何教前端技術,投影片的風格太有趣 XD 聊到了教設計師寫 code 其實要讓他們自動自發的學習,也是一個最重要的觀念:激勵。這個議程談的不是技術,而是一種經驗和體悟。
最後剩下的時間,大家交流的問題也很有趣。就有某設計師提到:他原本是設計師,但他每次提出的效果,前端工程師都跟他說這做不出來,他為了證明做的到,便開始自動自發的學習寫 code,來向前端工程師證明。
【up chen】介紹了很多實用的工具,但可能我自己本身就是一個很偷懶的人,(偷懶的人都會想要去找一些好用的工具讓自己做事更快)所以覺得這一個議程沒有得到很多新知。
但像是 sublime text 的 column selection 便是之前沒有在使用,但是覺得對自己很方便的技巧:)
【Rex Huang】這個主題很酷,在瀏覽器上面建立一個 os,而且執行起來很順,重點是利用 js 去打造的,同時也是大會的贊助商,未來應該很有發展,但是對於現階段的我來說比較艱深,所以就沒有很仔細的記錄筆記。
【KKBOX】kkbox 來談談利用 node.js 開發的心得。介紹了一些 node 的用法還有經驗,但這種議程最難過的就是自己對於 node.js 接觸還沒有很深,所以聽起來會有點懵懵懂懂,印象最深刻的就是這句話:
利用 JavaScript 去寫後台聽起來很酷,但做起來會想要哭。
前端,你的名字叫熱血
前輩也提到,技術不是重點,態度才是,避免盲目的追求技術,好的前端不等於知道很多技術。積極分享、多回饋,這點我真的在 Josephj 身上看到,從他架設的 f2eclass 就可以知道他願意分享的態度。
【york】這是一場非常酷的 live demo,台上講者的投影片,透過連線到 jsdc.york.tw 可以即時的在自己電腦切換,且下方就 demo 了 socket.io 的實作。雖然自己還沒有接觸到這一塊,但知道 socket.io 是利用 websocket 去做,在沒有實作 websocket 的瀏覽器,會切換別的通道,讓他們也可以 work,所以支援的瀏覽器很廣。
【大澤木小鐵】小鐵與我們分享了在 JavaScript 上面實作 MVC、MVP、MVVM(簡稱 MV*)的心法,這場我聽的津津有味!利用圖解的方式搭配很多時事梗 XD 重點是讓沒什麼經驗的我也可以略懂,相信未來在開發 JS 的時候,會再想到這一份投影片。
【othree】othree 和我們分享了 this 的用法。在不同情況呼叫 this 的時候,他針對的 function 以及目標會不同。然而我覺得這對我來說也還是太深了 XD 開發的經驗還是不太足夠,但這一個主題應該是很多有經驗的前輩們都想要討論的問題,期待自己能好好的加強自己,等到我也遇到 this 的問題,我相信我又更加進步了。
【阿修(Justin Lee)】阿修介紹到了在與設計溝通的時候,其實為了記錄當下討論的結果,利用 inline 把效果寫進 html 最快,等到往後要維護的時候,工程師在自己去 maintain,著實讓我上了一課。阿修也示範了一套很酷的 Titanium,可以即時的在 web 編輯 app 程式,然後利用手機即時連線,為了加速效果,也利用了Tishaow去 built,存檔的同時就可以即時看到,效率非常高。
【Mozilla】介紹了利用 Emscripten 去把不同的語言編譯成 JavaScript,可以在瀏覽器上面運行這樣。
【Nowill】Nowill 介紹了 RWD 的一些想法,她是一個非常有經驗的前輩,參與很多專案,與很多公司有很多合作經驗。她說了,RWD 不是口頭上說說就是用 media query 就可以解決的,還有很多技巧是要自己真的去做過才會知道的。提到了五大秘訣:掌握解析度、流動式布局、使用者經驗、體驗操作、推敲思考。且她也提到,不是每一個網站都適合作 RWD,因為在需要圖片解析度,文字閱讀的網站,應該針對不同的 case 去做因應。
參與了本次的 JSDC,我體會到了那些專注於在一件事情上面的美好,那種熱忱,都是在這些前輩們上面可以看到。前端工程師要做的事情,會一點設計,會知道要怎樣寫程式,其實就是將這世界上美的事物,利用 web 的方式讓大家看的見。
我相信分享是這世界上最美好的事情,聽到許多前輩的分享,我自己會有一種想要讓自己變很強的衝動,但有時候會忘記那些最單純的人際關係。在周遭一些朋友身上,我看見了他們很強,很厲害,讓我打從心底佩服;但不知道為什麼,對於他們我會有一種不太想向他們請教的感覺,或許是他們平時透露出來的訊息吧,讓我感覺不是那麼好。
我認為交流是一件很愉悅的事情,可以讓彼此在短時間就可以學習到很多,因此我很樂意分享,把自己知道不多但覺得實用的東西給記錄下來,期許大家可以開心的交流,開心的分享,而並非是那種『我知道這個很好用,但你沒有問,你沒有一起來討論,所以我不想告訴你。』提醒自己,要當一個樂意分享的人,在前端開發這一塊,我又重新充滿電了!
在變強之前,我想要先變好:)
<!-- more -->
當你要有一個自己的 GitHub Pages,其實分成兩項。
yourname.github.com
,則會產生的 page 路徑為 yourname.github.io
。本部落格是建立在 github 上面,就是利用這一個 GitHub Pages 模式去建立,可以參考hexo 架 blog 初體驗、建立自己 blog 的 subdomain這兩篇文章。repo-name
,則會產生的 page 路徑為 yourname.github.io/repo-name
,這邊的 repo name 就不像第一種模式,沒有固定名稱。在每一個新的 repo 下面,正常的情況都是在 mater 這一個 branch。分享一下我自己的作法。
git clone git@github.com:kpman/first-repo.git
cd first-repo
gh-pages
,這步驟很重要,Github Pages 就是看這一個 branch 去決定頁面的。git branch gh-pages
git checkout gh-pages
或者
git checkout --orphan gh-pages //建立一個沒有parent的branch,並移到該branch上
git add .
git commit
git push origin gh-pages
http://github.com/kpman/liteAccordion這樣代表連回 GitHub 的 code 頁面
http://kpman.github.io/liteAccordion因為新增到gh-pages
這一個 branch,所以可以看到靜態的 html 展示頁面。
其他範例
two.js
textillate
<!-- more -->
在終端機下輸入以下
npm install hexo-generator-feed --save
權限沒有取得的記得前面加上sudo
。
在主目錄底下的_config.yml
檔加上
plugins:
- hexo-generator-feed
如此一來便完成了環境設置。
在 hexo generate 之後,會發現 public 資料夾底下多了 atom.xml
在feedly這類的閱讀收集器
只要輸入domain/atom
就可以找到
例如要訂閱我的 blog 只要輸入code.kpman.cc/atom
就可以搜尋到囉!
突然發現我的 blog 只有一個人訂閱,那個人就是我自己...
感謝強者阿志耐心面授機宜:)
]]><!-- more -->
不會出現在 html 的文件裡面,而是利用 css 讓瀏覽器去實作。以下列出的都是可以使用的 pseudo-element。
before
但是是在之後插入。為了區分偽元素和偽類,CSS3 的 guildline 將偽元素的寫法修正,以往只要加一個冒號「:」,現在則是加兩個冒號「::」,部分可支援的瀏覽器包含 webkit, firefox, opera。) -- by MUKI
<div class="icon">
<img src="img/icon01.png" alt="正確觀念" />
<span>正確觀念</span>
</div>
.icon {
display: inline-block;
position: relative;
padding: 1em;
padding-top: 5px;
span {
position: absolute;
bottom: -7px;
left: 12px;
}
}
原本利用 div > span 的效果去把中文字顯現出來,所以在 html 的架構下,需要多一個span
的 tag。
且利用img
去把圖片給 show 出來。
<!-- html只剩一行!-->
<div data-text="正確觀念" class="concept_label icon"></div>
.icon {
display: inline-block;
position: relative;
padding: 1em;
padding-top: 5px;
&:before {
position: absolute;
bottom: -7px;
left: 12px;
content: attr(data-text); // 關鍵作法
}
}
.concept_label {
background-image: url(../img/icon01_c.png);
}
###觀念整理
data-*
屬性::before
取代原本的span
content: attr(data-text);
將 date-text 文字取出來<!-- more -->
+Add
申請一個新的帳號shortname
記住!
_ 修改本機目錄下的 _confid.yml
_ 在 disqus_shortname
貼上自己的 shortname完成~
※ 後記:
若你在申請完 disqus 貼到自己的 blog 上面,發現出現很多不必要的連結,請到 disqus 的 dashboard 去修改。
右上方 Setting
-> Discovery
-> 將 Discovery level 拉到最右邊,如下圖
在標題就已經破梗囉 XD
右方出現那些『Tag』『Tag Cloud』等欄位,其實就是修改 widget 過後得到的結果。
方法如下:
theme/light/layout/_widget
,可以發現很多 ejs 檔category
recent_posts
等theme/light/_config.yml
內,修改widgets,在底下新增你要的 ejs 檔名即可~同樣方法可以自己寫 ejs 檔,一樣在theme/light/_config.yml
加上檔名就可以自訂。
預設為 Home、Archives 這兩個
當然得要自訂才爽 XD
方法一樣很簡單如下:
theme/light/_config.yml
裡面的第一行http://google.com
之類的連結。favicon.png
)主目錄/source
底下theme/light/layout/_partial/head.ejs
做修改<link href="<%- config.root %>favicon.png" rel="icon">
以上介紹一些目前知道的自訂方法,
希望還有人可以跟我多介紹一些技巧,一起分享吧!
kpman.github.io
這一個網址實在太難記了,所以就決定將自己有的 domain 利用 subdomain 的方式指過來。<!-- more -->
雖然像是廢話 XD
但這邊推薦去GoDaddy買 domain.
方便又快,在更新 domain 的時間非常快速,理論上一個小時內就可以指到你要的 ip 位置。
進到 DNS manager
新增一筆 CNAME
GoDaddy 部分完成
這邊要在 主目錄-source
底下放一個檔名為 CNAME
的檔案
裡面為你要指向的路徑,這邊我是指向 http://code.kpman.cc
至於如何設置 CNAME 檔案,我是利用在 github repo 上面的 create new file
設置完之後 clone 下來,然後複製進去主目錄-source
底下 XD
(有人知道怎樣做比較好嗎?)
理論上這樣子就完成囉!
如果有漏掉的麻煩留言給我一起討論:)
報著取之於人,回饋之於人的心情,我想要把自己從無到有架設這一個 blog 的過程記錄下來
謝謝那些願意指導我的朋友們!
<!-- more -->
首先你要有 node.js,這是一套快速、簡單且功能強大的 Node.js 網誌框架。
接著你要用 npm install -g hexo
來安裝
理論上這樣就完成了,我個人在裝的時候 npm 不給裝,發現前面加上 sudo
就可以解決。
你要申請一個自己的 github 帳號
在主頁右上方創建一個新的 repo
Repository name 填入 github帳號.github.com
用來創建 github page
建立 hexo
hexo init
建立一篇文章,將會是Markdown形式,可以到 source/_post/title.md 去修改
hexo new "title"
生成 public 檔
hexo generate
開啟 server 觀看(預設在 localhost:4000)
hexo server
修改 _config.yml
檔案,最下面加上
deploy:
type: github
repository: git@github.com:github 帳號/github 帳號.github.com.git
branch: master
我那時候用 git 的時候,沒有 ssh 認證,如果遇到相同問題可以看這篇。
照著上面的指令走就可以完成,唯獨 mac 裡面似乎內建 id_rsa
這把 key,這邊我不清楚 XD
接著只要輸入
hexo deploy
等待 10 分鐘,你的網誌理論上就架好了!網址為 http://github帳號.github.io