<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>solomon</title><description>PoiScript's Blog</description><link rel="self" href="https://blog.poi.cat/rss"></link><link rel="alternate" href="https://blog.poi.cat"></link><generator>solomon 2.0.0</generator><lastBuildDate>Sun, 15 Mar 2026 15:05:14 +0000</lastBuildDate><language>zh-Hans</language><copyright>Content licensed under CC-BY-SA-4.0.</copyright><item><title>用 Org-mode 写编程文档</title><author>PoiScript</author><link>https://blog.poi.cat/post/write-a-programming-doc-with-org-mode</link><guid isPermaLink="false">/post/write-a-programming-doc-with-org-mode</guid><category>emacs</category><category>org-mode</category><pubDate>Tue, 14 Aug 2018 00:00:00 +0000</pubDate><description><![CDATA[<section></section><h1>为什么选择 Org-mode</h1><section><p>为什么需要选择 Org-mode 和 Emacs 这两个少见的组合来写文档，甚至强调是编程文档呢。简单来说 Org-mode 即足够的简单也足够的复杂，说是简单是因为 Org-mode 使用的是类似 Markdown 的标记语言，不需要担心排版问题；说是复杂则是因为 Org-mode 不仅可以直接内嵌 LaTeX 的语句，同时也可以直接输出 LaTeX 格式，对后期排版提供了很大的自由度。同时 Emacs 和 Org-mode 的组合提供了许多实用的功能，接下来我们就一点一点来介绍。</p><blockquote><p>下面的快捷键都需要在 Emacs 的环境中使用，快捷键中的括号表示 Spacemacs 的快捷键。<s>不用 Emacs 玩什么 Org-mode。</s></p></blockquote></section><h1>在文档和代码之间快速切换编辑</h1><section><p>首先我们需要插入代码块。直接输入 <code>&lt;s</code> 然后按一下 <code>&lt;tab&gt;</code> 键就可以直接输入 <code>#+BEGIN_SRC</code> 和 <code>#+END_SRC</code>，在两个标记之间的区域就是我们的代码块了。类似 markdown 我们也可以指定代码块的语言：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-src.png" alt="org src block"><figcaption>org src block</figcaption></figure></p><p>如果想要编辑代码块中的代码，可以直接使用 <code>C-c &apos; (org-edit-special)</code> 直接进入到编辑代码的功能，这是就可以直接使用该代码块中对应的编程语言的 major mode 了，例如对上述的代码块使用改功能的话，就会出现另外一个编辑窗口，在其中就可以使用 emacs-lips mode 的补全功能了。</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-src-edit-special.png" alt="org mode src block edit special"><figcaption>org mode src block edit special</figcaption></figure></p><p>当然，修改完之后使用相同的快捷键就可以退出对代码的编辑，返回到对 Org-mode 文档的编辑。</p></section><h1>执行文档中的代码</h1><section><p>另外一个 Org-mode 强悍的功能就是可以直接执行代码块中代码，然后将他们的输出反馈到 Org-mode 文件中或者输出到另外一个文件中。例如我们将光标放到代码块上，然后使用 <code>C-c C-c (org-ctrl-c-ctrl-c)</code> 就可以得到输出的结果：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-evaluate.png" alt="org-mode evaluate"><figcaption>org-mode evaluate</figcaption></figure></p><p>当然我们也可以设置在哪里以及用什么参数来调用这个函数。同时也支持在行内调用：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-function-calls.png" alt="org-mode function calls"><figcaption>org-mode function calls</figcaption></figure></p><p><s>使用调用函数的方法可以有效避免你忘记了 IEEE 754 标准而产生错误的结果。</s></p><blockquote><p>这里的 <code>{{{result}}}</code> 就表示一个 Org-mode 中的一个宏。它可以在导出 Org-mode 文件的时候，将一些定义的变量插入到其中。</p></blockquote></section><h1>自由方便地引用文件</h1><section><p>对于比较复杂的代码，直接写在 Org-mode 文件中就比较繁琐了。所以我们可以使用更直接的方式，使用 <code>#INCLUDES</code> 直接引入代码的文件。</p><p>同样的，<code>#INCLUDES</code> 也有类似的输入方式：直接输入 <code>&lt;I</code> 然后按一下 Tab 就是类似如下的效果：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-include.png" alt="org-mode include"><figcaption>org-mode include</figcaption></figure></p><p>第一个参数就是文件的路径，第二个参数 <code>src</code> 表示的是作为代码块引入，最后一个就是编程语言的设置。<code>:lines</code> 就是引入行数：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-include-edit-secial.png" alt="org-mode include edit special"><figcaption>org-mode include edit special</figcaption></figure></p><blockquote><p>当然直接跳到代码编辑模式的功能还是有，快捷键也是相同的 <code>C-c &apos; (org-edit-special)</code>。</p></blockquote><p>举个例子来说明将代码和 Org-mode 文件分开存放有什么好处。假设我们写了一个简单的 Rust 函数和它的单元测试：</p><pre><code class="lang-rust"><span class="token keyword">pub</span> <span class="token keyword">fn</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span>a<span class="token punctuation">:</span> <span class="token keyword">i32</span><span class="token punctuation">,</span> b<span class="token punctuation">:</span> <span class="token keyword">i32</span><span class="token punctuation">)</span> <span class="token punctuation">-></span> <span class="token keyword">i32</span> <span class="token punctuation">{</span>
    a <span class="token operator">+</span> b
<span class="token punctuation">}</span>

