diff --git a/pages/fixed/about.mdx b/pages/fixed/about.mdx index e7f2245..6693ddb 100644 --- a/pages/fixed/about.mdx +++ b/pages/fixed/about.mdx @@ -1,63 +1,10 @@ import { Layout } from 'lib/components' -import { Button, Row, Spacer, Dot, Link, useModal, Modal, Image } from '@geist-ui/react' -import NextLink from 'next/link' export const meta = { - title: '关于我', - date: '2019-01-09T10:20:10.027Z', - description: 'description', + title: 'About me', + date: '', } -export const Btc = () => { - const address = '1KE89oa6aMNaVEPUeQPxLTmQGmXL6W1mGi' - const { setVisible, bindings } = useModal() - return ( - <> - - - BTC - This is Witt's BTC address - -

{address}

-
- close()}>OK -
- - ) -} - -export const Alipay = () => { - const { setVisible, bindings } = useModal() - return ( - <> - - - Alipay - Scan code with Alipay - - - - close()}>OK - - - ) -} - -你好,我是维特,一个自由主义者,JavaScript 全栈? 工程师。对 Serverless 与 DX (Developer experience) 很感兴趣。 -喜欢 `CoffeeScript` 与 `Ruby`,但一直在写 `TypeScript`。主要贡献是创造一些应用和类库来帮助提高开发者体验。 - -我很少写博客,_Hello World_ 文章实在讨人厌,不想成为智障。但水平不够写不出好东西,所以不写。如果你发现博文更新,那就是我的工作不饱和了。 -此外这里也提供 **Review** 服务,你可以在 Review It 中提交自己的 `Pull Request` 链接, -我会在收到通知后立刻帮你分析代码,并尽可能多的提出对提高你编程能力有显著帮助的建议。 - -我非常乐意倾听来自你的想法,你可以通过 邮件 与我取得联系。 -现在你所见的站点来自我的开源项目 unix/unix.bio (MIT) , -你可以无偿的使用它构建属于自己的博客。 - -由于缺乏时间维护开源项目,我当前已经不再接受赞助。非常感谢 所有的赞助者。 +About me. export default ({ children }) => {children} diff --git a/pages/fixed/projects.mdx b/pages/fixed/projects.mdx deleted file mode 100644 index 9315bd6..0000000 --- a/pages/fixed/projects.mdx +++ /dev/null @@ -1,130 +0,0 @@ -import { Layout } from 'lib/components' -import { Spacer, Dot, Link, Code, Grid, Card, Text, Image } from '@geist-ui/react' - -export const meta = { - title: '开源项目', - date: '2019-04-02T11:31:33.705Z', - description: '', -} - -export const Item = ({ name, git, desc, image }) => { - return ( - - - - {image && ( -
- -
- )} - - {name} - {desc} - -
- -
- ) -} - -我正在创造一些能够改善开发者体验的工具和开源项目,如果你有不错的想法或正面临一些重复性的工作, -想要有任何改进?请让我知道。 - - - - - - - - - - - -维护中项目 - - -- [geist-vue](https://github.com/geist-org/vue) Geist UI 的 Vue 实现 -- [koa-ts](https://github.com/unix/koa-ts) Koa2 与 TS 的最佳实践,包含脚手架 -- [done](https://github.com/unix/done) 自动化、标准化的 Release 工具 -- [cdn-viewer](https://github.com/unix/cdn-viewer) 以文件树审阅任何版本时的仓库或包 -- [func-service](https://github.com/unix/func-service) 使用 func 构建的 func 脚手架 -- [store-now](https://github.com/unix/store-now) 命令行工具文件存储 -- [geist-style](https://github.com/geist-org/style) Geist UI 的 CSS 实现 -- [typographic](https://github.com/geist-org/typographic) Geist UI 字体排版的实现 -- [geist-ui/vue-icons](https://github.com/geist-org/vue-icons) Geist UI 的 Vue 图标组件 -- [geist-ui/react-icons](https://github.com/geist-org/react-icons) Geist UI 的 React 图标组件 -- [v-decorators](https://github.com/unix/v-decorators) Vue 实用 decorators 库 - - -维护频率低的项目 - - -- [aliyun-logs](https://github.com/unix/aliyun-logs) - 阿里云日志 SDK -- [docs](https://github.com/unix/docs) Geek 风格的文档中心 -- [pages](https://github.com/unix/pages) - 为每个项目生成主页 -- [euv](https://github.com/DhyanaChina/euv) Vue 的控制反转实现 -- [vue-auto](https://github.com/unix/vue-auto) Vue 自动 import 服务 -- [koa-custom-response](https://github.com/unix/koa-custom-response) Koa Restful 定制方法库 -- [cobot](https://github.com/unix/cobot) - Gitlab 机器人框架,更语义化的事件与 Git Event 提示 -- [v2ex-cli](https://github.com/unix/v2ex-cli) v2ex 命令行工具 -- [todo-live](https://github.com/unix/todo-live) todo 命令行工具 - - -不再维护的项目 - - -- [ng-mocker](https://github.com/DhyanaChina/ng-mocker) 适用于 Angular 的开发 mock 库 -- [storage-angular](https://github.com/DhyanaChina/storage-angular) Angular Storage 增强 -- [back-loader](https://github.com/unix/back-loader) 预加载资源库 -- [readyLoadImg](https://github.com/unix/readyLoadImg) 预加载图片资源 -- [react-native-smartbar](https://github.com/unix/react-native-smartbar) react-native tabbar -- [touch-dog](https://github.com/unix/touch-dog) 划词翻译 Chrome 扩展 -- [simpler-paper](https://github.com/unix/simpler-paper) static site 打包工具 -- [easy-jsdelivr](https://github.com/unix/easy-jsdelivr) jsdelivr 界面管理与包文件查找 -- [element-angular](https://github.com/ElemeFE/element-angular) Element UI 的 Angular 实现 - - -其他语言项目 - - -- [just-fine/cs](https://github.com/just-fine/cs) coffeescript 的众多框架脚手架,包含 [coffee-vue](https://github.com/just-fine/vue-coffee) - [coffee-sails](https://github.com/just-fine/sails-coffee) [coffee-micro](https://github.com/just-fine/micro-coffee) [coffee-react-next](https://github.com/just-fine/next-coffee) -- [just-fine/fine.sh-cli](https://github.com/just-fine/fine.sh-cli) 一键部署文档工具 -- [now-coffee](https://github.com/unix/now-coffee) Now 的 coffeescript 编译过 -- [DungeonWatchDog](https://github.com/unix/DungeonWatchDog) 魔兽世界 Lua 插件,地下城过滤器,[用户量>40k](https://www.curseforge.com/wow/addons/dungeonwatchdog)。 -- [Nioro](https://github.com/unix/Nioro) 魔兽世界 Lua 插件,框架界面增强,[用户量>14k](https://www.curseforge.com/wow/addons/Nioro) - -export default ({ children }) => {children} diff --git a/pages/others/sponsors.mdx b/pages/others/sponsors.mdx deleted file mode 100644 index 77c12d1..0000000 --- a/pages/others/sponsors.mdx +++ /dev/null @@ -1,44 +0,0 @@ -import { Layout } from 'lib/components' -import { Link, Spacer, Text, User, useTheme, Grid } from '@geist-ui/react' -import NextLink from 'next/link' -import Sponsors from '../../sponsors' - -export const meta = { - title: '感谢名单', - date: '2020-04-08T03:20:00.763Z', -} - -export const SponsorList = () => { - const theme = useTheme() - const list = Sponsors.map(item => ({ - ...item, - amount: item.amount.toFixed(2), - })).sort((a, b) => (a.isSponsor ? -1 : 1)) - return ( - - {list.map((item, index) => ( - - - - {item.amount}${' '} - - {item.isSponsor ? '/ month' : ''} - - - - - ))} - - ) -} - -非常感谢各位慷慨的赞助者帮助我确保开源项目得以维护。捐赠不是必须的,但作为礼物,如果你有需要可以邮件我, -我可以帮助你远程解决一些问题或是 Review 代码。 - -你可以在 关于我 的页面找到捐赠链接, -按月赞助来自于 GitHub Sponsors。 - - - - -export default ({ children }) => {children} diff --git a/pages/posts/about.mdx b/pages/posts/about.mdx index 57efa5e..96419b7 100644 --- a/pages/posts/about.mdx +++ b/pages/posts/about.mdx @@ -8,7 +8,7 @@ export const meta = { Welcome to `unix.bio`, run `npm run post` to create your first post. - diff --git a/pages/posts/best-practices-for-open-source-project-release.mdx b/pages/posts/best-practices-for-open-source-project-release.mdx deleted file mode 100644 index 106d429..0000000 --- a/pages/posts/best-practices-for-open-source-project-release.mdx +++ /dev/null @@ -1,189 +0,0 @@ -import { Layout } from 'lib/components' -import { Spacer, Display, Code, Link, Dot, Image, Snippet } from '@geist-ui/react' - -export const meta = { - title: '开源项目版本发布的最佳实践', - date: '2019-04-02T11:31:33.243Z', - description: '', -} - -对于存放在 Github / Gitlab 的开源项目,视项目大小与功用也有不同的发布方式、发布内容与自动化工程化解决方案。 -这里以包含 `package.json` 的项目为例,介绍各种不同的发布方法。 - - - -## Release - -在发布之前我们需要先升级版本,也有少部分自动发布的脚手架中包含升级版本与 `CHANGE_LOG` 收集,但通常你需要自己解决这个问题。 - - - -### 1. 手动更改 - -顾名思义,打开 `package.json` 或者你的其他包管理文件手动改一个版本。 - - - -### 2. 在遵循语义化版本的基础上手动更改 - -所谓语义化版本即是按约定对版本号进行约束与声明,与多数人采用同一套规则来升级版本,具体规则可以参考 Semver version。 -也就是大家熟悉的主-次-修订: - -
    -

    - - MAJOR{' '} - {' '} - 不兼容升级 -

    -

    - - MINOR - {' '} - 向下兼容的功能性升级 -

    -

    - - PATCH - {' '} - 向下兼容的问题修复 -

    -

    - - pre-release - {' '} - 先行版本 -

    -
- -每次 Release 新版本时参考所有的修改内容决定下个版本号,再进行修改。如果你不小心修改错了, -则需要发布一个新的版本来修复这个错误。当然这只是约定,如果你觉得这很繁琐不知道该怎么办可以看下文。 - - - -### 3. 在遵循语义化版本的基础上自动更改 - -自动 release 工具可以分为 本地 / CI / 机器人不同类型,本地工具大多是 CLI (也有可视化),通过命令手动帮助你改动文件与 release git tag, -CI 工具则是在你每次的 PR / PUSH / 本地 Hook 触发后通过相应的规则检查,通过后自动升级版本,机器人模式在 github 很常见, -通常是运行一个服务端来监听 Git 事件,在合适的时机向你发起一个 PR 升级版本。(有关 robot 你可以关注我的项目 cobot.gitlab) - -cobot.gitlab 以语义化的接口完成 Bot 开发

}> - -```js -const bot = cobot.lift() -bot.on(BotEvents.MergeRequest, async context => { - const notes = await context.actions.findNotes() - console.log(notes) -}) -``` - -
- -对于本地升级版本的方式你可以试试 DONE, -它可以帮助你生成一个标准化的版本,在发布时自动修改 `package.json`,同时也支持对版本的发布信息做 hook 补充。 -也就是说,当你正在使用 `done` 时,可以不用考虑任何事情,通过一行命令发布一个版本并生成一个 Git Tag: - - - 什么是 NPX? - - - - done可以不下载直接使用 -