<span class="token attribute attr-name">#[cfg(test)]</span>
<span class="token keyword">mod</span> <span class="token module-declaration namespace">tests</span> <span class="token punctuation">{</span>
    <span class="token keyword">use</span> <span class="token keyword">super</span><span class="token punctuation">::</span><span class="token operator">*</span><span class="token punctuation">;</span>
    <span class="token attribute attr-name">#[test]</span>
    <span class="token keyword">fn</span> <span class="token function-definition function">test_add</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token macro property">assert_eq!</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token macro property">assert_eq!</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">,</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token macro property">assert_eq!</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>我们只想出现第 1-3 行的代码，而隐藏琐碎的单元测试。但是需要注意的是 Org-mode 不会引入指定的行数区域的最后一行，所以我们应该写成：</p><pre><code class="lang-">#+INCLUDE: "./src/main.rs" :lines "1-4" src rust
</code></pre><p>这样在最后导出 Org-mode 文件的时候，就只会出现第 1-3 行的函数定义部分了。而同时，我们还可以继续用 <code>cargo</code> 来运行单元测试：</p><pre><code class="lang-bash">$ cargo <span class="token builtin class-name">test</span>
<span class="token comment">#    Compiling add v0.1.0 (file:///tmp/add)</span>
<span class="token comment">#     Finished dev [unoptimized + debuginfo] target(s) in 0.58s</span>
<span class="token comment">#      Running target/debug/deps/add-1b966bd47a8d604b</span>
<span class="token comment">#</span>
<span class="token comment"># running 1 test</span>
<span class="token comment"># test tests::test_add ... ok</span>
<span class="token comment">#</span>
<span class="token comment"># test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out</span>
</code></pre><p>此外，Org-mode 文件还可以引入其他 Org-mode 文件。而且在引入其他 Org-mode 文件时，功能更加强大，不仅可以设置行数，还可以设置引入哪个段落。例如，在其他 Org-mode 文件中引入我们现在的「自由方便地引用文件」这个段落，就可以写做：</p><pre><code class="lang-">#+INCLUDE: "write-a-programming-book-with-org-mode.org::*自由方便地引用文件"
</code></pre><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-include-org.png" alt="org-mode include org"><figcaption>org-mode include org</figcaption></figure></p></section><h1>输出 LaTeX &amp; PDF</h1><section><p>最后，当然就是输出成 LaTeX 了，直接使用快捷键 <code>C-c C-e l l
(org-export-dispatch)</code> 就能保存成 LaTeX 文件。</p><p>当然 LaTeX 就有很多可说的了，例如模板和宏包等。不过这样就扯远了，偏离本篇文章的主题了。</p><p>这里我就只补充一点，如果需要给输出的 PDF 文件加上代码高亮的话，我们需要安装 <a target="_blank" rel="noreferrer noopener" href="https://github.com/gpoore/minted">minted</a> 宏包和 <a target="_blank" rel="noreferrer noopener" href="http://pygments.org/">pygments</a> 这个高亮的工具并且设置 Emacs 让 Org-mode 输出LaTeX 的时候把这个包带上：</p><pre><code class="lang-emacs-lisp"><span class="token punctuation">(</span><span class="token keyword">setq</span> org-latex-listings <span class="token quoted-symbol variable symbol">'minted</span><span class="token punctuation">)</span>
<span class="token punctuation">(</span><span class="token car">add-to-list</span> <span class="token quoted-symbol variable symbol">'org-latex-packages-alist</span> <span class="token punctuation">'(</span><span class="token string">""</span> <span class="token string">"minted"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre><p>然后在使用 pdflatex 命令输出 PDF 时候，我们需要加点参数：</p><pre><code class="lang-bash">$ pdflatex -shell-escape -interaction nonstopmode <span class="token operator">&lt;</span>tex-file<span class="token operator">></span>
</code></pre><p>最后输出的结果就是这样啦：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/latex-output-pdf-with-syntax-highlight.png" alt="latex outputs pdf with syntax highlight"><figcaption>latex outputs pdf with syntax highlight</figcaption></figure></p><blockquote><p>在输出 PDF 的时候，minted 可能会出现类似这样的错误：<code>Undefined control
sequence.</code>。这个时候就需要手动删除 <code>_minted-test</code> 这个文件夹，然后再运行即可。具体可以查看 <code>minted</code> 的 <a target="_blank" rel="noreferrer noopener" href="https://github.com/gpoore/minted/issues/92">issue</a>。</p></blockquote></section><h1>结语</h1><section><p>其实上述提到的很多功能在别的编辑器或是标记语言中都都能找到。就我所知的：JetBrains 的 IDE 已经可以在 Markdown 文档中使用相应语言的编辑模式编辑代码块；RMarkdown 和 Jupyter Notebook 等标记语言也提供了执行文档内代码的功能；而最后转换成 LaTeX 的功能更是有 pandoc 等多种实现了。但是能将上述这些功能集成在一起，形成一个完美的 workflow 的，几乎没有。</p><p>有时候我会觉得 Emacs 之于 Org-mode 的意义应该是大于 Org-mode 之于 Emacs。毕竟现在人们对 Emacs 的印象大多是：运行速度慢，快捷键反人类，<code>))))))))</code> 和「要不是为了 Org-mode，我早就把 Emacs 卸载了！」:)</p></section>]]></description></item><item><title>Angular Package Format 简介</title><author>PoiScript</author><link>https://blog.poi.cat/post/introduce-angular-package-format</link><guid isPermaLink="false">/post/introduce-angular-package-format</guid><category>angular</category><pubDate>Thu, 15 Mar 2018 00:00:00 +0000</pubDate><description><![CDATA[<section><p><a target="_blank" rel="noreferrer noopener" href="https://goo.gl/jB3GVv">Angular Package Format(APF)</a> 指的是 Angular 框架中的包的结构和规范。该规范的作者是 <a target="_blank" rel="noreferrer noopener" href="https://github.com/IgorMinar">IgorMinar</a> 和 <a target="_blank" rel="noreferrer noopener" href="https://github.com/jasonaden">Jason Aden</a>，两者都是 Angular 的 Contributor。</p><p>它详细地介绍了 Angular 的核心库，例如 <code>@angular/core</code> 和 <code>@angular/common</code> 的 npm 包的结构和原因，同时还涉及到了在开发自己的库的时候如何优化编译结果，适配各种的优化工具等等。了解这方面的内容不仅有助于我们在写自己的 Angular 库，同时也可以我们了解 Angular 的编译器是如何工作的。</p><p>目前知道和了解这个文档的人还不多，而且恰巧今天（2018-3-15）该文档也更新到了 v6 版本（虽然目前看起来和 v5 长得一模一样）。于是我萌生了写一遍简单的关于 APF 的介绍，内容基本沿袭自上述的文档和<a target="_blank" rel="noreferrer noopener" href="https://youtu.be/QfvwQEJVOig">这个视频</a>。</p></section><h1>为什么需要 APF？</h1><section><p>最大的因为就是需要兼容各种各样的运行环境和工具。</p><p>在 Angular 4 中加入了 <code>@angular/platform-server</code> 被并入 Angular 的核心 repo，说明 Angular 的 Sever-side rending 得到越来越多的重视，所以 Angular 的库不仅要兼容浏览器环境，还要能兼容 Nodejs 的服务器环境。</p><p>此外，不是所有的 Angular 项目都是基于 Angular CLI 的，Angular 的用户有可能使用各式各样的编译器和优化工具，例如 Webpack、Rollup、Uglify 和 Google Closure 等等，而且每种编译器和优化工具都有不同的需求。</p><p>所以仅仅只提供一种编译格式是不可能满足上述所有的需求，所以我们就需要一个包的规范来实现上述需求，这就是 APF。</p></section><h1>APF 中规定了什么？</h1><h2>JavaScript 的模块规范</h2><section><p>要能过完美的兼容各种运行环境，第一个需要考虑的问题是使用 JavaScript 模块规范。例如，很多打包工具和优化工具基本只针对 ECMAScript Module(ESM) 有效（例如 Angular AoT 就只对 ESM 有效，而不能用于 CommonJS）。而在服务器端 Nodejs 上运行的时候需要对符合 CommonJS 规范的。</p><blockquote><p>在最新的 nodejs 中已经支持了 ESM，但是这个功能目前处在实验阶段，需要单独的 <code>--experimental-modules</code> flag。而且也并非每个人都能用上最新的 nodejs。所以这个让 nodejs 使用 ESM 还不是目前最佳的解决方法。</p></blockquote><p>APF 使用的是更加通用的 UMD 的模块规范，该规范可以同时满足 CommonJS 和 AMD 的规范。这样该模块就既可在 Nodejs 使用，也可以在浏览器中通过 script tag 使用。</p><p>此外，APF 还推荐在打包的时候还打包 Flat ES Module(FESM)，即将所有的独立的 Module 打包成一个文件。这样可以降低用户使用独立 Module 时的所需体积。</p><p>根据上面的描述，似乎只要提供 ESM、FESM 和 UMD 三种格式的编译后的文件就行了吗？其实不止，另外一个重要的要素是编译后的 JavaScript 的版本。</p></section><h2>JavaScript 的版本</h2><section><p>虽然目前很多打包工具默认都是输出 ECMAScript 5(ES5)。但是考虑到所有的 evergreen browsers（一般来说，指可以脱离系统独立更新的浏览器）都已经提供原生的 ES2015(ES6) 支持。所以 APF 推荐提供 ES2015 版本的编译结果，这样可以满足部分不需要支持旧浏览器的用户。</p><p>另外一个原因就是不同的优化工具对不同语言版本支持情况也不一样。例如 Uglify 目前只支持 ES5。</p></section><h2>其他</h2><section><p>最后，因为 Angular 官方支持的第一开发语言为 TypeScript，所以提供 TypeScript 需要的 <code>.d.ts(Type Declaration File)</code> 也是有必要的。同时，因为 <code>.d.ts</code> 提供的信息不足以给 Angular 的编译器使用，还需要一个提供额外的包含每个 decorator 中元数据的 <code>metadata.json</code>。</p><blockquote><p>目前，Angular AoT 模式中会将所有的元数据编译成类的静态类型，所以也就不需要像以前那样通过 Metadata Reflection API 获取元数据了。</p></blockquote></section><h1>APF 的具体实现</h1><section><p>下面我们以一个具体的遵循 APF 的包 <code>@angular/material</code> 为例子，描述具体的文件结构。</p><p>首先是整体的文件结构，主要分成了 <code>esm2015/</code>、<code>esm5/</code>、<code>bundles/</code> 和其他各自的独立的 module 的文件夹。其中 <code>esm2015/</code>、<code>esm5/</code> 和 <code>bundles/</code> 就分别表示了 ESM+ES2015、ESM+ES5 和 UMD+ES5 的编译结果。</p><pre><code class="lang-">.
├── esm2015
├── esm5
├── bundles
├── autocomplete
└── ...
</code></pre><p>而在 <code>esm2015</code>、<code>esm5</code> 和 <code>bundles</code> 中则包括了该库中所有编译结果：每个独立的 module 的编译结果和整个库的 module 的都在其中。而每一个独立的 module 而编译结果都是一个 Flat Module。</p><pre><code class="lang-">.
├── material-tooltip.umd.js
├── material-tooltip.umd.min.js
│   ...
├── material.umd.js
└── material.umd.min.js
</code></pre><p>而在每个独立的 module 中都包含的各自该 module 的 <code>package.json</code>、<code>metadata.json</code> 和 <code>.d.ts</code> 文件：</p><pre><code class="lang-">.
├── index.d.ts
├── index.metadata.json
├── package.json
└── typings
    ├── button.d.ts
    ├── button-module.d.ts
    ├── index.d.ts
    ├── index.metadata.json
    └── public-api.d.ts
</code></pre><p>最后，在每个 <code>package.json</code> 中，无论是根层级的，还是独立的 module 中的，都含有以下几个字段：</p><pre><code class="lang-">"main": "./bundles/material.umd.js",
"module": "./esm5/material.es5.js",
"es2015": "./esm2015/material.js",
"typings": "./material.d.ts",
</code></pre><p><code>main</code> 字段指向 UMD+ES5 的编译结果，主要用于 Nodejs 的环境；<code>module</code> 字段指向 ESM+ES5 的编译结果，主要用于 Webpack 等；<code>es2015</code> 字段指向 ESM+ES2015 的编译结果，主要用于配置过的 Webpack 和 Google Closure 等；最后 <code>typings</code> 指向 <code>.d.ts</code>文件，用于 TypeScript。</p><p>最后，在包的根目录里还有根整个 module 和每个独立的 module 的 <code>metadata.json</code> 和 <code>.d.ts</code> 文件。</p></section><h1>更多</h1><section><p>这篇文章只是对 APF 的一个简单的介绍和一个典型的例子的分析，还有更多的细节没有提及：例如，如何提供多个 entry point 这样可与成让用户只导入需要的模块，降低编译后的体积。如果感兴趣的话可以查看<a target="_blank" rel="noreferrer noopener" href="https://goo.gl/jB3GVv">原文档</a>，以及 Juri Strumpflohner 在 ng-be 上的<a target="_blank" rel="noreferrer noopener" href="https://youtu.be/K4YMmwxGKjY">发言视频</a>。</p></section>]]></description></item><item><title>2017 HTTPS 调查</title><author>PoiScript</author><link>https://blog.poi.cat/post/https-in-2017</link><guid isPermaLink="false">/post/https-in-2017</guid><category>https</category><pubDate>Thu, 07 Dec 2017 00:00:00 +0000</pubDate><description><![CDATA[<section><blockquote><p>这篇文章改写自 poi 的计算机网络课论文。</p></blockquote></section><h1>前言</h1><section><p>自 1991 年，第一个带有文档的 HTTP ──<a target="_blank" rel="noreferrer noopener" href="https://www.w3.org/Protocols/HTTP/AsImplemented.html">HTTP V0.9</a> 问世以来，HTTP 协议得到了广泛的运用。</p><blockquote><p>据 <a target="_blank" rel="noreferrer noopener" href="https://www.cisco.com/c/en/us/solutions/collateral/service-provider/visual-networking-index-vni/vni-hyperconnectivity-wp.html#_Toc484556816">Cisco 统计</a>，在 2016 年，全球的的网络流量达到了惊人的<b> 1.2 ZB</b> 流量，其中视频的流量就达到了 73%。而目前的视频传输协议中最常用的分别是 Apple 的<a target="_blank" rel="noreferrer noopener" href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming">HLS (HTTP Live Stream)</a> 和 Google 的 <a target="_blank" rel="noreferrer noopener" href="https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP">DASH (Dynamic Adaptive Streaming over HTTP)</a>，而两者都是基于 HTTP 的协议。</p></blockquote><p>随着互联网的发展，网络通信中的安全性也得到了重视。在 1995 年，<a target="_blank" rel="noreferrer noopener" href="https://web.archive.org/web/19970614020952/http://home.netscape.com/newsref/std/SSL.html">TLS 协议的前身 SSL 协议应运而生</a>。而我们今天要讨论的就是在 SSL 诞生的 22 年之后，互联网上的 HTTPS 运用到达了什么程度。</p></section><h1>总览</h1><section><p>我收集了目前 Alexa 排名前 1,000 的网站，虽然数量不多但是都是无疑占据了互联网上大多数的流量，通过来一窥互联网当前 HTTPS 的普及程度。部分的数据来自于 <a target="_blank" rel="noreferrer noopener" href="https://www.ssllabs.com">ssllibs</a>。</p><blockquote><p>实际上，我有 Alexa 前 1,000,000 的网站的名单。但是 ssllib 的 API 调用起来极其费时，对一个网站 ssl 配置的查询需要 2~3 分钟的时间，所以只能退而求其次。只考察前 1,000 个网站。</p></blockquote><p>首先，在这 1,000 个网站中，目前有效的网站只有 981 个，其他的网站都无法成功解析其域名，可能是网站己经下线或者迁移到别的域名了，所以其是否支持 HTTPS 就不得而知了。</p><p>然后，在这 981 个网站中，支持 HTTPS 的恰好有 800 个，占比约有 81.5%。而在前 100 的网站中，只有 10 个网站不支持 HTTPS。</p><p>虽然这 800 个网站都支持 HTTPS 协议，但是他们在协议的实现上的不同也会产生安全性的差异。例如 RC4 加密曾经是 TLS 所采用的加密算法之一，但是在 2015 年遭到了<a target="_blank" rel="noreferrer noopener" href="https://blog.qualys.com/ssllabs/2013/03/19/rc4-in-tls-is-broken-now-what">破解</a>，因此已经禁止在 TLS 中使用了。可目前仍然有网站使用 RC4 加密，甚至作为唯一的加密手段，那其 HTTPS 就形同虚设了。</p></section><h1>TLS 版本</h1><section><p>我统计了这 800 个网站所支持的最高的 TLS 协议的版本：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/https-tls-version.png" alt="网站最高支持的 TLS 版本对比"><figcaption>网站最高支持的 TLS 版本对比</figcaption></figure></p><p>绝大多数的网站（98%）的网站支持了 TLS 1.2 标准。比较遗憾的是，没有看到网站使用了最新的 TLS 1.3 标准，不过考虑到 TLS 1.3 还处在草案阶段，不使用也是合情合理的。此外，我也发现了还有 37 个网站仍在使用已经被认为是<a target="_blank" rel="noreferrer noopener" href="https://www.openssl.org/~bodo/ssl-poodle.pdf">不安全</a>的 SSL 3.0。</p></section><h1>TLS 握手</h1><section><p>TLS 中最常见的握手方式两种：一种是基于 RSA 算法的；另外一种是基于 DH（Diffie-Hellman）算法。RSA 算法历史悠久，兼容性好；DH 算法在加密速度上更有优势，而且拥有前向安全性。TLS 握手的一个目的就是保证安全性。DH 握手可以使用除了RSA 以外的证书来提高性能，例如 ECC。ECC 证书内置了 ECDSA 公钥，使用了ECC（Elliptic curve cryptography）算法，它的密钥更短 ── 256 bit 的 ECC 算法强度和 3072 bit 的 RSA 算法安全性相当 ── 而加密速度更快。在 800 个支持 HTTPS 的网站中，大多数的网站还是选择了传统的 RSA 证书，只有少量的网站（35 个）选择了 ECC 证书：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/https-key.png" alt="网站使用证书的对比"><figcaption>网站使用证书的对比</figcaption></figure></p><p>TLS 握手的另外一个目的就是可以验证网站的身份，即需要数字签名。目前主要的签名方式有两者，分别是 RSA 和 ECDSA（Elliptic Curve Digital Signature Algorithms）。在这 800 个网站中，有 21 个网站使用了 ECDSA 签名：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/https-sig.png" alt="网站使用的签名证书的对比"><figcaption>网站使用的签名证书的对比</figcaption></figure></p></section><h1>从 HTTPS 到 HSTS</h1><section><p>当一个网站部署了 HTTPS 后，当用户再发起 HTTP 请求时，应该主动将用户引导到更安全的 HTTPS。目前有两种方法引导：一是当用户发起 HTTP 请求时，直接将其重定向到 HTTPS；二是设置 HSTS 响应头。</p><p>在这 800 个网站中，当我们直接发起 HTTP 请求时，HTTP 的状态码分布为：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/http-status-code.png" alt="HTTP 状态码"><figcaption>HTTP 状态码</figcaption></figure></p><p>我们知道在 HTTP 的状态码中，20X 的状态码表示资源已经找到；30X 表示重定向；40X 的状态码表示资源不存在；50X 表示服务器发生了错误；因此，我们的粗略的统计为 573 个网站会将用户重定向到安全的 HTTPS 页面。其余的不会。</p><p>和直接重定向到 HTTPS 相比，HSTS 的方法要好得多：网站在 HTTP 应答头中加入<a target="_blank" rel="noreferrer noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">HSTS (Strict-Transport-Security)</a> 的信息。它指定了一个有效时间，在有效时间内，当浏览器下次访问该域名时将直接使用 HTTPS 协议访问。</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/hsts-in-http-header.png" alt="HTTP 响应头中的 HTST"><figcaption>HTTP 响应头中的 HTST</figcaption></figure></p><p>上述两种办法都有一个缺陷：都需要先经过一次 HTTP 请求后，才能过渡到 HTTPS 请求。如果第一次的 HTTP 请求就遭到了劫持，那就无能为力了。所以，相比简单的设置 HSTS 应答头，加入 HSTS Preloading List 的意义更大。</p><p>HSTS Preloading List 的原理就是在浏览器中内置一张支持 HSTS 网站的列表，当用户访问当中的网站时，如果在有效期内的话，浏览器会直接使用 HTTPS 链接，不会再发起 HTTP 请求。</p><p>目前的 HSTS Preloading List 主要由 <a target="_blank" rel="noreferrer noopener" href="https://www.chromium.org/hsts">Chrome</a> 维护，<a target="_blank" rel="noreferrer noopener" href="https://blog.mozilla.org/security/2012/11/01/preloading-hsts/">Firefox</a> 和 <a target="_blank" rel="noreferrer noopener" href="https://blogs.msdn.microsoft.com/ie/2015/02/16/http-strict-transport-security-comes-to-internet-explorer/">Edge/IE</a> 都在使用。截至 2017 年 11 月 30 号，Chrome 的 <a target="_blank" rel="noreferrer noopener" href="https://cs.chromium.org/chromium/src/net/http/transport_security_state_static.json">HSTS Preloading List</a> 中一共有 41312 个域名。而在这 800 个支持 HTTPS 的域名中，有124 个网站也在其中。</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/htst-preloading.png" alt="网站在 HSTS Preloading List 占比"><figcaption>网站在 HSTS Preloading List 占比</figcaption></figure></p><p>Chrome 的 HSTS Preloading List 支持<a target="_blank" rel="noreferrer noopener" href="https://hstspreload.org/">申请</a>，所以可以见这个列表来的域名将会越来越多，最后覆盖到我们常用的域名。</p></section><h1>维护证书安全</h1><section><p>HTTPS 链接中，另一个容易受到攻击的地方就是证书。证书是由 CA（证书颁发机构）颁布的，而受信任的 CA 多达上百个。我们先来看看这 800 个网站的证书是由哪些 CA 颁发的：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/https-ca.png" alt="网站中 CA 占比"><figcaption>网站中 CA 占比</figcaption></figure></p><p>现有的证书信任链还有一个问题就是任意一个受信任的 CA 都可以给任意一个域名颁发证书。所以如果出现了中间证书甚至是根证书被盗签时，对于浏览器来说是无法分辨的。</p><p>一种解决办法就是在应答头中加入 <a target="_blank" rel="noreferrer noopener" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning">HPKP(HTTP Public Key Pinning)</a> 信息：即将自己的证书指纹放在 HTTP 的应答头里，让浏览器记录这些信息。当浏览器下次访问时都会验证该指纹，如果指纹不对的话，即使证书合法，浏览器也会直接断开链接。</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/hpkp-in-http-header.png" alt="HTTP 响应头中的 HPKP"><figcaption>HTTP 响应头中的 HPKP</figcaption></figure></p><p>显然，HPKP 和 HSTS 一样也有 HTTP 被劫持的危险。所以也有相应的 <a target="_blank" rel="noreferrer noopener" href="https://dxr.mozilla.org/mozilla-central/source/security/manager/tools/PreloadedHPKPins.json">HPKP Preloading List</a>。但是和 HSTS Preloading List 不同，个人用户<a target="_blank" rel="noreferrer noopener" href="https://tools.ietf.org/html/rfc7469#section-2.7">无法申请</a>将自己的网站加入其中。所以目前的 HPKP Preloading List 的规模还比较小，而在这 800 个网站中也只有 2个网站设置了 HPKP 的应答头。</p><p>此外，Chrome 提出了另外一种解决办法，那就是 <a target="_blank" rel="noreferrer noopener" href="https://www.certificate-transparency.org">Certificate Transparency</a>：即将整个证书签名的过程透明化，任何证书持有者和 CA 都可以将自己证书的记录提交到相应的 Certificate Logs 服务器中，而这整个记录都是受到审计和监控的；浏览器在访问网站时就会对证书进行校验，进而做出不同的反应。</p><p>下面就是一个支持 Certificate Transparency 的页面的例子：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/certificate-transparency.png" alt="certificate transparency"><figcaption>certificate transparency</figcaption></figure></p><p>但是，Certificate Transparency 目前只有 Chrome 支持。而 Chrome 知道其利用率还太低，所以即使在 Certificate Logs 服务器中查不到相应的证书也不会有太大影响。</p></section><h1>结论</h1><section><p>从上面的分析我们可以得出：尽管大多数的网站都已经支持 HTTPS 协议了，但是实现还是参差不齐。而面对一些新型的攻击，例如首次 HTTP 协议遭到劫持，证书被盗签等问题准备得还是不够充分。</p></section><h1>P.S.</h1><section><p>很可惜，没有收集到所有 1,000,000 个域名的 HTTPS 信息，不过还是可以统计一下其他的信息。例如，域名的长度：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/domain-length.png" alt="域名的长度的分布"><figcaption>域名的长度的分布</figcaption></figure></p><p>和顶级域名的分布：</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/tld.png" alt="顶级域名的分布"><figcaption>顶级域名的分布</figcaption></figure></p></section>]]></description></item><item><title>NgModule 的作用域</title><author>PoiScript</author><link>https://blog.poi.cat/post/ngmodule-and-its-scope</link><guid isPermaLink="false">/post/ngmodule-and-its-scope</guid><category>angular</category><pubDate>Mon, 16 Oct 2017 00:00:00 +0000</pubDate><description><![CDATA[<section></section><h1>前言</h1><section><p>在开始聊这个话题之前，先提一个我遇到过问题：假设有一个 <code>SpinnerService</code>，这是一个可以在进行发送 HTTP 请求等异步操作的显示一个加载动画的 Service。</p><p>这样看来，它应该是每个 Module 和 Component 中都可能用到的一个 Service，那么我们把放到 <code>SharedModule</code> 的 <code>providers</code> 当中。然后在需要这个 <code>SpinnerService</code> 的 Module 中导入这个 <code>SharedModule</code> 即可。代码看起来大概是这样的：</p><pre><code class="lang-typescript"><span class="token comment">// app/shared/spinner.service.ts</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">Injectable</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SpinnerService</span> <span class="token punctuation">{</span>
  <span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">/* code logic */</span>
  <span class="token punctuation">}</span>
  <span class="token function">hide</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">/* code logic */</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// app/shared/shared.module.st</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">NgModule</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  providers<span class="token operator">:</span> <span class="token punctuation">[</span>SpinnerService<span class="token punctuation">]</span>
  <span class="token comment">// ... other staffs</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SharedModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token comment">// app/some/some.module.ts</span>