- }> - npx done -
- -比如当你选择 minor 版本时即是修改本地的文件并将这些信息打包成 tag,然后把这些改动与 Tag 一并推送到远程源。 - - - -### 4. 如何收集 `CHANGE_LOG` - -在自动化 `CHANG_LOG` 之前我们需要弄明白的一件事是,即便是自动化生成也需要有数据源,如果你没有写日志也没有合适的 commit message 就无法做到自动化版本日志。 - -- 安装 Commitizen 规范化 commit message。 - -- Commit Lint 的规范和约束细节可以借助 `config-conventional` 工具完成。 - -- 通过 Commitlint 来进行规范检查。 - -- 在每次提交时以 Husky 触发 Git Hooks 自动检查。 - -- 在 CI 中使用 @Commitlint/travis-cli 进行规范检查。 - -- `Release` 时借助 conventional-changelog-cli 自动生成所有的 `CHANGE_LOG` - - - -## Publish - -在升级版本后你还需要做一些打包工作,再把代码或包发布到指定的平台上才算完成这次发布。 - - - -### 1. 仅发布源代码 - -如果无需编译或执行脚本,发布全部的源代码是非常方便的,只需要在 Github / Gitlab 手动创建一个 Release 即可完成发布。创建的 Release 会自动附带当前的所有源码信息。 - - - -### 2. 自动发布源代码 - -在发布源码的基础上使用 release-drafter 等 Gitbot, -可以在 PR 被合并或指定的关键词触发时,在项目上添加一个 `Release Draft`,并且可以通过手动配置的方式添加版本信息。 - - - Release Drafter 自动完成的草稿 -

- }> - -
- - - -### 3. 手动发布编译后包 - -在有些需要编译的项目中我们常会先压缩或是执行一些编译脚本,并且排除掉对生产无用的文件才发布,因为包的大小会影响用户的下载时间与使用体验。 -现在我们尝试在本地发布一个执行脚本后的包。以包含 `package.json` 文件的项目为例: - -- 你需要在升级版本后在本地手动执行一次 Build 脚本,或是在 `scripts.prepublish` 中指定 Build 语句。 - -- 在 `files` 配置项中指定可以被发布的文件、文件夹。 - -- 指定 `main` `module` `unpkg` 或其他入口。 - -- 运行 `npm publish`。 - -- 如果包属于一个组织,你需要在组织中发布公开项目则需要指定 `access`: `npm publish --access public`。 - - - -### 4. 自动发布编译后包 - -你需要借助服务器来自动完成编译或运行脚本的过程,这里以 travis-ci 为例: - -- 使用 travis.rb 初始化项目并生成密钥信息。 - -- 如果发布在 github 你可以在 Github/token - 页面自行生成 token,但仍旧需要 `travis.rb` 进行加密。配置信息可以参考 travis-deployment, - 发布在 npm 则需要 npm 的登录信息。关于 travis 是如何加密你的敏感信息,可以参见 这篇文章。 - -- 注意设定跳过 CI 的清理过程 `skip_cleanup: true` 与移除分支限制 `branches.only`。 - -- 当设定的触发条件 (如 Tag) 达成时,travis 会根据 `.travis.yml` 的配置运行相应脚本并发布指定文件至 npm 或 github。 - -如果你已可以使用 `Github Actions`,这件事就简单很多,`Github Actions` 有非常好的配置共享特色,你可以使用社区已经得到验证的、健壮的脚本管理自动发布任务。 -相关的脚本配置可以参考我的项目 geist-vue。 - -export default ({ children }) => {children} diff --git a/pages/posts/building-modern-command-line-tools.mdx b/pages/posts/building-modern-command-line-tools.mdx deleted file mode 100644 index 25a0cf9..0000000 --- a/pages/posts/building-modern-command-line-tools.mdx +++ /dev/null @@ -1,144 +0,0 @@ -import { Layout } from 'lib/components' -import NextLink from 'next/link' -import { - Display, - Image, - Spacer, - Code, - Link, - Button, - Text, - Snippet, -} from '@geist-ui/react' - -export const meta = { - title: '构建现代化的命令行工具', - date: '2019-06-15T10:36:57.723Z', - description: '', -} - -每当我们想要创建一个基于 NodeJS 的命令行工具时,就会衍生出一堆问题需要解决,比如如何准备开发环境,如何打包转译代码, -如何使代码在转译后保持可调用的状态同时尽可能的压缩体积,以及怎样设计项目分配 `Command` 与 `Option` 等等,这会浪费巨大的时间,而且并非一定有成果。 -这时你可以注意到社区几乎所有的命令行工具都是自成一派,并没有严谨的框架或约定约束,也无所谓的最佳实践, -这使想要特别是第一次想要开发命令行工具的开发者望而却步,或是几番努力最后却不尽如人意。 - -举个例子来说,腾讯的 `omi` 是一个有众多使用者的框架,但其命令行工具 `omi` / `omi-cli` 却让人贻笑大方。仅一些简单的下载和创建模板的任务,造出长篇大论的文件不说, -下载时依赖数千,包的体积巨大,整体项目毫无设计几乎是随心所欲、天马行空,这就是开发者本身并不擅长此道, -只学会了 _糊屎_。(何谓 _糊屎_,参阅 JS 优雅指南 2 ) - - - 现代化的命令框架 func{' '} -

- }> - -
- - - func 的出现就是为了解决这些问题。 - func 本身的实现参阅了社区内诸多基于 NodeJS 的命令行工具的优秀实现,与流行的框架设计思路相结合,以优雅的设计、小体积、高性能 - 等为目标, 同时关注开发者体验,大幅度的提升了命令行工具项目的可扩展性与可读性,几乎是如今 - NodeJS 社区中开发命令行工具的最优解。我们可以尝试使用 func 构建一个命令行工具。 - - - - -## 构建项目 - -在以前流行的一些命令行参数解析的库中,我们在构建项目前需要准备大量的脚本与配置,甚至还要解决文件权限、`bin`、代码转译等等问题, -但使用 `func`,我们可以仅通过一行命令初始化项目: - - - 运行命令快速创建 func 项目 -