<span class="token decorator"><span class="token at operator">@</span><span class="token function">NgModule</span></span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  imports<span class="token operator">:</span> <span class="token punctuation">[</span>SharedModule<span class="token punctuation">]</span>
  <span class="token comment">// ... other staffs</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">SomeModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
</code></pre><p>代码看起来没什么问题，而且也能正常运行。但是如果你在两个不同的 Component 中分别调用 <code>SpinnerService</code> 中的 <code>show()</code> 和 <code>hide()</code> 函数，你会发现加载动画没有如愿的消失；更近一步，如果在 Service 中有共用的数据的，你会发现两个不同的 Component 中对数据的修改也无法同步。Component 中的 Service 是由 Module 提供的，所以问题就出在 Module 上。</p><p>有的读者的可能已经猜到了：虽然两个 Module 中会提供一个 <code>SpinnerService</code>，但是他们不是同一个实例。</p></section><h1>Dependency Injection</h1><section><p>在继续解释为什么不是同一个实例的之前，我想先粗略地说一下为什么应该会是同一个实例。这就涉及到一个概念：Dependency Injection，动态注入。</p><p>以 angular.io 上的例子为例：假设有一个 <code>Car</code> 类，其依赖一个 <code>Engine</code> 类。</p><p>于是你可以这么写（不使用 Dependency Injection）：</p><pre><code class="lang-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Car</span> <span class="token punctuation">{</span>
  <span class="token keyword">public</span> engine<span class="token operator">:</span> Engine<span class="token punctuation">;</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>engine <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Engine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>当然也可以这么写（使用 Dependency Injection）：</p><pre><code class="lang-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">Car</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">public</span> engine<span class="token operator">:</span> Engine<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre><p>两者看起来区别不大，而且当需要调用 <code>engine</code> 的时候，用法也是相同的。但是后者的好处却有不少：</p><ol><li><p><code>Engine</code> 和 <code>Car</code> 的结构是完全独立的：传入 <code>Car</code> 中的是 <code>Engine</code> 的一个实例而不是在 <code>Car</code> 的新建这个实例。所以如果 <code>Engine</code> 的构造函数发生变化的时候也不需要修改 <code>Car</code> 中的构造函数。</p></li><li><p>因为传入 <code>Car</code> 中的是一个实例，所以可以复用这个实例，减少内存占用。</p></li><li><p>因为传入的只是一个实例，所以甚至可以传入一个虚假用于单元测试的 <code>Engine</code>。</p></li></ol><p>Angular 通过 Hierarchical Dependency Injectors 实现了上述的 Dependency Injection。Hierarchical Dependency Injectors 具体的实现，例如 Service 的复用，Component的 Injectors 等都是很值得一说的，不过和本文相关性不大，这里按下不表。</p></section><h1>Providers &amp; Declarations</h1><section><p>我们知道一个 Module 中可以有 Components、Directives、Pipes 和 Services。前三者都是与 HTML 模板相关的，需要放在在 Module 的 <code>declarations</code> 中；后者一般用来处理数据，放在 <code>providers</code> 中。</p><p>那么 <code>providers</code> 和 <code>declarations</code> 的区别是什么呢？其中一个就是两者的作用域不同：<code>providers</code> 中是全局的；而 <code>declarations</code> 则是本地的，只有该 Module 中可以使用。</p><blockquote><p>Services 为什么会是全局的呢？其实很好理解，因为 Services 经过编译之后最后都是生成一个类，在导入或是导出它们的时候都会限定在命名空间内。</p></blockquote><p>所以，Services 应该只在 <code>providers</code> 中出现一次；而其他三者则是在需要的 Module 的 <code>declarations</code> 地方都出现。</p><blockquote><p>当然，在所有出现的地方都需要写一遍也是非常烦杂。这时我们就可以把他们放到一个 <code>SharedModule</code> 的 <code>declarations</code> 和 <code>exports</code>，最后在需要的 Module 中导入该 <code>SharedModule</code> 即可。</p></blockquote><p>对应到上面的问题，你会发现确实也只有在 <code>SharedModule</code> 的 <code>providers</code> 中出现了 <code>SpinnerService</code> 呀，但是问题还是存在啊。</p><p>其实不止 <code>SharedModule</code>，导入了 <code>SharedModule</code> 的 <code>SomeModule</code> 相当于也提供了 <code>SpinnerService</code>。所以在两个 Module 中就会有两个由不同的 Module 提供的，两个不同的 <code>SpinnerService</code>。</p><p>导入拥有提供了 Services 的 Module，相当与自己提供了相同的 Services。这样的例子这样的情况你可能早就接触过了：当你在 <code>AppModule</code> 中导入了 <code>HttpModule</code> 之后，你就可以使用 <code>Http</code> 这个全局 Service 来发送 HTTP 请求了。</p><blockquote><p>这里的 <code>AppModule</code> 指 Root Module，下同。</p></blockquote><p>另一方面，如果一个 Module 既有 Components 也有 Services 时则需要分别对待了：在 <code>AppModule</code> 中导入这个 Module 的时候需要调用 <code>forRoot()</code>，它返回的是一个 <code>ModuleWithProviders</code>；而在其他的 Module 则是直接导入这个 Module 或者调用 <code>forChild()</code>。例如 <code>RouterModule</code> 就既有 Component <code>&lt;router-outlet&gt;</code> 和 Directive <code>routerLink</code>，也有 Service <code>ActivatedRoute</code>。</p></section><h1>Best Practice</h1><section><p>至此，要解决文章开头的问题可以很简单：将 <code>SpinnerService</code> 放到 <code>AppModule</code> 的 <code>providers</code> 里即可。</p><p>但是，这样的简单粗暴地将每一个 Service 都交由 <code>AppModule</code> 提供的解决方法违反了我们一贯的原则：尽可能保持每个 Moudle 的功能和结构简单。</p><p>所以，我们确实应该将 <code>SpinnerService</code> 移出 <code>SharedModule</code>，然而也不应该放进 <code>AppModule</code> 而是可以考虑放进一个新建的 <code>CoreModule</code> 中。而这个 <code>CoreModule</code> 也应该作为一个纯粹的只提供 Services 的 Module，而只在 <code>AppModule</code> 中导入它。</p><blockquote><p>当然，因为只在 <code>AppModule</code> 中导入，所以如果有一些只需要在 <code>AppComponent</code> 中使用的 Component，如 <code>NavComponent</code> 和 <code>FooterComponent</code> 等也可以考虑放到其中。</p></blockquote></section><h1>References</h1><section><ol><li><p>文章中提到了可以使用一个虚假的 Service 用于 Component 的单元测试，<a target="_blank" rel="noreferrer noopener" href="https://angular.io/guide/testing#test-a-component-with-a-dependency">这里</a>介绍了具体应该怎么做。</p></li><li><p>Angular 的 Hierarchical Dependency Injectors 系统，这是一个很有趣的系统，每一个 Component 都有一个与之对应的可编辑的 Injector。具体可以查看的 Angular 的官方文档：<a target="_blank" rel="noreferrer noopener" href="https://angular.io/guide/hierarchical-dependency-injection">Hierarchical Dependency Injectors</a>。</p></li><li><p>写 Angular 应用的一个原则都是保持每一个 Module 的功能和结构的简单和统一，这一点和 Unix 的哲学不谋而合：<b>Write programs that do one thing and do it well.</b>那么我们怎么应该这么设计一个好的 Module 呢？Angular 官方的 NgModule FAQs 中其实给出了<a target="_blank" rel="noreferrer noopener" href="https://angular.io/guide/ngmodule-faq#feature-modules">答案</a>。从中我们可以看出，<code>CoreModule</code> 这种只提供 Services 和<code>SharedModule</code> 这种只提供 Components，Directives 和 Pipes 的 Module 是目前来说官方认为最好的设计。</p></li></ol></section>]]></description></item><item><title>Spacemacs 和 Org-mode 和 LaTeX</title><author>PoiScript</author><link>https://blog.poi.cat/post/spacemacs-plus-org-mode-plus-latex</link><guid isPermaLink="false">/post/spacemacs-plus-org-mode-plus-latex</guid><category>emacs</category><category>latex</category><category>org-mode</category><pubDate>Tue, 27 Jun 2017 00:00:00 +0000</pubDate><description><![CDATA[<section></section><h1>前言</h1><section><p>我曾经试图将我的所有课笔记转换成 Markdown 文档以永久保存。我的笔记包含了大量的数学公式，所以需要在 Markdown 里内嵌 LaTeX。</p><blockquote><p>LaTeX 是对著名排版系统 TeX 的二次开发，TeX 是公认的数学公式排版最好的系统。</p></blockquote><p>但是，在 Markdown 里编辑 LaTeX 简直是噩梦：编辑器不提供 LaTeX 的补全。一个公式里可能有大量重复的 <code>\alpha</code>、<code>\sigama</code> 和 <code>\rightarrow</code> 等符号，而它们全部需要我一个字母一个字母地输入。</p><p>此外，在 Markdown 中内嵌 LaTeX 大多是通过 MathJax 实现的，但是 MathJax 支持的宏十分有限:</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/mathjax-doesnt-support-some-marcos.png" alt="MathJax 不支持很多 LaTeX 宏"><figcaption>MathJax 不支持很多 LaTeX 宏</figcaption></figure></p><p>所以在花费了大量时间将一本书的笔记录成 Markdown 之后，我放弃了。:)</p><hr><p>后来在 <code>#archlinux-cn</code> 的 <a target="_blank" rel="noreferrer noopener" href="https://twitter.com/riaqn0">riaqn</a> 安利下，我开始使用 Emacs 的 Org-mode 来作笔记。不得不说相比 Markdown，Org-mode 配合 Emacs 的生态，在各种体验上都上升了不止一个等级。</p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/org-mode-latex-preview.png" alt="Emacs 和 Org-mode 和 LaTeX"><figcaption>Emacs 和 Org-mode 和 LaTeX</figcaption></figure></p><p>当然，一个编辑器想要达到最佳的输入效果，一番配置是少不了的。下面就是我的整个 Emacs + Org-mode + LaTeX 环境的配置过程，大家可以选择性观看。</p></section><h1>Spacemacs</h1><section><p><b>Emacs(Editor MACroS)</b> 是一个文本编辑器，著名的编辑器之战的两大主角之一（另一个是 vi 即其派生版本）。</p><p>在 openSUSE 中安装 Emacs 只需要在 root 用户下运行：</p><pre><code class="lang-bash">$ <span class="token function">zypper</span> <span class="token keyword">in</span> emacs