- }> - npm init func -
- -项目初始化后进入文件夹,随机使用 `npm install` 或 `yarn` 安装依赖,现在就可以正式开发了。 -可以注意到,`func` 的项目模板中为我们准备了 `start` 与 `build` 2 个脚本,它们都是由 `func-service` 驱动的, -帮助你一键切换开发与生产模式,我们所要做的就是专注于命令行逻辑本身,实现逻辑就够了。 - - - -## 实现逻辑 - -我们可以随意的创建一个类,当它被加上 `Command` 注解时这就是一个命令,而被加上 `Option` 注解时就会转变为一个选项: - -```ts -import { Command } from 'func' - -@Command({ name: 'test' }) -export class Test {} -``` - -在命令行中运行 ` test` 时,`Test` 类将会被调用。选项也是同理。你可以在 `constructor` 中开始自己的代码, -这和你在任何地方写 NodeJS 没有什么不同,当你觉得差不多的时候,运行 `npm build` 就可以将它打包,一切就是这么简单。 - -有时候,当我们需要使用到命令行携带的参数时,比如处理 ` something params -option` 这样复杂的输入,你可以直接在类中注入命令行参数,对,就是像 IoC 那样: - -```ts -@Command({ name: 'test' }) -export class Test { - constructor(private args: CommandArgsProvider) {} -} -``` - -`CommandArgsProvider` 实际上是一个 `class` 类型,当你标记一个参数为此类型时,`func` 会在运行时为你注入所有的命令参数, -同样的也支持 `OptionArgsProvider` 、`RegisterProvider` 等等,你可以在 官方的文档 阅读它们的具体类型。 - - - -## 打包与发布 - -运行 `npm build` 可以得到一个打包后的文件,这是由 `ncc` 编译后的文件,通常它只有一个 (如果携带 extra 可能会有多个,但它们会自动链接), -同时为你链接好了 `bin`,你要做的唯一一件事就是将包发布出去。为什么 `func` 使用这种方式发布呢? - -我们知道当你在安装一个包或是使用 `npx` 执行包时 (这在使用命令行工具的人群中很常见),NPM 所花费的时间大约在 3 个部分,即对比包的依赖,下载包,执行。 -首先我们知道 `func` 的项目足够的小,能够大量介绍下载时间,同时也有足够好的性能,现在要解决的就是在大量依赖时的对比分析问题, -将文件打包成单文件不依赖外部环境时会极大的减少所需时间,如果你再将所有的依赖移入 `devDependencies` 中, -几乎能够在一瞬间完成 分析 - 下载 - 运行 这三个步骤。这样的体验是难以想象的。是的,这里推荐你把所有的依赖当做开发依赖处理, -这似乎违背了 NPM Package 的开发哲学,但在使用 `func` 构建命令行应用时这样做却大有裨益。 - -在运行 `func` build 完成的包时,我们注意到几乎无需任何依赖,这是因为在单个文件中已经 `bundle` 了所有的所需资源,也就意味着用户在运行 `.js` 文件时, -堆栈中真的就只有 `.js` 文件内的内容,不会引用其他,不会加载任何无关紧要的东西。此时我们也就无需用户关心 `dependencies`,甚至可以移除它们,这样一来, -下载或即时运行时就直接跳过了 _对比依赖版本_ 这一步,这其中省略了无数的请求也就会会极大的增加速度,`npm init func` 能够在 1 秒左右立刻开始安装也是这样的道理。 - - - -## 优化与经验 - -现在你已经知道了怎样快速的构建一个合格且优雅的命令行工具,那怎样做的更好呢?通常来说你需要遵循这几点: - -- **不因为小功能引入巨大的包,不引入依赖爆炸的包。** - - 举例来说,`download-git-repo` 是一个很不错的包,它能够为你节约很多时间,但请注意它依赖了 `download`,如果你仅为了下载单个文件或只有很少的下载需求时, - 这就显得有些大材小用,`download` 会为你增加约 450 kb 的重量,却只做了一件你 5 分钟可以搞定的事情。同样你的用户也会为此付出巨大的时间代价。 - -- **不要显示错误堆栈信息。** - - 在多数情况下我们都需要尽可能的显示堆栈或是引用的错误信息便于 debug,但是在命令行工具中这样做只会使你的用户非常困惑。 - 这主要归结于命令行中不能很好高亮的显示代码块,大量的代码信息会使用户不知所措。建议你始终构建一个错误处理模块来解决问题, - 同时为用户提供良好的反馈,最后可以提供类似于 `--debug` 的选项让开发者调试。 - -- **不要太依赖同步操作。** - - 在 NodeJS 与其社区流行的 I/O 库中,我们通常会有异步和同步函数两种选择,如 `readFile` 与 `readFileSync`,虽然同步函数可以为你节约一些开发时间, - 但也会阻塞你的代码,很多情况下会有难以理解的问题。比如当你设置定时器显示一个 Loading 图标的同时操作了同步 API, - 那么你的 Loading 图标就会因为阻塞而无法运动 (因为无法 render 到终端),或是你同时操作多个文件,同步的 API 会使你花费巨大的时间。 - -- **不要发布无用信息。** - - 命令行工具很多时候的角色是充当复杂的脚本,性能和体验是至关重要的,发布无用的信息在你的 package 中会使下载时间更长。(使用 `files` 来约束发布的文件) - -- **不要修改临时文件夹与配置区以外的信息。** - - 对于命令行工具来说,运行时的权限是巨大的,但不要因此弄脏用户的系统。你可以使用 `require('os').tmpdir()` 获取用户操作系统的临时文件夹目录,无论何时, - 你都拥有这里的写权限。 - -export default ({ children }) => {children} diff --git a/pages/posts/elegant-js-1.mdx b/pages/posts/elegant-js-1.mdx deleted file mode 100644 index dff6c18..0000000 --- a/pages/posts/elegant-js-1.mdx +++ /dev/null @@ -1,243 +0,0 @@ -import { Spacer } from '@geist-ui/react' -import NextLink from 'next/link' -import { Layout } from 'lib/components' - -export const meta = { - title: 'JS 优雅指南.1', - date: '2019-06-03T13:47:31+08:00', - description: '', -} - -好的代码不仅是在性能、体积、内存,更要 `code for humans`。 - -我们知道代码被人阅读的难度远胜于引擎,要写出好的代码需要脱离自己的视角,以他人的眼光审视,重新理解上下文含义, -此时代码的架构、拆分、组合、技巧则给予人阅读的幸福感,我们致力于构建优秀的代码意味着不仅以代码为工具,更是将其视作传达智慧、 -思想、理念的桥梁。这是一种智慧上的锤炼与分享。 - - - -## 1. 优先使用 `const`。 - -`const` 在 JavaScript 中不仅可以用于命名常量,因为其用于保证内存地址不可变,所以也常用于声明对象与数组。在编程中多使用 `const` 代替 `let`, -可以在风格上向 `immutable` 靠拢,在编程思维上开始摈弃副作用。更多的使用 `const` 虽然可能使声明项增多,但对于开发者来说, -更少的心智负担和语义化命名会使代码质量大大上升。 - -在 JS 中如果过多的使用 `let` 声明变量,阅读者往往需要贯穿上下文反复阅读才能理解当前变量的值,且变量可能被其他函数引用更改,显而易见, -使用变量越多理解的成本也就越高,而且你很难追踪变量具体的值。如下方代码统计数组每个值的总和。使用 `const` 命名一个常量后, -你将无法使用 `forEach` 在每一次循环时改动它,转而使用 `reduce`,我们减少了变量 `count`,增加了常量 `count`, -在随后代码的引用中就无需担忧变量的状态,因为我们知道,`count` 只能是一个数值,而不会变化。 - -```js -// bad -let count = 0 -[...].forEach(item => { - count += item -}) - -// good -const count = [...].reduce((pre, current) => pre + current, 0) -``` - - - -## 2. 使用函数表达式优于函数声明 - -配合上文所提到的 `const`,我们能够使用函数表达式来创建一个函数,更多的时候我们会与箭头函数配合食用 `const f = () => {}`。它们优于传统函数声明的地方在于: - -- 语义化的指明函数是不可变的。 -- 函数表达式可以被看做赋值语句,更加简单易懂,且无法被覆盖。(常量不可以被重复声明) -- 函数声明会在解析时被提升,存在先使用后声明。高可读的代码应当先声明再调用,使用表达式范式可以约束这一点。 -- 搭配箭头函数使用可减轻对 `this` 的思维依赖。 - -```js -// bad -function addOne(value) { - return value + 1 -} - -// good -const addOne = value => value + 1 -``` - - - -## 3. 使用 return 减少缩进 - -缩进问题在 JS 代码中更普遍,推荐在可能的代码块中使用 `return` 优先返回,如 `function` `if else` 等语句。这样可以有效的减少缩进, -同时也能使代码更加的清晰可读,因为在同一时间内总是只做一件事。 - -我们还可以在必要时优先 `return` 较短的语句,使代码更美观。 - -```js -// bad -const render = () => { - if (isServer) { - // server code - } else { - // client code - } -} - -// good -const render = () => { - if (isServer) return // server code - // client code -} -``` - - - -## 4. 不要过度优化 - -如果你不是编写类库、底层代码等对性能要求极为苛刻时,请勿过度优化性能。绝大多数不必要的性能优化会使代码可读性急剧下降。这非常重要。 - -**例 1**,不必要的减少内存空间 - -```js -// bad -let fullname -users.forEach(user => { - fullname = user.firstname + user.lastname - // ... - register(fullname) -}) - -// good -users.forEach(user => { - const fullname = user.firstname + user.lastname - // ... - register(fullname) -}) -``` - -**例 2**,不必要的运算优化 - -```js -// bad -let len = users.length -for (i = 0; i < len; i++) {} - -// good -users.forEach(user => {}) -``` - - - -## 5. 减少魔术字符 - -魔术字符 (魔术数字) 指的是代码中出现意义不明的字符,从上下文无法推论其来源与作用,这会使代码难以扩展。 - -通常,我们还会把所有的字符或数字统一声明在一个常量文件内,如 `host` `defaultSettings` `port` 等等,这会有益于维护。 - -```js -// bad -const url = `${host}/2/users` - -// good -const apiVersion = 2 -const apis = { - users: 'users', - projects: 'projects', - // ... -} - -const url = `${host}/${apiVersion}/${apis.users}` -``` - - - -## 6. 函数不要有过多参数 - -在不断延展的需求中,我们的函数会有越来越多的参数,但要注意,当一个函数的参数较多时会使调用方困扰。我们并非需要所有的函数都实现 `curry`, -但减少函数参数、合并参数、分离函数都会显著提升代码的可读性与扩展性。 - -在调用较多参数的函数时,我们不仅要紧记每个参数的顺序,还要对空缺的参数进行补位 (如传入 `null` `undefined` 等), -这会导致声明与调用的代码中都被迫存在非常多的变量与判断。在函数个数增长时可以考虑将其中的一部分合成为一个对象参数,或是将一部分功能拆离,作为一个新的函数。 - -```js -// bad -const createUser = (id, name, createdAt, telephone, email, address) => {} - -// good -const createUser = (id, userOptions) => {} -``` - - - -## 7. 保持函数的专注 - -在一个函数中组好只做一件事,同时也最好保证这件事是与函数的名称是相关的。在单个函数中累积逻辑会给阅读者带来非常大的心智负担,如果我们予以函数拆分、 -合理化的命名、组合,就能使代码整体获得极大的美感,看起来井井有条,泾渭分明。 - -```js -// bad -const getUser = id => { - const headers = // ... - const options = // ... - options.headers = headers - const host = // ... - const url = // ... - if (id.length > 1) // ... - return fetch(url, options) -} - -// good -const makeOptions = () => {} -const makeUrl = id => {} - -const getuser = id => { - const options = makeOptions() - const url = makeUrl(id) - return fetch(url, options) -} -``` - - - -## 8. 使用语义化命名代替长条件 - -过长的条件判断常常会在一段时间后变的匪夷所思,很难理解其中的逻辑,将其使用语义化的常量代替则可向阅读者提示意义,更省略了不必要的注释。 - -```js -// bad - -// the code for teen-ager -if (user.age < 19 && user.age > 13) { - // ... -} - -// good -const isTeenAgerMember = user.age < 19 && user.age > 13 -if (isTeenAgerMember) // ... -``` - - - -## 9. 减少函数的副作用 - -减少函数的副作用并非总是需要以纯函数来解决所有问题,不必慌张,我们知道副作用会使状态的变化难以琢磨,在编程中应当以较少的副作用函数为目标, -使函数的预期与实际保持一致的同时不至于造成过多的影响,这或许会使在构思和声明时花费一些时间,但对上下文的代码块来说,是一件好事。 - -你可能发现有些时候会不可避免的改变了某些外部状态,比如缓存某些值,为此你陷入了重构的苦思,事实是不必过于担忧,你想做的就必然有道理。 -这就是 **编程中的取舍** 。学会在编程、架构、工程上有所取舍 (不是随心所欲) 后构建出的产品自然会嵌上独具一格的风采,这就是你的编程。 - -```js -// bad -let user = getUser() -const upload = () => { - user.name = `${user.name}-upload` - // fetch user ... -} - -// good -const user = getUser() -const upload = user => { - const body = Object.assign({}, user, { name: `${user.name}-upload` }) - // fetch body ... -} -upload(user) -``` - -下文,JS 优雅指南 - 2。 - -export default ({ children }) => {children} diff --git a/pages/posts/elegant-js-2.mdx b/pages/posts/elegant-js-2.mdx deleted file mode 100644 index 31bc27e..0000000 --- a/pages/posts/elegant-js-2.mdx +++ /dev/null @@ -1,196 +0,0 @@ -import { Layout } from 'lib/components' -import NextLink from 'next/link' -import { Spacer, Link } from '@geist-ui/react' - -export const meta = { - title: 'JS 优雅指南.2', - date: '2019-06-08T08:12:39+08:00', - description: '', -} - -接上文,在 JS 优雅指南 - 1 中我们聊到了基础的 JavaScript 编程指南, -包括如何可靠的声明变量、常量、函数、以及利用函数正确的构建逻辑,现在我们思考改善代码质量的第一步,如何命名。 - -在阅读代码之初,编码的风格与命名会比抽象方式、设计技巧更令我们印象深刻,很多时候也会为项目的风格定下基调。比如当你阅读一些算法工程师写的代码块, -常常会见到 _单字母变量_ 、_反复的声明_ 、_不知所以的赋值与拷贝_ 、_累赘的条件判断_ 等等,我们不能说这里有问题, -因为他们的代码大多的的确确是可以正常工作的,甚至在性能或储存空间上有一些优势,但这在工程,特别是大型工程中是不值得称道的。 -根据经验我们可以这类代码归类为 "糊屎",充其量是 "糊" 了一滩高性能的 "屎"。 - -现在我们做的是脱离 "**编程只不过是工具**" 的阶段,脱离 "糊屎男孩",让机器面有喜色、富有人性,使阅读者在某个瞬间切实地感受到创作者的思维热情与审美哲学。 - -
- -## 1. 富有准确性的命名 - -事实上,你完全可以使用 `doSomeThing` 代替所有的函数,毕竟它们真的只是提供某些微不足道的功能,但当你有了多个甚至是成百上千的函数时,这是一个灾难。 -这是一个浅显易懂的道理,即便是毫无经验的开发人员也会意识到命名爆炸的问题,他们隐约明白了什么是好的编程风格,但最后, -甚至是大多数都止步于 `doSomeThing` 到 "优美" 的某一站上。"这就够了" —— 他们这样告诉自己。 - -

- -**例 1**, 命名只需要有必要的词,除非有必要,否则不要堆砌 - -```js -// bad -const theBook = {} -const _b = {} -const bookObj = {} -const newBook = {} - -// good -const book = {} -``` - -**例 2**,可读的条件判断 - -```js -// bad -if (username && username.includes('prefix-')) { -} - -// bad -const prefix = username && username.includes('prefix-') - -// bad -const availableName = username && username.includes('prefix-') - -// good -const hasPrefixName = username && username.includes('prefix-') -``` - -**例 3**,可读的函数 - -当我们要从网络上获取用户信息时,`getUser` 就不是一个准确的表达,`get` 过于宽泛, -从数据库或网络、以用户名或 ID 都有区别,现在我们可以先从命名上思考它们的区别: - -```js -// bad -const getUser = name => {} -``` - -```js -// good -const fetchUsersByName = name => {} - -// good -const findOneUserByID = id => {} - -// good (any environment, any params) -const getUsers = (...params) => {} -``` - -**例 4**,准确的表达 - -属性可以避免不必要的描述,言简意赅: - -```js -// bad -const book = { - bookname: '', - length: '', -} - -// good -const book = { - title: '', - pages: '', -} -func(book.title) -``` - -注意单复数: - -```js -// bad -const book = findBooks() - -// good -const books = findBooks() -``` - -**例 5**,不必要的约定 - -通常在示例或无意义的遍历中,我们会把每一个回调函数的参数写作 `item` / `value` / `v` 等等,这在一些场景的确可以让阅读者忽略掉不必要的描述, -专注于逻辑本身,但并非总是合适的,特别是我们需要表达状态时: - -```js -// bad -const titles = books.map(item => item.title).filter(item => item.length > 0) - -// good -const titles = books.map(book => book.title).filter(title => title.length > 0) -``` - - - -## 2. 观察与思考 - -观察是一个模糊的界定,可以尝试让同事朋友阅读你的代码,问问他们的感受,哪些命名生硬,哪些词不达意,哪些是感同身受的。 -一个可学习的方式是在 `github` 上阅读代码,你也能注意到开发者们情绪的波动,这里真的需要这些定义吗、这里有必要写的这么短小吗、这是不是太 `OC` 了…… -试图理解开发者创作时想法是很自然的,这些情绪的波动能让你明白他们大约处于编程、人生、情感的什么状态,有助于你深刻的理解接口与设计。 - -在一个项目中见到 `created` 时,便可以知道这调用在 `create` 之后 (而非之前或之中),依次类推便可以有 `destroyed` 或其他函数,如果项目来自是富有经验的开发者, -这些细节会帮助你在代码中极快的理解作者的构思。 - -又如在 暴雪的官方 API 文档 中可以见到 `GetRewardSpell` 接口,既然有 `reward spell`, -我们可以推断自然是有 `GetRewardXP`、`GetRewardMoney` 一类接口,而它们的参数自然也相差不多。真的是这样吗?不,完全不是, -当你阅读几十分钟的文档后才会明白,这些接口创造之初或许有一些设计,但在各个版本的补丁与不断的重构后已面目全非,尽管命名的结构相差无几参数却大相径庭。 -在不断的阅读后将你开始注意到重新命名的接口、参数、返回值似乎正在朝某个方向改进,他们似乎在修补一些问题。 - -在阅读 暴雪界面源码 后我们更能注意到这些细节, -一些界面功能被改变了,程序员们只能被迫去修改这些接口,同时想要与原有的保持的一些同步,有时也会衍生出一些新的接口,它们被置于一些结构体中,随着时间的推移可以推理, -未来的一些接口也会被移入这些结构体 —— 这就是我们对于命名的观察与思考。我曾经见过有人说 "狗是人类的朋友,taobao 的文档连狗都不如", -这就是他观察的结果了,虽然令人不那么开心。 - - - -## 3.巧妙与投机取巧 - -我们可以断定 **巧妙** 是优雅命名中重要的一部分,如在 `rvm` 与其他一些命令行工具中的 uninstall 就是 `implode`,这有点意思,是吧。但事实上我见到 -绝大多数的巧妙不过是 "投机取巧",他们苦心孤诣的作品是一大段没有说明的八进制、二进制代码,一堆三元堆砌的单行逻辑,一些谐音名字,不合时宜地正则表达式等等, -这并不漂亮,这是歧路。 - -**例 1**,自作聪明的谐音 - -```js -// bad -const markdown2html = template => {} - -// good -const markdownToHTML = template => {} -``` - -**例 2**,可以语义化的正则 - -代码并非越简短越好,多数场景下我们需要尽可能的避免长篇累牍,但必要时,可以使用命名和语义化的代码块来试图说明逻辑: - -```js -// bad -const isUser = /^name/.test(user.name) && /^http/.test(user.blog) - -// good -const isUserName = user.name.startsWith('name') -const isUserBlog = user.blog.startsWith('http') -const isUser = isUserName && isUserBlog -``` - -**例 3**,优美 - -```js -// bad -const hasDirOrCreateDir = path => {} - -// good -const ensureDir = path => {} -``` - -```js -// bad -const moveCursorToLastLine = () => {} - -// good -const cursorUp = () => {} -const cursorDown = () => {} -``` - -export default ({ children }) => {children} diff --git a/pages/posts/getting-started-with-geist-ui-react.mdx b/pages/posts/getting-started-with-geist-ui-react.mdx deleted file mode 100644 index 27ea77f..0000000 --- a/pages/posts/getting-started-with-geist-ui-react.mdx +++ /dev/null @@ -1,202 +0,0 @@ -import { Layout } from 'lib/components' -import { - Link, - Note, - Spacer, - Text, - Code, - Display, - Image, - Tabs, - Snippet, -} from '@geist-ui/react' - -export const meta = { - title: '在 React 中尝试 Geist UI', - date: '2020-03-22T22:57:30.027Z', - description: '', - image: - 'https://user-images.githubusercontent.com/11304944/76085431-fd036480-5fec-11ea-8412-9e581425344a.png', -} - -有意思的是,和 在 Vue 的指南 中非常相似,我们也提供了 Geist UI 在 React 框架上的支持, -包括数十个有着统一风格的组件,当然还有完善的文档支持。令人兴奋的是,所有的组件都以 `React FC` 的方式实现, -它们都是以优雅的方案构建出的难以想象的高性能组件,也有着 React Hooks 与 Context 精彩的应用,在未来 `Concurrent` 受到支持时我们也会立刻考虑在它们在组件中的优化方案。 -就是这样,`@geist-ui/react` 是**进步的,高效的,极简风格且优雅的组件库**。 - - - - - 请确保你拥有最新版本的{' '} - - NodeJS - - , 同时也需要准备 NodeJS 的包管理器 - NPM - 或是 - Yarn - - - - - -为了轻易的和任何 React 项目接轨,也作为简易的示范,我们使用知名的脚手架 create-react-app 创建通用项目。 -**即便你的项目并非此脚手架创建,也能以此通用项目作为参考,非常简单的使用 `Geist UI`**。 - - - -## 创建 React 项目 - - - - - - - NPM - - } - value="npm"> - - 使用 npx 命令可以在线运行命令 -

- }> - npx create-react-app my-app -
- - 安装成功后可运行 npm start 确认。现在只需要将{' '} - @geist-ui/react 组件库安装至项目即可: - - - 使用 npm 安装 UI 组件 -

- }> - npm install @geist-ui/react -
-
- - - Yarn - - } - value="yarn"> - - 使用 yarn 创建 React 应用 -

- }> - yarn create react-app my-app -
- - 安装成功后可运行 yarn start 确认。现在只需要将{' '} - @geist-ui/react 组件库安装至项目即可: - - - 使用 yarn 安装 UI 组件 -