</code></pre><p>其他的 Linux 发行版请尝试使用包管理器安装，或者和 Windows/macOS 用户一起看看<a target="_blank" rel="noreferrer noopener" href="https://www.gnu.org/software/emacs/download.html">官方文档</a>。</p><p>而 Spacemacs 是一个由社区驱动的 Emacs 的分支，它的口号是：</p><blockquote><p>The best editor is neither Emacs nor Vim, it&apos;s Emacs<b> and</b> Vim!</p></blockquote><p>正如他们的口号所言，Spacemacs 的最大的特点就是 Vim 风格中通过 <a target="_blank" rel="noreferrer noopener" href="https://www.emacswiki.org/emacs/Evil">Evil</a> 实现了 Vim 的键位绑定。在 Emacs中也能使用 Vim 中独特的 <code>insert mode</code> 和 <code>visual mode</code> 了。</p><p>Spacemacs 的默认风格是 Vim，同时也提供了 Emacs 和 Hybrid 适用不同的人。</p><p>安装 Spacemacs 只需要：</p><pre><code class="lang-bash">$ <span class="token function">mv</span> ~/.emacs.d ~/.emacs.d.old
$ <span class="token function">git</span> clone https://github.com/syl20bnr/spacemacs ~/.emacs.d
</code></pre><p>在 Windows 下的话，不仅要将 Spacemacs 的 <code>.emacs</code> 放在 <code>C:\Users\{username}\</code> 同时也要在 <code>C:\Users\{username}\AppData\Roaming</code> 里。此外，还有可能出现 <code>packages
unavailable</code> 的情况，此时可以参考一下官方的 <a target="_blank" rel="noreferrer noopener" href="https://github.com/syl20bnr/spacemacs/blob/master/doc/FAQ.org#why-are-all-packages-unavailable">FAQ</a>。</p><p>无论是什么 Editor/IDE，装好的第一步应该都是修改字体吧。Spacemacs 的默认等宽字体是 Abode 的<b> Source Code Pro</b>，这曾经是我也是我的首选，不过我现在喜欢用带连字（ligatures）的<b> Fira Code</b>。</p><p>Spacemacs 的配置文件在 <code>~/.spacemacs</code>，打开后找到 <code>dotspacemacs-default-font</code>修改成：</p><pre><code class="lang-emacs-lisp">dotspacemacs-default-font <span class="token punctuation">'(</span><span class="token string">"Fira Code"</span>
                            <span class="token lisp-property property">:size</span> <span class="token number">21</span>
                            <span class="token lisp-property property">:weight</span> normal
                            <span class="token lisp-property property">:width</span> normal
                            <span class="token lisp-property property">:powerline-scale</span> <span class="token number">1.4</span><span class="token punctuation">)</span>
</code></pre><p>其中字体大小，宽度等参数见个人喜好，你可以在<a target="_blank" rel="noreferrer noopener" href="https://github.com/syl20bnr/spacemacs/blob/master/doc/DOCUMENTATION.org#font">这里</a>看到各个参数的含义。</p><p>英文的等宽字体配置完后，还可以选择性地配置其他的语言，例如中文（这里需要先判断一下是不是 GUI）：</p><pre><code class="lang-emacs-lisp"><span class="token comment">;; Set Chinese Font</span>
<span class="token comment">;; Put this inside `dotspacemacs/user-config()`</span>
<span class="token punctuation">(</span><span class="token keyword">if</span> window-system
    <span class="token punctuation">(</span><span class="token car">dolist</span> <span class="token punctuation">(</span><span class="token car">charset</span> <span class="token punctuation">'(</span><span class="token car">kana</span> han cjk-misc bopomofo<span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">(</span><span class="token car">set-fontset-font</span> <span class="token punctuation">(</span><span class="token car">frame-parameter</span> <span class="token boolean">nil</span> <span class="token quoted-symbol variable symbol">'font</span><span class="token punctuation">)</span>
                        charset <span class="token punctuation">(</span><span class="token car">font-spec</span> <span class="token lisp-property property">:family</span> <span class="token string">"Noto Sans SC"</span> <span class="token lisp-property property">:size</span> <span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre><blockquote><p>如果不配置中文字体的话，在打开带有中文字符的文件时，Emacs 会出现严重的卡顿。:(</p></blockquote></section><h1> Org-mode &amp; LaTeX</h1><section><p>Org-mode 是 Emacs 中的一种编辑模式，支持编写 To-do list、日志和笔记等功能。其中（对于我来说）最重要的就是支持<a target="_blank" rel="noreferrer noopener" href="http://orgmode.org/manual/Embedded-LaTeX.html">内嵌 LaTeX</a> 了。Org-mode 从Emacs 22 开始就已经成为默认安装的部分了，所以不需要额外的安装操作，只需要打开带有 <code>.org</code> 的文件，就会自动进入 Org-mode 了。</p><p>不过想要有 LaTeX 的预览/导出的话，还需要安装 <code>texlive</code>。openSUSE 下依旧是在 root 环境下运行：</p><pre><code class="lang-bash">$ <span class="token function">zypper</span> <span class="token keyword">in</span> texlive
</code></pre><blockquote><p><code>#archliux-cn</code> 的 farseerfc 前辈提示我可以用 texlive 的 Unix Installer，方便管理各个版本的 texlive，不过我还未尝试过。</p></blockquote><p>其他的系统也可以使用包管理器安装或者请参照一下<a target="_blank" rel="noreferrer noopener" href="https://www.tug.org/texlive/">官方文档</a>。</p><p>Org-mode 内嵌 LaTeX 有几种方式，display 我喜欢用 <code>\[E=mc^2\]</code>，inline 我偏好 <code>$E=mc^2$</code>。预览时只需要用 <code>C-c C-x C-l (org-toggle-latex-fragment)</code> 即可。</p><p>默认的预览有点小，所以我在 <code>.spacemacs</code> 里拉伸了一下大小：</p><pre><code class="lang-emacs-lisp"><span class="token comment">;; Enlarge LaTeX Fragment in Org-mode</span>
<span class="token comment">;; Put this inside `dotspacemacs/user-config`</span>
<span class="token punctuation">(</span><span class="token keyword">require</span> <span class="token quoted-symbol variable symbol">'org</span><span class="token punctuation">)</span>
<span class="token punctuation">(</span><span class="token car">plist-put</span> org-format-latex-options <span class="token lisp-property property">:scale</span> <span class="token number">2</span><span class="token punctuation">)</span>
</code></pre></section><h1>CDLaTeX</h1><section><p>到这里看来 Emacs + Org-mode 的组合相比 Markdown 似乎没有给输入太大的便利吧。所以接下来要介绍的就是 Emacs 的另外一个 mode 了，这个 mode 可以大大提高在 Org-mode 中输入 LaTeX 的效率 ——<code>CDLaTeX</code>。</p><p>在介绍 <code>CDLaTeX</code> 的工作原理之前，先说一下 Emacs 中的 mode：Org-mode 和 <code>CDLaTeX</code>虽然同是 Emacs 的 mode，但是地位不同的。Org-mode 是 Major Mode（主要模块）；<code>CDLaTeX</code> 是 Minor Mode（辅助模块）。一般来说 Emacs 是不能同时使用多个 Major Mode。</p><blockquote><p>如果想在 Emacs 的 buffer 中开多个 Major Mode，参见：<a target="_blank" rel="noreferrer noopener" href="https://www.emacswiki.org/emacs/MultipleModes">MultipleModes</a></p></blockquote><p>所以 <code>CDLaTeX</code> 作用其实是实现了部分来自另外一个 Major Mode ——<code>AUCTeX</code> 的功能，让你可以在其他的 Major Mode 里快速输入 LaTeX。</p><p>相比直接在 Org-mode 里使用 <code>CDLaTeX</code>，Org-mode 推荐使用 Org-mode 自带的轻量版的 <code>org-cdlatex-mode</code>。</p><p><code>org-cdlatex-mode</code> 依赖 <code>cdlatex.el</code> 和 <code>texmathp.el</code>（这个来自 <code>AUTeX</code>，作用是判定目前光标所在位置是否在内嵌的 LaTeX 中）。所以我们需要在 <code>.spacemacs</code> 加入我们需要安装的 <code>CDLaTeX</code> 和 <code>AUTeX</code>：</p><pre><code class="lang-elisp">dotspacemacs-additional-packages <span class="token punctuation">'(</span><span class="token car">cdlatex</span> auctex<span class="token punctuation">)</span>
</code></pre><p>然后重启 Emacs，Spacemacs 会自动帮你安装好他们。</p><p>注意全部使用小写，而且不要用 <code>M-x package-install</code> 来安装，因为 Spacemacs 会在重启之后<a target="_blank" rel="noreferrer noopener" href="https://github.com/syl20bnr/spacemacs/blob/master/doc/FAQ.org#why-are-packages-installed-with-package-install-automatically-deleted-by-spacemacs-when-it-boots">删掉他们</a>……</p><p>再次打开 Org-mode，用 <code>M-x org-cdlatex-mode</code> 打开 <code>CDLaTeX</code>。然后你就可以体验的 <code>CDLaTeX</code> 带来的快速输入 LaTeX 的功能了：</p><ol><li><p>用 <code>&lt;TAB&gt;</code> 自动补全一些 LaTeX 模板，例如：<code>fr =&gt; ~\frac{}{}</code> 和 <code>lr( =&gt;
   \left(\right)</code>。此外，<code>&lt;TAB&gt;</code> 也可以用于在各个 <code>{}</code> 之间跳跃；</p></li><li><p><code>_</code> 和 <code>^</code> 会自动补全 <code>{}</code>，不过如果 <code>{}</code> 里只有一个元素的话，也会自动去掉；</p></li><li><p>在 <code>`</code> 后加一个字母可以快速输入一些 LaTeX 的符号，例如 <code>`a =&gt; \alpha</code> 和 <code>`b
   =&gt; \beta</code>；</p></li><li><p>在一些字母之后加上 <code>&apos;</code> 则是可以修改该字母的字体或者加修饰，例如 <code>a&apos;. =&gt;
   \dot{a}</code> 和 <code>a&apos;1 =&gt; {\displaystyle a}</code>。</p></li></ol><p><figure><img loading="lazy" src="https://blog.poi.cat/images/cdlatex-apostrophe-completions.png" alt="' 的补全"><figcaption>' 的补全</figcaption></figure></p><p><figure><img loading="lazy" src="https://blog.poi.cat/images/cdlatex-backtick-completions.png" alt="` 的补全"><figcaption>` 的补全</figcaption></figure></p></section><h1>后语</h1><section><p>经过上述配置和折腾之后，整理笔记终于成了一件不那么麻烦的事了。:)</p><p>一直有人问我为什么那么费劲的在各种标记语言里嵌套 LaTeX，而不直接用 LaTeX 写呢？嗯…其实我一直有一个想法就是建立一个属于自己的类似知识库的东西，所以我的期望是可以导出成 HTML 以便于时刻可以浏览，而 LaTeX 在这方面就比较吃力了。</p><p>不过使用了 Org-mode 之后也有一个问题：目前似乎没有现成的基于 Org-mode 的 Wiki Software，所以这意味着我可能又需要自己造个轮子了……:(</p></section>]]></description></item></channel></rss>