- }> - yarn add @geist-ui/react -
-
-
- -至此为止我们已经准备好了基础项目。如果你是初学者,在这一步遇到困难,可以在 这里 留下你的问题。 - - - -## 引用组件库 - -在 `src/index.js` 中,我们添加基础的 Context 支持 `GeistProvider` 到根组件下,同时别忘了引入组件 `CSSBaseline`, -它提供基础的样式重置与声明,类似于你熟知的 _normalize.css_ 。 - -```jsx -// ... -import { GeistProvider, CssBaseline } from '@geist-ui/react' - -ReactDOM.render( - - - {' '} - // 基础 Context 支持 - // 基础样式组件 - - - , - document.getElementById('root'), -) -``` - -你可以在 示例项目 中查看这次的代码变更,以确保完成了引入工作。 - -是的,`@geist-ui/react` 没有任何 `.css` 文件,我们把每个组件的样式分发给组件本身,你完全无需考虑样式的问题。 -这在你未来需要**统一定制主题风格**时非常有帮助,甚至能帮助你在 **Server Render** 时获的最佳效果。有关定制主题等高级内容, -你可以参阅 如何定制主题的文档。 - - - -## 第一个页面 - -在最初使用组件库时,完全可以不必知晓组件的属性与用法。现在可以清理 `src/app.js` 的内容, -添加一些 Geist UI 组件先试试: - -```jsx -// ... -import { Card, Code, Note, Spacer } from '@geist-ui/react' - -function App() { - return ( -
- - hello, world. I am using @geist-ui/react ! - - - - This note details something important. - -
- ) -} -``` - -我们添加了一些基础的组件,现在运行 `npm start` 就可以在页面中看到不错的效果。到此为止我们似乎做的不错,运用 Geist UI 构建了一个简单的页面。 -你可以在 示例项目 中查看这次的提交。 - -想要了解更多的组件?你可以看看 Geist - UI React 的 官方文档。 - - - -## 额外的支持 - -`@geist-ui/react` 还默认提供了一套非常易于使用的**黑暗模式**,当你切换到黑暗模式时,所有的组件、文字、色彩都会自动的跟随变化, -现在你所看到的博客页面就使用了它,点击下方的图标按钮可以预览效果。 - -当然,任何时候你都可以自定义所有的样式变量,这包括主题与单个组件。同时,在自定义这些基础变量时也无需担心过多的**重复渲染 (re-render)**, -`@geist-ui/react` 只会重新渲染那些你自定义过的色彩、间距或其他表现力参数,我们在样式的框架设计上充分利用了 React API 提供的 `memoized` 与 `diff` 特性, -在你的页面获得最佳表现力时也确保了性能与体验。 - -最后,`@geist-ui/react` 完整且优先的提供 `Server Render` 支持,你所见的博客与我们的文档都是由服务端预渲染所完成, -你可以在 这里了解如何配置。 - - - -## 问题和建议 - -`Geist UI` 是一个 **公益性的开源项目**,我们没有借助它盈利。但不用担心,项目至今为止已经开发且维护了超过一年,并且会持续地维护, -你有任何的问题、想法,都可以在 这里 留言, -我们很高兴看到每个新想法,即使你是一个刚刚入门的开发者,也可以帮助我们做的更好。 - -export default ({ children }) => {children} diff --git a/pages/posts/getting-started-with-geist-ui-vue.mdx b/pages/posts/getting-started-with-geist-ui-vue.mdx deleted file mode 100644 index a8cb189..0000000 --- a/pages/posts/getting-started-with-geist-ui-vue.mdx +++ /dev/null @@ -1,205 +0,0 @@ -import { Layout } from 'lib/components' -import { Link, Note, Text, Spacer, Display, Code, Image, Snippet } from '@geist-ui/react' - -export const meta = { - title: '开始在 Vue 中使用 Geist UI', - date: '2020-03-08T16:00:41.619Z', - description: '', -} - - - - Giest UI - Vue - {' '} - 是一个现代化的 Vue 组件库, - 它是一个用于构建现代网站和应用程序的开源设计系统。它提供了数十个具备统一化风格的组件与基础样式支持, - 也能帮助你轻易的通过组件的方式使用图标。通过本文你可以快速的搭建一个基于此组件库的前端项目。 - - - - - - 请确保你拥有最新版本的{' '} - - NodeJS - - , 同时也需要准备 NodeJS 的包管理器 - NPM - 或是 - Yarn - 。 - - - - -为了轻易的和任何 Vue 项目接轨,我们使用 Vue 官方的脚手架 @vue/cli 创建通用项目。 -**即便你的项目并非官方脚手架创建,也能以此通用项目作为参考,非常简单的使用 `Geist UI`**。 - - - -## 创建 Vue 项目 - -默认场景,你需要先安装 Vue 脚手架 (如果是自行编写 Bundle,可以跳过此节): - - - 安装 @vue/cli 到全局 -

- }> - npm install -g @vue/cli -
- -随后,我们可以使用命令生成基础的 Vue 项目: - - -vue create hello-world - - -选择默认选项即可完成项目的创建。事实上,这些项目打包与编译方式都不会影响我们使用 `Geist UI`,对于富有经验的开发者来说,可以自行定制。 -如果你在这一步遇到了问题,可以参见 官方文档 调试。 - - - 选择 默认 即可创建项目 -

- }> - -
- - - -## 安装 Geist UI - -现在,我们可以运行命令安装 `@geist-ui/vue`: - - -npm i @geist-ui/vue - - -打开项目的入口文件: `/src/main.js`,并在其中引入组件包: - -别忘了在项目中添加样式文件: geist-ui.css

}> - -```js -import Vue from 'vue' -import GeistUI from '@geist-ui/vue' -import App from './App.vue' -import '@geist-ui/vue/dist/geist-ui.css' - -Vue.config.productionTip = false -Vue.use(GeistUI) - -new Vue({ - render: h => h(App), -}).$mount('#app') -``` - -
- -运行 `npm serve` 试试,项目可以正常运行,你现在已经在使用 `Geist UI` 了。看起来没有什么变化?别担心,下面我们写一些内容就可以看到了。 - -至此为止,你的项目代码应该和 示例工程 一样,如果有问题可以参考它修正。 - - - -## 第一个页面 - -打开 `/src/components/HelloWorld.vue` 文件,将模板替换为我们自己的内容。这包含了一句话与两个按钮: - -```html - -``` - -这里的以 `zi-` 起始的标签就是 `Geist UI` 提供的组件。是的,我们引入一次后可以在项目的任何文件内随意使用它们,这些组件的接口简洁、易于使用, -同时也很酷,不是吗? - -想要了解更多的组件,可以看看 所有的组件文档。 - -你可以在这里参考至此为止 示例工程 的代码。 - - - -## 使用图标 - -我们准备了非常多的高质量图标可供使用,它们都是一个 Vue 组件,这意味着你从来不需要关心如何引用、添加、维护的问题,也没有任何的学习成本,只需要像往常一样使用组件即可。 - -图标由额外的库提供支持,运行命令安装 `vue-icons`: - -npm i @geist-ui/vue-icons - -随后你就可以在任何组件内使用这些 **图标组件**: - -```js - - - -``` - -觉得这里有些难以理解?没有关系,我们并不急着使用图标,你完全可以先熟悉 `Geist UI`。若是你对此感兴趣想要了解更多的图标,不妨看看我们所准备的 所有的图标文档。 - - - -## 仅在 HTML 中使用 - -有些时候仅仅只需要单个 `.html` 文件就可以完成所有事情,这样我们就不必构建一个大而全的项目,幸运的是,即便是在单个 HTML 文件中,我们也能轻松的使用 `Geist UI`。 - - - - 仅在 .html 文件中使用组件库意味着你必须还要在页面中引入 vue.js{' '} - 。 - - - -我们在 这里 准备完整的 UMD 示例, -你也可以按照这些代码创建一个极简的页面。 - - - -## 只使用样式 - -极少数情况下,你的项目已自制或是不需要任何组件,只想要单独的样式 (`.css`) 文件,不必担心, -你不用引入和组件库相关的样式,我们为你准备了一份单独开发的纯 `.css` 样式。 - -CDN 上直接使用样式文件

}> - -```html - -``` - -
- -你可以在 Geist Style 项目浏览。 -这份独立的样式文件实现了部分纯 `.css` 组件的同时保持了极小的体积,大约只有 `11kb`。 - - - -## 问题和建议 - -`Geist UI` 是一个 **公益性的开源项目**,我们没有借助它盈利。但不用担心,项目至今为止已经开发且维护了超过两年,并且会持续地维护, -你有任何的问题、想法,都可以在 这里 留言, -我们很高兴看到每个新想法,即使你是一个刚刚入门的开发者,也可以帮助我们做的更好。 - -export default ({ children }) => {children} diff --git a/pages/posts/how-to-get-npm-package-name.mdx b/pages/posts/how-to-get-npm-package-name.mdx deleted file mode 100644 index 61a9fef..0000000 --- a/pages/posts/how-to-get-npm-package-name.mdx +++ /dev/null @@ -1,63 +0,0 @@ -import { Layout } from 'lib/components' -import { Link, Spacer } from '@geist-ui/react' - -export const meta = { - title: '怎样获得一个很酷的 NPM 包名', - date: '2019-06-03T11:38:04.563Z', - description: '', -} - -当你开始写一个开源项目时总是要考虑有没有 github / npm / domain ... 各类命名冲突,如果你需要发布在 NPM 上那就更困难了, -因为在 NPM 初期有很多人创建了非常多的包仅仅是为了**占位**,现在看来仍旧有一大批简短、语义化的包名被占据连续六七年没有任何更新, -比如 `fish` `ok` `command` 等等。现在你可以通过一些 NPM 争议政策来要求将包的所有权转移给你,因为这些占位包对社区并没有价值。 - -我通过邮件获取了几个很酷的包名 done, -处理时间约在 1 - 4 周,时间取决于包的所有者是否有回复。即便当前包所有者的邮箱已经不再使用,没有任何回复, -只需要你的申请得当,NPM 工作人员还是会在 4 周后自动将包的 owner 设置为你的用户名。 - - - -### 哪些申请可能被接受 - -并非所有的包都能可以被申请转移,比如拥有较多下载量或依赖量的包。符合以下几种条件会被视为可转移的包: - -- 无或非常低的下载量 -- 长时间没有 release 或 update 活动 (数年以上) -- 没有内容的 package (仅占位) -- 没有 major 版本 -- 侵犯商标或版权 - - - -### 怎样申请 - -1. 首先运行 `npm owner ls ` 获取当前包所有者的公开邮件地址 -2. 发送邮件至 **包的所有者并且抄送** `support@npmjs.com` - -按照 "npm disputes" 的说明,要求转移的邮件内容必须要 _足够的友善_ ,至少要包括 package 可能违反的条约、 -可以转移的原因 (如无维护无贡献)、包的名称以及你的 `npm username`。这里最好使用 NPM 的通知邮箱发送,便于 NPM 工作人员查询你的账号与发布记录。 - -需要注意的是,如果你想要转移的 "npm package" 存在商标版权侵犯或者是占位问题,NPM 工作人员将立刻处理,这不在上述的 4 周流程之类。 -即便是备作未来使用也会被视为占位,在 NPM 的包管理中,处理占位将是第一优先级。但是 NPM 并不会主动去扫描所有的包识别占位问题,除非你申请解决。 -(主动扫描只会迫使占位用户去发布一些假内容) - -根据我的经验,大部分开发者在面对不再维护或占位的包名时都是乐意转让的,但这也可能被拒绝(试想别人邮件你要求包名时),如果被明确的拒绝同时也非占座和版权商标侵犯问题, -你应该都得不到这个包名了 (建议发邮件时保持礼貌。)。 - - - -### 关于组织与用户名 - -组织 (`Organization`) 有些特别,因为在 NPM 中默认是不展示组织的私有包的,所以你无法判断这个组织是否有私有包还是简单的占座。 -想要请求转移组织必须要直接联系 `support@npmjs.com` 并提供组织名,NPM 会帮助你与组织的所有者沟通。这里也遵循 4 周规则 (即无答复 4 周自动转移)。 - -用户名理论上与组织名可以采用相同的申请方式,但据我所知,好像并没有申请成功的案例。NPM 也说明转移用户名是 "几乎不可能的"。 - - - -### 关于举报 - -请不要因为占位或内容不符在 `package page` 上点击 `report`。这是用于报告包本身的安全内容问题。如果因为包含违反当地法律、不合适的内容、侵权等等, -可以尝试举报滥用: `abuse@npmjs.com` 。(注册与你的包名非常非常相近的名称,有意误导也可以算作此类) - -export default ({ children }) => {children} diff --git a/pages/posts/talk-about-nextjs-after-9_3.mdx b/pages/posts/talk-about-nextjs-after-9_3.mdx deleted file mode 100644 index 059b6f5..0000000 --- a/pages/posts/talk-about-nextjs-after-9_3.mdx +++ /dev/null @@ -1,57 +0,0 @@ -import { Layout } from 'lib/components' -import { Link } from '@geist-ui/react' - -export const meta = { - title: '在 9.3 版本之后聊聊 Next.js', - date: '2020-04-12T00:58:51.562Z', - description: '', - image: '', -} - -在 Next.js 9 Release 之后最受关注的是动态路由,它是来自社区的第一个 Feature 提案, -新的动态路由系统用很好的方式回答了系统文件映射路由的解决方案如何处理动态路由的问题。 -尽管这些 `Rails` 风格的路由系统看起来很好用,但对于服务端渲染框架来说动态路由也引入了新问题, -比如在所有动态页面中规划 SSG 与 SSR,在 9.3 版本发布之后,Next.js 给出了新的答案。 - -我们知道 SSR (server-side rendering / 服务端渲染) 框架的内容渲染是在每次请求之后,由服务端生成一个最小可用页面, -这是确保 SEO 和首屏体验的重要方式,由于 TCP 的 slow start 与物理延迟等原因,SSR 仍旧在首屏体验上远超 SPA 应用, -关于这一点你可以参阅 构建 Web 应用 7 要素 这篇文章。 -但 SSR 应用无法充分的利用首屏缓存,比如应用动态页面中的一部分是根据用户分发的私有信息,它们必须在服务端从持久化存储服务中检索, -这会使所有的页面,即便是那些首屏需要缓存的公共静态页面都丢失 SPA (Single Page Applications / 单页面应用) 带来的优势。 -在 Next.js 9 中,一个被称为 Auto SSG 的方案解决这个问题。 - -Auto SSG (Automatic Static Site Generation / 自动静态站点生成) 不是我们熟知的 `next export`,新的方案不会把整站导出为静态文件, -而是**针对站点中一部分不需要服务端渲染的页面导出静态文件**,在余下的部分我们仍旧使用高体验的 SSR。这意味着我们的单个应用也是 **混合构建 (hybrid built)**, -由开发者自己分配哪些页面需要 SSR 或是 SSG,这带来的进步和体验是难以想象的。从技术实现上来看, -Next.js 总是把没有要求 `getInitialProps` 数据预获取的页面标记为静态的,这给我们省去了极大的工作量,现在看来也是合理的。 -但我们知道动态路由在共用一个**页面文件**,那么在动态路由的页面文件中,要么我们使用 `getInitialProps` 标记所有 `[params]` 为动态的, -要么所有的 `[params]` 页面都是静态的,只能选择一种,这是混合渲染在动态路由系统中遇到的新问题。 - -在大型的电商或营销系统中,即便是处于同一个 `url pathname` 页面,也有可能需要完全不同的数据策略,比如 `/foods/apple` 正在面向所有用户, -此页面可以受到缓存保持长时间不变化,但 `/foods/organic` 是一个新推出的产品,希望能够在一定的维度上进行灰度试验, -通常的做法是由 SPA 的客户端向服务端发送检查来确认当前用户可以浏览页面的哪些内容,如果你想要更好的优化,SSR 会在服务端预渲染之前拼接页面内容, -但在 `/foods/[params]` 文件中添加 `getInitialProps` 会让整个动态路由转变为服务端渲染,这不是你想要的。你可能要更多的 hack 来解决这个问题。 -Next.js 9.3 之后,为了解决此类问题,将数据的预获取分为三个部分: - -- `getStaticProps`: 在构建时获取数据 -- `getStaticPaths`: 在构建时为动态路由指定预渲染 -- `getServerSideProps`: 在服务端为每个请求获取数据 - -现在我们可以在动态路由的页面中指定 `getStaticPaths` 返回哪些 `paths`,从而精细的控制自己的动态页面使用哪种方式展示。 -这里是 更详细的文档。 - -为了保持 API 的一致性和扩展静态能力,Next.js 也增加了 `getStaticProps` 纯静态页面在构建时的数据问题。 -这些 9.3 以后带来的变化在我看来是非常了不起的,这些 API 让 Next.js 成为了一个名副其实的混合构建框架。 - -在通常场景中,我们构建一个包含指定内容的静态页面有两种选择,一种是 SPA 式的,你需要在 webpack / rollup 等构建工具运行时从持久化存储服务中取得数据, -再分发给每个页面或是存储为结构化可缓存的公共文件,一种是 SSR 式,在服务端对每个请求进行查询分发。目前看来这两种方式或是包括传统模板渲染的解决方案, -都会面临我上文所说的困境,同时开发体验也是低下的。但 Next.js 9.3+ 给了我们一个非常巧妙的答案,至少在当下看,仍旧是最优的解决方案。 - -值得关注的是这些工作都是对 CMS 解决方案的一种改良,以优雅的方式解决静态与动态最大的受益者是营销站点、广告、媒体等 Web 应用, -从 Now 去年移除掉非常多体验不那么友好的 Serverless 语言支持来看,这些领域将是他们未来的主要客户, -在对原来横向扩展过多的 Serverless 语言、域名等服务进行收敛之后,似乎找到了领域内的发展方向,Next.js 也非常出色的完成企业内部的技术驱动任务。 - -是的,Next.js 不是社区驱动的,这显然是一个来自企业内部业务调整而衍生的 Feature。尽管 9.3 让我非常满意,它还是我最喜爱的框架。 -但我还是希望 _Guillermo Rauch_ 不要太快把钱花完,以免制约了这些的开源发展。 - -export default ({ children }) => {children} diff --git a/pages/posts/thinking-under-serverless.mdx b/pages/posts/thinking-under-serverless.mdx deleted file mode 100644 index a4d760c..0000000 --- a/pages/posts/thinking-under-serverless.mdx +++ /dev/null @@ -1,410 +0,0 @@ -import { Layout } from 'lib/components' -import { Link, Spacer, Code } from '@geist-ui/react' - -export const meta = { - title: '基于 Serverless 的部署平台构建与思考', - date: '2020-01-18T19:53:39.606Z', - description: '', -} - -在适合的场景中将业务部署至 `Serverless` 相比于传统服务有极大优势,诸如降低成本、弹性伸缩、高扩展能力、高稳定性等等这里不再细说, -我们这次聊聊在业务中如何把服务合理的部署到 `Serverless`。本文中的 `Serverless` 指代适用于部署服务的 `FaaS` 元素, -大多数云平台都提供 `FaaS` 支持,如 AWS Lambda / Azure Functions 等。 - -由于 `Serverless` 的特性约束,我们需要在固定的编程模型上构建模块,这无论是对移植还是构建新项目都有巨大阻碍, -同时还会与原有服务的结合性、部署与维护的难度,标准化等等问题值得考虑,如今市场上推出了很多基于云平台的 `Serverless` 提供的快速部署服务, -它们在抹平差异与提高体验上做出了很多成果,遗憾的是至今没有以此为基础纯粹的开源系统, -这里我借鉴了很多 `Serverless` 部署平台设计的经验,提出一种基于无服务化的可在生成使用的工程化的部署平台设计思路, -大家可以在此基础上设计构建现代化的部署平台。 - -在此之前,你可以阅读有关 Serverless 架构的基础知识, -了解其与传统运维架构的区别。另,我在下文提出的模型已在生产上得到实践,设计细节也经过一些优化,但介绍时会尽量减少技术细节,多谈论具有普适性的方法。 - - - -## 优势 - -在开始之前我们先讨论构建专用的 `Serverless` 部署平台的优缺点,其中一部分是 `Serverless` 本身的特性所致, -这证明了合适的场景下迁移至 `Serverless` 是有巨大成效的,一部分是平台设计上的优势: - -1. 高速的部署。 - - 传统部署方式常常一次花费数分钟,且难以控制更别说维护与扩展。新平台让部署始终保持在秒的量级,部分项目从触发到完成部署能够保持在 10 秒以下。 - -2. 项目具备自动伸缩与高可用性。极低的资源消耗。 - - 这是 `Serverless` 自有的优势。 - -3. 高可控。 - - 下文的设计中我们保持了多个函数端点,回滚与备用方案简单可靠。 - -4. 轻量。 - - 也是由于部署成为轻、快、小的工作,大量的工程都得以拆分,按模块推进迭代使所有的工作都向轻的方向发展。 - -5. 高开发者体验。 - - 除去项目上的优势,部署平台保证了在任意阶段回溯、中断的可能性,且可拆卸非常易于维护,对于应用的开发者来说也具备感知低、体验高的优势。 - -## 流程总览 - -我们可以把所有的部署环节分为 4 个步骤,包括从发起部署请求开始到部署完成的阶段: - -1. 在用户机器 (或 CI/CD 服务端机器) 的本地同步环节 -2. 通用服务端的鉴权、处理、同步构建信息 -3. 构建服务的打包与编译 -4. 部署服务,部署至 Serverless 并搜集必要信息 - -``` - - [serverless bridge] --> [builder] --> [init shell] - [auth] ↓ -[ci/cd hook] | ↓ |--> [build service] -[user cli] | <--> [general service] --| -[website] | ↓ |--> [deploy service] --> [server less] - [build database] |--> [router] - |--> [log][health][...] - ----------------- - [code storage] -``` - -**关于取舍上的疑问:** - -- 我们为什么把 **编译** 与 **部署** 的工作不放在一个环境中运行? - - 部署 Serverless 时,未来很有可能扩展多个语言,并不仅局限于前端,甚至可能业务需求超出当前的平台的构建功能, - 这时可以允许用户为构建过程自定义脚本,此时编译部分相当于只提供上下文的一个容器环境,它是清洁的, - 用户即便在其中如何操作也只会影响相对于此用户的业务代码,并不会对部署的环境变量、脚本、私密信息造成影响。另一方面, - 部署服务实际上消耗的 CPU 极低,相比于构建服务更健壮,将部署服务单独运行不仅便于实现 `redeploy` 类的需求,也更能保证在回滚的可靠性。 - -- 区分多个服务的通信成本? - - 存在通信成本,但很低。实际上面对更复杂的业务时我们可能以上步骤拆分的更细致,但服务之间还是只有 触发/被触发 的事件关系, - 并不会长时间挂起通信。即便我们把所有服务设计在一个进程中,也只是共享内存,省去了对用户部署项目的源码读写时间, - 事实上这与构建时间相比不值一提。另外由于我们在 `code diff` 上有着良好的设计,每次对用户源码的读写成本会更低。 - (事实上即便你把所有的功能杂糅在一期,在回滚、查看构建结果等需求的面前,还是需要保存用户代码) - -- 接入第三方鉴权的难度? - - 很多公司已经有了自己的鉴权系统,服务越多意味着每个服务都要单独接入鉴权系统且针对不同的权限设计不同的控制范围 (而且面对上游的变动你得做非常多的垃圾工作), - 这是很多让人头疼的工作。这里我们只需将通用服务端接入鉴权,其他的服务既鉴权也不鉴权,具体我们下文细说。 - -**按上所示我们梳理一下全部的部署流程:** - -1. 用户端通过 `cli` `hook` 或其他方式将代码上传到通用服务 (我们也可以称为 `web service` ,因为主要为前端提供服务),在通用服务中我们至少链接一个鉴权服务与部署数据库, - 即可以辨明 **用户 - 项目 - 部署** 等关系。 -2. 在通用服务中还需要对待部署的源码 diff,将变更后的代码储存到代码库 (`data-storage`) 并更新一个版本,随后向 `build-service` 发起一个构建通知。 -3. `build-service` 根据指定标识拉取指定代码,预处理代码并在众多的 **构建服务堆** 中找到一个合适的构建脚本去处理,最后开始构建。 -4. `build-service` 完成一次构建之后只发出通知并开始清理环境 (如上传完成的代码、清理构建日志等)。准备开始下一次构建。 -5. 在通用服务中通知 `deploy service` 开始部署。 -6. `deploy service` 也是通过标识开始拉取构建完成的代码,检查后根据通知部署至 `serverless` 并设置指定的路由、网关、日志等操作。 -7. 最后由 `general service` 通知用户部署完毕,返回各地址与详细信息。 - - - -## 收集部署代码 - -无论是通过 Git Hook 还是用户本地运行命令,收集源码都是一次部署的第一步,与常见的打包上传、构建后上传、推镜像等方式不同,我们需要更注重用户代码块的重量与收集速度, -因为按本文的架构设计我们需要把所有的源码文件全部上传到 `general service`,这当然不可能每次传输数十兆的文件,常见的解决方案是将用户的每个基础文件或文件夹与记录对比, -只上传修改后文件,这和 Git 有点像。 - -假设我们的服务中包含了用户所有的文件描述与哈希,那么就可以轻易对比一个文件是否改动过,在逻辑的实现上可以考虑以目录的层级为基础,广度优先逐层对比, -直到收集完所有的文件与它们的描述信息,再逐一传送给服务端,最后我们向服务端请求一个部署指令。在此也包含几个常见问题: - -- 用户的鉴权 -- 项目的归属 -- 部署的权限 -- 部署版本等单次部署信息 - -面对 **用户-项目-部署** 这样非常简单的逻辑关系我们可以轻易在服务端实现,不必细说,只谈谈何时为它们建立关系。 - -我们可以把 **项目的维护**、**部署的维护** 看作是不同的资源,但它们与项目的源码都没有任何关系。在记录时,假设这次部署没有任何源码, -我们只为用户记录 `建立项目 - 绑定项目与用户 - 建立部署版本 - 绑定部署版本与项目` 简单的关系,最后再开始接受这次的源码上传, -将这次的源码看做一个库存放在 OSS 或其他低频写入的数据库中,在写入源码时可以将部署版本写作前缀或是描述信息中 (非 SQL)。这样就可以通过任一源码文件找到部署版本, -也能通过任意版本找到所有源码。举个例子, -本地存在多个文件存放于不同文件夹中,开始这次的源码收集: - -1. 同步用户、项目信息,创建一个部署版本,准备使用部署版本来上传源码。 -2. 收集源码: - - 2.a 从目录层级浅到深逐一收集,立即与服务端对比,如果未改动则放弃文件夹下的所有文件 - - 2.b 在原有文件对象 (如: `Stats`) 的基础上为每一个已经改动的文件创建新的描述对象,包含该文件的相对位置 - - 2.c 统计改动大小等信息,依次上传所有的改动 - - 2.d 所有的改动上传完毕,请求服务端锁住所有文件并且不再接受改动 - -3. 服务端验证所有文件的合法性后将 **文件描述信息** 与文件一同写入 OSS (这里以 OSS 为例) 的某个属于此项目的版本集合内。 -4. 服务端收到部署 `v-d.d.d` 版本的请求与部署相关信息,向 `build service` 发起通知同时更新版本的记录信息。 - -先说说这样设计的目的。在收集代码部分我们着重于两点:一是把所有的源码拆分开按文件上传,只记录它们的位置信息,而非普通的打包; -二是将源码与 **部署**、**项目** 等业务逻辑关系分开处理。记住这两点,这是此文部署服务设计的关键所在。 - -很多的部署平台、服务的需求提现上之所以不能够按需部署就是因为他们粗暴的把单次源码打包上传,对于来自 Git Hook 触发的源码更是如此,即便有 `diff` 也是在经过网络传输之后, -这其中已经浪费了非常多的网络传输时间,想象一下当你改动了一个数十甚至上百兆的项目后,被迫要等数十秒甚至数分钟才能完成构建的触发,特别是在外网、跳板连接时,效率可想而知。 -甚至它们在构建后只是粗暴的回收构建容器,更无需谈与上次构建的源码对比。 -这里我们可能只需要通过数十次 **简单地**、**快速地**、**并发地** `HEAD` 请求就可以弄清楚究竟要上传多少代码,服务端也只需要并发地将 `hash` 与 OSS 内项目的集合进行查询是否有此 `hash` 相关文件即可, -实际落地时,大型项目的 `query & diff` 时间也能控制在数秒左右, -这样的设计可以极大的节约构建前的准备时间。 - -上文多次提及将编号版本的源码存储上 OSS 或其他数据库中,这样设计有多个意义:源码在多数情况下是较大较多的文件块,甚至有时还会包含媒体类型文件, -特点又是单次写入永久不会改写,未来在其他服务中最多也只会读取,单独存储这样的大量数据就不会影响原有的数据库, -也非常利于我们在未来环境变更、业务变更、性能优化。比如当你需要迁移到某个云服务商时, -你可以考虑他们内网通信的一些大文件存储服务。 - - - -## 准备构建的设计 - -### 在 `general service` 的准备 - -按上文设计,直到文件同步全部完成我们都没有开始构建,这是因为我们把 **构建** 这个动作的权限仍旧放在 `general service` 上, -这样设计的优势在于用户可以在任何时间以自己的鉴权信息构建指定版本号的所有文件,`rebuilt` 这类的需求在当前部署系统的设计中可以顺理成章的实现。 -比如你正在使用 Git Hook 反复的触发构建而代码并没有变化时 -这显得很有用。 - -在构建的请求中,别忘了收集一些构建所需的信息,如用户青睐的脚本、指定的 `before-build`、指定的 `builder` (下文详细说明) 等等。 -现在可以通过 `general service` 向 `build service` 发送一个通知:『构建开始了』。 - -在 `build service` 的准备工作中,几乎没有任何关于鉴权的要求,只要是来自服务推送的构建就完成,很简单。我们在代码层面几乎只接受一个简单的信息, -就是所谓的版本编号,因为所有的源码已经储存在了额外的空间,我们可以只通过此唯一版本号轻易的找到与本次构建相关的所有文件并将文件全部同步下来。 -(这次同步代码是只读的,但也必然是内网的同步,损耗时间的极短,不必担心) - -``` -[general service] <---> [build service] <--- [code storage] -``` - -在 `general service` 这端,我们还不能扔掉用户或是 Git,还需要 hold 住这个链接并向他们传输一下构建日志信息, -这里你可以选用第三方的日志服务或是直接透传构建服务器的日志数据库。无论如何,日志查看的权限仍旧还在中央服务的这里, -这非常易于你设计或接入权限系统。在未来,你还可以在中央服务上加入一些其他指令,如中止构建、立刻重新开始等等, -中央服务也只需要简单的更新一次自己的记录数据库并与 `build service` 发出通知即可。 - - - -### 在 `build service` 的准备 - -好了,我们直到收到构建通知时可以简单的开始在 `code storage` 上下载源码,但这还不够,我们还得将源码文件复原成用户上传之前的层级关系。 -也许你已经想到了,很简单。由于我们在每个文件的描述信息上都记录了它所在的相对位置,`build service` 只需要一次遍历即可恢复整个代码库。 - -当然,这些用户代码在未来需要隔离构建,我们至少要准备一些容器,这里有两种方案: - -1. 使用单独准备的脚本来控制容器。 - - 单独脚本的好处是单独的脚本更容易分离和维护,甚至你可以用自己熟悉的语言来写管理脚本,比如有很多成熟的 `python` `shell` 管理脚本,只需简单的修改即可使用。 - 难点在于当你要从代码中注入一段逻辑或参数时只能通过环境变量这个办法,如果有交互、控制类的需求还需要在代码中保留这个运行 `shell` 的子进程信息并管理。 - -2. 使用编程通过接口控制。 - - 与上述相反,你可以在代码中根据业务逻辑更加精细的调整容器,如预先建立一个池分配资源。当然你需要自己完成所有的控制细节,如创建、管理、挂载、同步日志等。 - -在真正的启动容器之前最好别忘了设置好环境变量。假设我们有一个基础的构建脚本 `build-init.sh`,`build-init.sh` 内不应该涉及任何具体的语言、框架、编译方式等信息, -我们借助于这个初始脚本来规划在不同的语言、框架时如何构建 (具体在下文中介绍) 。 -而在收到 **构建通知** 时携带的元信息最好在容器的环境变量中设定好,这可能是当前项目的构建偏好、指定初始化位置等信息, -但**绝不应该是任何密钥**。在构建容器初始化设置的所有环境变量都应该只与这次构建相关,与部署无关,不要有任何包含项目所处的业务信息。这是为了构建的安全考虑。 - - - -## 构建 - -我见过很多在 `Jenkins` 上写几句脚本就自称自动化、打包个镜像就算发布的平台 (可能你也正在公司里遭受这种『平台』的折磨),这些『平台』服务大多有一些通病: - -- 发布慢 (因为构建很吃力) -- 支持语言少,扩展极难 -- 构建脚本调试困难,构建脚本调试权限混乱 -- 构建用时不透明、不隔离、不可控 - -在你下定决心要优化构建脚本时,花了五牛二虎之力找到那个写在一堆命令中间的命令后发现,居然是随手安装的某个服务, -面对依赖众多年代久远长篇累赘的脚本时你才发现优化它们几乎是不可能的事。并无夸张,造成混乱的原因是构建平台设计之初没有将 **构建** 这一部分解耦, -更没有在根据不同语言设计可扩展的构建插槽,面对不断迭代的新语言、新框架需求,逐渐变得无法维护,体验低下。 -当然这些人中也不乏佼佼者,它们找到了一个偷懒的好办法,就是在每次构建时都让用户自行写一个 `dockerfile`,既不关心过程也不关心内容, -也就是我们常说的『无为运维』之道。 - - - -### 构建原理 - -由于要在 `Serverless` 上运行我们的构建结果,所以仅仅构建是不能满足需求的,我们还需要一些功能来抹平 `Serverless` 与单片机之间的沟壑。 -我们的目标是构建过程像在单片机上一样简单,但也要能够无缝迁移到 `Serverless`。除此之外,构建服务可能面临多语言、多框架的问题, -在能够完成任务的基础上要做到进行合理的抽象拆解,让构建过程简单到谁都可以写,每种框架的脚本也能自由切换。本文以 `NodeJS` 为例,介绍设计的方案: - -``` -[builder A] [builder B] [builder C] ... // repos ---------------------------------- - [npm / cdn / oss] ---------------------------------- - ↓ ---------- ---------- - init.sh - ↓ - starter.js (nodejs shim) - ↓ - require([builder])(configs) - ↓ - output files - ↓ - [end.sh] -``` - -在一个构建过程中,会有以下几步: - -1. 在容器启动时挂载启动 `init.sh` (或者任何你在容器指定命令时第一次启动的脚本)。这个脚本用于维护基础环境。 -2. 用于构建的 `starter.js`。当然你也可以跳过步骤 1 只用 `NodeJS` 解决所有问题。 - - 2.a `starter.js` 收集用户指定的构建目标,同时也可以自行做预构建,如分析目标框架。 - - 2.b 根据指定框架在 `npm` / `cdn` / `oss` 或者任何你想要的地方下载准备的脚本,我们可以称为 `builder`。 - - 2.c 引用 `builder` 并向其中注入当前构建信息。如 **入口**、**输出目标**、**可用工作目录**等。 - - 2.d 由于 `init.sh` 的初始化,`builder` 只在可用工作目录内构建并输出结果到出口。 - -3. `NodeJS` 退出,`end.sh` 收集退出码等信息并将输入文件上传或储存到 `code storage 2`,也就是专用的输出代码储存空间。 - -为了能够保证构建完成,我们也需要准备相应的 `builder`,粒度可根据场景决定。 -这样做的好处是我们所维护的 `builder` 不再是一段在子进程中执行的脚本,而是有上下文的,经过预处理的插件,比如这里外挂 `python` 插件, -编写者根本不需要考虑进程和预处理问题,`builder` 编写者只需要写一个有具体参数的函数 (当然你可以用一个通用类型来检查约束这个插件函数), -函数只做一件事,根据传入的参数做指定的构建。在公司内部,任何新框架新语言的改革都可以轻易的维护 `builder` 项目 (新增一种插件即可), -如果是企业级或开源服务,也可以让各个语言、框架的专家分别负责自己的 `builder` (`now` 就是这样做的), -他们对于自己浸淫已久的框架有有着自己的优化手段。 当然,如果你喜欢,也可以一直在插件里写 `shell`。 - -既然存在上下文同时又是框架专属插件,测试与维护起来更加轻而易举,在更改、新增一个插件时可以通过测试用例来保证构建的稳定性。 - - - -### 链接 Serverless - -上一节中提到在构建过程中插入以编程语言为基础的插件,也许你会担心这样做增加了系统的复杂性,其实不然, -在面对 `Serverless` 不同的编程语言接口时,我们还是需要考虑把每种编程语言、框架进行手动接入, -因为一个普通的项目是无法直接部署在 `Serverless` 平台上。不同平台在不同语言上对上下文、handler 等有着不同的约束, -你需要根据自己选用的平台 (如 AWS / Azure / Google / Aliyun) 和框架来考虑如何链接。 - -同样以 `NodeJS` 为例,在大多数的 `Serverless` 平台上他们对 `req` / `res` / `context` 等对象进行了自己的封装, -我们可以设计一个 `bridge.js` 来抹平之间的差异: - -```js -// bridge.js -module.exports.handler = (req, res, context) => { - // req.xx = ... - // res.json = function() {}... - - try { - // PLACEHOLDER - } catch (e) { - console.error(e) - process.exit(1) - } - return code(req, res, context) -} -``` - -在每个 `builder` 中,应考察平台与语言框架之间的差异,来设计 `bridge`,最后将 `PLACEHOLDER` 替换成构建后的入口。 -如在 `NodeJS` 中我们需要扩展 `req` / `res` 至标准 `HTTP` 对象。在不同的 `Serverless` 平台上真正部署时, -都可以指定从 `bridge` 开始执行,经过中间件抹平差异后开始引用业务代码。 - -在部署中我们尽可能的不 `hack` 用户代码,这里还可以统一集成一些监控、健康检查、日志的服务。 - - - -### 回收代码 - -在构建结束时构建服务需要将 `outputs` 回收到指定储存空间而非直接传递发布,一方面是便于多次覆盖发布,一方面是需要展示给用户查看具体的构建成果, -有必要时我们也可以让用户下载这些文件手动部署。构建完成得到正确的退出码时从容器中拷贝出代码上传,与收集代码的工作类似, -这时你可以为每个文件记录一次位置信息打散再保存,也可以打包压缩后储存在 OSS,因为大多数 `Serverless` 都支持从 OSS 直接部署一个压缩的代码包, -这样我们就省去了很多后续工作。 - -注意,到此为止我们都还没有处理业务相关的环境变量、密钥等业务信息,所以从容器 `outputs` 取出的代码包虽是已构建的但也不能正常运行, -我们还需要在部署服务中将它们结合分发到各个环境中。是的,本文的设计中构建环节并不区分环境,构建代码本身就不包含环境信息, -如果你的业务出现构建与环境强关联,建议你把构建服务部署在多个节点上。(当然,并不推荐这样做) - - - -## 部署 - -### 部署目标 - -`Serverless` 的特点是启动快,单次运行时并不处理复杂业务,能够将业务解耦成细粒度的逻辑使开发者专注于逻辑本身, -所以我们通常不会在每个 `Serverless` 节点的业务上部署业务路由。并非无法实现,你可以通过传统路由的方式识别不同的路径参数来解决不同事务, -但在很多人的设计下这可能会使单个 `Serverless` 服务成为一个巨大而全面的应用,保持着长时间运行而非拆分组件, -这就丧失了原本的优势:横向扩展能力、低成本、高速部署与启动、自动收缩等等。 - -试想一下,如果你业务中的核心业务被调用与计算的频率较高,而周边服务较少,甚至可以依赖缓存,那完全可以将它们解耦成多个服务, -通过你刚刚构建的平台一键高速部署,核心业务有着 `Serverless` 自动收缩、高负载、极速启动的的特点,而周边服务则多数时间的处于『休眠』状态节省资源, -如必要,还可以继续横向扩展服务无痛接入。这在某些场景下是非常难得且高效的解决方案。 - -那是不是所有的业务路由和转发都要交付**网关**或是服务商提供的**负载均衡器**来解决?不完全如此。 -当你考虑到所有业务适合作为单个模块时则可以讲它们部署在单个 `Serverless` 节点上,涉及模块之间的联系则可以交于外部, -绝不是单个**计算函数**只处理单个问题。简而言之你可以做到任何一点,其中的考量视业务场景而定。 - -还有一点值得在部署之前考虑,由于 `Serverless` 是无状态的,我们不会在容器内存储任何数据 (这样做没有意义), -常见的解决方案是连接第三方的数据库与服务,如在不同的云平台会有内网可操作的数据库与其他服务。部署时所有的第三方服务都需要有多环境。 -我们需要谨记无状态的特点,**它是好的编程风格**但不适合所有人,这意味着你不能再依赖内存保存所有状态, -也不能在所有的服务中共享内存信息 (它们本来就处于不同的容器中),每当你要保存一些状态时都要考虑通信。 - - - -### 部署方式 - -由于我们在构建时已经把代码包完整的保持在 OSS,所以代码块的部署工作非常简单,只需每次接受 `general service` 通知时找到相应的存储位置即可 (不下载)。按上文所述, -`general service` 在调用同时还会收集用户配置的 `Serverless` 外部路由信息、密钥、环境变量等,我们需要加以整理并逐一创建: - -1. 为用户在 `Serverless` 平台创建项目、服务等,具体视不同的平台接口而定。 -2. 创建一个 `Lambda` (视平台),入口指定为我们固定的 `bridge` 。 -3. 填入用户指定的内存大小、运行上限、密钥等信息。 -4. 创建网关等第三方服务 (如果有)。 -5. 根据用户、项目信息创建子域名,并指向此 `Lmabda`。 -6. 根据指定的缓存信息创建 CDN (如果有)。 - -完成这些步骤后一个可以被业务直接使用的 `Serverless` 服务基本部署完成, -我们可以在 `deploy service` 直接落库并通知 `general service` 部署完成,将聚合信息传递给用户。 - -在各个 `Serverless` 服务平台中,只要我们没有开始调用服务总是不计费 (或计费极低),所以在部署时的策略与传统的替换、滚动等是不同的, -我们总是新增而非修改,每当收到一个新的通知,无论是新项目、新版本还是重新部署我们都会创建一个全新的函数服务并配置全新的子域 (或其他标记方式)。 -当某个节点需要转到生产时我们以网关、负载均衡、自定义路由、域名解析等方式将生产流量指向函数即可,这样做的好处显而易见: - -1. 总是可以回溯到任意版本且设计简单、回滚高速 -2. 按端点统计资源,大型业务也能一目了然 -3. 可以做 `pre-production` 预热,无缝切换 -4. 多环境只需指定多服务与多域名连接至指定的端点即可 - -对于部署前端资源,可以让资源静态资源运行在一个函数中,在外层做 CDN 即可,对于前端现在流行的 SSR,函数计算的容器就是完美的服务端。 - - - -## 优化与实践 - -### 关于部署速度 - -与传统的打包部署相比,本文介绍的基于 `FaaS` 平台的部署架构可以极大的提高部署速度,同时由于项目的解构与拆解,一个函数模块从收集直到部署完成只需要几秒的时间, -即便你的项目足够大,也在秒的量级。一方面得益于代码包更新的优化,一方面得益于 `Serverless` 端点几乎可以忽视的启动时间。对于重视部署速度、高速迭代的团队而言, -这几乎最优的解决方案。 - -有少数项目在业务拆分后还是有非常多的外部依赖导致 `build` 时间过长,我们可以额外建立一个 `cache storage` 用于缓存的外部依赖, -在构建时按项目区分即可。这部分依赖可以提供在 `builder` 的上下文中,由每个 `builder` 自行对比决定什么场景需要使用外部缓存, -不同语言框架的设计各不相同,也可以做到各个领域内的优化。 - - - -### 关于构建容器 - -虽然文中推荐手动挂载容器构建代码,但你仍旧可以选择使用 `Serverless` 构建代码,也就是每一个构建单独创建一个函数端点, -优势是减少管理和开发成本,但目前国内的云平台普遍没有较高函数运行的内存、代码空间也比较低,实用性较差, -如果正在接入 AWS / Azure 等平台 (或是自建,如基于 `Kubernetes` 的 [Fission](https://github.com/fission/fission)), -可以试试这种更简单的方案。 - - - -### 安全性 - -关于平台的安全性与可用性你可以在相关 `Serverless` 平台查看,部署平台承担部署但并非运行容器。对于部署平台, -安全性的考虑集中在插入的部署脚本,但始终只在容器中进行构建并不会对其他项目产生影响,如必要也可以对 `builder` 采取白名单与过滤的做法。 - - - -### 管理 - -我们收集了关于项目、部署、日志等信息,如果你需要一个类似 CMDB 的可视化控制台,完全可以依据数据这些聚合给 WEB 服务,没有什么难度。 -在 `bridge` 中我们已统一插入了针对业务的监控,针对运行时性能监控可以链接到云平台的查询接口。 - -export default ({ children }) => {children} diff --git a/public/assets/alipay.png b/public/assets/alipay.png deleted file mode 100644 index e991780..0000000 Binary files a/public/assets/alipay.png and /dev/null differ diff --git a/public/assets/cli-new-project.png b/public/assets/cli-new-project.png deleted file mode 100644 index 6fa43e0..0000000 Binary files a/public/assets/cli-new-project.png and /dev/null differ diff --git a/public/assets/func_demo.png b/public/assets/func_demo.png deleted file mode 100644 index 92fe70a..0000000 Binary files a/public/assets/func_demo.png and /dev/null differ diff --git a/public/assets/getting-started-demo-1.png b/public/assets/getting-started-demo-1.png deleted file mode 100644 index f234c1a..0000000 Binary files a/public/assets/getting-started-demo-1.png and /dev/null differ diff --git a/public/assets/npm.png b/public/assets/npm.png deleted file mode 100644 index d144641..0000000 Binary files a/public/assets/npm.png and /dev/null differ diff --git a/public/assets/release-drafter.png b/public/assets/release-drafter.png deleted file mode 100644 index 3f850eb..0000000 Binary files a/public/assets/release-drafter.png and /dev/null differ diff --git a/public/assets/want.webp b/public/assets/want.webp deleted file mode 100644 index c6ac1e9..0000000 Binary files a/public/assets/want.webp and /dev/null differ diff --git a/public/assets/yarn.png b/public/assets/yarn.png deleted file mode 100644 index 49e47f7..0000000 Binary files a/public/assets/yarn.png and /dev/null differ diff --git a/sponsors.js b/sponsors.js deleted file mode 100644 index b8b56fe..0000000 --- a/sponsors.js +++ /dev/null @@ -1,107 +0,0 @@ -const Sponsors = [ - { - name: 'Reckfullol', - amount: 1, - isSponsor: true, - avatar: 'https://avatars2.githubusercontent.com/u/16449130?s=96&v=4', - }, - { - name: '凡辞', - amount: 10, - isSponsor: true, - avatar: 'https://avatars3.githubusercontent.com/u/15810641?s=96&v=4', - }, - { - name: 'Frank Xu', - amount: 5, - isSponsor: true, - avatar: 'https://avatars0.githubusercontent.com/u/6738274?s=96&v=4', - }, - { - name: '*进秋', - amount: 1.45, - }, - { - name: 'MinLiang Zeng', - amount: 1, - avatar: 'https://avatars3.githubusercontent.com/u/11664505?s=96&v=4', - }, - { - name: 'Teddy', - amount: 1, - isSponsor: true, - avatar: 'https://avatars1.githubusercontent.com/u/12698941?s=96&v=4', - }, - { - name: 'Hanxing Yang', - amount: 1, - isSponsor: true, - avatar: 'https://avatars0.githubusercontent.com/u/1492263?s=96&v=4', - }, - { - name: 'Neo Zhuo', - amount: 5, - isSponsor: true, - avatar: 'https://avatars3.githubusercontent.com/u/6374269?s=96&v=4', - }, - { - name: 'iqingting', - amount: 50, - avatar: 'https://avatars3.githubusercontent.com/u/3411958?s=96&v=4', - }, - { - name: '*博', - amount: 0.714, - }, - { - name: 'Kaito Sugimoto', - amount: 1, - isSponsor: true, - avatar: 'https://avatars0.githubusercontent.com/u/36184621?s=96&v=4', - }, - { - name: 'geeeger', - amount: 1, - avatar: 'https://avatars3.githubusercontent.com/u/19817649?s=96&v=4', - }, - { - name: '**龙', - amount: 1.43, - }, - { - name: 'Aquariuslt', - amount: 5, - isSponsor: true, - avatar: 'https://avatars3.githubusercontent.com/u/6554061?s=96&v=4', - }, - { - name: 'MonkeyLeeT', - amount: 5, - isSponsor: true, - avatar: 'https://avatars0.githubusercontent.com/u/6754057?s=96&v=4', - }, - { - name: 'Lzumikonata', - amount: 10, - isSponsor: true, - avatar: 'https://avatars2.githubusercontent.com/u/17999142?s=96&v=4', - }, - { - name: '*盈', - amount: 1.43, - }, - { - name: '*睿', - amount: 0.744, - }, - { - name: '**清', - amount: 1, - }, - { - name: '**强', - amount: 10, - }, -] - -export default Sponsors diff --git a/yarn.lock b/yarn.lock index 5f04425..dd3bf09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2077,16 +2077,11 @@ lodash.uniq@4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11: +lodash@^4.17.11, lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lodash@^4.17.19: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== - loose-envify@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"