<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>mogita</title><link>https://mogita.com/</link><description>Recent content on mogita</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><atom:link href="https://mogita.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Godot 2D Wall Occlusion</title><link>https://mogita.com/godot-2d-wall-occlusion.html</link><pubDate>Sun, 08 Oct 2023 15:06:19 +0800</pubDate><guid>https://mogita.com/godot-2d-wall-occlusion.html</guid><description>&lt;p>In Godot, to let the player occlude the wall when in the front and the wall block the player when in the back, there are 2 approaches. The built-in and easier way is Y Sort. Another approach is coding a &amp;ldquo;trapdoor trigger&amp;rdquo; that makes the wall visible.&lt;/p>
&lt;p>For both approaches, one would need to clip the &amp;ldquo;wall&amp;rdquo; out from the background image and put it in the same position to &amp;ldquo;fake&amp;rdquo; a consistent look of the map. The bottom line is there are two layers to achieve the occlusion effect.&lt;/p>
&lt;div style="display: flex; justify-content: space-between;">
&lt;img style="width: 49%;" src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/scr-20231008-lmlh.png" />
&lt;img style="width: 49%;" src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/scr-20231008-lreb.png" />
&lt;/div>
&lt;h1 id="with-triggers">
With Triggers
&lt;a class="heading-link" href="#with-triggers">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/xhtQhED24MA?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"
>&lt;/iframe>
&lt;/div>
&lt;p>Two collision nodes are used. One is the &lt;code>CollisionShape2D&lt;/code> node that signals the root node to &amp;ldquo;show&amp;rdquo; the wall sprite, thus impeding the player standing behind. Another one is the &lt;code>CollisionPolygon2D&lt;/code> node, which signals the root node to &amp;ldquo;hide&amp;rdquo; the wall when the player is standing in the front.&lt;/p>
&lt;p>This approach requires delicate adjustments on the collision nodes to make the final look correct and smooth. But you have total control of how the environments should interact with the character. With a large scene set, it could easily go into a mess. Use Y Sort whenever possible.&lt;/p>
&lt;h1 id="with-y-sort">
With Y Sort
&lt;a class="heading-link" href="#with-y-sort">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>Y Sort is robust and more convenient. It doesn&amp;rsquo;t require additional collision nodes. In Godot 4, check the Y Sort Enabled option on the parent node. In Godot 3, there&amp;rsquo;s a node type called &lt;a href="https://docs.godotengine.org/en/3.5/classes/class_ysort.html" class="external-link" target="_blank" rel="noopener">YSort&lt;/a>. In my case, I have enabled Y Sort by checking the box on the root node named &amp;ldquo;Level.&amp;rdquo;&lt;/p>
&lt;div style="display: flex; justify-content: center; align-items: center;">
&lt;img style="width: 100%; max-width: 500px;" src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/scr-20231008-mrdf.png" />
&lt;/div>
&lt;p>To start with, put all the elements in place and enable the &amp;ldquo;Y Sort Enabled&amp;rdquo; option in the root node. All of its child nodes inheriting &lt;code>CamvasItem&lt;/code> are Y Sorted. This means when the player sprite moves to a lower position than the wall sprite (with a larger &amp;ldquo;Y&amp;rdquo; value on the axis), Y Sort makes the player occlude the wall, and vice-versa. There&amp;rsquo;s only one catch.&lt;/p>
&lt;p>By default, the origin is at the center when an image is dragged into a &lt;code>Sprite2D&lt;/code> node. Adjust the &amp;ldquo;Y&amp;rdquo; value in the offset. Usually, one moves the image to the &amp;ldquo;above&amp;rdquo; by decreasing &amp;ldquo;Y&amp;rdquo; or moving the origin point to the bottom. Depending on the exact image used in the scene, the origin might need to be set to a different position. Then, adjust the player character&amp;rsquo;s origin, placing it around the foot of the character&amp;rsquo;s image. Now, Y Sort should function properly.&lt;/p>
&lt;div style="display: flex; justify-content: space-between;">
&lt;img style="width: 49%;" src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/origin-at-center.png" />
&lt;img style="width: 49%;" src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/origin-at-bottom.png" />
&lt;/div>
&lt;p>One thing to notice is that the Y Sorted nodes should be on the same Z Index. In my case, the wall and the character are both on Z Index 0, while the backdrop (all buildings) is on -1, making it not involved in the Y Sorting among the wall and the character to always stay behind them both.&lt;/p>
&lt;p>The result of using Y Sort:&lt;/p>
&lt;p>&lt;video src="https://static.mogita.com/blog/assets/2023-10-08-godot-2d-wall-occlusion/godot-2d-occlusion-with-y-sort.mp4" controls="yes" type="video/mp4" width="100%">&lt;/video>&lt;/p>
&lt;h1 id="credits">
Credits
&lt;a class="heading-link" href="#credits">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>Inspired by &lt;a href="https://www.reddit.com/r/godot/comments/1109shm/ysort_godot_35_vs_4/" class="external-link" target="_blank" rel="noopener">https://www.reddit.com/r/godot/comments/1109shm/ysort_godot_35_vs_4/&lt;/a>&lt;/li>
&lt;li>Background Illustration &lt;a href="https://headtopics.com/us/photo/waitress-at-kieler-woche-2023-132326" class="external-link" target="_blank" rel="noopener">https://headtopics.com/us/photo/waitress-at-kieler-woche-2023-132326&lt;/a>&lt;/li>
&lt;li>RPG Character Builder &lt;a href="https://store.steampowered.com/app/1154430/RPG_Character_Builder/" class="external-link" target="_blank" rel="noopener">https://store.steampowered.com/app/1154430/RPG_Character_Builder/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>A Personal Mastodon Instance Setup</title><link>https://mogita.com/a-personal-mastodon-instance-setup.html</link><pubDate>Thu, 22 Dec 2022 12:57:46 +0800</pubDate><guid>https://mogita.com/a-personal-mastodon-instance-setup.html</guid><description>&lt;p>I’ve not always wanted to bring up a Mastodon instance for it was not that intriguing to me, until recently Musk has worked on Twitter in an amateur way.&lt;/p>
&lt;p>On the other hand, to host an instance of my own is a way of paying tribute to the past ‘90s self-host Email compaign which I was too young and poor to have the chance to experience. At least for now, we are seeing a blooming new way of social networking, whether or not a long-term fight against spam and junk information in the future awaits.&lt;/p>
&lt;blockquote>
&lt;p>&lt;a href="https://twitter.com/mogita/status/1605188553423536134" class="external-link" target="_blank" rel="noopener">https://twitter.com/mogita/status/1605188553423536134&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>I’ll open source my setup. You can skip to the Code section to see the project.&lt;/p>
&lt;p>You can also check out my instance &lt;a href="https://mog.blue/@mogita" class="external-link" target="_blank" rel="noopener">@mogita@mog.blue&lt;/a>. I&amp;rsquo;m always ready for a little chat.&lt;/p>
&lt;h1 id="how-to-run">
How To Run
&lt;a class="heading-link" href="#how-to-run">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>I’m going to serve my Mastodon instance through Docker with &lt;code>docker-compose&lt;/code> for an easier and consistent management workflow.&lt;/p>
&lt;h1 id="what-to-include">
What To Include
&lt;a class="heading-link" href="#what-to-include">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>Inside the &lt;code>docker-compose.yml&lt;/code> file I’ll put these components:&lt;/p>
&lt;ul>
&lt;li>Mastodon web: &lt;code>tootsuite/mastodon:latest&lt;/code> + ES analyzer tweak (for indexing non-English languages)&lt;/li>
&lt;li>Database: &lt;code>postgres:14-alpine&lt;/code>&lt;/li>
&lt;li>Redis: &lt;code>redis:7-alpine&lt;/code>&lt;/li>
&lt;li>ElasticSearch: &lt;code>elasticsearch:7.17.8&lt;/code> + &lt;a href="https://github.com/medcl/elasticsearch-analysis-ik" class="external-link" target="_blank" rel="noopener">ik&lt;/a> + &lt;a href="https://github.com/medcl/elasticsearch-analysis-stconvert" class="external-link" target="_blank" rel="noopener">stconvert&lt;/a>&lt;/li>
&lt;li>Streaming: &lt;code>tootsuite/mastodon:latest&lt;/code>&lt;/li>
&lt;li>Sidekiq: &lt;code>tootsuite/mastodon:latest&lt;/code>&lt;/li>
&lt;li>Nginx: &lt;code>nginx:latest&lt;/code>&lt;/li>
&lt;/ul>
&lt;h1 id="es-analyzer-tweak">
ES Analyzer Tweak
&lt;a class="heading-link" href="#es-analyzer-tweak">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>A vanilla Mastodon instance is fairly easy to setup from the basic versions of the said docker images. However, adding optimization for other languages in its search functionality requires code modification which would impair the simplicity of just following the minimum workflow. Hope this necessity can be replaced with a more proper way, like configurable through &lt;code>.env&lt;/code>.&lt;/p>
&lt;p>At the time of writing, there are 3 steps according to the &lt;a href="https://docs.joinmastodon.org/admin/optional/elasticsearch/#search-optimization-for-other-languages" class="external-link" target="_blank" rel="noopener">official documentation&lt;/a>:&lt;/p>
&lt;ol>
&lt;li>Install ES plugin &lt;a href="https://github.com/medcl/elasticsearch-analysis-ik" class="external-link" target="_blank" rel="noopener">https://github.com/medcl/elasticsearch-analysis-ik&lt;/a>&lt;/li>
&lt;li>Install ES plugin &lt;a href="https://github.com/medcl/elasticsearch-analysis-stconvert" class="external-link" target="_blank" rel="noopener">https://github.com/medcl/elasticsearch-analysis-stconvert&lt;/a>&lt;/li>
&lt;li>Modify Mastodon’s source code on index definition&lt;/li>
&lt;/ol>
&lt;p>Previous 2 steps can be achieved relatively easier. Just create a &lt;code>Dockerfile&lt;/code> for a custom ES build and make the installation on the official ES image. Pay attention to the version consistency though.&lt;/p>
&lt;p>As of step 3, I’ll &lt;code>patch&lt;/code> the diff code provided by the documentation. Nonetheless a custom image is necessary too.&lt;/p>
&lt;p>To sum up, I’ll need to build 2 images:&lt;/p>
&lt;ul>
&lt;li>A custom Mastodon image for: Mastodon web, Streaming and Sidekiq&lt;/li>
&lt;li>A custom ElasticSearch image for: ElasticSearch with non-English languages support&lt;/li>
&lt;/ul>
&lt;h1 id="miscellaneous">
Miscellaneous
&lt;a class="heading-link" href="#miscellaneous">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;h2 id="https-support">
HTTPS Support
&lt;a class="heading-link" href="#https-support">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>My Mastodon setup will enjoy the benefits from &lt;code>nginx-proxy&lt;/code> and &lt;code>acme-companion&lt;/code> for automatic SSL certificates challenging and renewal. This is why I have an Nginx component on the list. It’s the reverse proxy to handle SSL traffic.&lt;/p>
&lt;h2 id="object-storage-support">
Object Storage Support
&lt;a class="heading-link" href="#object-storage-support">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Using a cheap S3 compatible object storage service is my go-to plan on a minimum user base. Having it gone through a Cloudflare CDN might help with lowering the bandwidth cost too.&lt;/p>
&lt;h2 id="email-relay">
Email Relay
&lt;a class="heading-link" href="#email-relay">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Google Workplace SMTP relay service is my first choice since I’m on a business plan. For free alternatives, any relay service shall do and many offer a free amount for around 100 or more Emails per day.&lt;/p>
&lt;h2 id="backups">
Backups
&lt;a class="heading-link" href="#backups">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Since the object storage will be used, the only critical thing to backup is the database. A script that sends a daily &lt;code>pg_dump&lt;/code> archive to one of my storage service provider shall do. If things go crazy in the future, like I’ll need to host the instance for hundreds of users, then master-slave replica might be useful as an additional measurement.&lt;/p>
&lt;h2 id="scaling">
Scaling
&lt;a class="heading-link" href="#scaling">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>No need to consider right now. Cost effectivity, on the contrary, shall be my first priority given the instance being invitation only.&lt;/p>
&lt;h1 id="code">
Code
&lt;a class="heading-link" href="#code">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>I named my Mastodon setup project the &lt;code>monsts&lt;/code>, it stands for “Mastodon Of Not So Typical Setup”.&lt;/p>
&lt;p>You can browse the source code here: &lt;a href="https://gitlab.com/mogita/monsts" class="external-link" target="_blank" rel="noopener">https://gitlab.com/mogita/monsts&lt;/a>&lt;/p></description></item><item><title>Things I Use</title><link>https://mogita.com/uses.html</link><pubDate>Wed, 12 Oct 2022 15:09:29 +0800</pubDate><guid>https://mogita.com/uses.html</guid><description>&lt;p>These are the listings of the tools, hardware, and software I use on a daily basis in my home office.&lt;/p>
&lt;h1 id="hardware">
Hardware
&lt;a class="heading-link" href="#hardware">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>💻 MacBook Pro (M4 Max, 2024)&lt;/p>
&lt;ul>
&lt;li>RAM: 48GB&lt;/li>
&lt;li>Storage: 1TB&lt;/li>
&lt;li>OS: macOS&lt;/li>
&lt;/ul>
&lt;p>📺 Mi Monitor A27U, 27&amp;quot; 4K Display&lt;/p>
&lt;p>🖱️ Hecate G3Pro&lt;/p>
&lt;p>⌨️ HHKB Professional BT Keyboard&lt;/p>
&lt;p>📱 iPhone 14 Pro&lt;/p>
&lt;p>🎧 Philips TAH7508 Headphone, with Noise Canceling&lt;/p>
&lt;p>🎙️ Audio-Technica ATR2500-x USB&lt;/p>
&lt;p>🎮 Xbox Series Joystick&lt;/p>
&lt;h1 id="software">
Software
&lt;a class="heading-link" href="#software">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>macOS&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>iTerm2 (oh-my-zsh &amp;amp; starship)&lt;/li>
&lt;li>Neovim&lt;/li>
&lt;li>&lt;del>Firefox&lt;/del> Zen Browser&lt;/li>
&lt;li>Apple Mail&lt;/li>
&lt;li>&lt;del>Tweetbot&lt;/del> Ivory&lt;/li>
&lt;li>Notion&lt;/li>
&lt;li>&lt;del>1Password&lt;/del> Bitwarden&lt;/li>
&lt;li>Reeder&lt;/li>
&lt;li>Dropbox&lt;/li>
&lt;li>Apple Pro Apps for A/V editing&lt;/li>
&lt;li>Godot&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>iPhone&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Apple Mail&lt;/li>
&lt;li>WaterMinder&lt;/li>
&lt;li>&lt;del>Overcast&lt;/del> PocketCasts&lt;/li>
&lt;li>Duolingo&lt;/li>
&lt;li>Ivory&lt;/li>
&lt;/ul></description></item><item><title>Resume</title><link>https://mogita.com/resume.html</link><pubDate>Wed, 12 Oct 2022 12:22:42 +0800</pubDate><guid>https://mogita.com/resume.html</guid><description>&lt;h1 id="professional-skills">
Professional Skills
&lt;a class="heading-link" href="#professional-skills">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>Languages
&lt;ul>
&lt;li>Go&lt;/li>
&lt;li>Node.js&lt;/li>
&lt;li>Python&lt;/li>
&lt;li>Web: React.js / ES6 / HTML / CSS&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Databases
&lt;ul>
&lt;li>RDBS: PostgreSQL / MySQL&lt;/li>
&lt;li>KV: BoltDB / Redis&lt;/li>
&lt;li>NoSQL: MongoDB&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>GIS
&lt;ul>
&lt;li>PostGIS&lt;/li>
&lt;li>OpenStreetMap / Mapbox&lt;/li>
&lt;li>OSRM / Valhalla / Overpass / OpenMapTiles&lt;/li>
&lt;li>Visualization / QGIS&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>DevOps
&lt;ul>
&lt;li>Docker&lt;/li>
&lt;li>Kubernetes&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="experiences">
Experiences
&lt;a class="heading-link" href="#experiences">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://getstream.io" class="external-link" target="_blank" rel="noopener">Stream&lt;/a>, 2025~present&lt;/strong> / Amsterdam&lt;/p>
&lt;p>Staff software engineer&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://nextbillion.ai" class="external-link" target="_blank" rel="noopener">NextBillion.ai&lt;/a>, 2020~2025&lt;/strong> / Singapore&lt;/p>
&lt;p>Fullstack software engineer: real-time traffic flow, map data and SDK, sales system&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>&lt;a href="https://grab.com" class="external-link" target="_blank" rel="noopener">Grab&lt;/a>, 2017~2020&lt;/strong> / Beijing&lt;/p>
&lt;p>Software engineer manager: geo team, leading Grab Maps, Grab street view, and real-time visualization&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Geekniu&lt;/strong> (&lt;a href="https://pitchhub.36kr.com/project/1678508619002881" class="external-link" target="_blank" rel="noopener">ref&lt;/a>) &lt;strong>2015~2017&lt;/strong> / Beijing&lt;/p>
&lt;p>Software engineer: BaaS bootstrapping, in-store digital sales solution&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="education">
Education
&lt;a class="heading-link" href="#education">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>Beijing International Studies University, 2012~2015&lt;/strong>&lt;/p>
&lt;p>Master of aesthetics&lt;/p>
&lt;p>&lt;strong>Beijing International Studies University, 2006~2010&lt;/strong>&lt;/p>
&lt;p>Bechelor of Spanish&lt;/p>
&lt;h1 id="references">
References
&lt;a class="heading-link" href="#references">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>Available upon request or visit &lt;a href="https://linkedin.com/in/yun-wang-646d753d/" class="external-link" target="_blank" rel="noopener">https://linkedin.com/in/yun-wang-646d753d/&lt;/a>&lt;/p></description></item><item><title>Minikube With Podman on Apple Silicon</title><link>https://mogita.com/minikube-podman-on-m1-apple-silicon.html</link><pubDate>Mon, 29 Aug 2022 11:38:55 +0800</pubDate><guid>https://mogita.com/minikube-podman-on-m1-apple-silicon.html</guid><description>&lt;p>&lt;a href="https://minikube.sigs.k8s.io/docs/" class="external-link" target="_blank" rel="noopener">Minikube&lt;/a> has been a go-to environment for running a single-node cluster to test out the Kubernetes features on a local platform. For macOS computers with an Apple Silicon, a.k.a. M1 or M2, minikube provides an ARM64 version and can be installed with &lt;a href="https://formulae.brew.sh/formula/minikube" class="external-link" target="_blank" rel="noopener">Homebrew&lt;/a>.&lt;/p>
&lt;p>However when it comes to start minikube after successfully installed it, it would require Docker as the default driver. Here comes my problem. As a matter of fact, Docker on macOS has long been released under the name of &amp;ldquo;Docker Desktop&amp;rdquo;, which contains a huge pile of Electron based applications and UI. It starts slow too. I don&amp;rsquo;t ever want to install such a &lt;strong>monstrous&lt;/strong> thing in my system. So I have to give up the default driver and look for alternatives.&lt;/p>
&lt;p>At the time of writing, minikube supports various kinds of drivers, such as Docker, Hyperkit, Hyper-V, KVM, Parallels, Podman, VirtualBox, and VMware Fusion/Workstation. After an hour of struggling and testing, Podman stood out. It&amp;rsquo;s backed by a QEMU virtual machine, supports Docker API and Docker-based tools, and can be intsalled in a very smooth fasion.&lt;/p>
&lt;p>So I&amp;rsquo;m going to record the steps to initialize and maintain a minikube cluster with Podman.&lt;/p>
&lt;h1 id="installation">
Installation
&lt;a class="heading-link" href="#installation">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>With the help of the &lt;a href="https://minikube.sigs.k8s.io/docs/start/" class="external-link" target="_blank" rel="noopener">Get Started&lt;/a> documentation for minikube, just pick an installation option that works. Mine was &lt;code>brew install minikube&lt;/code>.&lt;/p>
&lt;p>Then the Podman installation done with &lt;code>brew install podman&lt;/code>.&lt;/p>
&lt;p>Simple enough.&lt;/p>
&lt;h1 id="start-up">
Start Up
&lt;a class="heading-link" href="#start-up">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>Create a Podman machine with the following command, giving it 2 CPU cores and 8Gi memory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>podman machine init --cpus 2 --memory 8192 --disk-size 80
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>podman machine start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>podman system connection default podman-machine-default-root
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now Podman should be running in the background. One can check out the information with &lt;code>podman info&lt;/code> command, or find out its running stats with &lt;code>podman stats&lt;/code> command.&lt;/p>
&lt;p>To start minikube, follow the step below. By default, minikube uses Docker as the driver. One should pass a &lt;code>--driver&lt;/code> argument to change that.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>minikube start --driver=podman --container-runtime=cri-o
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>One should see the output similar to the following logs:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">❯&lt;/span> minikube start &lt;span style="color:#81a1c1">--&lt;/span>driver&lt;span style="color:#81a1c1">=&lt;/span>podman &lt;span style="color:#81a1c1">--&lt;/span>container&lt;span style="color:#81a1c1">-&lt;/span>runtime&lt;span style="color:#81a1c1">=&lt;/span>cri&lt;span style="color:#81a1c1">-&lt;/span>o
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">😄&lt;/span> minikube v1&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">26.1&lt;/span> on Darwin &lt;span style="color:#b48ead">12.5&lt;/span>&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">1&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>arm64&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">✨&lt;/span> Using the podman &lt;span style="color:#eceff4">(&lt;/span>experimental&lt;span style="color:#eceff4">)&lt;/span> driver based on existing profile
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">👍&lt;/span> Starting control plane node minikube &lt;span style="color:#81a1c1;font-weight:bold">in&lt;/span> cluster minikube
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🚜&lt;/span> Pulling base image &lt;span style="color:#81a1c1">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>E0829 &lt;span style="color:#b48ead">11&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">34&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">04.822031&lt;/span> &lt;span style="color:#b48ead">11181&lt;/span> cache&lt;span style="color:#81a1c1">.&lt;/span>go&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">203&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> Error downloading kic artifacts&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">not&lt;/span> yet implemented&lt;span style="color:#eceff4">,&lt;/span> see issue &lt;span style="color:#616e87;font-style:italic">#8426&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🔄&lt;/span> Restarting existing podman container &lt;span style="color:#81a1c1;font-weight:bold">for&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;minikube&amp;#34;&lt;/span> &lt;span style="color:#81a1c1">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🎁&lt;/span> Preparing Kubernetes v1&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">24.3&lt;/span> on CRI&lt;span style="color:#81a1c1">-&lt;/span>O &lt;span style="color:#b48ead">1.24&lt;/span>&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">1&lt;/span> &lt;span style="color:#81a1c1">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>E0829 &lt;span style="color:#b48ead">11&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">34&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">09.704438&lt;/span> &lt;span style="color:#b48ead">11181&lt;/span> start&lt;span style="color:#81a1c1">.&lt;/span>go&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">129&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> Unable to get host &lt;span style="color:#bf616a">IP&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> RoutableHostIPFromInside is currently only implemented &lt;span style="color:#81a1c1;font-weight:bold">for&lt;/span> linux
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🔗&lt;/span> Configuring CNI &lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#bf616a">Container&lt;/span> Networking Interface&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🔎&lt;/span> Verifying Kubernetes components&lt;span style="color:#81a1c1">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#bf616a">▪&lt;/span> Using image gcr&lt;span style="color:#81a1c1">.&lt;/span>io&lt;span style="color:#81a1c1">/&lt;/span>k8s&lt;span style="color:#81a1c1">-&lt;/span>minikube&lt;span style="color:#81a1c1">/&lt;/span>storage&lt;span style="color:#81a1c1">-&lt;/span>provisioner&lt;span style="color:#eceff4">:&lt;/span>v5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#bf616a">▪&lt;/span> Using image kubernetesui&lt;span style="color:#81a1c1">/&lt;/span>dashboard&lt;span style="color:#eceff4">:&lt;/span>v2&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">6.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#bf616a">▪&lt;/span> Using image kubernetesui&lt;span style="color:#81a1c1">/&lt;/span>metrics&lt;span style="color:#81a1c1">-&lt;/span>scraper&lt;span style="color:#eceff4">:&lt;/span>v1&lt;span style="color:#81a1c1">.&lt;/span>&lt;span style="color:#b48ead">0.8&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🌟&lt;/span> Enabled addons&lt;span style="color:#eceff4">:&lt;/span> storage&lt;span style="color:#81a1c1">-&lt;/span>provisioner&lt;span style="color:#eceff4">,&lt;/span> default&lt;span style="color:#81a1c1">-&lt;/span>storageclass&lt;span style="color:#eceff4">,&lt;/span> dashboard
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">🏄&lt;/span> Done&lt;span style="color:#81a1c1">!&lt;/span> kubectl is now configured to use &lt;span style="color:#a3be8c">&amp;#34;minikube&amp;#34;&lt;/span> cluster &lt;span style="color:#81a1c1;font-weight:bold">and&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;default&amp;#34;&lt;/span> namespace by default
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alternative to explicitly passing the &lt;code>--driver&lt;/code> argument, one can change the defautl driver with this command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>minikube config set driver podman
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="stop--restart">
Stop / Restart
&lt;a class="heading-link" href="#stop--restart">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>To stop the minikube + Podman system, follow these commands:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>minikube stop
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>podman machine stop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To restart the system, simply run &lt;code>start&lt;/code> respectively (note the sequence, &lt;code>podman&lt;/code> should go first as &lt;code>minikube&lt;/code> depends on it):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>podman machine start
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>minikube start --driver&lt;span style="color:#81a1c1">=&lt;/span>podman --container-runtime&lt;span style="color:#81a1c1">=&lt;/span>cri-o
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># or with the default driver changed to podman:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>minikube start
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="anything-else">
Anything Else?
&lt;a class="heading-link" href="#anything-else">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>Q&lt;/strong>: If there are multiple clusters to manage, including but not limited to GKE (by Google Cloud) and EKS (by AWS) etc., how to manage and switch from these clusters?&lt;/p>
&lt;p>&lt;strong>A&lt;/strong>: This can be configured through &lt;code>~/.kube/config&lt;/code> easily as Kubernetes has the concept of &amp;ldquo;Context&amp;rdquo;, which basically means different clusters. Upon minikube successfully starting, a new context called &lt;code>minikube&lt;/code> shall be inserted automatically to this config file. One can list and switch to a certain context using the following commands (switching to minikube for example):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>kubectl config get-contexts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl config set current-context minikube
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>&lt;strong>Q&lt;/strong>: How to test out a named &lt;code>StorageClass&lt;/code> in a PVC object with minikube?&lt;/p>
&lt;p>&lt;strong>A&lt;/strong>: By default, minikube comes with a built-in SC named &lt;code>standard&lt;/code>. It provides dynamic storage provisioning when one creates a PVC with non-named storage class. One should create a storage class with the customized name before creating the PVC with a named storage class to enable auto-creation of PV and PVC. First, create the named SC like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">apiVersion&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> storage.k8s.io/v1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">kind&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> StorageClass
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">metadata&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">name&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> your-custom-name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">annotations&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">storageclass.kubernetes.io/is-default-class&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;false&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">provisioner&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> k8s.io/minikube-hostpath
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s important to set the provisioner field to &lt;code>k8s.io/minikube-hostpath&lt;/code>, otherwise no auto-creation would happen. After the SC has been successfully created, when applying the following PVC object, one should observe that a PV being automatically created and the PVC gets instantly fulfilled:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">kind&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> PersistentVolumeClaim
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">apiVersion&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> v1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">metadata&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">name&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> {{ .Name }}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">namespace&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> {{ .Namespace }}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">spec&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">accessModes&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#eceff4">[&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;ReadWriteOnce&amp;#34;&lt;/span> &lt;span style="color:#eceff4">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">storageClassName&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> your-custom-name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">volumeMode&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> Filesystem
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">resources&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">requests&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">storage&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> {{ .DiskSize }}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You&amp;rsquo;ll find this article helpful with understanding &lt;a href="https://platform9.com/blog/tutorial-dynamic-provisioning-of-persistent-storage-in-kubernetes-with-minikube/" class="external-link" target="_blank" rel="noopener">dynamic provisioning of persistent storage with minikube&lt;/a>.&lt;/p></description></item><item><title>About</title><link>https://mogita.com/about.html</link><pubDate>Tue, 23 Mar 2021 12:32:03 +0800</pubDate><guid>https://mogita.com/about.html</guid><description>&lt;p>I&amp;rsquo;m Yun, using the handle of @&lt;a href="https://mog.blue/@mogita" class="external-link" target="_blank" rel="noopener">mogita&lt;/a> (nearly) everywhere. I&amp;rsquo;ve worked as a professional software engineer since 2015. Before that, I wrote programs by self-teaching. You can download my &lt;a href="resume.html" >resume&lt;/a> or check my recent coding activity &lt;a href="https://wakatime.com/@mogita" class="external-link" target="_blank" rel="noopener">&lt;img src="https://wakatime.com/badge/user/aaec41c4-55c8-45b4-84b5-749679d30c6f.svg" alt="wakatime">&lt;/a>.&lt;/p>
&lt;p>Here&amp;rsquo;s a list of devices and softwares &lt;a href="https://mogita.com/uses.html" >that I use&lt;/a> on a daily basis.&lt;/p>
&lt;p>This is my personal website. Opinions are my own.&lt;/p>
&lt;h1 id="bio">
Bio
&lt;a class="heading-link" href="#bio">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>🧑‍💻 Staff software engineer
🎮 Indie game dev
🎧 &lt;a href="https://space.bilibili.com/70342" class="external-link" target="_blank" rel="noopener">Drummer&lt;/a>, bedroom &lt;a href="https://soundcloud.com/mogita" class="external-link" target="_blank" rel="noopener">producer&lt;/a>
🇸🇬 Located in Singapore
🇨🇳 中文 🇬🇧 English 🇯🇵 日本語で数えるのはできる 🇪🇸 A veces leo en español&lt;/p>
&lt;h1 id="contact">
Contact
&lt;a class="heading-link" href="#contact">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>Email &lt;a href="mailto:me@mogita.com" >me@mogita.com&lt;/a>&lt;/li>
&lt;li>PGP &lt;a href="https://keybase.io/mogita/pgp_keys.asc?fingerprint=6db72fe720f619f710cdb4c9a0aa1b9c57a48ecf" class="external-link" target="_blank" rel="noopener">&lt;code>6DB7 2FE7 20F6 19F7 10CD B4C9 A0AA 1B9C 57A4 8ECF&lt;/code>&lt;/a>&lt;/li>
&lt;li>Calendar &lt;a href="https://cal.com/mogita" class="external-link" target="_blank" rel="noopener">https://cal.com/mogita&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="netizenship">
Netizenship
&lt;a class="heading-link" href="#netizenship">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://mog.blue/@mogita" class="external-link" target="_blank" rel="noopener">Mastodon&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita" class="external-link" target="_blank" rel="noopener">GitHub&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://lobste.rs/u/mogita" class="external-link" target="_blank" rel="noopener">Lobsters&lt;/a>&lt;/li>
&lt;li>&lt;em>&lt;del>&lt;a href="https://gitlab.com/mogita" class="external-link" target="_blank" rel="noopener">GitLab&lt;/a>&lt;/del>&lt;/em>&lt;/li>
&lt;li>&lt;em>&lt;del>&lt;a href="https://twitter.com/mogita" class="external-link" target="_blank" rel="noopener">Twitter&lt;/a>&lt;/del>&lt;/em>&lt;/li>
&lt;/ul>
&lt;h1 id="projects">
Projects
&lt;a class="heading-link" href="#projects">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;h2 id="open-source">
Open Source
&lt;a class="heading-link" href="#open-source">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://github.com/mogita/go-sylt" class="external-link" target="_blank" rel="noopener">go-sylt&lt;/a> - A pretty convenient CLI tool that reads and writes synced lyrics to MP3 files in SYLT (SYnchronized Lyrics/Text) format.&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita/osmflux" class="external-link" target="_blank" rel="noopener">osmflux&lt;/a> - Cross-platform OSM tools with GUI&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita/inoreader-js" class="external-link" target="_blank" rel="noopener">inoreader-js&lt;/a> - A TypeScript library for the Inoreader API&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita/go-fanfou" class="external-link" target="_blank" rel="noopener">Go Fanfou&lt;/a> - A Fanfou API library for Go&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita/vue-zydialog" class="external-link" target="_blank" rel="noopener">Vue Zydialog&lt;/a> - A dialog component for Vue.js&lt;/li>
&lt;/ul>
&lt;h2 id="services">
Services
&lt;a class="heading-link" href="#services">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://gao.mobi" class="external-link" target="_blank" rel="noopener">GMB&lt;/a> - Your AI assistant for work, GPT chatting, multiple models, image creation, with lower prices.&lt;/li>
&lt;li>&lt;a href="https://github.com/mogita/yocat" class="external-link" target="_blank" rel="noopener">YoCat&lt;/a> - Powering the same named &lt;a href="https://mog.blue/@yocat" class="external-link" target="_blank" rel="noopener">bot&lt;/a> for recognizing and repost cat pictures&lt;/li>
&lt;li>&lt;a href="https://soundmono.com" class="external-link" target="_blank" rel="noopener">Soundmono&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://fanstafou.mogita.com" class="external-link" target="_blank" rel="noopener">FanstaFou&lt;/a> (Retired)&lt;/li>
&lt;/ul>
&lt;h1 id="portkeys-supa-hrefhttpsharrypotterfandomcomwikiportkey-target_blankasup">
Portkeys &lt;sup>&lt;a href="https://harrypotter.fandom.com/wiki/Portkey" target="_blank">?&lt;/a>&lt;/sup>
&lt;a class="heading-link" href="#portkeys-supa-hrefhttpsharrypotterfandomcomwikiportkey-target_blankasup">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;ul>
&lt;li>&lt;a href="https://www.yocson.com" class="external-link" target="_blank" rel="noopener">配信中&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://archeanz.com" class="external-link" target="_blank" rel="noopener">Archean&amp;rsquo;s Blog&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://rockneko.xyz" class="external-link" target="_blank" rel="noopener">Rock Neko&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>The Tracking Pixels in Your Emails</title><link>https://mogita.com/the-tracking-pixels-in-your-emails.html</link><pubDate>Fri, 12 Mar 2021 12:41:00 +0000</pubDate><guid>https://mogita.com/the-tracking-pixels-in-your-emails.html</guid><description>&lt;h1 id="what-is-a-tracking-pixel">
What Is A Tracking Pixel
&lt;a class="heading-link" href="#what-is-a-tracking-pixel">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>A Tracking Pixel is a technique found these days in Email marketing, promotions, newsletters and even casual notifications contents of Email campaign services. It inserts a transparent &lt;code>1px x 1px&lt;/code> image to the mail body that is invisible to human eyes and uses the good-old Web technology to send information to whoever&amp;rsquo;s on the other end collecting as many aspects as they want about the person who reads the Email:&lt;/p>
&lt;ul>
&lt;li>Operating system&lt;/li>
&lt;li>Device model&lt;/li>
&lt;li>Type of the Email client&lt;/li>
&lt;li>The reader&amp;rsquo;s screen resolution&lt;/li>
&lt;li>When and how many times the Email was read or website visited&lt;/li>
&lt;li>Activities on the website in a session&lt;/li>
&lt;li>The IP address of the reader (this gives away the ISP information and even location based on some extremely precise GeoIP databases that are very easy to get)&lt;/li>
&lt;/ul>
&lt;p>A sample tracking pixel snippet from &lt;a href="https://github.com/apparition47/MailTrackerBlocker" class="external-link" target="_blank" rel="noopener">MailTrackerBlocker&lt;/a>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-diff" data-lang="diff">&lt;span style="display:flex;">&lt;span>&amp;lt;a style=&amp;#34;color: #770506;&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;img src=&amp;#34;http://cdn.website.com/newsletter/logo.png&amp;#34; width=&amp;#34;438&amp;#34; height=&amp;#34;42&amp;#34; border=&amp;#34;0&amp;#34; style=&amp;#34;max-width: 90%; height: auto&amp;#34; alt=&amp;#34;logo.png&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;br&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;a href=&amp;#34;https://website.us5.list-manage.com/unsubscribe?u=abdef&amp;#34;&amp;gt;Click here to unsubscribe&amp;lt;/a&amp;gt; or &amp;lt;a href=&amp;#34;https://website.us5.list-manage.com/profile?u=abdef&amp;#34;&amp;gt;Update subscription preferences&amp;lt;/a&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#bf616a">- &amp;lt;img width=&amp;#34;0&amp;#34; height=&amp;#34;0&amp;#34; class=&amp;#34;mailtrack-img&amp;#34; alt=&amp;#34;&amp;#34; style=&amp;#34;display:flex&amp;#34; src=&amp;#34;https://mailtrack.io/trace/mail/0eabccbe98c98e9b8e9a8b89eab89ce9ab89e8bc.png?u=1234567&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Note that the highlighted line of code inserts the invisible pixel of image to the mail body.&lt;/p>
&lt;p>Actually very few people ever noticed that their behaviours were tracked (or to say harvested) by the people who sent the Emails. But as soon as one realises it, bad feelings come pouring in: you don&amp;rsquo;t know which Emails have tracking pixels; you can never &amp;ldquo;opt-out&amp;rdquo; a tracking pixel; you don&amp;rsquo;t know exactly what information were collected by the trackers and what they plan to do with it.&lt;/p>
&lt;h1 id="how-to-avoid-being-tracked">
How to avoid being tracked?
&lt;a class="heading-link" href="#how-to-avoid-being-tracked">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>If you are using a macOS computer, check out the open-source project &lt;a href="https://github.com/apparition47/MailTrackerBlocker" class="external-link" target="_blank" rel="noopener">MailTrackerBlocker&lt;/a>. It&amp;rsquo;s a plug-in for the Apple Mail app. Actually the list above was sorted out using this plug-in.&lt;/p>
&lt;p>Alternatively you can choose an Email service that provides tracking pixel blocking natively, like &lt;a href="https://hey.com" class="external-link" target="_blank" rel="noopener">Hey&lt;/a> (paid). In fact the makers of Hey mail made this website to stand up against tracking pixels: &lt;a href="https://notospypixels.com" class="external-link" target="_blank" rel="noopener">https://notospypixels.com&lt;/a>&lt;/p>
&lt;p>For blocking tracking pixels on other platforms, applications or Email providers, you can &lt;a href="mailto:me@mogita.com?subject=[Otaku%20Blog]%20Avoid%20Tracking%20Pixels" >write to me&lt;/a> and tell me your method or tools. I&amp;rsquo;d be happy to add them here.&lt;/p>
&lt;h1 id="who-are-tracking-me">
Who Are Tracking Me
&lt;a class="heading-link" href="#who-are-tracking-me">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>To name just a few Email senders I&amp;rsquo;ve caught embedding the tracking pixels, here&amp;rsquo;s a list:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Sender&lt;/th>
&lt;th>From Address&lt;/th>
&lt;th>Tracking Pixel Service Provider&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Sony PlayStation&lt;/td>
&lt;td>&lt;a href="mailto:reply@txn-email.playstation.com" >reply@txn-email.playstation.com&lt;/a>&lt;/td>
&lt;td>Salesforce&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Adobe&lt;/td>
&lt;td>&lt;a href="mailto:mail@mail.adobe.com" >mail@mail.adobe.com&lt;/a>&lt;br />&lt;a href="mailto:message@adobe.com" >message@adobe.com&lt;/a>&lt;/td>
&lt;td>Adobe&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IFTTT&lt;/td>
&lt;td>&lt;a href="mailto:alerts@ifttt.com" >alerts@ifttt.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Nintendo&lt;/td>
&lt;td>&lt;a href="mailto:nintendo-noreply@ccg.nintendo.com" >nintendo-noreply@ccg.nintendo.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LinkedIn&lt;/td>
&lt;td>&lt;a href="mailto:messages-noreply@linkedin.com" >messages-noreply@linkedin.com&lt;/a>&lt;/td>
&lt;td>LinkedIn&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Duolingo&lt;/td>
&lt;td>&lt;a href="mailto:hello@duolingo.com" >hello@duolingo.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Flickr&lt;/td>
&lt;td>&lt;a href="mailto:flickrteam@arrow.flickr.com" >flickrteam@arrow.flickr.com&lt;/a>&lt;/td>
&lt;td>SparkPost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Lark Suite (a.k.a Feishu)&lt;/td>
&lt;td>&lt;a href="mailto:support@service.larksuite.com" >support@service.larksuite.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bedroom Producers Blog&lt;/td>
&lt;td>&lt;a href="mailto:tomislav@bedroomproducersblog.com" >tomislav@bedroomproducersblog.com&lt;/a>&lt;/td>
&lt;td>MailerLite&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Trello&lt;/td>
&lt;td>&lt;a href="mailto:taco@trello.com" >taco@trello.com&lt;/a>&lt;br />&lt;a href="mailto:do-not-reply@trello.com" >do-not-reply@trello.com&lt;/a>&lt;br />&lt;a href="mailto:invitation-do-not-reply@trello.com" >invitation-do-not-reply@trello.com&lt;/a>&lt;/td>
&lt;td>Atlassian&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dropbox&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@dropbox.com" >no-reply@dropbox.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CodeSandbox&lt;/td>
&lt;td>&lt;a href="mailto:hello@codesandbox.io" >hello@codesandbox.io&lt;/a>&lt;/td>
&lt;td>Mailgun&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Mapbox&lt;/td>
&lt;td>&lt;a href="mailto:newsletter@mapbox.com" >newsletter@mapbox.com&lt;/a>&lt;br />&lt;a href="mailto:billing@mapbox.com" >billing@mapbox.com&lt;/a>&lt;br />&lt;a href="mailto:sales@mapbox.com" >sales@mapbox.com&lt;/a>&lt;br />&lt;a href="mailto:community@mapbox.com" >community@mapbox.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MapTiler&lt;/td>
&lt;td>&lt;a href="mailto:news@maptiler.com" >news@maptiler.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Mapillary&lt;/td>
&lt;td>&lt;a href="mailto:support@mapillary.com" >support@mapillary.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Apple App Store&lt;/td>
&lt;td>&lt;a href="mailto:no_reply@email.apple.com" >no_reply@email.apple.com&lt;/a>&lt;/td>
&lt;td>Apple&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>iCloud (GCBD)&lt;/td>
&lt;td>&lt;a href="mailto:no_reply@iCloud.gzdata.com.cn" >no_reply@iCloud.gzdata.com.cn&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Apple iTunes Connect&lt;/td>
&lt;td>&lt;a href="mailto:do_not_reply@email.apple.com" >do_not_reply@email.apple.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Apple Developer&lt;/td>
&lt;td>&lt;a href="mailto:developer@insideapple.apple.com" >developer@insideapple.apple.com&lt;/a>&lt;/td>
&lt;td>Apple&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Be My Eyes&lt;/td>
&lt;td>&lt;a href="mailto:account@bemyeyes.com" >account@bemyeyes.com&lt;/a>&lt;br />&lt;a href="mailto:onboarding@bemyeyes.com" >onboarding@bemyeyes.com&lt;/a>&lt;/td>
&lt;td>Postmark&lt;br />Mixpanel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Goodreads&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@mail.goodreads.com" >no-reply@mail.goodreads.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Foursquare&lt;/td>
&lt;td>&lt;a href="mailto:noreply@foursquare.com" >noreply@foursquare.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Baidu&lt;/td>
&lt;td>&lt;a href="mailto:no-reply-bce@baidu.com" >no-reply-bce@baidu.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GitHub&lt;/td>
&lt;td>&lt;a href="mailto:notifications@github.com" >notifications@github.com&lt;/a>&lt;/td>
&lt;td>GitHub&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Microsoft&lt;/td>
&lt;td>&lt;a href="mailto:Microsoft365@mail.microsoft365.com" >Microsoft365@mail.microsoft365.com&lt;/a>&lt;br />&lt;a href="mailto:msa@communication.microsoft.com" >msa@communication.microsoft.com&lt;/a>&lt;br />&lt;a href="mailto:azure@email.microsoft.com" >azure@email.microsoft.com&lt;/a>&lt;/td>
&lt;td>Microsoft&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cloudflare&lt;/td>
&lt;td>&lt;a href="mailto:newsletter@cloudflare.com" >newsletter@cloudflare.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sentry&lt;/td>
&lt;td>&lt;a href="mailto:webinars@sentry.io" >webinars@sentry.io&lt;/a>&lt;/td>
&lt;td>Hubspot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Upwork&lt;/td>
&lt;td>&lt;a href="mailto:donotreply@upwork.com" >donotreply@upwork.com&lt;/a>&lt;/td>
&lt;td>Upwork&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AWS&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@marketplace.aws" >no-reply@marketplace.aws&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Facebook&lt;/td>
&lt;td>&lt;a href="mailto:security@facebookmail.com" >security@facebookmail.com&lt;/a>&lt;br />&lt;a href="mailto:notification@facebookmail.com" >notification@facebookmail.com&lt;/a>&lt;/td>
&lt;td>Facebook&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Instagram&lt;/td>
&lt;td>&lt;a href="mailto:security@mail.instagram.com" >security@mail.instagram.com&lt;/a>&lt;/td>
&lt;td>Facebook&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Paypal&lt;/td>
&lt;td>&lt;a href="mailto:service@intl.paypal.com" >service@intl.paypal.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;br />Return Path&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stripe&lt;/td>
&lt;td>&lt;a href="mailto:receipts&amp;#43;masked@stripe.com" >receipts+masked@stripe.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Auth0&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@emails.auth0.com" >no-reply@emails.auth0.com&lt;/a>&lt;br />&lt;a href="mailto:no-reply-support@auth0.com" >no-reply-support@auth0.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;br />Mandrill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Airbnb&lt;/td>
&lt;td>&lt;a href="mailto:automated@airbnb.com" >automated@airbnb.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Coursera&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@m.mail.coursera.org" >no-reply@m.mail.coursera.org&lt;/a>&lt;/td>
&lt;td>SparkPost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Console.dev&lt;/td>
&lt;td>&lt;a href="mailto:weekly@console.dev" >weekly@console.dev&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AudioThing&lt;/td>
&lt;td>&lt;a href="mailto:news@audiothing.net" >news@audiothing.net&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BugSnag&lt;/td>
&lt;td>&lt;a href="mailto:bugsnagmarketing@bugsnag.com" >bugsnagmarketing@bugsnag.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Slite&lt;/td>
&lt;td>&lt;a href="mailto:marc@slite.com" >marc@slite.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GOG&lt;/td>
&lt;td>&lt;a href="mailto:newsletter@email2.gog.com" >newsletter@email2.gog.com&lt;/a>&lt;/td>
&lt;td>&lt;del>Google&lt;/del>&lt;br />GetResponse&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IMDB&lt;/td>
&lt;td>&lt;a href="mailto:do-not-reply@imdb.com" >do-not-reply@imdb.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CloudApp&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@getcloudapp.com" >no-reply@getcloudapp.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PreSonus&lt;/td>
&lt;td>&lt;a href="mailto:presonus@e.presonus.com" >presonus@e.presonus.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Internet Archive&lt;/td>
&lt;td>&lt;a href="mailto:info@archive.org" >info@archive.org&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Box&lt;/td>
&lt;td>&lt;a href="mailto:boxteam@customer.box.com" >boxteam@customer.box.com&lt;/a>&lt;/td>
&lt;td>spmailtechnol&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IHG (A hospitality company)&lt;/td>
&lt;td>&lt;a href="mailto:IHGRewards@mc.ihg.com" >IHGRewards@mc.ihg.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker, more than 1 per mail)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TuringBook.com&lt;/td>
&lt;td>&lt;a href="mailto:ebook@turingbook.com" >ebook@turingbook.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Raycast&lt;/td>
&lt;td>&lt;a href="mailto:thomas@raycast.com" >thomas@raycast.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RescueTime&lt;/td>
&lt;td>&lt;a href="mailto:notifications@rescuetime.com" >notifications@rescuetime.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Chatra&lt;/td>
&lt;td>&lt;a href="mailto:support@chatra.com" >support@chatra.com&lt;/a>&lt;/td>
&lt;td>Postmark&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bandcamp&lt;/td>
&lt;td>&lt;a href="mailto:noreply@bandcamp.com" >noreply@bandcamp.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Craft.do&lt;/td>
&lt;td>&lt;a href="mailto:team@craft.do" >team@craft.do&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Journal&lt;/td>
&lt;td>&lt;a href="mailto:hello@usejournal.com" >hello@usejournal.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Mockaroo&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@mockaroo.com" >no-reply@mockaroo.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ghost.org (A blogging software)&lt;/td>
&lt;td>&lt;a href="mailto:hello@ghost.org" >hello@ghost.org&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Asana&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@asana.com" >no-reply@asana.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Remove.bg&lt;/td>
&lt;td>&lt;a href="mailto:noreply@remove.bg" >noreply@remove.bg&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>uTalk&lt;/td>
&lt;td>&lt;a href="mailto:hello@offers2.utalk.com" >hello@offers2.utalk.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Envato&lt;/td>
&lt;td>&lt;a href="mailto:do-not-reply@market.envato.com" >do-not-reply@market.envato.com&lt;/a>&lt;/td>
&lt;td>Mandrillapp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RapidAPI&lt;/td>
&lt;td>&lt;a href="mailto:support@rapidapi.com" >support@rapidapi.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BundleHunt&lt;/td>
&lt;td>&lt;a href="mailto:support@bundlehunt.com" >support@bundlehunt.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TIDAL&lt;/td>
&lt;td>&lt;a href="mailto:account@info.tidal.com" >account@info.tidal.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ClickUp&lt;/td>
&lt;td>&lt;a href="mailto:katie@clickup.com" >katie@clickup.com&lt;/a>&lt;br />&lt;a href="mailto:success@clickup.com" >success@clickup.com&lt;/a>&lt;br />&lt;a href="mailto:help@clickup.com" >help@clickup.com&lt;/a>&lt;br />&lt;a href="mailto:clickupdates@clickup.com" >clickupdates@clickup.com&lt;/a>&lt;/td>
&lt;td>Close&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Clubhouse.io&lt;/td>
&lt;td>&lt;a href="mailto:support@clubhouse.io" >support@clubhouse.io&lt;/a>&lt;br />&lt;a href="mailto:mayrelease@splash.events" >mayrelease@splash.events&lt;/a>&lt;/td>
&lt;td>Intercom&lt;br />SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Postman.com&lt;/td>
&lt;td>&lt;a href="mailto:postman-team@notifications.postman.com" >postman-team@notifications.postman.com&lt;/a>&lt;/td>
&lt;td>Fastic&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Taiga&lt;/td>
&lt;td>&lt;a href="mailto:peter@mail.taiga.io" >peter@mail.taiga.io&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Koingo Software&lt;/td>
&lt;td>&lt;a href="mailto:appdeals@koingo.com" >appdeals@koingo.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Lobe.ai&lt;/td>
&lt;td>&lt;a href="mailto:lobe@e-mail.microsoft.com" >lobe@e-mail.microsoft.com&lt;/a>&lt;/td>
&lt;td>Salesforce&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Native Instruments&lt;/td>
&lt;td>&lt;a href="mailto:newsletter@news.native-instruments.com" >newsletter@news.native-instruments.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Atlassian&lt;/td>
&lt;td>&lt;a href="mailto:info@e.atlassian.com" >info@e.atlassian.com&lt;/a>&lt;/td>
&lt;td>Return Path&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Twitch&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@twitch.tv" >no-reply@twitch.tv&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>JustFont&lt;/td>
&lt;td>&lt;a href="mailto:su.weihsiang@justfont.com" >su.weihsiang@justfont.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>JFrog&lt;/td>
&lt;td>&lt;a href="mailto:team@go.jfrog.com" >team@go.jfrog.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NameSilo&lt;/td>
&lt;td>&lt;a href="mailto:abnous@namesilo.com" >abnous@namesilo.com&lt;/a>&lt;/td>
&lt;td>MailerLite&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Riot Games&lt;/td>
&lt;td>&lt;a href="mailto:noreply@mail.accounts.riotgames.com" >noreply@mail.accounts.riotgames.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NASA Mars&lt;/td>
&lt;td>&lt;a href="mailto:no-reply-mars@jpl.nasa.gov" >no-reply-mars@jpl.nasa.gov&lt;/a>&lt;/td>
&lt;td>iContact&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Zeplin&lt;/td>
&lt;td>&lt;a href="mailto:hi@zeplin.io" >hi@zeplin.io&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pipedream&lt;/td>
&lt;td>&lt;a href="mailto:sacerdoti@pipedream.com" >sacerdoti@pipedream.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Google Payments&lt;/td>
&lt;td>&lt;a href="mailto:payments-noreply@google.com" >payments-noreply@google.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Google Workspace&lt;/td>
&lt;td>&lt;a href="mailto:google-workspace-alerts-noreply@google.com" >google-workspace-alerts-noreply@google.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Google Cloud Platform&lt;/td>
&lt;td>&lt;a href="mailto:CloudPlatform-noreply@google.com" >CloudPlatform-noreply@google.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Climb App&lt;/td>
&lt;td>(hidden due to Apple Login)&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>China Merchants Bank&lt;/td>
&lt;td>&lt;a href="mailto:95555ad@message.cmbchina.com" >95555ad@message.cmbchina.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Magoosh&lt;/td>
&lt;td>&lt;a href="mailto:help@magoosh.com" >help@magoosh.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Todoist&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@todoist.com" >no-reply@todoist.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MuseScore&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@musescore.com" >no-reply@musescore.com&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GitGuardian&lt;/td>
&lt;td>&lt;a href="mailto:support@gitguardian.com" >support@gitguardian.com&lt;/a>&lt;br />&lt;a href="mailto:security@mail.gitguardian.com" >security@mail.gitguardian.com&lt;/a>&lt;br />&lt;a href="mailto:contact@gitguardian.com" >contact@gitguardian.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;br />Mailgun&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Speed Dial 2&lt;/td>
&lt;td>&lt;a href="mailto:hello@speeddial2.com" >hello@speeddial2.com&lt;/a>&lt;/td>
&lt;td>Postmark&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sococo&lt;/td>
&lt;td>&lt;a href="mailto:noreply@sococo.net" >noreply@sococo.net&lt;/a>&lt;/td>
&lt;td>Mandrill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Chamaileon&lt;/td>
&lt;td>&lt;a href="mailto:hello@chamaileon.io" >hello@chamaileon.io&lt;/a>&lt;/td>
&lt;td>Sendy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Browserstack Team&lt;/td>
&lt;td>&lt;a href="mailto:news@browserstack.com" >news@browserstack.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ultimate Guitar&lt;/td>
&lt;td>&lt;a href="mailto:no_reply@ultimate-guitar.com" >no_reply@ultimate-guitar.com&lt;/a>&lt;/td>
&lt;td>Mailtrain&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bunny Studio&lt;/td>
&lt;td>&lt;a href="mailto:support@bunnystudio.com" >support@bunnystudio.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Waymo&lt;/td>
&lt;td>&lt;a href="mailto:noreply@waymo.com" >noreply@waymo.com&lt;/a>&lt;/td>
&lt;td>Google&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>数码荔枝 lizhi.io&lt;/td>
&lt;td>&lt;a href="mailto:hi@tongxun.lizhi.io" >hi@tongxun.lizhi.io&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Three Dots Labs&lt;/td>
&lt;td>&lt;a href="mailto:contact@threedotslabs.com" >contact@threedotslabs.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vercel&lt;/td>
&lt;td>&lt;a href="mailto:ship@vercel.com" >ship@vercel.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Guitar Pro&lt;/td>
&lt;td>&lt;a href="mailto:contact@arobas-music.com" >contact@arobas-music.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Loom&lt;/td>
&lt;td>&lt;a href="mailto:welcome@loom.com" >welcome@loom.com&lt;/a>&lt;br />&lt;a href="mailto:team@loom.com" >team@loom.com&lt;/a>&lt;br />&lt;a href="mailto:no-reply@loom.com" >no-reply@loom.com&lt;/a>&lt;/td>
&lt;td>Intercom&lt;br />Mandrill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Opstree&lt;/td>
&lt;td>&lt;a href="mailto:riya@opstree.com" >riya@opstree.com&lt;/a>&lt;/td>
&lt;td>Hubspot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>WeTransfer&lt;/td>
&lt;td>&lt;a href="mailto:noreply@wetransfer.com" >noreply@wetransfer.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Flim&lt;/td>
&lt;td>&lt;a href="mailto:hello@flim.ai" >hello@flim.ai&lt;/a>&lt;/td>
&lt;td>Mailjet&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grab&lt;/td>
&lt;td>&lt;a href="mailto:corpsupport.sg@grab.com" >corpsupport.sg@grab.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pusher&lt;/td>
&lt;td>&lt;a href="mailto:team@pusher.com" >team@pusher.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Chess.com&lt;/td>
&lt;td>&lt;a href="mailto:hello@chess.com" >hello@chess.com&lt;/a>&lt;br />&lt;a href="mailto:alert@chess.com" >alert@chess.com&lt;/a>&lt;br />&lt;a href="mailto:receipt@chess.com" >receipt@chess.com&lt;/a>&lt;/td>
&lt;td>Fastic&lt;br />SendGrid&lt;br />SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Soundtoys&lt;/td>
&lt;td>&lt;a href="mailto:news@soundtoys.com" >news@soundtoys.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>italki&lt;/td>
&lt;td>&lt;a href="mailto:noreply@italki.com" >noreply@italki.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Codecov&lt;/td>
&lt;td>&lt;a href="mailto:hello@codecov.io" >hello@codecov.io&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Wurkr&lt;/td>
&lt;td>&lt;a href="mailto:helena@wurkr.io" >helena@wurkr.io&lt;/a>&lt;/td>
&lt;td>Hubspot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Coinbase&lt;/td>
&lt;td>&lt;a href="mailto:info@cb.mail.coinbase.com" >info@cb.mail.coinbase.com&lt;/a>&lt;/td>
&lt;td>Fastic&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Zapier&lt;/td>
&lt;td>&lt;a href="mailto:billing@zapier.com" >billing@zapier.com&lt;/a>&lt;/td>
&lt;td>Litmus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HBO Max&lt;/td>
&lt;td>&lt;a href="mailto:HBOMax@mail.hbomax.com" >HBOMax@mail.hbomax.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Malaysia Kini&lt;/td>
&lt;td>&lt;a href="mailto:newslab@malaysiakini.com" >newslab@malaysiakini.com&lt;/a>&lt;br />&lt;a href="mailto:membership@malaysiakini.com" >membership@malaysiakini.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Book Depository&lt;/td>
&lt;td>&lt;a href="mailto:reply@support.bookdepository.com" >reply@support.bookdepository.com&lt;/a>&lt;/td>
&lt;td>Litmus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Anytype&lt;/td>
&lt;td>&lt;a href="mailto:hello@anytype.io" >hello@anytype.io&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Change.org&lt;/td>
&lt;td>&lt;a href="mailto:change@a.change.org" >change@a.change.org&lt;/a>&lt;/td>
&lt;td>(Self-owned Tracker)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Flo&lt;/td>
&lt;td>&lt;a href="mailto:legal@floemail.com" >legal@floemail.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Medium&lt;/td>
&lt;td>&lt;a href="mailto:noreply@medium.com" >noreply@medium.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LingQ&lt;/td>
&lt;td>&lt;a href="mailto:ana.rivera@lingq.com" >ana.rivera@lingq.com&lt;/a>&lt;br />&lt;a href="mailto:support@lingq.com" >support@lingq.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>WordPress&lt;/td>
&lt;td>&lt;a href="mailto:comment-reply@wordpress.com" >comment-reply@wordpress.com&lt;/a>&lt;/td>
&lt;td>WordPress&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Leanpub&lt;/td>
&lt;td>&lt;a href="mailto:news@leanpub.com" >news@leanpub.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pocket Casts&lt;/td>
&lt;td>&lt;a href="mailto:noreply@pocketcasts.com" >noreply@pocketcasts.com&lt;/a>&lt;/td>
&lt;td>Mandrill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Amazon Web Services&lt;/td>
&lt;td>&lt;a href="mailto:aws-cn-marketing-email-replies@amazon.com" >aws-cn-marketing-email-replies@amazon.com&lt;/a>&lt;/td>
&lt;td>Amazon SES&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Spotify&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@spotify.com" >no-reply@spotify.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Los Angeles Valley Collage&lt;/td>
&lt;td>&lt;a href="mailto:LAVCstudentsupport@laccd.edu" >LAVCstudentsupport@laccd.edu&lt;/a>&lt;/td>
&lt;td>iContact&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>StarWind&lt;/td>
&lt;td>&lt;a href="mailto:brooke.johnson@starwind.com" >brooke.johnson@starwind.com&lt;/a>&lt;/td>
&lt;td>Hubspot&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Product Hunt&lt;/td>
&lt;td>&lt;a href="mailto:hello@team.producthunt.com" >hello@team.producthunt.com&lt;/a>&lt;/td>
&lt;td>Mailjet&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Gengo Translator Team&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@gengo.com" >no-reply@gengo.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Robinhood Snacks&lt;/td>
&lt;td>&lt;a href="mailto:noreply@robinhood.com" >noreply@robinhood.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Live2D&lt;/td>
&lt;td>&lt;a href="mailto:support@live2d.com" >support@live2d.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>JetBrains&lt;/td>
&lt;td>&lt;a href="mailto:news@jetbrains.com" >news@jetbrains.com&lt;/a>&lt;/td>
&lt;td>Adobe&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Docker&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@notify.docker.com" >no-reply@notify.docker.com&lt;/a>&lt;/td>
&lt;td>Mailgun&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Discord&lt;/td>
&lt;td>&lt;a href="mailto:noreply@discord.com" >noreply@discord.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Skillshare&lt;/td>
&lt;td>&lt;a href="mailto:hello@skillshare.com" >hello@skillshare.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Kickstarter&lt;/td>
&lt;td>&lt;a href="mailto:no-reply@kickstarter.com" >no-reply@kickstarter.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MakeML&lt;/td>
&lt;td>&lt;a href="mailto:alexander@makeml.app" >alexander@makeml.app&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Roboflow&lt;/td>
&lt;td>&lt;a href="mailto:hello@roboflow.com" >hello@roboflow.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unity&lt;/td>
&lt;td>&lt;a href="mailto:accounts@unity3d.com" >accounts@unity3d.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Otoy&lt;/td>
&lt;td>&lt;a href="mailto:help@otoy.com" >help@otoy.com&lt;/a>&lt;/td>
&lt;td>Mailchimp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Memrise&lt;/td>
&lt;td>&lt;a href="mailto:hello@mail.memrise.com" >hello@mail.memrise.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Clozemaster&lt;/td>
&lt;td>&lt;a href="mailto:hello@clozemaster.com" >hello@clozemaster.com&lt;/a>&lt;/td>
&lt;td>SendGrid&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Descript&lt;/td>
&lt;td>&lt;a href="mailto:mail@descript.com" >mail@descript.com&lt;/a>&lt;/td>
&lt;td>Customer.io&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Heroku&lt;/td>
&lt;td>&lt;a href="mailto:noreply@mail.salesforce.com" >noreply@mail.salesforce.com&lt;/a>&lt;/td>
&lt;td>Salesforce&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>It can go very long as more and more services I use. And I was tracked all the way long.&lt;/p>
&lt;blockquote>
&lt;p>Update 2021-03-22: It appears that many of the &amp;ldquo;Self-owned Trackers&amp;rdquo; are not owned by the business owners. They don&amp;rsquo;t reveal the actual vendor by using the custom domain names. Certain tracing was done to discover the actual campaign Email sender and the tracker, which showed a certain pattern of how the custom domain names are formed. E.g. &lt;code>http://url1234.mail.some-service.io&lt;/code> (certain parts were masked with dummy characters) points to the IP address that resolves to be in possession of SendGrid. Oh my they use the &lt;code>http&lt;/code> that scatters people&amp;rsquo;s information all around the web un-encrypted while relentlessly collecting them.&lt;/p>
&lt;/blockquote></description></item><item><title>Reading Digest (Aug 2018)</title><link>https://mogita.com/reading-digest-aug-2018.html</link><pubDate>Thu, 16 Aug 2018 17:21:30 +0000</pubDate><guid>https://mogita.com/reading-digest-aug-2018.html</guid><description>&lt;h1 id="go">
Go
&lt;a class="heading-link" href="#go">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>Accessing the database with Go needs the &lt;code>database/sql&lt;/code> package. Apart from reading the official docs, this web site makes it even clearer on &amp;ldquo;how to&amp;rdquo; and &amp;ldquo;what not to do&amp;rdquo; matters. Learn idiomatic operations on it as well.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="http://go-database-sql.org/index.html" class="external-link" target="_blank" rel="noopener">Go database/sql Tutorial&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>While designing the inforcement for our data security, I came across an article which explained briefly and clearly on SQL injection and how to avoid it in Go.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://www.calhoun.io/what-is-sql-injection-and-how-do-i-avoid-it-in-go/" class="external-link" target="_blank" rel="noopener">What is SQL injection and how do I avoid it in Go?&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>It&amp;rsquo;s also worth a while reading this article, also on the topic of SQL injection in Go. In fact it belongs to a big collection of articles on Go application building.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://astaxie.gitbooks.io/build-web-application-with-golang/en/09.4.html" class="external-link" target="_blank" rel="noopener">9.4 SQL injection&lt;/a> (from the collection of &lt;a href="https://astaxie.gitbooks.io/build-web-application-with-golang/en/" class="external-link" target="_blank" rel="noopener">Build web application with Golang&lt;/a> )&lt;/li>
&lt;/ul>
&lt;h1 id="webassembly">
WebAssembly
&lt;a class="heading-link" href="#webassembly">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>As &lt;a href="https://webassembly.github.io/spec/core/index.html" class="external-link" target="_blank" rel="noopener">WebAssembly Specification&lt;/a> draft goes 1.0 yesterday, it&amp;rsquo;s time to take a closer look at the essential ideas of WebAssembly. Currently it supports C, C++ and Rust. Which one to take? Hmm&amp;hellip;&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.logrocket.com/webassembly-how-and-why-559b7f96cd71" class="external-link" target="_blank" rel="noopener">WebAssembly: How and why&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="metabase">
Metabase
&lt;a class="heading-link" href="#metabase">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>This isn&amp;rsquo;t a reading item actually lol. The &lt;a href="https://github.com/metabase/metabase" class="external-link" target="_blank" rel="noopener">Metabase&lt;/a> is a tool that is &lt;code>The simplest, fastest way to get business intelligence and analytics to everyone in your company&lt;/code>. In short, it&amp;rsquo;s an open-source BI software.&lt;/p>
&lt;p>One of our QA team member showed me this the other day. It supports running as a Java jar package, a docker image, on AWS and some other options. The setup process was even easier than setting up a new WordPress instance.&lt;/p>
&lt;p>The Metabase astonished me with its simplicity of design, both on the workflow and the UI. I &amp;ldquo;ask&amp;rdquo; it questions, it shows me the answers. That&amp;rsquo;s it.&lt;/p>
&lt;p>Digging deeper, I can gather the answers and make a dashboard to visualize the data. Or I can see the SQL it generates if I want to.&lt;/p>
&lt;p>Here&amp;rsquo;s a screenshot from its repo:&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/metabase-product-screenshot.png" alt="metabase screenshot">&lt;/p></description></item><item><title>Reading Digest (Jun 2018)</title><link>https://mogita.com/reading-digest-jun-2018.html</link><pubDate>Mon, 18 Jun 2018 18:24:49 +0000</pubDate><guid>https://mogita.com/reading-digest-jun-2018.html</guid><description>&lt;p>I was playing with RPG Maker MV recently. It supports iOS/Android packaging with Cordova. Here&amp;rsquo;s the collection of articles on the topic.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.phonegap.com/displaying-a-phonegap-app-correctly-on-the-iphone-x-c4a85664c493" class="external-link" target="_blank" rel="noopener">Displaying a PhoneGap App Correctly on the iPhone X&lt;/a>
A walkthrough of fitting the status bar and bottom area on an iPhone X.&lt;/li>
&lt;li>&lt;a href="https://stackoverflow.com/questions/46232812/cordova-app-not-displaying-correctly-on-iphone-x-simulator" class="external-link" target="_blank" rel="noopener">Cordova app not displaying correctly on iPhone X (Simulator)&lt;/a>
Q&amp;amp;A on making a right &lt;code>config.xml&lt;/code> and styles.&lt;/li>
&lt;li>&lt;a href="https://medium.com/the-web-tub/supporting-iphone-x-for-mobile-web-cordova-app-using-onsen-ui-f17a4c272fcd" class="external-link" target="_blank" rel="noopener">Supporting iPhone X for mobile web &amp;amp; Cordova app using Onsen UI&lt;/a>
A detailed guide on how to make the web app looks and behaves more like a native app.&lt;/li>
&lt;/ul>
&lt;p>On the Go based micro-service, gRPC is a must-know technology.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://speakerdeck.com/cecyc/from-monolith-to-microservice-building-apis-with-grpc-and-golang" class="external-link" target="_blank" rel="noopener">From Monolith to Microservice: Building APIs with gRPC &amp;amp; golang&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>Case study &amp;amp; tutorial on how to break up a legacy monolith codebase into microservices using gRPC + Protobuf + golang + the language of your choice!&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://grpc.io/docs/guides/" class="external-link" target="_blank" rel="noopener">What is gRPC&lt;/a>
The official portal for gRPC knowledge.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://medium.com/full-stack-tips/dependency-injection-in-go-99b09e2cc480" class="external-link" target="_blank" rel="noopener">Dependency Injection In Go&lt;/a>
A basic tutorial on how and why for DI in the Go language.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>React 状态管理</title><link>https://mogita.com/managing-react-state.html</link><pubDate>Tue, 27 Mar 2018 00:02:38 +0000</pubDate><guid>https://mogita.com/managing-react-state.html</guid><description>&lt;p>React 中的 &lt;code>state&lt;/code>（状态）是简单的 JavaScript 对象，用于管理组件内的状态。请牢记：只有用在渲染（render）过程中的数据才保存在状态里。&lt;/p>
&lt;h2 id="什么是状态state">
什么是状态（State）
&lt;a class="heading-link" href="#%e4%bb%80%e4%b9%88%e6%98%af%e7%8a%b6%e6%80%81state">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>React 组件用状态来保存相关数据，它本身就是一个简单的对象。状态和普通对象的区别在于，React 会监视状态的属性，并在它更新时触发组件重新渲染。&lt;/p>
&lt;p>状态更新会触发重新渲染，因此可以只用它来保存渲染所需的数据。换句话说，虽与组件相关但不用于 &lt;code>render&lt;/code> 方法的变量就无需保存在状态里了，使用实例变量即可。&lt;/p>
&lt;p>同时，你还可以认为状态就是组件的私有数据，只有在组件内才能读取或更新状态。我们不能在它的父组件或子组件里访问状态。&lt;/p>
&lt;h2 id="与属性props的区别">
与属性（Props）的区别
&lt;a class="heading-link" href="#%e4%b8%8e%e5%b1%9e%e6%80%a7props%e7%9a%84%e5%8c%ba%e5%88%ab">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>和 &lt;code>props&lt;/code> 类似，状态就是一个用于触发组件重新渲染的对象。区别在于 &lt;code>props&lt;/code> 来自父组件，状态是组件的内部数据。&lt;/p>
&lt;p>我们无法在组件内更新 &lt;code>props&lt;/code>。因为它是来自外部的数据，组件不能对其进行操作。状态处于组件内部，组件对它有完全操作的能力。&lt;/p>
&lt;p>如下图：&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/state_vs_props.png" alt="state vs props">&lt;/p>
&lt;h2 id="初始化状态">
初始化状态
&lt;a class="heading-link" href="#%e5%88%9d%e5%a7%8b%e5%8c%96%e7%8a%b6%e6%80%81">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我们有多种方式来初始化组件的状态：&lt;/p>
&lt;ul>
&lt;li>使用 &lt;code>getInitialState&lt;/code> 方法&lt;/li>
&lt;li>在 &lt;code>constructor&lt;/code> 方法内初始化&lt;/li>
&lt;li>以类属性的方式初始化&lt;/li>
&lt;/ul>
&lt;h4 id="使用-getinitialstate-方法">
使用 &lt;code>getInitialState&lt;/code> 方法
&lt;a class="heading-link" href="#%e4%bd%bf%e7%94%a8-getinitialstate-%e6%96%b9%e6%b3%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>使用 &lt;code>React.createClass&lt;/code> 来定义组件时，可以使用 &lt;code>getInitialState&lt;/code> 方法：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> React from &lt;span style="color:#a3be8c">&amp;#39;react&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> ExampleComponent &lt;span style="color:#81a1c1">=&lt;/span> React&lt;span style="color:#eceff4">.&lt;/span>createClass&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> getInitialState &lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someValue&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">},&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> render&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">&amp;lt;&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;{&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state&lt;span style="color:#eceff4">.&lt;/span>someKey&lt;span style="color:#eceff4">}&amp;lt;/&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">default&lt;/span> ExampleComponent&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="在-constructor-方法内初始化">
在 &lt;code>constructor&lt;/code> 方法内初始化
&lt;a class="heading-link" href="#%e5%9c%a8-constructor-%e6%96%b9%e6%b3%95%e5%86%85%e5%88%9d%e5%a7%8b%e5%8c%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>当你使用 &lt;code>ES6&lt;/code> 的 Class 关键字定义组件时，可以在 &lt;code>constructor&lt;/code> 方法里初始化状态：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> React from &lt;span style="color:#a3be8c">&amp;#39;react&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> ExampleComponent &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> React&lt;span style="color:#eceff4">.&lt;/span>Component &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> constructor&lt;span style="color:#eceff4">(&lt;/span>props&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">super&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>props&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someValue&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> render&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">&amp;lt;&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;{&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state&lt;span style="color:#eceff4">.&lt;/span>someKey&lt;span style="color:#eceff4">}&amp;lt;/&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">default&lt;/span> ExampleComponent&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>请不要忘记调用 &lt;code>super()&lt;/code> 方法，否则在 &lt;code>constructor&lt;/code> 内将无法使用 &lt;code>this&lt;/code>。不要漏掉了 &lt;code>super&lt;/code> 方法里的 &lt;code>props&lt;/code> 参数。&lt;/p>
&lt;h4 id="以类属性的方式初始化">
以类属性的方式初始化
&lt;a class="heading-link" href="#%e4%bb%a5%e7%b1%bb%e5%b1%9e%e6%80%a7%e7%9a%84%e6%96%b9%e5%bc%8f%e5%88%9d%e5%a7%8b%e5%8c%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>同样在使用 &lt;code>ES6&lt;/code> 的 Class 关键字时，我们还可以用类属性的方式初始化状态。这种写法可以节省几行代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> React from &lt;span style="color:#a3be8c">&amp;#39;react&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> ExampleComponent &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> React&lt;span style="color:#eceff4">.&lt;/span>Component &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> state &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someValue&amp;#39;&lt;/span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> render&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">&amp;lt;&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;{&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state&lt;span style="color:#eceff4">.&lt;/span>someKey&lt;span style="color:#eceff4">}&amp;lt;/&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">default&lt;/span> ExampleComponent&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="更新状态">
更新状态
&lt;a class="heading-link" href="#%e6%9b%b4%e6%96%b0%e7%8a%b6%e6%80%81">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>更新状态时，需要留意如下规则：&lt;/p>
&lt;ul>
&lt;li>不要直接修改状态的属性值&lt;/li>
&lt;li>警告：&lt;code>setState&lt;/code> 是一个异步方法&lt;/li>
&lt;li>状态更新会合并操作&lt;/li>
&lt;/ul>
&lt;h4 id="不要直接修改状态的属性值">
不要直接修改状态的属性值
&lt;a class="heading-link" href="#%e4%b8%8d%e8%a6%81%e7%9b%b4%e6%8e%a5%e4%bf%ae%e6%94%b9%e7%8a%b6%e6%80%81%e7%9a%84%e5%b1%9e%e6%80%a7%e5%80%bc">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>第一条规则就是「不要向任何人提起状态更新」，恩不对，好像进错片场了。第一条规则是：不要直接修改状态的属性值。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// 不要这样做
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state&lt;span style="color:#eceff4">.&lt;/span>someValueInState &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;NEW VALUE&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们只能在初始化状态时直接写入状态，比如在 &lt;code>constructor&lt;/code> 里进行定义。&lt;/p>
&lt;p>除了初始化之外，我们只能使用 &lt;code>this.setState&lt;/code> 方法来更新状态：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>setState&lt;span style="color:#eceff4">({&lt;/span>someValueInState&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;NEW VALUE&amp;#39;&lt;/span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="警告setstate-是一个异步方法">
警告：&lt;code>setState&lt;/code> 是一个异步方法
&lt;a class="heading-link" href="#%e8%ad%a6%e5%91%8asetstate-%e6%98%af%e4%b8%80%e4%b8%aa%e5%bc%82%e6%ad%a5%e6%96%b9%e6%b3%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>请留意 &lt;code>setState&lt;/code> 的两个关键点：第一，在计算下一个状态时不能依赖 &lt;code>this.state&lt;/code> 和 &lt;code>this.props&lt;/code>，它们可能会被异步更新：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// 不要这样做
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>setState&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> counter&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state&lt;span style="color:#eceff4">.&lt;/span>counter &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>props&lt;span style="color:#eceff4">.&lt;/span>increment&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这种情况下，用 &lt;code>function&lt;/code> 作为 &lt;code>setState&lt;/code> 的第一个参数即可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>setState&lt;span style="color:#eceff4">((&lt;/span>prevState&lt;span style="color:#eceff4">,&lt;/span> props&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">=&amp;gt;&lt;/span> &lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> counter&lt;span style="color:#81a1c1">:&lt;/span> prevState&lt;span style="color:#eceff4">.&lt;/span>counter &lt;span style="color:#81a1c1">+&lt;/span> props&lt;span style="color:#eceff4">.&lt;/span>increment
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>第二，由于 &lt;code>setState&lt;/code> 是异步执行的，因此我们不能依赖 &lt;code>this.setState&lt;/code> 之后马上取得的 &lt;code>this.state&lt;/code> 值。&lt;/p>
&lt;p>如果你要在状态真正完成更新时执行代码，请使用 &lt;code>this.setState&lt;/code> 的第二个参数，回调函数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>setState&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someValue&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">},&lt;/span> &lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">=&amp;gt;&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// 只有在状态完成更新后，才会执行这里的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="state-更新会合并操作">
&lt;code>state&lt;/code> 更新会合并操作
&lt;a class="heading-link" href="#state-%e6%9b%b4%e6%96%b0%e4%bc%9a%e5%90%88%e5%b9%b6%e6%93%8d%e4%bd%9c">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>当我们调用 &lt;code>setState&lt;/code> 方法时，React 将把参数传递的对象合并到当前的状态中。我们不必担心会出现属性覆盖的情况。&lt;/p>
&lt;p>假设我们在 &lt;code>constructor&lt;/code> 中定义了一个状态：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>constructor&lt;span style="color:#eceff4">(&lt;/span>props&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">super&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>props&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>state &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someValue&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someOtherKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someOtherValue&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之后，我们可以用 &lt;code>setState&lt;/code> 分别更新这两个属性：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-jsx" data-lang="jsx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>setState&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> someKey&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;someNewValue&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在上面的代码里，&lt;code>this.state.someOtherKey&lt;/code> 就不会被覆盖。&lt;/p></description></item><item><title>Hasta La Vista 2017</title><link>https://mogita.com/hasta-la-vista-2017.html</link><pubDate>Wed, 03 Jan 2018 00:18:48 +0000</pubDate><guid>https://mogita.com/hasta-la-vista-2017.html</guid><description>&lt;p>网易云音乐在 2018 的第二个工作日告诉我了去年的年度专辑，原来的确是&lt;a href="http://music.163.com/artist?id=16605" class="external-link" target="_blank" rel="noopener">伊藤サチコ&lt;/a>。以及在日亚购买了《&lt;a href="https://www.amazon.co.jp/gp/product/B0056YEEL6/ref=oh_aui_detailpage_o00_s00?ie=UTF8&amp;amp;psc=1" class="external-link" target="_blank" rel="noopener">ジブリを聴きながら、上を向いて歩こう&lt;/a>》。&lt;/p>
&lt;h2 id="技术">
技术
&lt;a class="heading-link" href="#%e6%8a%80%e6%9c%af">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>深入接触了 Vue.js + Vuex，延续了 Node.js + MongoDB 的路子。年底加入了 &lt;a href="https://www.grab.com" class="external-link" target="_blank" rel="noopener">Grab&lt;/a>，开始使用 React 和 Go 的技术栈。&lt;/p>
&lt;p>年中用 Vue.js + Electron 的方案启动了一个饭否客户端项目，到年末完成度依然不高。遇到问题会被卡很久的毛病在这个项目中被体现得淋漓尽致。用 DOM 的方式控制视图也有难以逾越的「鸿坑」。总之，许多不起眼的地方都带着一层厚厚的 Hack，甚至让人想要学习使用 &lt;a href="better-xcode-2018.jpg" >Xcode&lt;/a> 做原生应用了！&lt;/p>
&lt;p>夏季和@&lt;a href="https://github.com/LitoMore" class="external-link" target="_blank" rel="noopener">饭小默&lt;/a>参加了一个 Beary Chat 的线下活动，现场开发机器人的环节热情高涨却没能拿到奖项，看来并没能「有 bear 来」。&lt;/p>
&lt;p>把老项目里开发的一个对话框组件 &lt;a href="https://github.com/mogita/vue-zydialog" class="external-link" target="_blank" rel="noopener">Vue ZyDialog&lt;/a> 做成了一个独立的轮子，几乎没有任何观众。2017 年最后一个月下载量仅 18 次，估计 17.9 次都是爬虫吧😂&lt;/p>
&lt;p>年底开发了一个饭否机器人@&lt;a href="https://fanfou.com/yocat" class="external-link" target="_blank" rel="noopener">有猫&lt;/a>，基于 &lt;a href="https://cloud.google.com/vision/" class="external-link" target="_blank" rel="noopener">Google Vision API&lt;/a> 识别公共时间线上的猫图并转发。炸出一票饭否猫奴。并在上线 2 个月后 fo 数超过了我，目前仍在超慢速地上涨。&lt;/p>
&lt;h2 id="游戏">
游戏
&lt;a class="heading-link" href="#%e6%b8%b8%e6%88%8f">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Steam 收藏进入正轨，开始像一个普通的 Steamer 有事没事喜加一（我的 id 是 &lt;a href="http://steamcommunity.com/id/mogita/" class="external-link" target="_blank" rel="noopener">&lt;code>mogita&lt;/code>&lt;/a>）。在心愿单里最久的游戏是 &lt;a href="http://store.steampowered.com/app/730/CounterStrike_Global_Offensive/" class="external-link" target="_blank" rel="noopener">Counter-Strike: Global Offensive&lt;/a>；价格最贵的游戏是&lt;a href="http://store.steampowered.com/app/571260/THE_KING_OF_FIGHTERS_XIV_STEAM_EDITION/" class="external-link" target="_blank" rel="noopener">拳皇 14&lt;/a>；进度最远的游戏是 &lt;a href="http://store.steampowered.com/app/359310/Evoland_2/" class="external-link" target="_blank" rel="noopener">Evoland 2&lt;/a>；周目最多的游戏是 &lt;a href="http://store.steampowered.com/app/12120/Grand_Theft_Auto_San_Andreas/" class="external-link" target="_blank" rel="noopener">Grand Theft Auto: San Andreas&lt;/a>；最喜欢的游戏是 &lt;a href="http://store.steampowered.com/app/266010/LYNE/" class="external-link" target="_blank" rel="noopener">LYNE&lt;/a>、&lt;a href="http://store.steampowered.com/app/375820/Human_Resource_Machine/" class="external-link" target="_blank" rel="noopener">Human Resource Machine&lt;/a>、&lt;a href="http://store.steampowered.com/app/413150/Stardew_Valley/" class="external-link" target="_blank" rel="noopener">Stardew Valley&lt;/a>。买了还没开始玩的（这可能才是 Steam 的哲学）的游戏有 &lt;a href="http://store.steampowered.com/app/107100/Bastion/" class="external-link" target="_blank" rel="noopener">Bastion&lt;/a>、&lt;a href="http://store.steampowered.com/app/203160/Tomb_Raider/" class="external-link" target="_blank" rel="noopener">Tomb Raider&lt;/a>、&lt;a href="http://store.steampowered.com/app/495890/Montaro/" class="external-link" target="_blank" rel="noopener">Montaro&lt;/a>、&lt;a href="http://store.steampowered.com/app/553640/ICEY/" class="external-link" target="_blank" rel="noopener">ICEY&lt;/a>、&lt;a href="http://store.steampowered.com/app/322330/Dont_Starve_Together/" class="external-link" target="_blank" rel="noopener">Don&amp;rsquo;t Starve Together&lt;/a>、&lt;a href="http://store.steampowered.com/app/339200/Oceanhorn_Monster_of_Uncharted_Seas/" class="external-link" target="_blank" rel="noopener">Oceanhorn: Monster of Uncharted Seas&lt;/a> 等。&lt;/p>
&lt;p>年中进了 Minecraft 的碗，并买了个美团云架了私服。目前这个服务器主要在帮我跑自建 GitLab 的 CI 任务，而 Minecraft 也好久没登录了。&lt;/p>
&lt;p>年底在网易魔兽开了个死骑账号，充了一个月的游戏时间后发现已经不是按在线时间扣点了，玩与不玩剩余时间都会随着日子变少。大脚插件官方提取了核心组件，拷贝到 &lt;code>AddOns&lt;/code> 目录下就能被魔兽读取，不过 Windows 用户仍然享受安装包的雨露。&lt;/p>
&lt;h2 id="音乐">
音乐
&lt;a class="heading-link" href="#%e9%9f%b3%e4%b9%90">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>没有创造什么新东西。尝试了一段人声采样和 Wobble Bass 风格的东西，目前工程停滞中。&lt;/p>
&lt;p>几乎不再使用 ES61 了，一方面桌子太小，多数时间要让给开发工作，另一方面因为新买了 MPK mini，更适合玩玩音色小敲小打。&lt;/p>
&lt;h2 id="2018">
2018
&lt;a class="heading-link" href="#2018">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>这些成就待达成：&lt;/p>
&lt;ul>
&lt;li>做出一项公司产品&lt;/li>
&lt;li>完成饭否客户端项目&lt;/li>
&lt;li>完成人声 + Wobble 工程&lt;/li>
&lt;/ul>
&lt;p>新的故事，总得翻开的。&lt;/p></description></item><item><title>如何在自建 GitLab 中启用 CI Runner</title><link>https://mogita.com/how-to-setup-gitlab-ci-runner.html</link><pubDate>Mon, 23 Oct 2017 11:43:50 +0000</pubDate><guid>https://mogita.com/how-to-setup-gitlab-ci-runner.html</guid><description>&lt;p>GitLab 内置了 CI/CD 功能，在任何自建的 &lt;a href="https://about.gitlab.com/installation/" class="external-link" target="_blank" rel="noopener">GitLab CE&lt;/a> 服务器上都可以启用。对于中小型项目而言，这套内置的 CI/CD 工具足够用来完成测试、构建和部署自动化工作。&lt;/p>
&lt;p>本文以前一个端开发项目为例，简单介绍 GitLab Runner 的搭建和使用流程。&lt;/p>
&lt;blockquote>
&lt;p>什么是 GitLab Runner？&lt;/p>
&lt;p>GitLab Runner 是独立于 GitLab 的开源项目（采用 Go 语言编写），用来执行自动化任务，并能够与 GitLab 互传数据。它是 GitLab 官方采用的 CI 工具。&lt;/p>
&lt;/blockquote>
&lt;h2 id="安装和启用">
安装和启用
&lt;a class="heading-link" href="#%e5%ae%89%e8%a3%85%e5%92%8c%e5%90%af%e7%94%a8">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>你可以直接查阅 &lt;a href="https://docs.gitlab.com/runner/" class="external-link" target="_blank" rel="noopener">GitLab Runner 入门页面&lt;/a>来了解完整的安装步骤，这里仅作概括。&lt;/p>
&lt;p>建议使用独立于 GitLab 所在的服务器来安装 GitLab Runner，我所在的公司目前使用一台 2 核 4GB 的主机来运行所有项目的 CI 任务。同时，建议使用 Docker 作为任务运行环境，确保每个任务的执行环境相对干净独立。&lt;/p>
&lt;p>登录到要安装 GitLab Runner 的服务器，执行下列操作：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;a href="https://docs.docker.com/engine/installation/" class="external-link" target="_blank" rel="noopener">安装 Docker&lt;/a>（Docker 版本不低于 &lt;code>1.5.0&lt;/code>）&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo curl -sSL https://get.docker.com/ &lt;span style="color:#eceff4">|&lt;/span> sh
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>查看 GitLab 版本&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cat /opt/gitlab/version-manifest.txt &lt;span style="color:#eceff4">|&lt;/span>grep gitlab-ce&lt;span style="color:#eceff4">|&lt;/span>awk &lt;span style="color:#a3be8c">&amp;#39;{print $2}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>安装 GitLab Runner 软件源&lt;/p>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># RHEL/CentOS/Fedora 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -s https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh &lt;span style="color:#eceff4">|&lt;/span> sudo bash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># Debian/Ubuntu/Mint 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -s https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh &lt;span style="color:#eceff4">|&lt;/span> sudo bash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>
&lt;p>安装 GitLab Runner&lt;/p>
&lt;p>若 GitLab 版本低于或等于 8，要安装 GitLab Runner 1.11 版，新旧版 GitLab Runner 的 API 互不兼容&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># RHEL/CentOS/Fedora 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo yum install gitlab-ci-multi-runner-1.11.2-1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># Debian/Ubuntu/Mint 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install gitlab-ci-multi-runner-1.11.2-1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>GitLab 9 或以上版本，直接安装 GitLab Runner 包即可&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># RHEL/CentOS/Fedora 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo yum install gitlab-ci-multi-runner
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># Debian/Ubuntu/Mint 系统&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install gitlab-ci-multi-runner
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>注册 GitLab Runner&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>gitlab-runner register
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>跟着提示填写所需信息，其中要填写和 GitLab 对应的 Token 和 URL，用于连接 GitLab 和 GitLab Runner&lt;/p>
&lt;blockquote>
&lt;p>在哪里查找 Token 和 URL？&lt;/p>
&lt;p>如果你有 GitLab 管理员权限：
进入 Admin Area，打开 Overview - Runners 页面，可以找到 Token 和 URL。&lt;/p>
&lt;p>如果你没有 GitLab 管理员权限：
进入 GitLab 的任一项目，点击右上角的功能菜单选择 Runners（GitLab 8），或左侧的 Settings 菜单（GitLab &amp;gt; 8）选择 CI/CD，可以找到 Token 和 URL。&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;/ol>
&lt;h2 id="在项目中使用-gitlab-ci">
在项目中使用 GitLab CI
&lt;a class="heading-link" href="#%e5%9c%a8%e9%a1%b9%e7%9b%ae%e4%b8%ad%e4%bd%bf%e7%94%a8-gitlab-ci">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>假设我们要启用 CI 的项目叫做 &lt;code>fe-project&lt;/code>，在项目根目录创建一个 &lt;code>.gitlab-ci.yml&lt;/code> 文件，注意文件名开头的点字符。&lt;/p>
&lt;p>GitLab CI 包含三个常用的步骤（&lt;code>stage&lt;/code>），分别是 &lt;code>test&lt;/code>、&lt;code>build&lt;/code> 和 &lt;code>deploy&lt;/code>，用于执行测试、构建和部署任务。&lt;/p>
&lt;p>首先添加一段测试脚本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">the_unit_test&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">stage&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">script&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - npm install
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - npm run test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>第一行是这段任务的名称，用于查阅和区分。第二行指定这段脚本要在 &lt;code>test&lt;/code> 阶段执行。第三行是脚本数组，即这个任务具体执行的命令。具体测试命令可根据你的项目实际情况修改，以上给出的是比较常见的测试命令。&lt;/p>
&lt;p>然后，提交代码并推到远程仓库。现在进入 &lt;code>fe-project&lt;/code> 项目的 Pipelines 页面，即可看到当前正在运行刚刚添加的测试任务。&lt;/p>
&lt;blockquote>
&lt;p>任务未运行或显示 Pending？&lt;/p>
&lt;p>进入项目的 Runners 页面，检查是否已启用 Runner&lt;/p>
&lt;/blockquote>
&lt;p>如果运行过程中有报错（或任务本身执行失败、不通过等），可以点击任务 ID 查看详细的运行日志，查看命令行回显的错误内容并进行修正。&lt;/p>
&lt;p>接下来在测试脚本下方添加构建脚本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">build_the_project&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">stage&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">script&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - npm install
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - npm build
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">artifacts&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">expire_in&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#b48ead">1&lt;/span> week
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">paths&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - dist
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这段脚本与测试脚本结构相似，其中新增了一段 &lt;code>artifacts&lt;/code> 配置，用来指定构建出来的文件存放位置、过期时间等，供稍后的部署脚本使用。&lt;/p>
&lt;p>提交代码并推到远程仓库，进入 Pipelines 页面查看一下日志，如果没有问题则可以进入下一步部署。通常构建阶段比较耗时，根据 &lt;code>script&lt;/code> 执行的时长可能需要 3-5 分钟，具体耗时受项目本身的影响。&lt;/p>
&lt;p>最后，当测试和构建都通过时，就可以执行部署任务了，新代码将被上传到部署服务器。&lt;/p>
&lt;h4 id="初次执行部署任务前的准备工作">
初次执行部署任务前的准备工作
&lt;a class="heading-link" href="#%e5%88%9d%e6%ac%a1%e6%89%a7%e8%a1%8c%e9%83%a8%e7%bd%b2%e4%bb%bb%e5%8a%a1%e5%89%8d%e7%9a%84%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>初次部署之前，你需要做这样几件事：&lt;/p>
&lt;ul>
&lt;li>在生产服务器上配置好项目所需的服务，例如 Nginx、数据库等&lt;/li>
&lt;li>在生产服务器上创建一个 &lt;code>gitlab&lt;/code> 用户（用户名可以自拟）用于远程连接，并让项目所在的目录对这个用户可读写&lt;/li>
&lt;li>为这个用户创建一对 SSH 密钥，存放在此用户的 &lt;code>.ssh&lt;/code> 目录下，并拷贝私钥
&lt;ul>
&lt;li>注意：确保 &lt;code>authorized_keys&lt;/code> 文件权限 &lt;code>chmod 600 authorized_keys&lt;/code>&lt;/li>
&lt;li>注意：确保 &lt;code>.ssh&lt;/code> 目录的权限 &lt;code>chmod 700 ~/.ssh&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>进入项目的 GitLab 页面，并进入 Variables 页面（GitLab &amp;gt; 9 版本在 &lt;code>Settings - CI/CD - Secret Variables&lt;/code> 下），添加一个名为 &lt;code>CI_PRIVATE_KEY&lt;/code> 的变量，并将上一步拷贝的私钥粘贴在 Value 框内&lt;/li>
&lt;/ul>
&lt;h4 id="提交部署脚本">
提交部署脚本
&lt;a class="heading-link" href="#%e6%8f%90%e4%ba%a4%e9%83%a8%e7%bd%b2%e8%84%9a%e6%9c%ac">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>在构建脚本的下方添加部署脚本，关键信息已用注释标出：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">deploy_to_server&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 使用最小 Linux 发行版 Alpine 作为部署执行环境的镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">image&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> alpine:3.6
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">stage&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> deploy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">script&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 使用教育网 apk 镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - echo &amp;#34;http://mirrors.ustc.edu.cn/alpine/v3.6/main/&amp;#34; &amp;gt; /etc/apk/repositories
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 安装 rsync 和 openssh 包&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - apk add --no-cache rsync openssh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 创建 ssh 密钥，用来登录到生产服务器&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - mkdir -p ~/.ssh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - echo &amp;#34;$CI_PRIVATE_KEY&amp;#34; &amp;gt;&amp;gt; ~/.ssh/id_dsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - chmod 600 ~/.ssh/id_dsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - echo -e &amp;#34;Host *\n\tStrictHostKeyChecking no\n\n&amp;#34; &amp;gt; ~/.ssh/config
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 用准备工作中创建的用户登录到生产服务器，指定在 Nginx 中配置的项目路径&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 注意请将 0.0.0.0 换成实际的 IP 地址&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - rsync -av --progress --delete dist/ gitlab@0.0.0.0:/var/www/fe-project/dist/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># 在生产服务器进行一次代码备份，其中 CI_BUILD_REF 是 GitLab CI 的内置变量，表示构建任务的 ID 哈希值，用来区分不同构建的产出物，便于部署发现问题时立即将线上代码切换为任一可用版本&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - rsync -av --progress --delete dist/ gitlab@0.0.0.0:/var/www/fe-project/dist-$CI_BUILD_REF/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>提交代码并推送到远程仓库，并观察 Pipelines 页面的日志。若测试、构建和部署都通过，再次访问网站的线上地址时，就可以看到最新的改动了。&lt;/p>
&lt;h2 id="结束">
结束
&lt;a class="heading-link" href="#%e7%bb%93%e6%9d%9f">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>本文以最简洁的方式介绍了 GitLab CI 的配置和使用，在现实开发中你可能需要根据项目性质的不同来编排&lt;a href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html" class="external-link" target="_blank" rel="noopener">更加复杂的 CI 脚本&lt;/a>。此外，你还可以使用 GitLab 自带的 Webhooks、Services 来连接 Slack、&lt;a href="https://integram.org/" class="external-link" target="_blank" rel="noopener">Telegram&lt;/a> 等外部服务，实时知晓 CI 的运行状况和结果。&lt;/p></description></item><item><title>构建 Vue.js 工程时 .vue 文件解析报错</title><link>https://mogita.com/vue-js-doesn-t-export-content.html</link><pubDate>Mon, 10 Apr 2017 00:00:51 +0000</pubDate><guid>https://mogita.com/vue-js-doesn-t-export-content.html</guid><description>&lt;p>在一台 CentOS 6 服务器上构建 Vue.js 项目时，&lt;code>.vue&lt;/code> 文件大量报错：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>xxx&lt;span style="color:#81a1c1">.&lt;/span>vue doesn&lt;span style="color:#a3be8c">&amp;#39;t export content&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个错误的原因之一可能是 &lt;code>node-sass&lt;/code> 安装不正常。也就是说，在 &lt;code>.vue&lt;/code> 文件报错日志的最上方，或许能够看到 &lt;code>node-sass&lt;/code> 模块的相关出错信息。&lt;/p>
&lt;p>如果你能看到这个出错信息，可以参考一下我的解决方法。&lt;/p>
&lt;ul>
&lt;li>检查 &lt;code>node-sass&lt;/code> 包是否正确安装和编译，以及在 &lt;code>node-sass&lt;/code> 包目录下生成 &lt;code>vendor&lt;/code> 文件夹。如果没有正确生成，需检查工程所在目录的读写或执行权限。&lt;/li>
&lt;li>编译环境中 &lt;code>gcc&lt;/code> 版本要在 4.9 或以上。我原先的版本是 4.8.8，它导致 &lt;code>webpack&lt;/code> 在构建 Vue.js 工程时报错：&lt;code>Error: /usr/lib64/libstdc++.so.6: version CXXABI_1.3.5' not found&lt;/code>。你需要更新 gcc 版本。&lt;/li>
&lt;/ul>
&lt;p>更新 &lt;code>gcc&lt;/code> 方法参考：&lt;/p>
&lt;ol>
&lt;li>先安装 gcc 4.9&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">cd&lt;/span> /etc/yum.repos.d
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget http://linuxsoft.cern.ch/cern/scl/slc6-scl.repo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>yum -y --nogpgcheck install devtoolset-3-gcc devtoolset-3-gcc-c++
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="2">
&lt;li>然后修改 &lt;code>.bashrc&lt;/code> 设置 gcc 的执行路径&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>vi ~/.bashrc
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="3">
&lt;li>修改或插入 PATH 变量&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>PATH&lt;span style="color:#81a1c1">=/&lt;/span>opt&lt;span style="color:#81a1c1">/&lt;/span>rh&lt;span style="color:#81a1c1">/&lt;/span>devtoolset&lt;span style="color:#81a1c1">-&lt;/span>&lt;span style="color:#b48ead">3&lt;/span>&lt;span style="color:#81a1c1">/&lt;/span>root&lt;span style="color:#81a1c1">/&lt;/span>usr&lt;span style="color:#81a1c1">/&lt;/span>bin&lt;span style="color:#81a1c1">/&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#81a1c1">$&lt;/span>PATH
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol start="4">
&lt;li>进入工程目录，执行 &lt;code>rm -rf node_modules/node-sass/ &amp;amp;&amp;amp; yarn add node-sass&lt;/code> 再次安装并编译。如果安装过程无误，这次会由 gcc 4.9 进行编译。&lt;/li>
&lt;/ol>
&lt;p>编译完成后，执行 &lt;code>yarn build&lt;/code> 或你的工程定义的构建命令来进行构建，应该不会再看到文章开头的错误了。&lt;/p>
&lt;h2 id="20170601-更新">
20170601 更新
&lt;a class="heading-link" href="#20170601-%e6%9b%b4%e6%96%b0">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>CentOS 7 系统请参考这个 &lt;a href="https://gist.github.com/mogita/c3528c5229479c8e02d85bc3f96010ef" class="external-link" target="_blank" rel="noopener">gist&lt;/a>&lt;/p></description></item><item><title>Remove Nth Node From End of List 数组实现</title><link>https://mogita.com/remove-nth-node-from-end-of-list-with-array.html</link><pubDate>Tue, 07 Feb 2017 01:54:30 +0000</pubDate><guid>https://mogita.com/remove-nth-node-from-end-of-list-with-array.html</guid><description>&lt;h2 id="原题">
原题
&lt;a class="heading-link" href="#%e5%8e%9f%e9%a2%98">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Given a linked list, remove the nth node from the end of list and return its head.&lt;/p>
&lt;p>For example,&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Given linked list: 1-&amp;gt;2-&amp;gt;3-&amp;gt;4-&amp;gt;5, and n = 2.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>After removing the second node from the end, the linked list becomes 1-&amp;gt;2-&amp;gt;3-&amp;gt;5.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Note:&lt;/strong>&lt;/p>
&lt;p>Given n will always be valid.&lt;/p>
&lt;p>Try to do this in one pass.&lt;/p>
&lt;h2 id="数组方式解答">
数组方式解答
&lt;a class="heading-link" href="#%e6%95%b0%e7%bb%84%e6%96%b9%e5%bc%8f%e8%a7%a3%e7%ad%94">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>思路：先遍历整个链表，存成一个数组。然后删除从末尾起第 n 个元素（或从头起的第 &lt;code>length - n + 1&lt;/code> 个元素）。然后遍历数组建立新的链表，然后返回此链表，结束程序。&lt;/p>
&lt;p>我选择了从头算个数的方式，由于 Array 下标从 0 开始，因此传入的位置需要减 1，即最终传入 &lt;code>length - n&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> removeNthFromEnd &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>head&lt;span style="color:#eceff4">,&lt;/span> n&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> arrayBridge &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> currentNode &lt;span style="color:#81a1c1">=&lt;/span> head&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// first pass: get the length
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">while&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>currentNode&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> arrayBridge&lt;span style="color:#eceff4">.&lt;/span>push&lt;span style="color:#eceff4">(&lt;/span>currentNode&lt;span style="color:#eceff4">.&lt;/span>val&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentNode &lt;span style="color:#81a1c1">=&lt;/span> currentNode&lt;span style="color:#eceff4">.&lt;/span>next&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// for an edge situation where head has only 1 node
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#616e87;font-style:italic">// leetcode expects an [] as an empty ListNode object
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>n &lt;span style="color:#81a1c1">===&lt;/span> &lt;span style="color:#b48ead">1&lt;/span> &lt;span style="color:#81a1c1">&amp;amp;&amp;amp;&lt;/span> n &lt;span style="color:#81a1c1">===&lt;/span> arrayBridge&lt;span style="color:#eceff4">.&lt;/span>length&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#eceff4">[];&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> arrayBridge&lt;span style="color:#eceff4">.&lt;/span>splice&lt;span style="color:#eceff4">(&lt;/span>arrayBridge&lt;span style="color:#eceff4">.&lt;/span>length &lt;span style="color:#81a1c1">-&lt;/span> n&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> list &lt;span style="color:#81a1c1">=&lt;/span> curr &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> ListNode&lt;span style="color:#eceff4">(&lt;/span>arrayBridge&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#b48ead">0&lt;/span>&lt;span style="color:#eceff4">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> count &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">while&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>count &lt;span style="color:#81a1c1">&amp;lt;&lt;/span> arrayBridge&lt;span style="color:#eceff4">.&lt;/span>length&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> curr&lt;span style="color:#eceff4">.&lt;/span>next &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> ListNode&lt;span style="color:#eceff4">(&lt;/span>arrayBridge&lt;span style="color:#eceff4">[&lt;/span>count&lt;span style="color:#eceff4">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> curr &lt;span style="color:#81a1c1">=&lt;/span> curr&lt;span style="color:#eceff4">.&lt;/span>next&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> count&lt;span style="color:#81a1c1">++&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> list&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>此解已是一个可获得 Accepted 的答案，但离题目要求的「只用一通」颇有差距，最坏的情况需要几乎两通的时间。&lt;/p></description></item><item><title>8 个 ES6 实用特性</title><link>https://mogita.com/es6-handy-features.html</link><pubDate>Thu, 19 Jan 2017 10:45:49 +0000</pubDate><guid>https://mogita.com/es6-handy-features.html</guid><description>&lt;h2 id="模板字符串template-string">
模板字符串（Template String）
&lt;a class="heading-link" href="#%e6%a8%a1%e6%9d%bf%e5%ad%97%e7%ac%a6%e4%b8%b2template-string">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> block &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">`&amp;lt;div&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c"> &amp;lt;p&amp;gt;hello&amp;lt;/p&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;lt;/div&amp;gt;`&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>block&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">&amp;lt;&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">&amp;lt;&lt;/span>&lt;span style="color:#81a1c1">p&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>hello&lt;span style="color:#eceff4">&amp;lt;/&lt;/span>&lt;span style="color:#81a1c1">p&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">&amp;lt;/&lt;/span>&lt;span style="color:#81a1c1">div&lt;/span>&lt;span style="color:#eceff4">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="箭头函数">
箭头函数
&lt;a class="heading-link" href="#%e7%ae%ad%e5%a4%b4%e5%87%bd%e6%95%b0">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>解决父级作用域无法被回调函数访问的问题。例如下列 ES5 代码，必须在回调函数后添加 &lt;code>.bind(this)&lt;/code> 才可以正确运行：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> names&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;mogita&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;me&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;zhiyi&amp;#39;&lt;/span>&lt;span style="color:#eceff4">],&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> domain&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> method&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> mapEmail &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>item&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> item &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;@&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>domain&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}.&lt;/span>bind&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>names&lt;span style="color:#eceff4">.&lt;/span>map&lt;span style="color:#eceff4">(&lt;/span>mapEmail&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>obj&lt;span style="color:#eceff4">.&lt;/span>method&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">[&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;mogita@mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;me@mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;zhiyi@mogita.com&amp;#39;&lt;/span> &lt;span style="color:#eceff4">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="es6-用箭头函数替代">
ES6 用箭头函数替代
&lt;a class="heading-link" href="#es6-%e7%94%a8%e7%ae%ad%e5%a4%b4%e5%87%bd%e6%95%b0%e6%9b%bf%e4%bb%a3">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> names&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;mogita&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;me&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;zhiyi&amp;#39;&lt;/span>&lt;span style="color:#eceff4">],&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> domain&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> method&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>names&lt;span style="color:#eceff4">.&lt;/span>map&lt;span style="color:#eceff4">(&lt;/span>item &lt;span style="color:#eceff4">=&amp;gt;&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>item&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">@&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">this&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>domain&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>obj&lt;span style="color:#eceff4">.&lt;/span>method&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">[&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;mogita@mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;me@mogita.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;zhiyi@mogita.com&amp;#39;&lt;/span> &lt;span style="color:#eceff4">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="class-语法糖">
Class 语法糖
&lt;a class="heading-link" href="#class-%e8%af%ad%e6%b3%95%e7%b3%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Javascript 中并没有真正的「类」，一直以来多用原型扩展的方式来构建一个类，如下列 ES5 代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> Obj&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Obj constructed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Obj&lt;span style="color:#eceff4">.&lt;/span>prototype&lt;span style="color:#eceff4">.&lt;/span>method &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;This is Obj\&amp;#39;s method&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;About to construct&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> Obj&lt;span style="color:#eceff4">();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj&lt;span style="color:#eceff4">.&lt;/span>method&lt;span style="color:#eceff4">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>About to construct
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Obj constructed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>This is Obj&lt;span style="color:#a3be8c">&amp;#39;s method&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="es6-的-class-语法糖">
ES6 的 Class 语法糖
&lt;a class="heading-link" href="#es6-%e7%9a%84-class-%e8%af%ad%e6%b3%95%e7%b3%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> Obj &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> constructor&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Obj constructed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> method&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;This is Obj\&amp;#39;s method&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;About to construct&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> Obj&lt;span style="color:#eceff4">();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>obj&lt;span style="color:#eceff4">.&lt;/span>method&lt;span style="color:#eceff4">();&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span style="display:flex;">&lt;span>About to construct
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Obj constructed
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>This is Obj&lt;span style="color:#a3be8c">&amp;#39;s method&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="模块">
模块
&lt;a class="heading-link" href="#%e6%a8%a1%e5%9d%97">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>方便在不同 &lt;code>js&lt;/code> 文件之间共用代码，使项目模块化。&lt;/p>
&lt;h4 id="导入模块">
导入模块
&lt;a class="heading-link" href="#%e5%af%bc%e5%85%a5%e6%a8%a1%e5%9d%97">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> something from &lt;span style="color:#a3be8c">&amp;#39;framework&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> &lt;span style="color:#81a1c1">*&lt;/span> as something from &lt;span style="color:#a3be8c">&amp;#39;framework&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>matchedProp&lt;span style="color:#eceff4">}&lt;/span> from &lt;span style="color:#a3be8c">&amp;#39;framework&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="导出接口">
导出接口
&lt;a class="heading-link" href="#%e5%af%bc%e5%87%ba%e6%8e%a5%e5%8f%a3">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">default&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> something &lt;span style="color:#eceff4">{}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> value &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;value&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> another &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;another&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">export&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> matchedProp &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;matched&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="反构造destructure">
反构造（Destructure）
&lt;a class="heading-link" href="#%e5%8f%8d%e6%9e%84%e9%80%a0destructure">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Angular 等现代前端框架是用 TypeScript（ES6 的一个超集）编写的，反构造在导入语句中很常见，它可以用来选择所需的具体模块。&lt;/p>
&lt;p>例如 ES6 代码，只引用对象的若干选定属性：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> a&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> b&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#b48ead">2&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#b48ead">3&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">};&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>a&lt;span style="color:#eceff4">,&lt;/span> c&lt;span style="color:#eceff4">}&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> obj&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>a&lt;span style="color:#eceff4">,&lt;/span> c&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>1 3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="剩余参数">
剩余参数
&lt;a class="heading-link" href="#%e5%89%a9%e4%bd%99%e5%8f%82%e6%95%b0">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>如果最后一个命名了的参数前面加了三个点，该参数将含有该位置以及向后的所有传入参数。例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> hello&lt;span style="color:#eceff4">(...&lt;/span>numbers&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> numbers&lt;span style="color:#eceff4">.&lt;/span>forEach&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>item&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>item&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>hello&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">2&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">3&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">4&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>4
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="铺展操作符">
铺展操作符
&lt;a class="heading-link" href="#%e9%93%ba%e5%b1%95%e6%93%8d%e4%bd%9c%e7%ac%a6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>铺展操作符可以视为「剩余参数」的反向概念，用来动态扩展参数。例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> print&lt;span style="color:#eceff4">(&lt;/span>x&lt;span style="color:#eceff4">,&lt;/span> y&lt;span style="color:#eceff4">,&lt;/span> z&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>x &lt;span style="color:#81a1c1">+&lt;/span> y &lt;span style="color:#81a1c1">+&lt;/span> z&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> params &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">2&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#b48ead">3&lt;/span>&lt;span style="color:#eceff4">];&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>print&lt;span style="color:#eceff4">(...&lt;/span>params&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>6
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="对象字面量语法糖">
对象字面量语法糖
&lt;a class="heading-link" href="#%e5%af%b9%e8%b1%a1%e5%ad%97%e9%9d%a2%e9%87%8f%e8%af%ad%e6%b3%95%e7%b3%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>可以让对象属性名由变量组成。例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> obj &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;var_&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#b48ead">42&lt;/span>&lt;span style="color:#eceff4">]&lt;/span>&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;life&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>obj&lt;span style="color:#eceff4">.&lt;/span>var_42&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>输出&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>life
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>11 条提升工作效率的 npm 命令</title><link>https://mogita.com/npm-useful-quick-tips.html</link><pubDate>Sun, 04 Sep 2016 09:55:11 +0000</pubDate><guid>https://mogita.com/npm-useful-quick-tips.html</guid><description>&lt;p>Npm 是个容易上手但不易深入的工具，因为内置了数不尽的功能。如果要挨个学习和尝试，不知何年何月能够全部学完。&lt;/p>
&lt;p>以我自身为例，学会了一条 &lt;code>npm prune&lt;/code>（即本文第四节）之后，才明白如果项目里某些模块不再需要了，根本不必手动删除 &lt;code>node_modules&lt;/code> 目录再重新执行 &lt;code>npm install&lt;/code>。这个过程着实令人头痛。&lt;/p>
&lt;p>我们总结了 11 条易用的 npm 命令，能够帮你在任何工程中提升效率。&lt;/p>
&lt;h2 id="打开模块的主页">
打开模块的主页
&lt;a class="heading-link" href="#%e6%89%93%e5%bc%80%e6%a8%a1%e5%9d%97%e7%9a%84%e4%b8%bb%e9%a1%b5">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm home &amp;lt;package&amp;gt;&lt;/code>&lt;/p>
&lt;p>执行 &lt;code>home&lt;/code> 命令会打开 &lt;code>package&lt;/code> 指定包的主页。比如输入 &lt;code>lodash&lt;/code> 会打开 Lodash 的网站首页。无论这个包是否已被全局安装或工程内安装，都可以直接使用。&lt;/p>
&lt;h2 id="打开模块的-github-仓库">
打开模块的 GitHub 仓库
&lt;a class="heading-link" href="#%e6%89%93%e5%bc%80%e6%a8%a1%e5%9d%97%e7%9a%84-github-%e4%bb%93%e5%ba%93">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm repo &amp;lt;package&amp;gt;&lt;/code>&lt;/p>
&lt;p>和 &lt;code>home&lt;/code> 命令类似，&lt;code>repo&lt;/code> 命令会打开指定包的 GitHub 仓库页面。例如输入 &lt;code>express&lt;/code> 将打开 Express 的官方仓库页面。和 &lt;code>home&lt;/code> 一样，未安装的包也可以打开。&lt;/p>
&lt;h2 id="检查需更新的依赖">
检查需更新的依赖
&lt;a class="heading-link" href="#%e6%a3%80%e6%9f%a5%e9%9c%80%e6%9b%b4%e6%96%b0%e7%9a%84%e4%be%9d%e8%b5%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm outdated&lt;/code>&lt;/p>
&lt;p>你可以在工程里执行 &lt;code>outdated&lt;/code> 命令，npm 会检查哪些包已陈旧，需要更新。结果会展现在一张表格里，显示当前版本、所需版本和最新版本。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ chatroom git:&lt;span style="color:#81a1c1">(&lt;/span>master&lt;span style="color:#81a1c1">)&lt;/span> npm outdated
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Package Current Wanted Latest Location
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>express 4.13.4 4.13.4 4.14.0 chatroom
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>socket.io 1.4.6 1.4.6 1.4.8 chatroom
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="检查未被-packagejson-声明的包">
检查未被 package.json 声明的包
&lt;a class="heading-link" href="#%e6%a3%80%e6%9f%a5%e6%9c%aa%e8%a2%ab-packagejson-%e5%a3%b0%e6%98%8e%e7%9a%84%e5%8c%85">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm prune&lt;/code>&lt;/p>
&lt;p>当你执行 &lt;code>prune&lt;/code> 时，npm 会检查 &lt;code>package.json&lt;/code> 里的依赖列表，并对比工程的 &lt;code>node_modules&lt;/code> 目录。它会打印出未被 &lt;code>package.json&lt;/code> 声明却已存在的包。你可以评估一下哪些包是真正需要的（比如说通过 &lt;code>npm install&lt;/code> 安装却未加 &lt;code>--save&lt;/code> 参数的包），并可以将它们删除。&lt;/p>
&lt;h2 id="锁定依赖版本">
锁定依赖版本
&lt;a class="heading-link" href="#%e9%94%81%e5%ae%9a%e4%be%9d%e8%b5%96%e7%89%88%e6%9c%ac">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm shrinkwrap&lt;/code>&lt;/p>
&lt;p>执行 &lt;code>shrinkwrap&lt;/code> 命令将生成 &lt;code>npm-shrinkwrap.json&lt;/code> 文件。它能将安装在 &lt;code>node_modules&lt;/code> 目录中的依赖锁定在一个特定的版本。当 &lt;code>npm-shrinkwrap.json&lt;/code> 文件存在时，&lt;code>npm install&lt;/code> 将遵守其中的版本号，而不再使用 &lt;code>package.json&lt;/code> 里的更高版本号。&lt;/p>
&lt;p>如果你想检查 &lt;code>package.json&lt;/code>、&lt;code>npm-shrinkwrap.json&lt;/code> 以及 &lt;code>node_modules&lt;/code> 中依赖版本的一致性，请尝试使用 &lt;a href="https://github.com/uber/npm-shrinkwrap" class="external-link" target="_blank" rel="noopener">npm-shrinkwrap&lt;/a> 包。&lt;/p>
&lt;h2 id="使用-npm-v3-和-nodejs-v4-lts">
使用 npm v3 和 Node.js v4 LTS
&lt;a class="heading-link" href="#%e4%bd%bf%e7%94%a8-npm-v3-%e5%92%8c-nodejs-v4-lts">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm install -g npm@3&lt;/code>&lt;/p>
&lt;p>用 npm 全局安装 &lt;code>npm@3&lt;/code> 将把 v2 版的 npm 升级到 v3 版，甚至在 Node.js v4 LTS（Argon）上也可以这样做。这样，你的 v4 LTS 运行环境里就可以使用最新稳定版的 npm v3 了。&lt;/p>
&lt;h2 id="让-npm-install--g-不再请求-sudo">
让 &lt;code>npm install -g&lt;/code> 不再请求 &lt;code>sudo&lt;/code>
&lt;a class="heading-link" href="#%e8%ae%a9-npm-install--g-%e4%b8%8d%e5%86%8d%e8%af%b7%e6%b1%82-sudo">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm config set prefix &amp;lt;dir&amp;gt;&lt;/code>&lt;/p>
&lt;p>执行这个命令时，需要在 &lt;code>&amp;lt;dir&amp;gt;&lt;/code> 参数指定新的全局安装模块的目录，这样你就可以避免全局安装时要输入密码的情况了。指定的目录将成为新的全局 bin 目录。需要注意的是，新指定的目录必须对你的当前用户提供写权限，可以使用例如 &lt;code>chown -R &amp;lt;USER&amp;gt; &amp;lt;dir&amp;gt;&lt;/code> 的命令来修改权限。&lt;/p>
&lt;h2 id="修改工程里的保存前缀">
修改工程里的保存前缀
&lt;a class="heading-link" href="#%e4%bf%ae%e6%94%b9%e5%b7%a5%e7%a8%8b%e9%87%8c%e7%9a%84%e4%bf%9d%e5%ad%98%e5%89%8d%e7%bc%80">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>执行：&lt;/strong>&lt;code>npm config set save-prefix ~&lt;/code>&lt;/p>
&lt;p>在安装新模块并用 &lt;code>--save&lt;/code> 或 &lt;code>--save-dev&lt;/code> 保存时，波浪号 &lt;code>~&lt;/code> 比 npm 默认的 &lt;code>^&lt;/code> 号保守。波浪号会把依赖锁定在当前小版本号，也就是说可以通过 &lt;code>npm update&lt;/code> 来安装 patch 发行版。而 &lt;code>^&lt;/code> 代表锁定在大版本号，通过 &lt;code>npm update&lt;/code> 安装的是 minor 发行版。&lt;/p>
&lt;h2 id="上线前去掉工程里的-devdependencies">
上线前去掉工程里的 &lt;code>devDependencies&lt;/code>
&lt;a class="heading-link" href="#%e4%b8%8a%e7%ba%bf%e5%89%8d%e5%8e%bb%e6%8e%89%e5%b7%a5%e7%a8%8b%e9%87%8c%e7%9a%84-devdependencies">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>当你的项目准备上线进入生产环境时，请无比在安装模块时加上 &lt;code>--production&lt;/code> 标记。这个标记将只安装 &lt;code>dependencies&lt;/code>，忽略 &lt;code>devDependencies&lt;/code>。这就保证了线上版本的代码库里不含有调试用的工具包等。&lt;/p>
&lt;p>其实，你可以在 &lt;code>NODE_ENV&lt;/code> 环境变量中设置 &lt;code>production&lt;/code>，从而保障线上工程的 &lt;code>devDependencies&lt;/code> 永远不被安装。&lt;/p>
&lt;h2 id="使用-npmignore-时需谨慎">
使用 &lt;code>.npmignore&lt;/code> 时需谨慎
&lt;a class="heading-link" href="#%e4%bd%bf%e7%94%a8-npmignore-%e6%97%b6%e9%9c%80%e8%b0%a8%e6%85%8e">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>如果你尚未使用过 &lt;code>.npmignore&lt;/code>，其默认值还是很安全的。&lt;/p>
&lt;p>一旦你在工程里添加了 &lt;code>.npmignore&lt;/code> 文件，那么 &lt;code>.gitignore&lt;/code> 里的规则就会被无视了。结局就是你必须保持这两个文件内容的同步，否则有可能在发布时泄漏敏感信息。&lt;/p>
&lt;h2 id="设置-npm-init-的默认值">
设置 &lt;code>npm init&lt;/code> 的默认值
&lt;a class="heading-link" href="#%e8%ae%be%e7%bd%ae-npm-init-%e7%9a%84%e9%bb%98%e8%ae%a4%e5%80%bc">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>当你在新工程里执行 &lt;code>npm init&lt;/code> 时，会从头到尾走一遍设置流程，最后生成 &lt;code>package.json&lt;/code>。如果你想要设定一些可以让 &lt;code>npm init&lt;/code> 一直使用的默认值，可以执行 &lt;code>config set&lt;/code> 命令，执行方法如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm config &lt;span style="color:#81a1c1">set&lt;/span> init.author.name &amp;lt;name&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>npm config &lt;span style="color:#81a1c1">set&lt;/span> init.author.email &amp;lt;email&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>或者你想要直接修改初始化脚本文件，做一些自定的初始化逻辑，那么请执行下面的命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm config &lt;span style="color:#81a1c1">set&lt;/span> init-module ~/.npm-init.js&lt;span style="color:#a3be8c">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>下面是个脚本范例，它会提问一些私人问题，并在需要时创建 GitHub 仓库。请修改默认的 GitHub 用户名 &lt;code>YOUR_GITHUB_USERNAME&lt;/code>，作为 GitHub 用户名的默认参数。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> cp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;child_process&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> priv&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> USER &lt;span style="color:#81a1c1">=&lt;/span> process&lt;span style="color:#eceff4">.&lt;/span>env&lt;span style="color:#eceff4">.&lt;/span>GITHUB_USERNAME &lt;span style="color:#81a1c1">||&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;YOUR_GITHUB_USERNAME&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>module&lt;span style="color:#eceff4">.&lt;/span>exports &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name&lt;span style="color:#81a1c1">:&lt;/span> prompt&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;name&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> basename &lt;span style="color:#81a1c1">||&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">package&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>name&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> version&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;0.0.1&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">private&lt;/span>&lt;span style="color:#81a1c1">:&lt;/span> prompt&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;private&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;true&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>val&lt;span style="color:#eceff4">){&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> priv &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">typeof&lt;/span> val &lt;span style="color:#81a1c1">===&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;boolean&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">?&lt;/span> val &lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1">!!&lt;/span>val&lt;span style="color:#eceff4">.&lt;/span>match&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;true&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> create&lt;span style="color:#81a1c1">:&lt;/span> prompt&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;create github repo&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;yes&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>val&lt;span style="color:#eceff4">){&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> val &lt;span style="color:#81a1c1">=&lt;/span> val&lt;span style="color:#eceff4">.&lt;/span>indexOf&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;y&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">!==&lt;/span> &lt;span style="color:#81a1c1">-&lt;/span>&lt;span style="color:#b48ead">1&lt;/span> &lt;span style="color:#81a1c1">?&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">true&lt;/span> &lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">false&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>val&lt;span style="color:#eceff4">){&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;enter github password:&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cp&lt;span style="color:#eceff4">.&lt;/span>execSync&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;curl -u &amp;#39;&amp;#34;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>USER&lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;&amp;#39; https://api.github.com/user/repos -d &amp;#34;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a3be8c">&amp;#34;&amp;#39;{\&amp;#34;name\&amp;#34;: \&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>basename&lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;\&amp;#34;, \&amp;#34;private\&amp;#34;: &amp;#34;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#eceff4">((&lt;/span>priv&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">?&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;true&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;false&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;}&amp;#39; &amp;#34;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cp&lt;span style="color:#eceff4">.&lt;/span>execSync&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;git remote add origin &amp;#39;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;https://github.com/&amp;#39;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>USER&lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;/&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> basename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.git&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">undefined&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main&lt;span style="color:#81a1c1">:&lt;/span> prompt&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;entry point&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;index.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> repository&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> type&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;git&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;git://github.com/&amp;#39;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>USER&lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;/&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> basename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.git&amp;#39;&lt;/span> &lt;span style="color:#eceff4">},&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bugs&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#eceff4">{&lt;/span> url&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;https://github.com/&amp;#39;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>USER&lt;span style="color:#a3be8c">&amp;#39;/&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> basename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;/issues&amp;#39;&lt;/span> &lt;span style="color:#eceff4">},&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> homepage&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;https://github.com/&amp;#34;&lt;/span>&lt;span style="color:#81a1c1">+&lt;/span>USER&lt;span style="color:#81a1c1">+&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;/&amp;#34;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> basename&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> keywords&lt;span style="color:#81a1c1">:&lt;/span> prompt&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>s&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> s&lt;span style="color:#eceff4">.&lt;/span>split&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#ebcb8b">/\s+/&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> license&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;MIT&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cleanup&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>cb&lt;span style="color:#eceff4">){&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cb&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">null&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">undefined&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="最后的话">
最后的话
&lt;a class="heading-link" href="#%e6%9c%80%e5%90%8e%e7%9a%84%e8%af%9d">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>如果你还想了解更多关于 npm、Node.js、JavaScript、Docker、Kubernetes 和 Electron 等的话题，请关注 &lt;a href="https://twitter.com/nodesource" class="external-link" target="_blank" rel="noopener">@NodeSource&lt;/a> 的 Twitter 账号。&lt;/p></description></item><item><title>Hexo 博客写作快速指南</title><link>https://mogita.com/hexo-blog-hands-on.html</link><pubDate>Fri, 02 Sep 2016 19:57:24 +0000</pubDate><guid>https://mogita.com/hexo-blog-hands-on.html</guid><description>&lt;p>今天把博客更新到了 &lt;a href="https://hexo.io/" class="external-link" target="_blank" rel="noopener">Hexo&lt;/a>，取代了之前的 &lt;a href="http://wordpress.org" class="external-link" target="_blank" rel="noopener">WordPress&lt;/a> 网站。WordPress 是个十足优秀的博客程序，但 &lt;a href="https://daringfireball.net/projects/markdown/" class="external-link" target="_blank" rel="noopener">Markdown&lt;/a> 已经成为了我日常工作中的主要文档书写方式。&lt;/p>
&lt;h2 id="快速指南">
快速指南
&lt;a class="heading-link" href="#%e5%bf%ab%e9%80%9f%e6%8c%87%e5%8d%97">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>因为是快速指南，所以操作细节会比较简略。如果你对任何细节有疑惑，请参考&lt;a href="https://hexo.io/docs/index.html" class="external-link" target="_blank" rel="noopener">官方文档&lt;/a>，这里有详尽的讲解。&lt;/p>
&lt;h4 id="什么是-hexo">
什么是 Hexo？
&lt;a class="heading-link" href="#%e4%bb%80%e4%b9%88%e6%98%af-hexo">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>Hexo 是一个基于 &lt;code>Node.js&lt;/code> 的静态博客生成器。换成简单的说法就是：Hexo 是个命令行软件，可以把你的 &lt;code>Markdown&lt;/code> 文件转换成一组漂亮的 &lt;code>html&lt;/code> 页面构成的网站，把这个网站上传（部署）到 GitHub 或 FTP 服务器，就可以让读者访问你的博客了。&lt;/p>
&lt;h4 id="如何使用">
如何使用？
&lt;a class="heading-link" href="#%e5%a6%82%e4%bd%95%e4%bd%bf%e7%94%a8">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>既然 Hexo 是个软件，那么在使用之前就需要安装它。打开你的终端程序，执行下面的命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm install -g hexo-cli
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>什么是 &lt;a href="https://docs.npmjs.com/getting-started/installing-node" class="external-link" target="_blank" rel="noopener">npm&lt;/a>？&lt;/p>
&lt;/blockquote>
&lt;p>接下来，用 Hexo 命令来创建一个全新的博客吧。非常简单，执行下面的命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexo init &amp;lt;folder&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用你想要的名称替换 &lt;code>&amp;lt;folder&amp;gt;&lt;/code> 即可。这个命令会在当前所在的目录创建一个文件夹，里面装着一套 Hexo 博客的基本文件内容。当然现在它还是个尚未装修的毛坯房，你需要先对它进行一些敲敲打打。执行下面两条命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">cd&lt;/span> &amp;lt;folder&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>npm install
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>上述命令将切换到博客所在的文件夹，并让 &lt;code>npm&lt;/code> 自动安装所需的依赖。&lt;/p>
&lt;p>然后，用你喜欢的文本编辑器打开 &lt;code>_config.yml&lt;/code> 文件，这是 Hexo 博客的全部配置所在，比如网站域名、博客名称、作者昵称等。设置项的名称指明了它的作用，如果想要深入挖掘配置方法，请参考&lt;a href="https://hexo.io/docs/configuration.html" class="external-link" target="_blank" rel="noopener">此页文档&lt;/a>。&lt;/p>
&lt;h2 id="写文章">
写文章
&lt;a class="heading-link" href="#%e5%86%99%e6%96%87%e7%ab%a0">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>首先，你需要知道文章都保存在哪里（事实上你不用手动创建 &lt;code>.md&lt;/code> 文件，但仍需要知道在哪里能找到它们）。进入 &lt;code>/&amp;lt;folder&amp;gt;/source/_posts&lt;/code> 文件夹，就能看到所有文章了，一篇文章对应一个 &lt;code>.md&lt;/code> 文件。&lt;/p>
&lt;p>怎样创建文章呢？执行下面的命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexo new &lt;span style="color:#81a1c1">[&lt;/span>layout&lt;span style="color:#81a1c1">]&lt;/span> &amp;lt;name&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中 &lt;code>&amp;lt;name&amp;gt;&lt;/code> 是文章的文件名，不用附加 &lt;code>.md&lt;/code> 后缀。&lt;code>layout&lt;/code> 参数可以填入 &lt;code>post&lt;/code>（文章）或 &lt;code>page&lt;/code>（页面）。忽略 &lt;code>layout&lt;/code> 默认创建文章。如果创建的是文章，&lt;code>.md&lt;/code> 文件位于 &lt;code>/&amp;lt;folder&amp;gt;/source/_posts&lt;/code>，如果创建的是页面，则在 &lt;code>/&amp;lt;folder&amp;gt;/source/&amp;lt;name&amp;gt;/index.md&lt;/code>。&lt;/p>
&lt;p>然后，用你喜欢的 &lt;a href="http://www.williamlong.info/archives/4319.html" class="external-link" target="_blank" rel="noopener">Markdown 编辑器&lt;/a>编辑文章吧。我正在使用的是 &lt;a href="http://typora.io/" class="external-link" target="_blank" rel="noopener">Typora&lt;/a>。&lt;/p>
&lt;h2 id="生成网站">
生成网站
&lt;a class="heading-link" href="#%e7%94%9f%e6%88%90%e7%bd%91%e7%ab%99">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>文章写完了，需要先生成网站。也就是把 &lt;code>.md&lt;/code> 文件全部转换成 &lt;code>html&lt;/code> 等静态 Web 文件，同时自动套用主题、插件等。注意，虽然这里也提到了「主题」、「插件」这样的字眼，但与 WordPress 的主题和插件有着本质的不同，静态 Web 站不需要像 WordPress 那样运行服务端脚本，插件等扩展功能都是在生成时一步到位，网站访问者请求的是一个货真价实的纯 &lt;code>html&lt;/code> 网站。还有什么比直接访问 &lt;code>html&lt;/code> 文件更快的呢？&lt;/p>
&lt;p>执行下面的命令生成网站：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexo generate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="部署">
部署
&lt;a class="heading-link" href="#%e9%83%a8%e7%bd%b2">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>所谓部署，对于静态 Web 站而言，就是「上传」了。无论是 GitHub 托管，还是通过 FTP 上传到自建网站，都需要把生成好的 &lt;code>html&lt;/code> 文件等资源上传到服务端。&lt;/p>
&lt;p>部署的配置方法已在官方文档中有详细的描述，比如 &lt;a href="https://hexo.io/docs/deployment.html#Git" class="external-link" target="_blank" rel="noopener">Git&lt;/a> 方式，或 &lt;a href="https://hexo.io/docs/deployment.html#FTPSync" class="external-link" target="_blank" rel="noopener">FTP&lt;/a> 方式。我的选择是 &lt;code>FTP&lt;/code>，因为我已经有一台虚拟主机了。&lt;/p>
&lt;p>执行下面的命令就可以开始部署了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>hexo deploy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果没有出错，就可以去访问你的新博客了。&lt;/p>
&lt;h4 id="一步部署命令">
一步部署命令
&lt;a class="heading-link" href="#%e4%b8%80%e6%ad%a5%e9%83%a8%e7%bd%b2%e5%91%bd%e4%bb%a4">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>使用下面的命令，可以在执行完生成后，自动执行部署命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>hexo generate --deploy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span># 甚至更简略的命令，功能同上
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>hexo g -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="文章编辑小贴士">
文章编辑小贴士
&lt;a class="heading-link" href="#%e6%96%87%e7%ab%a0%e7%bc%96%e8%be%91%e5%b0%8f%e8%b4%b4%e5%a3%ab">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Hexo 文章除了支持基本的 &lt;code>Markdown&lt;/code> 语法外，还额外支持一系列特殊标签，下面摘抄一些比较常用的标签。通过这些标签可以自动生成漂亮的排版元素。&lt;/p>
&lt;h4 id="块引用">
块引用
&lt;a class="heading-link" href="#%e5%9d%97%e5%bc%95%e7%94%a8">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>{% quote Seth Godin http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html Welcome to Island Marketing %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Every interaction is both precious and an opportunity to delight.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% endquote %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>效果&lt;/p>
&lt;p>{% quote Seth Godin &lt;a href="http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html" class="external-link" target="_blank" rel="noopener">http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html&lt;/a> Welcome to Island Marketing %}
Every interaction is both precious and an opportunity to delight.
{% endquote %}&lt;/p>
&lt;h4 id="代码块">
代码块
&lt;a class="heading-link" href="#%e4%bb%a3%e7%a0%81%e5%9d%97">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>可以使用 &lt;code>Markdown&lt;/code> 的 &lt;code>Code fence&lt;/code> 语法，也可以使用下面的标签：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>{% code Array.map %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>array.map(callback[, thisArg])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% endcode %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>效果&lt;/p>
&lt;p>{% code Array.map %}
array.map(callback[, thisArg])
{% endcode %}&lt;/p>
&lt;h3 id="gist">
Gist
&lt;a class="heading-link" href="#gist">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>{% gist gist_id [filename] %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="插入图片和附件">
插入图片和附件
&lt;a class="heading-link" href="#%e6%8f%92%e5%85%a5%e5%9b%be%e7%89%87%e5%92%8c%e9%99%84%e4%bb%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>在 &lt;code>Markdown&lt;/code> 文章里插入图片的方法十分简单，使用下面的语法就可以了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>![&lt;span style="color:#81a1c1">Image of Yaktocat&lt;/span>](&lt;span style="color:#8fbcbb">https://octodex.github.com/images/yaktocat.png&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你想要有序地管理所有图片，可以使用 Hexo 的&lt;a href="https://hexo.io/docs/asset-folders.html#Post-Asset-Folder" class="external-link" target="_blank" rel="noopener">资源文件夹&lt;/a>特性。首先需要在 &lt;code>_config.yml&lt;/code> 文件中进行如下配置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>post_asset_folder: true
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，每次执行 &lt;code>hexo new [layout] &amp;lt;name&amp;gt;&lt;/code> 时会自动创建 &lt;code>&amp;lt;name&amp;gt;&lt;/code> 同名的文件夹，把文章所需的图片放入该文件夹即可。用下面的语法即可插入图片：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>{% asset_img slug [title] %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// 举例
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% asset_img example.jpg This is an example image %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% asset_img &amp;#34;spaced asset.jpg&amp;#34; &amp;#34;spaced title&amp;#34; %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其实，自动创建的 &lt;code>&amp;lt;name&amp;gt;&lt;/code> 同名文件夹不仅可以用来存放图片，它本质上是一个「资源」文件夹，你还可以把其他类型的文件放在里面。比如，想要让读者下载一个 &lt;code>PDF&lt;/code> 文件的话，你可以把文件放进资源文件夹，然后在文章中这样引用它：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>{% asset_link file.pdf My PDF File %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="结尾">
结尾
&lt;a class="heading-link" href="#%e7%bb%93%e5%b0%be">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Hexo 是一款基于 &lt;code>Node.js&lt;/code> 的静态博客生成器，你可以用 &lt;code>Markdown&lt;/code> 语法和 Hexo 的扩展标签来编写文章内容和进行排版、插入图片或附件等。完成编写后，需要生成并部署网站资源，可以部署到 &lt;code>GitHub&lt;/code> 通过 &lt;code>GitHub Page&lt;/code> 来访问你的静态网站，也可以部署到 &lt;code>FTP&lt;/code> 服务器。公开域名，就可以开始欢迎你的第一批访问者了！&lt;/p></description></item><item><title>理解 JavaScript Promises：上、背景和基础</title><link>https://mogita.com/understanding-javascript-promise-part-1.html</link><pubDate>Mon, 18 Jul 2016 15:34:24 +0000</pubDate><guid>https://mogita.com/understanding-javascript-promise-part-1.html</guid><description>&lt;h2 id="许诺之地">
许诺之地
&lt;a class="heading-link" href="#%e8%ae%b8%e8%af%ba%e4%b9%8b%e5%9c%b0">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>ES2015 带来的最大变化之一就是&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" class="external-link" target="_blank" rel="noopener">原生的 Promise&lt;/a>（即「许诺」，下同）特性，用来解决各种回调问题，让开发者写出同步风格的异步代码。&lt;/p>
&lt;p>讲道理来说，Promise 和 Generator 代表着异步的新标准。无论你是否使用它们，都应该了解一下。&lt;/p>
&lt;p>Promise 特性由很简单的 API 构成，但学习曲线稍显陡峭。初学者或许会感到它的概念十分艰涩，需要一个了解的过程和充分的示例。&lt;/p>
&lt;p>读完本文后，你将能做到：&lt;/p>
&lt;ul>
&lt;li>明白为什么会有 Promise，以及它们能解决什么问题；&lt;/li>
&lt;li>解释什么是 Promise，包括它们的实现以及应用；&lt;/li>
&lt;li>将常见的回调设计用 Promise 进行重写&lt;/li>
&lt;/ul>
&lt;p>注意，文中示例均以 Node 环境为前提，你可以手动拷贝代码，也可以 &lt;a href="https://github.com/Peleke/promises/" class="external-link" target="_blank" rel="noopener">clone 我的代码&lt;/a>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>git clone https://github.com/Peleke/promises/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git checkout Part_1-Basics
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>下面是本文的整体框架：&lt;/p>
&lt;ul>
&lt;li>回调的问题&lt;/li>
&lt;li>Promise：定义以及 A+ 规范&lt;/li>
&lt;li>Promise 和反控制反转&lt;/li>
&lt;li>用 Promise 控制流程&lt;/li>
&lt;li>理解 &lt;code>then&lt;/code>、&lt;code>reject&lt;/code> 和 &lt;code>resolve&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="异步式">
异步式
&lt;a class="heading-link" href="#%e5%bc%82%e6%ad%a5%e5%bc%8f">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>任何写过一些 JavaScript 代码的开发者都知道，它是「非阻塞」或「异步执行」的。这究竟是什么意思？&lt;/p>
&lt;h4 id="同步和异步">
同步和异步
&lt;a class="heading-link" href="#%e5%90%8c%e6%ad%a5%e5%92%8c%e5%bc%82%e6%ad%a5">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>&lt;strong>同步代码&lt;/strong>会逐行运行，前行执行完了才执行后行。同步代码的近义词是阻塞执行，因为一行代码的执行会阻塞下面代码的执行，直到这行代码执行完毕。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// readfile_sync.js
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This example uses Node, and so won&amp;#39;t run in the browser.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;text.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Reading file . . . &amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// readFileSync BLOCKS execution until it returns.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// The program will wait to execute anything else until this operation finishes.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> file &lt;span style="color:#81a1c1">=&lt;/span> fs&lt;span style="color:#eceff4">.&lt;/span>readFileSync&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This will ALWAYS print after readFileSync returns. . .
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Done reading file.&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// . . . And this will ALWAYS print the contents of &amp;#39;file&amp;#39;.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`Contents: &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">()&lt;/span>&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://mogita.com/img/1.png" alt="1">&lt;/p>
&lt;p>&lt;strong>异步代码&lt;/strong>正好相反：当前正在执行的这行代码不会阻止接下来的代码，就算它正在执行需要较长时间的操作，例如 I/O 或网络请求。这就是「非阻塞代码」。下面是用异步方式模拟上面的代码段：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// readfile_async.js
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This example uses Node, so it won&amp;#39;t run in the browser.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;text.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> getContents &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> printContent &lt;span style="color:#eceff4">(&lt;/span>file&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">try&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> file&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">();&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">catch&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>TypeError&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> file&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Reading file . . . &amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>repeat&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#b48ead">76&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// readFile executes ASYNCHRONOUSLY.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// The program will continue to execute past LINE A while
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// readFile does its business. We&amp;#39;ll talk about callbacks in detail
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// soon -- for now, just pay mind to the the order of the log
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// statements.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> file&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> contents&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file &lt;span style="color:#81a1c1">=&lt;/span> contents&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`Uh, actually, now I&amp;#39;m done. Contents are: &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span> getContents&lt;span style="color:#eceff4">(&lt;/span>file&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span> &lt;span style="color:#616e87;font-style:italic">// LINE A
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// These will ALWAYS print BEFORE the file read is complete.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// Well, that&amp;#39;s both misleading and useless.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`Done reading file. Contents are: &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>getContents&lt;span style="color:#eceff4">(&lt;/span>file&lt;span style="color:#eceff4">)&lt;/span>&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;=&amp;#34;&lt;/span>&lt;span style="color:#eceff4">.&lt;/span>repeat&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#b48ead">76&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://mogita.com/img/2.png" alt="2">&lt;/p>
&lt;p>同步代码最大的好处在于可读，便于理解。同步代码自上而下顺序执行，与我们读书的方式相同。第 n 行的代码一定比第 n+1 行代码先执行完。&lt;/p>
&lt;p>但同步代码的坏处在于执行慢。比如，用户点击按钮后，浏览器要在完成一个 2 秒的网络请求之后，再继续响应用户操作，这种体验是无法接受的。&lt;/p>
&lt;p>这个例子也正好说明为什么 JavaScript 是非阻塞型的。&lt;/p>
&lt;h4 id="异步式带来的挑战">
异步式带来的挑战
&lt;a class="heading-link" href="#%e5%bc%82%e6%ad%a5%e5%bc%8f%e5%b8%a6%e6%9d%a5%e7%9a%84%e6%8c%91%e6%88%98">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>异步代码带来了速度上的提升，但不再具有线性特征。即使上例中琐碎的代码段也是如此。请注意：&lt;/p>
&lt;ul>
&lt;li>无法知道 &lt;code>file&lt;/code> 什么时候可用，除非将控制权交给 &lt;code>readFile&lt;/code> 并让它在准备好的时刻通知我们&lt;/li>
&lt;li>我们的代码不再按照它的书写顺序来执行，理解起来会不那么直观&lt;/li>
&lt;/ul>
&lt;p>这几点问题将成为本文接下来讨论的中心。&lt;/p>
&lt;h2 id="回调和回退">
回调和回退
&lt;a class="heading-link" href="#%e5%9b%9e%e8%b0%83%e5%92%8c%e5%9b%9e%e9%80%80">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我们来精简一下异步 &lt;code>readFile&lt;/code> 代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;throwaway.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> file&lt;span style="color:#eceff4">,&lt;/span> useless&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>useless &lt;span style="color:#81a1c1">=&lt;/span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> callback &lt;span style="color:#eceff4">(&lt;/span>error&lt;span style="color:#eceff4">,&lt;/span> contents&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file &lt;span style="color:#81a1c1">=&lt;/span> contents&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`Got it. Contents are: &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>contents&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`. . . But useless is still &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>useless&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">.`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// Thanks to Rava for catching an error in this line.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`File is &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>useless&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">, but that&amp;#39;ll change soon.`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>由于 &lt;code>readFile&lt;/code> 是非阻塞的，它不得不立即返回，以便代码继续往下执行。而「立即返回」也就意味着它没有足够的时间来完成 I/O 操作，它除了返回 &lt;code>undefined&lt;/code> 之外也别无他法。但我们需要的是在 &lt;code>readFile&lt;/code> 方法真正地读完文件。&lt;/p>
&lt;p>问题来了，我们怎么知道读取已经完成？&lt;/p>
&lt;p>答案也很简单：无法预知。但是，&lt;code>readFile&lt;/code> 方法可以。上面的代码段中，我们向&lt;code>readFile&lt;/code> 方法传递了两个参数：文件名，以及一个函数，这个函数就叫&lt;strong>回调函数&lt;/strong>。在文件读取完成的时刻，它就会被执行。&lt;/p>
&lt;p>把这段代码换成白话就是：「&lt;code>readFile&lt;/code>，请查看一下 &lt;code>${__dirname}/${filename}&lt;/code>这个路径里面有什么内容，这个过程需要一点时间。你查看完之后，请调用 &lt;code>callback&lt;/code> 方法，传入 &lt;code>contents&lt;/code>，如果有错误请让我知道 &lt;code>error&lt;/code> 是什么。」&lt;/p>
&lt;p>其要点就是我们无法获知读取文件的完成时间，只有 readFile 方法自己知道。这就是为什么我们要给它传递一个回调函数，并且信任 &lt;code>readFile&lt;/code> 会对这个函数做正确的事情。&lt;/p>
&lt;p>这便是以往人们使用异步方法的基本方式：用参数调用它，并传递一个回调函数来处理结果。&lt;/p>
&lt;p>回调是一种异步解决方案，但它们并不完美。两个比较大的问题在于：&lt;/p>
&lt;ol>
&lt;li>控制反转&lt;/li>
&lt;li>复杂的错误处理方式&lt;/li>
&lt;/ol>
&lt;h4 id="控制反转">
控制反转
&lt;a class="heading-link" href="#%e6%8e%a7%e5%88%b6%e5%8f%8d%e8%bd%ac">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>首先是关于信任的问题。&lt;/p>
&lt;p>当我们向 &lt;code>readFile&lt;/code> 传递回调函数时，我们&lt;strong>相信&lt;/strong>它会执行这个回调。然而，事实上它却从没保证过它会执行这个回调。就算它确实会被调用，也没人保证传递给这个回调函数的参数将一定正确，或者顺序一定正确，又或者执行的次数也一定正确。&lt;/p>
&lt;p>现实当中，这倒不是什么致命的问题：我们已经写了 20 多年回调，从未让网络崩坏。在此基础之上，我们觉得，把控制权直接交给 Node 核心代码应该也是安全的。&lt;/p>
&lt;p>但是，把一个应用中最关键的部分交给第三方去管理，总感觉有点冒险，过去已经造成了无数个难以捕捉的 &lt;a href="https://en.wikipedia.org/wiki/Heisenbug" class="external-link" target="_blank" rel="noopener">Heisenbug&lt;/a>。&lt;/p>
&lt;h4 id="显式错误处理">
显式错误处理
&lt;a class="heading-link" href="#%e6%98%be%e5%bc%8f%e9%94%99%e8%af%af%e5%a4%84%e7%90%86">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>在同步式编程中，我们可以用 &lt;code>try&lt;/code>/&lt;code>catch&lt;/code>/&lt;code>finally&lt;/code> 来把握错误处理。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This example uses Node, and so won&amp;#39;t run in the browser.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;text.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Reading file . . . &amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> file&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">try&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// Wrong filename. D&amp;#39;oh!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> file &lt;span style="color:#81a1c1">=&lt;/span> fs&lt;span style="color:#eceff4">.&lt;/span>readFileSync&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;a&amp;#39;&lt;/span>&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`Got it. Contents are: &amp;#39;&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">catch&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`There was a/n &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>err&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">: file is &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;Catching errors, like a bo$$.&amp;#39;&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>而在异步式编程中，还想这么写的话，就走远了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This example uses Node, and so won&amp;#39;t run in the browser.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;throwaway.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Reading file . . . &amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">let&lt;/span> file&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">try&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// Wrong filename. D&amp;#39;oh!
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;a&amp;#39;&lt;/span>&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> contents&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file &lt;span style="color:#81a1c1">=&lt;/span> contents&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// This shouldn&amp;#39;t run if file is undefined
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`Got it. Contents are: &amp;#39;&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">catch&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// In this case, catch should run, but it never will.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#616e87;font-style:italic">// This is because readFile passes errors to the callback -- it does /not/
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#616e87;font-style:italic">// throw them.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`There was a/n &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>err&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">: file is &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这段代码根本不会以设想的方式执行。因为 &lt;code>try&lt;/code> 语句块包含 &lt;code>readFile&lt;/code> 方法，后者永远会成功地返回一个 &lt;code>undefined&lt;/code> 值。也就是说，&lt;code>try&lt;/code> 永远会成功完成，而不会发生任何意外。&lt;/p>
&lt;p>要捕获 &lt;code>readFile&lt;/code> 产生的错误的方式只有一种，就是给它传递一个回调函数，并在这个回调中获得错误信息，然后再来处理错误。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#34;use strict&amp;#34;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// This example uses Node, and so won&amp;#39;t run in the browser.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> filename &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;throwaway.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;Reading file . . . &amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>__dirname&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">/&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>filename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;a&amp;#39;&lt;/span>&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">`&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> contents&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span> &lt;span style="color:#616e87;font-style:italic">// catch
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`There was a/n &lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>err&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">.`&lt;/span> &lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">else&lt;/span> &lt;span style="color:#eceff4">{&lt;/span> &lt;span style="color:#616e87;font-style:italic">// try
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">(&lt;/span> &lt;span style="color:#a3be8c">`Got it. File contents are: &amp;#39;&lt;/span>&lt;span style="color:#a3be8c">${&lt;/span>file&lt;span style="color:#a3be8c">}&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;`&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个例子的代码还不算太糟糕，但是工程量足够大时，这些问题会后患无穷。&lt;/p>
&lt;p>Promise 则一下解决了这两个问题等一系列不足之处，通过「反控制反转」，和「同步化」异步代码，来实现我们更熟悉的错误处理。&lt;/p>
&lt;h2 id="promise-机制">
Promise 机制
&lt;a class="heading-link" href="#promise-%e6%9c%ba%e5%88%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>假设你从 O’Reilly 订购了全套「&lt;a href="https://github.com/getify/You-Dont-Know-JS/blob/master/README.md#you-dont-know-js-book-series" class="external-link" target="_blank" rel="noopener">You Don’t Know JS&lt;/a>」丛书。你把辛苦搬砖挣来的钞票给了他们，他们发给你一张收据，声明你将在下周一收到一套崭新的图书。在那之前，你都并未拥有这套图书。但你可以相信，一定会收到，因为他们保证过（promised）一定会寄送。&lt;/p>
&lt;p>基于这种 Promise，在图书送达之前，你就可以计划好每天要阅读多少本，计划借给朋友哪几本，甚至给老板请好一周的休假来奋力读书。在制定这些计划的时候，你其实都处于并未拥有这套图书的状态，你仅仅是知道你会拥有。&lt;/p>
&lt;p>当然，O’Reilly 可能过几天之后告诉你，由于七七八八的原因，订单无法完成了。到那时，你可以取消每日阅读计划，告诉你朋友他们不用等了（因为你自己都拿不到书），然后告诉老板，下周你会照常上班。&lt;/p>
&lt;p>&lt;strong>Promise&lt;/strong>，就像这张收据一样，是替一个还未就绪的值占位的对象，但稍后一定会就绪。换句话说，一个未来的值。你可以把 Promise 当成一个你正在等待的值，并在假设你已获得它的前提下编写代码。&lt;/p>
&lt;p>如果在事件过程中出现岔子，Promise 会在自身内部流程去处理中断的地方，并且让你用一个特殊的 &lt;code>catch&lt;/code> 关键词来处理错误。这和同步式的代码有点不一样，但至少比异步处理一堆错误要舒服。&lt;/p>
&lt;p>并且，由于 Promise 会在一个值准备就绪时将其传递给你，你就可以决定要如何使用这个值。这就解决了控制反转的问题：你自己来直接掌握应用的逻辑，无需把控制权转交给第三方。&lt;/p>
&lt;h4 id="promise-的生命周期状态states简介">
Promise 的生命周期：状态（States）简介
&lt;a class="heading-link" href="#promise-%e7%9a%84%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e7%8a%b6%e6%80%81states%e7%ae%80%e4%bb%8b">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>假设你用 Promise 进行了一次 API 调用。&lt;/p>
&lt;p>由于服务器不可能立即发回响应，Promise 是不可能立即获得最终值的，也不可能立即报告错误。这个状态下的 Promise 被称为 &lt;strong>Pending&lt;/strong>（等候中），好比你在等待订购的那套新书。&lt;/p>
&lt;p>一旦服务器发回了响应，就会产生两种可能：&lt;/p>
&lt;ol>
&lt;li>Promise 得到了它期待的值，这个状态被称为 &lt;strong>Fulfilled&lt;/strong>（已满足）。好比你收到了那套书。&lt;/li>
&lt;li>请求事件中出现了错误，这时 Promise 的状态被称为 &lt;strong>Rejected&lt;/strong>（被拒绝）。这就好比你收到通知，购书订单无法完成了。&lt;/li>
&lt;/ol>
&lt;p>这三个状态就是 Promise 的所有可能状态。一旦 Promise 变成了 Fulfilled 或 Rejected，则无法再转化为任何其他状态了。&lt;/p>
&lt;p>关于概念的阐释就说这么远，下面我们来看看究竟怎样应用这些东西。&lt;/p>
&lt;h2 id="promise-的基础方法">
Promise 的基础方法
&lt;a class="heading-link" href="#promise-%e7%9a%84%e5%9f%ba%e7%a1%80%e6%96%b9%e6%b3%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>引用 &lt;a href="https://promisesaplus.com/" class="external-link" target="_blank" rel="noopener">Promises/A+ 标准&lt;/a>的一段话：&lt;/p>
&lt;blockquote>
&lt;p>Promise 代表一个异步操作的最终结果。与 Promise 进行交互的主要方式，是使用它的 &lt;code>then&lt;/code> 方法。该方法会注册回调函数，接收 Promise 的最终值，或 Promise 无法被满足的原因。&lt;/p>
&lt;/blockquote>
&lt;p>接下来的部分，我们将对 Promise 的基本用法进行详细介绍：&lt;/p>
&lt;ol>
&lt;li>用构造器创建 Promise&lt;/li>
&lt;li>用 &lt;code>resolve&lt;/code> 处理成功事件&lt;/li>
&lt;li>用 &lt;code>rejected&lt;/code> 处理错误事件&lt;/li>
&lt;li>用 &lt;code>then&lt;/code> 和 &lt;code>catch&lt;/code> 来建立控制流程&lt;/li>
&lt;/ol>
&lt;p>在这个例子中，我们将使用 Promise 来搞定上边 &lt;code>fs.readFile&lt;/code> 的遗留代码。&lt;/p>
&lt;h2 id="创建-promise">
创建 Promise
&lt;a class="heading-link" href="#%e5%88%9b%e5%bb%ba-promise">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>最基本的 Promise 创建方法就是通过构造器直接创建。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a3be8c">&amp;#39;use strict&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> fs &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;fs&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> text &lt;span style="color:#81a1c1">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> &lt;span style="color:#81a1c1">Promise&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// Does nothing
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意，我们向 Promise 构造器传递了一个函数作为参数。在这里我们将告诉 Promise 怎样执行异步操作、得到期待值后需要做什么，以及出错了怎么办。细说起来就是：&lt;/p>
&lt;ol>
&lt;li>&lt;code>Resolve&lt;/code> 参数也是一个函数，里面包含当获得&lt;strong>期待值&lt;/strong>的时候我们想要做的事。当我们得到了期待值 &lt;code>val&lt;/code> 的时候，我们这样调用：&lt;code>resolve(val)&lt;/code>。&lt;/li>
&lt;li>&lt;code>Reject&lt;/code>参数也是一个函数，代表得到&lt;strong>错误&lt;/strong>的时候我们要怎么做。如果有错误&lt;code>err&lt;/code>，则要这样调用：&lt;code>reject(err)&lt;/code>。&lt;/li>
&lt;li>最后，我们传递给 Promise 构造器的函数将处理异步代码本身。如果它按期待返回了，我们就调用 &lt;code>resolve&lt;/code> 并传入获得的返回值。如果抛出错误，我们就调用&lt;code>reject&lt;/code> 并传入错误。&lt;/li>
&lt;/ol>
&lt;p>我们的示例需要把 &lt;code>fs.readFile&lt;/code> 封装成一个 Promise。那么，应该怎样写 &lt;code>resolve&lt;/code> 和&lt;code>reject&lt;/code> 呢？&lt;/p>
&lt;ol>
&lt;li>在成功的事件中，我们用 &lt;code>console.log&lt;/code> 打印文件内容&lt;/li>
&lt;li>在错误的事件中，我们做一样的事：用 &lt;code>console.log&lt;/code> 打印错误信息&lt;/li>
&lt;/ol>
&lt;p>也就是这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// constructor.js
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> resolve &lt;span style="color:#81a1c1">=&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reject &lt;span style="color:#81a1c1">=&lt;/span> console&lt;span style="color:#eceff4">.&lt;/span>log&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来，我们需要把传递给构造器的函数补充完整。记住，我们的任务是：&lt;/p>
&lt;ol>
&lt;li>读取一个文件&lt;/li>
&lt;li>如果成功，&lt;code>resolve&lt;/code>（解决）内容&lt;/li>
&lt;li>否则，&lt;code>reject&lt;/code>（拒绝）并给出错误&lt;/li>
&lt;/ol>
&lt;p>因此：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// constructor.js
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> text &lt;span style="color:#81a1c1">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> &lt;span style="color:#81a1c1">Promise&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// Normal fs.readFile call, but inside Promise constructor . . .
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;text.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> text&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// . . . Call reject if there&amp;#39;s an error . . .
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reject&lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// . . . And call resolve otherwise.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// We need toString() because fs.readFile returns a buffer.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span> resolve&lt;span style="color:#eceff4">(&lt;/span>text&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，我们就算完成工作了。这段代码会创建一个 Promise，它会严格执行我们想要的逻辑。但是，当你运行这段代码的时候，什么都不会打印出来。&lt;/p>
&lt;h2 id="她做出了承诺然后">
她做出了承诺，然后…
&lt;a class="heading-link" href="#%e5%a5%b9%e5%81%9a%e5%87%ba%e4%ba%86%e6%89%bf%e8%af%ba%e7%84%b6%e5%90%8e">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>原因是，我们写了 &lt;code>resolve&lt;/code> 和 &lt;code>reject&lt;/code> 方法，却并没有把它们传递给 Promise！怎么传？很简单，利用 Promise 的流程控制方法 &lt;code>then&lt;/code> 来完成。&lt;/p>
&lt;p>每个 Promise 对象都有一个叫做 &lt;code>then&lt;/code> 的方法，它仅接受两个参数：&lt;code>resolve&lt;/code> 和&lt;code>reject&lt;/code>，且要保证顺序一致。调用 Promise 的 &lt;code>then&lt;/code> 方法并传递这些函数，才能使构造器里的回调函数访问这两个函数。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// constructor.js
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> text &lt;span style="color:#81a1c1">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> &lt;span style="color:#81a1c1">Promise&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;text.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> text&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reject&lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> resolve&lt;span style="color:#eceff4">(&lt;/span>text&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>then&lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，我们的 Promise 会读取文件，在成功时调用 &lt;code>resolve&lt;/code> 方法。&lt;/p>
&lt;p>有个很关键的点值得注意，&lt;strong>then 方法永远会返回一个 Promise 对象&lt;/strong>。这就是说，你可以用多个 &lt;code>then&lt;/code> 来构造复杂的、同步风格的流程控制，以此控制一串异步操作。在下一篇文章中我们会深入这个话题，接下来我们先用 &lt;code>catch&lt;/code> 范例来领略一下链式调用的风采。&lt;/p>
&lt;h2 id="捕获错误的语法糖">
捕获错误的语法糖
&lt;a class="heading-link" href="#%e6%8d%95%e8%8e%b7%e9%94%99%e8%af%af%e7%9a%84%e8%af%ad%e6%b3%95%e7%b3%96">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我们向 &lt;code>then&lt;/code> 传递了两个方法：&lt;code>resolve&lt;/code>，用于成功事件；&lt;code>reject&lt;/code> 用于错误的事件。&lt;/p>
&lt;p>Promise 还暴露了一个类似于 &lt;code>then&lt;/code> 的方法，&lt;code>catch&lt;/code>。它只接受一个参数，那就是&lt;strong>reject&lt;/strong>。&lt;/p>
&lt;p>由于 &lt;code>then&lt;/code> 总是返回一个 Promise，在上面的例子中，我们可以只给 &lt;code>then&lt;/code> 传递一个&lt;code>resolve&lt;/code>，然后紧接着用 &lt;code>catch&lt;/code> 来处理 reject。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-javascript" data-lang="javascript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> text &lt;span style="color:#81a1c1">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> &lt;span style="color:#81a1c1">Promise&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;tex.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> text&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reject&lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> resolve&lt;span style="color:#eceff4">(&lt;/span>text&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>then&lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">catch&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>reject&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最后，必须要记住，&lt;code>catch(reject)&lt;/code> 只是 &lt;code>then(undefined, reject)&lt;/code> 的语法糖。即，我们也可以这样写：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">const&lt;/span> text &lt;span style="color:#81a1c1">=&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">new&lt;/span> &lt;span style="color:#81a1c1">Promise&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fs&lt;span style="color:#eceff4">.&lt;/span>readFile&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;tex.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">,&lt;/span> text&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> reject&lt;span style="color:#eceff4">(&lt;/span>err&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> resolve&lt;span style="color:#eceff4">(&lt;/span>text&lt;span style="color:#eceff4">.&lt;/span>toString&lt;span style="color:#eceff4">());&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">})&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>then&lt;span style="color:#eceff4">(&lt;/span>resolve&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>then&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">undefined&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> reject&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但显然还是使用了语法糖的代码更易读。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a class="heading-link" href="#%e6%80%bb%e7%bb%93">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Promise 是异步编程中不可或缺的一个工具。初学时，它们显得有些可怕，但这仅仅是由于你对它还不够熟悉：多用几次，你就能像使用 &lt;code>if&lt;/code>/&lt;code>else&lt;/code> 一样使用它了。&lt;/p>
&lt;p>下篇文章，我们来实践一下，把基于回调的传统代码用 Promise 进行重构。然后调研一个 Promise 库：&lt;a href="https://github.com/kriskowal/q" class="external-link" target="_blank" rel="noopener">Q&lt;/a>。&lt;/p>
&lt;p>你还可以阅读一下 Domenic Denicola 的「&lt;a href="https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md" class="external-link" target="_blank" rel="noopener">States and Fates（状态与命运）&lt;/a>」来全面掌握术语，以及先前我们订购的丛书中 Kyle Simpson 关于 &lt;a href="https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch3.md" class="external-link" target="_blank" rel="noopener">Promises&lt;/a> 的章节。&lt;/p>
&lt;p>照例，如果你有问题，请在下边进行评论，或者在 Twitter 上直接找我（&lt;a href="http://www.twitter.com/PelekeS" class="external-link" target="_blank" rel="noopener">@PelekeS&lt;/a>）。我保证（Promise）会回复！&lt;/p></description></item><item><title>解决 AS3 推流无法发送 MetaData 的问题</title><link>https://mogita.com/flash-as3-rtmp-metadata-sending-issue.html</link><pubDate>Mon, 18 Jul 2016 13:55:08 +0000</pubDate><guid>https://mogita.com/flash-as3-rtmp-metadata-sending-issue.html</guid><description>&lt;p>在学习 ActionScript 3 推流的过程中遇到了一个棘手的问题，用 &lt;code>ns.publish()&lt;/code> 方法可以正常推流，但无法发送任何 MetaData。&lt;/p>
&lt;p>在 RTMP/FMS 服务器配置正确的情况下，可以检查一下是否有如下代码：&lt;/p>
&lt;p>在初始化 NetConnection 实例后，添加一行 &lt;code>nc.objectEncoding = 0&lt;/code>。&lt;/p>
&lt;p>这样，就可以正常收发 onMetaData 事件了。&lt;/p></description></item><item><title>用 gulp 压缩 CSS 和 JavaScript</title><link>https://mogita.com/compress-css-and-js-with-gulp.html</link><pubDate>Tue, 03 May 2016 19:44:10 +0000</pubDate><guid>https://mogita.com/compress-css-and-js-with-gulp.html</guid><description>&lt;p>最近接触前端工作比较多，所以需要学习一些自动化的工作方式。今天的学习内容是 &lt;a href="http://gulpjs.com/" class="external-link" target="_blank" rel="noopener">gulp&lt;/a>。本文直接以 CSS 和 JavaScript 文件压缩作为入门范例，这两种自动化处理也是最常见的处理之一，能对前端页面字节精简，缩短用户的页面载入等待，也能减少数据流量消耗。&lt;/p>
&lt;h2 id="安装-gulp">
安装 gulp
&lt;a class="heading-link" href="#%e5%ae%89%e8%a3%85-gulp">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>在安装 gulp 之前，本机必须已安装 &lt;a href="https://nodejs.org/en/" class="external-link" target="_blank" rel="noopener">npm&lt;/a>，一个随 Node.js 安装包一起安装的包管理器。&lt;/p>
&lt;p>首先，全局安装 gulp。在命令行中执行：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install --global gulp-cli
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后进入工程根目录，在工程中安装 gulp，执行：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install --save-dev gulp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意：若工程未进行 npm 初始化，请在安装 gulp 之前执行 &lt;code>npm init&lt;/code>。&lt;/p>
&lt;p>接下来，创建 &lt;code>gulpfile.js&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;default&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic">// place code for your default task here
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最后，执行 gulp：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>gulp
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们会在终端看到「default」任务的执行结果。当然，目前是不会有任何动作，因为我们的「default」任务里没有代码。&lt;/p>
&lt;h2 id="sass-自动编译">
Sass 自动编译
&lt;a class="heading-link" href="#sass-%e8%87%aa%e5%8a%a8%e7%bc%96%e8%af%91">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>当保存好一个 .scss 文件后，通常我们需要用 sass 命令来将其编译成 .css 文件。&lt;/p>
&lt;p>通过 gulp，把这个处理放入自动流程就可以了。&lt;/p>
&lt;p>进入工程根目录，安装 &lt;a href="https://www.npmjs.com/package/gulp-sass" class="external-link" target="_blank" rel="noopener">gulp-sass&lt;/a> 包：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-sass --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意：如果需要安装 sass，请根据&lt;a href="http://sass-lang.com/install" class="external-link" target="_blank" rel="noopener">官方文档&lt;/a>进行安装。&lt;/p>
&lt;p>编辑 &lt;code>gulpfile.js&lt;/code> 文件，加入对 gulp-sass 的引用，并添加 styles 任务（任务名可以随意起）。完整代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-sass&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>sass&lt;span style="color:#eceff4">.&lt;/span>sync&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outputStyle&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;expanded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在工程根目录执行 &lt;code>gulp styles&lt;/code>，即可看到生成的普通 css 文件。&lt;/p>
&lt;h2 id="autoprefixer-支持浏览器特定的-css-前缀">
Autoprefixer 支持浏览器特定的 CSS 前缀
&lt;a class="heading-link" href="#autoprefixer-%e6%94%af%e6%8c%81%e6%b5%8f%e8%a7%88%e5%99%a8%e7%89%b9%e5%ae%9a%e7%9a%84-css-%e5%89%8d%e7%bc%80">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>或许我们看到过（或者每天都要写）这样的 CSS 代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">a&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1">-webkit-&lt;/span>&lt;span style="color:#81a1c1;font-weight:bold">border-radius&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#b48ead">5&lt;/span>&lt;span style="color:#81a1c1">px&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">border-radius&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#b48ead">5&lt;/span>&lt;span style="color:#81a1c1">px&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>可以看到，除了 &lt;code>border-radius&lt;/code> 之外，还有一行 &lt;code>-webkit-border-radius&lt;/code>，这个就是为 webkit 内核准备的 CSS 前缀。如果使用 PhpStorm 这样的 IDE，它或许会为我们自动补充所需的前缀。但只写不带前缀的语句，能够提高 CSS 代码的可读性，只需把添加前缀的工作交给 gulp 来执行即可。&lt;/p>
&lt;p>首先，还是安装 gulp-autoprefixer 依赖：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-autoprefixer --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在 &lt;code>gulpfile.js&lt;/code> 文件中引用它，并添加 pipe。完整代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-sass&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoprefixer &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-autoprefixer&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>sass&lt;span style="color:#eceff4">.&lt;/span>sync&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outputStyle&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;expanded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>autoprefixer&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;last 2 version&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后在工程根目录执行 &lt;code>gulp styles&lt;/code> 即可。&lt;/p>
&lt;h2 id="css-压缩">
CSS 压缩
&lt;a class="heading-link" href="#css-%e5%8e%8b%e7%bc%a9">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>前端页面优化的重要一环就是文件压缩（minify），以达到节省用户的流量，加速页面载入的目的。下面我们对工程的 CSS 文件进行字节精简，也就是压缩。&lt;/p>
&lt;p>第一步，仍然是安装依赖：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-minify-css --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同时，为了遵循压缩版文件在文件名中标明「.min」的惯例，我们需要一个文件改名包：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-rename --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来，仍然是编辑 &lt;code>gulpfile.js&lt;/code> 文件，完整代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-sass&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoprefixer &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-autoprefixer&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> minifycss &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-minify-css&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rename &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-rename&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>sass&lt;span style="color:#eceff4">.&lt;/span>sync&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outputStyle&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;expanded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>autoprefixer&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;last 2 version&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>rename&lt;span style="color:#eceff4">({&lt;/span>suffix&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.min&amp;#39;&lt;/span>&lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>minifycss&lt;span style="color:#eceff4">())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在工程根目录执行一下 &lt;code>gulp styles&lt;/code>，未压缩版和压缩版的 main.css 都已经转换完毕了。&lt;/p>
&lt;h2 id="javascript-压缩">
JavaScript 压缩
&lt;a class="heading-link" href="#javascript-%e5%8e%8b%e7%bc%a9">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>继续页面优化，我们需要把 JS 文件进行「合并（concatenate）」处理，并「丑化（uglify）」，形成一个几乎无法阅读但可以被浏览器执行的压缩版 JS 文件。&lt;/p>
&lt;p>先安装依赖：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-uglify --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>以及：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>npm install gulp-concat --save-dev
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后编辑 &lt;code>gulpfile.js&lt;/code> 文件，完整代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-sass&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoprefixer &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-autoprefixer&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> minifycss &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-minify-css&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rename &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-rename&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> concat &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-concat&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uglify &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-uglify&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>sass&lt;span style="color:#eceff4">.&lt;/span>sync&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outputStyle&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;expanded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>autoprefixer&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;last 2 version&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>rename&lt;span style="color:#eceff4">({&lt;/span>suffix&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.min&amp;#39;&lt;/span>&lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>minifycss&lt;span style="color:#eceff4">())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;scripts&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/scripts/*.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>concat&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;main.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/scripts/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>rename&lt;span style="color:#eceff4">({&lt;/span>suffix&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.min&amp;#39;&lt;/span>&lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>uglify&lt;span style="color:#eceff4">())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/scripts/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在工程根目录执行一下 &lt;code>gulp scripts&lt;/code>，JS 压缩就完成了。&lt;/p>
&lt;h2 id="将自动化进行到底">
将「自动化」进行到底
&lt;a class="heading-link" href="#%e5%b0%86%e8%87%aa%e5%8a%a8%e5%8c%96%e8%bf%9b%e8%a1%8c%e5%88%b0%e5%ba%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我们可以利用 gulp 的 &lt;code>watch&lt;/code> 方法，让 gulp 主动监控我们对文件所做的修改，并自动启动所需的处理任务。&lt;/p>
&lt;p>在 &lt;code>gulpfile.js&lt;/code> 中添加我们的两行 watch 语句，完整代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">var&lt;/span> gulp &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sass &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-sass&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> autoprefixer &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-autoprefixer&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> minifycss &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-minify-css&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rename &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-rename&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> concat &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-concat&amp;#39;&lt;/span>&lt;span style="color:#eceff4">),&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uglify &lt;span style="color:#81a1c1">=&lt;/span> require&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;gulp-uglify&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>sass&lt;span style="color:#eceff4">.&lt;/span>sync&lt;span style="color:#eceff4">({&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outputStyle&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;expanded&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>autoprefixer&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;last 2 version&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>rename&lt;span style="color:#eceff4">({&lt;/span>suffix&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.min&amp;#39;&lt;/span>&lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>minifycss&lt;span style="color:#eceff4">())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/styles/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;scripts&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">return&lt;/span> gulp&lt;span style="color:#eceff4">.&lt;/span>src&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/scripts/*.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>concat&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;main.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/scripts/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>rename&lt;span style="color:#eceff4">({&lt;/span>suffix&lt;span style="color:#81a1c1">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;.min&amp;#39;&lt;/span>&lt;span style="color:#eceff4">}))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>uglify&lt;span style="color:#eceff4">())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">.&lt;/span>pipe&lt;span style="color:#eceff4">(&lt;/span>gulp&lt;span style="color:#eceff4">.&lt;/span>dest&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;dest/scripts/&amp;#39;&lt;/span>&lt;span style="color:#eceff4">));&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>gulp&lt;span style="color:#eceff4">.&lt;/span>task&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;watch&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span>&lt;span style="color:#eceff4">()&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gulp&lt;span style="color:#eceff4">.&lt;/span>watch&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/scripts/*.js&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;scripts&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> gulp&lt;span style="color:#eceff4">.&lt;/span>watch&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;src/styles/*.scss&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;styles&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">});&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>进入工程根目录，执行 &lt;code>gulp watch&lt;/code>，我们会发现这次执行到「Finished…」之后并没有退出，而是一直挂在这一行。没错，gulp 已经在监视源文件目录了，这时候对&lt;code>src/scripts/&lt;/code> 和 &lt;code>src/styles/&lt;/code> 下的文件进行修改，gulp 就会自动执行处理。&lt;/p>
&lt;h2 id="结尾">
结尾
&lt;a class="heading-link" href="#%e7%bb%93%e5%b0%be">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>以上处理已经能够为我们的工程提供一定的前端页面字节精简了，在 JS、CSS 代码量巨大的工程中，优化效果尤为明显。&lt;/p>
&lt;p>参考：&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md" class="external-link" target="_blank" rel="noopener">https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://community.nitrous.io/tutorials/setting-up-gulp-with-livereload-sass-and-other-tasks" class="external-link" target="_blank" rel="noopener">https://community.nitrous.io/tutorials/setting-up-gulp-with-livereload-sass-and-other-tasks&lt;/a>&lt;/li>
&lt;li>&lt;a href="http://tagtree.tv/gulp" class="external-link" target="_blank" rel="noopener">http://tagtree.tv/gulp&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>多个域名访问同一个 CodeIgniter 的不同 Controllers</title><link>https://mogita.com/codeigniter-map-controllers-to-multiple-domains.html</link><pubDate>Mon, 18 Jan 2016 23:03:17 +0000</pubDate><guid>https://mogita.com/codeigniter-map-controllers-to-multiple-domains.html</guid><description>&lt;p>一套 CodeIgniter 部署，可以承载不同的业务：不同的 MVC 模块，用不同的域名访问。严格来讲，要让一个模块独立形成一套 MVC，需使用 wiredesignz 的 &lt;a href="https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc" class="external-link" target="_blank" rel="noopener">codeigniter-modular-extensions-hmvc&lt;/a> 扩展。后者则是 CI 自身的路由所提供的特性。&lt;/p>
&lt;p>关于 HMVC 的讲解之后有时间再写。本文只讲如何用不同域名访问不同的 Controllers。&lt;/p>
&lt;h2 id="配置-nginx">
配置 Nginx
&lt;a class="heading-link" href="#%e9%85%8d%e7%bd%ae-nginx">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我的服务器环境基于 Nginx，所以这里贴出我的代码（需修改自定义部分后才可复用）。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nginx" data-lang="nginx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">server&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">server_name&lt;/span> &lt;span style="color:#a3be8c">user.example.com&lt;/span> &lt;span style="color:#a3be8c">admin.example.com&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">root&lt;/span> &lt;span style="color:#a3be8c">/path/to/code/base&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">index&lt;/span> &lt;span style="color:#a3be8c">index.php&lt;/span> &lt;span style="color:#a3be8c">index.html&lt;/span> &lt;span style="color:#a3be8c">index.htm&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#a3be8c">/&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">try_files&lt;/span> $uri $uri/ &lt;span style="color:#a3be8c">/index.php?&lt;/span>$query_string&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#eceff4">~&lt;/span> &lt;span style="color:#ebcb8b">\.php($|/)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_pass&lt;/span> 127.0.0.1&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">9000&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_index&lt;/span> &lt;span style="color:#a3be8c">index.php&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_split_path_info&lt;/span> &lt;span style="color:#a3be8c">^(.+\.php)(.*)&lt;/span>$&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_param&lt;/span> &lt;span style="color:#a3be8c">PATH_INFO&lt;/span> $fastcgi_path_info&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_param&lt;/span> &lt;span style="color:#a3be8c">SCRIPT_FILENAME&lt;/span> $document_root$fastcgi_script_name&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">include&lt;/span> &lt;span style="color:#a3be8c">fastcgi_params&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#eceff4">~&lt;/span> &lt;span style="color:#ebcb8b">/\.ht&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">deny&lt;/span> &lt;span style="color:#a3be8c">all&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意把所有域名放在 &lt;code>server_name&lt;/code> 的同一行，中间用空格分开。&lt;/p>
&lt;p>我们照例使用 &lt;code>try_files&lt;/code> 去除 URL 中的 index.php。&lt;/p>
&lt;h2 id="部署-codeigniter-并配置-routesphp">
部署 CodeIgniter 并配置 routes.php
&lt;a class="heading-link" href="#%e9%83%a8%e7%bd%b2-codeigniter-%e5%b9%b6%e9%85%8d%e7%bd%ae-routesphp">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>部署过程就不细说了，若有问题直接甩评论上来吧。&lt;/p>
&lt;p>仅以演示为目的，我们进入 &lt;code>/application/controllers&lt;/code> 目录，把 CI 自带的&lt;code>Welcome.php&lt;/code> 代码修改成如下内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>defined&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;BASEPATH&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">OR&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">exit&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;No direct script access allowed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> &lt;span style="color:#8fbcbb">Welcome&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> CI_Controller &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">public&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#88c0d0">index&lt;/span>&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">echo&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;Hello user&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，复制 &lt;code>Welcome.php&lt;/code>，并命名为 &lt;code>Welcome_admin.php&lt;/code>，并对文件内容也进行相应的修改：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>defined&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;BASEPATH&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">OR&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">exit&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;No direct script access allowed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> &lt;span style="color:#8fbcbb">Welcome_admin&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> CI_Controller &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">public&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#88c0d0">index&lt;/span>&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">echo&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;Hello admin&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接着，让魔法在 &lt;code>routes.php&lt;/code> 文件里发生吧。打开 &lt;code>/application/config&lt;/code> 目录中的&lt;code>routes.php&lt;/code>。在已有代码的后面插入下列代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">if&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>$_SERVER&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;HTTP_HOST&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">==&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;user.example.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> $route&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;(:any)&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;welcome&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">elseif&lt;/span> &lt;span style="color:#eceff4">(&lt;/span>$_SERVER&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;HTTP_HOST&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">==&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;admin.example.com&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> $route&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;(:any)&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;welcome_admin&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>现在，分别访问 &lt;code>http://user.example.com&lt;/code> 和 &lt;code>http://admin.example.com&lt;/code>，你将看到两个不同的控制器所生成的页面。即达到了多域名访问不同控制器的目的。&lt;/p>
&lt;p>再配合 HMVC 扩展，我们可以将控制器分成一个个独立的模块，与 CI 的基础代码解耦，让开发时条理更加清晰。&lt;/p></description></item><item><title>CodeIgniter 的多版本 RESTful 接口</title><link>https://mogita.com/build-versioned-restful-service-with-codeigniter.html</link><pubDate>Sun, 17 Jan 2016 19:55:34 +0000</pubDate><guid>https://mogita.com/build-versioned-restful-service-with-codeigniter.html</guid><description>&lt;p>&lt;a href="https://www.codeigniter.com/" class="external-link" target="_blank" rel="noopener">CodeIgniter&lt;/a> 是基于 PHP 的框架程序，采用 MVC 式设计。配合 &lt;a href="https://github.com/chriskacerguis/codeigniter-restserver" class="external-link" target="_blank" rel="noopener">codeigniter-restserver&lt;/a> 能够快速搭建 RESTful 服务。&lt;/p>
&lt;p>本文介绍的是在 Nginx 服务器上搭建多版本 RESTful 服务的例子。即创建 &lt;code>http://api.example.com/v1/user&lt;/code> 这种在 URL 中包含版本号的接口。&lt;/p>
&lt;h2 id="nginx-配置">
Nginx 配置
&lt;a class="heading-link" href="#nginx-%e9%85%8d%e7%bd%ae">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>首先配置 CodeIgniter 的运行环境，在 Nginx 配置中添加如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nginx" data-lang="nginx">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">server&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#616e87;font-style:italic"># api server definition
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">server_name&lt;/span> &lt;span style="color:#a3be8c">api.example.com&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">root&lt;/span> &lt;span style="color:#a3be8c">/your/codeigniter/folder&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">index&lt;/span> &lt;span style="color:#a3be8c">index.php&lt;/span> &lt;span style="color:#a3be8c">index.html&lt;/span> &lt;span style="color:#a3be8c">index.htm&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#a3be8c">/&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">try_files&lt;/span> $uri $uri/ &lt;span style="color:#a3be8c">/index.php?&lt;/span>$query_string&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#eceff4">~&lt;/span> &lt;span style="color:#ebcb8b">\.php($|/)&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_pass&lt;/span> 127.0.0.1&lt;span style="color:#eceff4">:&lt;/span>&lt;span style="color:#b48ead">9000&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_index&lt;/span> &lt;span style="color:#a3be8c">index.php&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_split_path_info&lt;/span> &lt;span style="color:#a3be8c">^(.+\.php)(.*)&lt;/span>$&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_param&lt;/span> &lt;span style="color:#a3be8c">PATH_INFO&lt;/span> $fastcgi_path_info&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">fastcgi_param&lt;/span> &lt;span style="color:#a3be8c">SCRIPT_FILENAME&lt;/span> $document_root$fastcgi_script_name&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">include&lt;/span> &lt;span style="color:#a3be8c">fastcgi_params&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">location&lt;/span> &lt;span style="color:#eceff4">~&lt;/span> &lt;span style="color:#ebcb8b">/\.ht&lt;/span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">deny&lt;/span> &lt;span style="color:#a3be8c">all&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中使用 &lt;code>try_files&lt;/code> 指令来重写 URL，去掉 CI 默认 URL 中的 &lt;code>/index.php&lt;/code> 部分。&lt;/p>
&lt;p>部署好 CI 的代码之后，访问域名，就能看到 Welcome 画面了。&lt;/p>
&lt;h2 id="配置-codeigniter">
配置 CodeIgniter
&lt;a class="heading-link" href="#%e9%85%8d%e7%bd%ae-codeigniter">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>下面对 CI 进行以下简单的设置，配合去除 &lt;code>/index.php&lt;/code> 美化 URL 的工作。&lt;/p>
&lt;p>找到 &lt;code>/application/config/config.php&lt;/code> 文件，将 38 行改成如下内容（行号可能因为具体的 CI 版本而不同，我的版本是 3.0.4）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>$config&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;index_page&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后找到同文件中的 55 行，确保内容与下面的相同：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>$config&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;uri_protocol&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;REQUEST_URI&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="引入-codeigniter-rest-server">
引入 CodeIgniter Rest Server
&lt;a class="heading-link" href="#%e5%bc%95%e5%85%a5-codeigniter-rest-server">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>下载 &lt;a href="https://github.com/chriskacerguis/codeigniter-restserver" class="external-link" target="_blank" rel="noopener">codeigniter-restserver&lt;/a> 后，将压缩包里的&lt;code>application/libraries/Format.php&lt;/code>、&lt;code>application/libraries/REST_Controller.php&lt;/code>和 &lt;code>application/config/rest.php&lt;/code> 三个文件分别放进你自己的 CI 部署中的对应文件夹。&lt;/p>
&lt;p>更多 CodeIgniter Rest Server 的使用说明请参考其 readme.md 文件。&lt;/p>
&lt;h2 id="创建-api-方法和转发规则">
创建 API 方法和转发规则
&lt;a class="heading-link" href="#%e5%88%9b%e5%bb%ba-api-%e6%96%b9%e6%b3%95%e5%92%8c%e8%bd%ac%e5%8f%91%e8%a7%84%e5%88%99">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>我们将通过在 &lt;code>controllers&lt;/code> 目录下创建子目录的方式来实现多版本 API。在本文撰写之前，曾有 &lt;a href="https://github.com/chriskacerguis/codeigniter-restserver/issues/272#issuecomment-33138531" class="external-link" target="_blank" rel="noopener">issue&lt;/a> 表示 CI 只支持在 &lt;code>controllers&lt;/code> 中创建一层子目录，无法使用多层。而本人实测后，目前版本的 CI 已经支持多层子目录了。下面是创建方法。&lt;/p>
&lt;p>在 &lt;code>controllers&lt;/code> 目录下创建文件夹，命名为 &lt;code>api&lt;/code>。然后在 &lt;code>/controllers/api&lt;/code> 下再创建一个文件夹 &lt;code>v1&lt;/code>。在里面创建一个 &lt;code>Example.php&lt;/code> 文件，输入如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>defined&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;BASEPATH&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">OR&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">exit&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;No direct script access allowed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">require_once&lt;/span> APPPATH &lt;span style="color:#81a1c1">.&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;/libraries/REST_Controller.php&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> &lt;span style="color:#8fbcbb">Example&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> REST_Controller
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">public&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#88c0d0">new_get&lt;/span>&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> $this&lt;span style="color:#81a1c1">-&amp;gt;&lt;/span>&lt;span style="color:#8fbcbb">response&lt;/span>&lt;span style="color:#eceff4">([&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a3be8c">&amp;#39;error&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">=&amp;gt;&lt;/span> &lt;span style="color:#b48ead">0&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a3be8c">&amp;#39;data&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">=&amp;gt;&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;user data from v1&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">],&lt;/span> REST_Controller&lt;span style="color:#81a1c1">::&lt;/span>&lt;span style="color:#8fbcbb">HTTP_OK&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，再回到 &lt;code>/controllers/api&lt;/code> 目录，创建文件夹 &lt;code>v2&lt;/code>，在其中创建 &lt;code>Example.php&lt;/code>文件，输入如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>defined&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;BASEPATH&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">OR&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">exit&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;No direct script access allowed&amp;#39;&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">require_once&lt;/span> APPPATH &lt;span style="color:#81a1c1">.&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;/libraries/REST_Controller.php&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">class&lt;/span> &lt;span style="color:#8fbcbb">Example&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">extends&lt;/span> REST_Controller
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#81a1c1;font-weight:bold">public&lt;/span> &lt;span style="color:#81a1c1;font-weight:bold">function&lt;/span> &lt;span style="color:#88c0d0">new_get&lt;/span>&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">{&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> $this&lt;span style="color:#81a1c1">-&amp;gt;&lt;/span>&lt;span style="color:#8fbcbb">response&lt;/span>&lt;span style="color:#eceff4">([&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a3be8c">&amp;#39;error&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">=&amp;gt;&lt;/span> &lt;span style="color:#b48ead">0&lt;/span>&lt;span style="color:#eceff4">,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a3be8c">&amp;#39;data&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">=&amp;gt;&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;user data from v2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">],&lt;/span> REST_Controller&lt;span style="color:#81a1c1">::&lt;/span>&lt;span style="color:#8fbcbb">HTTP_OK&lt;/span>&lt;span style="color:#eceff4">);&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#eceff4">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其实两个 &lt;code>Example.php&lt;/code> 文件的内容大部分相同，唯一的区别是返回的字符串标识出它们来自哪个版本的 API，方便我们测试。&lt;/p>
&lt;p>接下来，就是定义 &lt;code>routes.php&lt;/code> 中的规则了。打开 &lt;code>/config/routes.php&lt;/code> 文件，在最下边添加如下代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>$route&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;v(:num)/(:any)/(:any)&amp;#39;&lt;/span>&lt;span style="color:#eceff4">]&lt;/span> &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;api/v$1/$2/$3&amp;#39;&lt;/span>&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这就是版本切换魔法的所在，这一行代码表示把任何形如 &lt;code>http://api.example.com/v1/any_request&lt;/code> 的请求重定向到&lt;code>/controllers/api/v1/any_request&lt;/code> 这个类中。其中，&lt;code>v1&lt;/code> 的数字部分可以替换成任何数字，只要存在该数字对应的 API 版本即可访问。详细点说，等号左边的 &lt;code>(:num)&lt;/code> 和两个 &lt;code>(:any)&lt;/code> 分别对应右边的 $1、$2 和 $3，代表 URL 中的可变部分：$1 为 API 版本号，$2 为类名，$3 为方法名。也就是说，右边的层级实际上反映出文件系统中的目录和类下方法的层级。&lt;/p>
&lt;p>现在，可以直接在浏览器中访问 &lt;code>http://api.example.com/v1/example/new&lt;/code>，你会看到一串 json 内容，&lt;code>data&lt;/code> 部分为「user data from v1」。接着，将 URL 中的 v1 换成 v2，&lt;code>data&lt;/code> 部分就会显示出「user data from v2」。&lt;/p>
&lt;p>这样，对 API 用户而言，切换版本号不会影响到已有的大部分代码。当然，前提是他们没有把版本号写死在自己的代码里。&lt;/p>
&lt;h2 id="总结">
总结
&lt;a class="heading-link" href="#%e6%80%bb%e7%bb%93">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>用 CodeIgniter 作为 API 后端的框架是个非常好的选择，官方文档和社区贡献内容都较丰富。除此之外，你也可以考虑使用 Slim 或者 Phalcon 等更加轻便的框架。&lt;/p></description></item><item><title>Laravel 5 Homestead 日常工作流程</title><link>https://mogita.com/laravel-homestead-daily-workflow.html</link><pubDate>Mon, 23 Nov 2015 10:51:15 +0000</pubDate><guid>https://mogita.com/laravel-homestead-daily-workflow.html</guid><description>&lt;blockquote>
&lt;p>本文是教程的第三步，并假设你已安装好全部所需软件。&lt;/p>
&lt;/blockquote>
&lt;p>本章将探索两种 composer 工具：homestead 和 laravel。接下来讲解一种典型的日常工作流程，通过 6 个步骤，来建立一个新的 Laravel 5.1 工程。&lt;/p>
&lt;h2 id="目录">
目录
&lt;a class="heading-link" href="#%e7%9b%ae%e5%bd%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="#56-homestead-tools" >Homestead 工具&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-homestead-overview" >常用 Homestead 命令概览&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-homestead-yaml" >了解 Homestead.yaml&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-homestead-software" >向 Homestead 虚拟机添加软件&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-daily-workflow" >日常工作流程&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-6-steps" >六个步骤开启一个全新 Laravel 5.1 工程&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#56-step-1" >第一步 – 创建应用骨架&lt;/a>&lt;/li>
&lt;li>&lt;a href="56-step-2" >第二步 – 配置 Web 服务器&lt;/a>&lt;/li>
&lt;li>&lt;a href="56-step-3" >第三步 – 添加主机记录到 Hosts 文件&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-step-4" >第四步 – NPM 本地安装&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-step-5" >第五步 – 创建应用的数据库&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-step-6" >第六步 – 在浏览器中测试&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#56-homestead-tips" >其他 Homestead 小贴士&lt;/a>&lt;/li>
&lt;li>&lt;a href="#56-recap" >本文回顾&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="homestead-工具">
Homestead 工具
&lt;a class="heading-link" href="#homestead-%e5%b7%a5%e5%85%b7">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>什么是控制台&lt;/strong>&lt;/p>
&lt;p>每当你需要在控制台里进行一些操作时，你需要搞清楚是哪种控制台。&lt;/p>
&lt;p>&lt;strong>Homestead 控制台&lt;/strong>是指通过 SSH 连接到 Homestead 虚拟机。在 Windows 系统中，可以是通过 PuTTY 建立的连接（详情请阅读在 Windows 系统建立虚拟机的章节）。在其他操作系统中，在终端执行&lt;code>homestead ssh&lt;/code> 命令就可以进行连接了。在本文中，凡是跟在 &lt;code>$&lt;/code> 后边的命令，都是在 Homestead 终端执行的。&lt;/p>
&lt;p>&lt;strong>OS 控制台&lt;/strong>是指 Windows 的命令行提示符或者其他你正在使用的终端软件。（本文中 &lt;code>%&lt;/code> 符号后边的命令都是在 OS 控制台中执行的）&lt;/p>
&lt;/blockquote>
&lt;p>在宿主机的控制台中，我们输入 homestead 命令（不带任何参数），就可以轻松查看所有可用的 homestead 参数和命令。&lt;/p>
&lt;h2 id="homestead-命令">
Homestead 命令
&lt;a class="heading-link" href="#homestead-%e5%91%bd%e4%bb%a4">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>% homestead
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Laravel Homestead version 2.0.9
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Usage:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[options] command [arguments]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Options:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--help -h Display this help message.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--quiet -q Do not output any message.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--version -V Display this application version.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--ansi Force ANSI output.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--no-ansi Disable ANSI output.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--no-interaction -n Do not ask any interactive question.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Available commands:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>destroy Destroy the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>edit Edit the Homestead.yaml file
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>halt Halt the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>help Displays help for a command
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>init Create a stub Homestead.yaml file
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>list Lists commands
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>provision Re-provisions the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>resume Resume the suspended Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>run Run commands through the Homestead machine via SSH
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh Login to the Homestead machine via SSH
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>status Get the status of the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>suspend Suspend the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>up Start the Homestead machine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>update Update the Homestead machine image
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们每天接触最多的一个命令是 &lt;code>homestead up&lt;/code>，用来启动 Homestead 虚拟机。&lt;/p>
&lt;h2 id="常用-homestead-命令概览">
常用 Homestead 命令概览
&lt;a class="heading-link" href="#%e5%b8%b8%e7%94%a8-homestead-%e5%91%bd%e4%bb%a4%e6%a6%82%e8%a7%88">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>这里是一系列常用的 Homestead 命令。&lt;/p>
&lt;ul>
&lt;li>&lt;code>homestead up&lt;/code> 启动 Homestead 虚拟机，或者说「点亮」虚拟机的电源。如果你使用了 provision 选项（&lt;code>homestead up --provision&lt;/code>），则 provision 配置将对所有新增加的站点都生效。&lt;/li>
&lt;li>&lt;code>homestead halt&lt;/code> 停止 Homestead 虚拟机，或者说「关闭」虚拟机的电源。&lt;/li>
&lt;li>&lt;code>homestead suspend&lt;/code> 暂停 Homestead 虚拟机，可以理解为休眠。&lt;/li>
&lt;li>&lt;code>homestead resume&lt;/code> 将暂停的 Homestead 虚拟机恢复运行。&lt;/li>
&lt;li>&lt;code>homestead edit&lt;/code> 编辑 Homestead.yaml 文件。这个命令将使用当前与 YAML 文件格式关联的编辑器软件。&lt;/li>
&lt;li>&lt;code>homestead status&lt;/code> 查看 Homestead 虚拟机的当前状态&lt;/li>
&lt;/ul>
&lt;h2 id="了解-homesteadyaml">
了解 Homestead.yaml
&lt;a class="heading-link" href="#%e4%ba%86%e8%a7%a3-homesteadyaml">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>Laravel Homestead 的配置都在 Homestead.yaml 文件中。该文件位于你的宿主系统 home 目录中的 &lt;code>.homestead&lt;/code> 目录下。&lt;/p>
&lt;p>查看该文件的内容，大致如下。&lt;/p>
&lt;h4 id="homesteadyaml-的内容">
Homestead.yaml 的内容
&lt;a class="heading-link" href="#homesteadyaml-%e7%9a%84%e5%86%85%e5%ae%b9">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8fbcbb">---&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">ip&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;192.168.10.10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">memory&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#b48ead">2048&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">cpus&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> &lt;span style="color:#b48ead">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">authorize&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> ~/.ssh/id_rsa.pub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">keys&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- ~/.ssh/id_rsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">folders&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#81a1c1">map&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> ~/Code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">to&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> /home/vagrant/Code
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">sites&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#81a1c1">map&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> homestead.app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">to&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> /home/vagrant/Code/Laravel/public
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">databases&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- homestead
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">variables&lt;/span>&lt;span style="color:#eceff4">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- &lt;span style="color:#81a1c1">key&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> APP_ENV
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1">value&lt;/span>&lt;span style="color:#eceff4">:&lt;/span> local
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>下面逐一解释各个配置部分：&lt;/p>
&lt;ul>
&lt;li>&lt;code>ip&lt;/code> 访问虚拟机所用的内网 IP。&lt;/li>
&lt;li>&lt;code>memory&lt;/code> 虚拟机的可用内存。&lt;/li>
&lt;li>&lt;code>cpus&lt;/code> 虚拟机的可用 CPU 数目。&lt;/li>
&lt;li>&lt;code>authorize&lt;/code> 此项应该指向你的 SSH 公钥。&lt;/li>
&lt;li>&lt;code>keys&lt;/code> 你的 SSH 私钥。&lt;/li>
&lt;li>&lt;code>folders&lt;/code> 共享文件夹，即宿主机中的文件夹和对应在虚拟机中的文件夹。在 Windows 系统中，&lt;code>~/Code&lt;/code> 目录大致上等同于 &lt;code>C:\Users\YOU\Code&lt;/code> 文件夹。在 OS X 中为&lt;code>/Users/YOU/Code&lt;/code>。在 Linux 中通常是 &lt;code>/home/YOU/Code&lt;/code>。当你在宿主机的这个文件夹里进行文件操作时，虚拟机中的对应目录中也会立即发生改变。&lt;/li>
&lt;li>&lt;code>sites&lt;/code> 站点列表（各个域名指向的路径），每当 Homestead 虚拟机启动时将生效。&lt;/li>
&lt;li>&lt;code>databases&lt;/code> Homestead 应该能自动创建的数据库列表。&lt;/li>
&lt;li>&lt;code>variables&lt;/code> 将在 Homestead 环境中生效的变量。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>配置小技巧&lt;/strong>&lt;/p>
&lt;p>我一般只对一处进行修改，将数据库下边的 homestead 改名为 xhomestead。这样做的目的在于，如果我忘记为新应用创建数据库，就会报错。否则，会使用默认的 homestead 数据库，我却无法察觉。&lt;/p>
&lt;/blockquote>
&lt;p>那么现在，除了 &lt;strong>database&lt;/strong> 之外，我们不修改任何配置。&lt;/p>
&lt;h4 id="homestead-虚拟机详细配置">
Homestead 虚拟机详细配置
&lt;a class="heading-link" href="#homestead-%e8%99%9a%e6%8b%9f%e6%9c%ba%e8%af%a6%e7%bb%86%e9%85%8d%e7%bd%ae">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>字段&lt;/strong>&lt;/th>
&lt;th>&lt;strong>值&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Hostname&lt;/td>
&lt;td>homestead&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IP Address&lt;/td>
&lt;td>192.168.10.10&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Username&lt;/td>
&lt;td>vagrant&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>SU Password&lt;/td>
&lt;td>vagrant&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Database Host&lt;/td>
&lt;td>127.0.0.1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Database Port&lt;/td>
&lt;td>33060&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Database Username&lt;/td>
&lt;td>homestead&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Database Password&lt;/td>
&lt;td>secret&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="向-homestead-虚拟机添加软件">
向 Homestead 虚拟机添加软件
&lt;a class="heading-link" href="#%e5%90%91-homestead-%e8%99%9a%e6%8b%9f%e6%9c%ba%e6%b7%bb%e5%8a%a0%e8%bd%af%e4%bb%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>若你需要在 Homestead 虚拟机中安装新的软件，可以使用 Ubuntu 的 &lt;code>apt-get&lt;/code> 命令。&lt;/p>
&lt;p>这是非常简便的两个步骤：&lt;/p>
&lt;ul>
&lt;li>升级 Ubuntu&lt;/li>
&lt;li>用 apt-get 安装&lt;/li>
&lt;/ul>
&lt;p>下边举例说明，当我们需要 unzip 这个软件时，应该怎样进行安装。&lt;/p>
&lt;h4 id="升级-ubuntu">
升级 Ubuntu
&lt;a class="heading-link" href="#%e5%8d%87%e7%ba%a7-ubuntu">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># 将 Ubuntu 软件升级到最新&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vagrant@homestead:~$ sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vagrant@homestead:~$ sudo apt-get upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>选择「Y」来继续安装。如果安装过程中出现什么提示，尽量选择已有的或默认配置。&lt;/p>
&lt;p>升级完 Homestead 虚拟机里的 Ubuntu 系统之后，就可以安装 unzip 了。&lt;/p>
&lt;h4 id="通过-apt-get-安装-unzip">
通过 apt-get 安装 unzip
&lt;a class="heading-link" href="#%e9%80%9a%e8%bf%87-apt-get-%e5%ae%89%e8%a3%85-unzip">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># 在 Homestead 虚拟机中安装 unzip&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vagrant@homestead:~$ sudo apt-get install unzip
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="日常工作流程">
日常工作流程
&lt;a class="heading-link" href="#%e6%97%a5%e5%b8%b8%e5%b7%a5%e4%bd%9c%e6%b5%81%e7%a8%8b">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>通常，使用 homestead 处理日常工作的流程主要分成三个步骤：&lt;/p>
&lt;p>&lt;strong>第一步：&lt;/strong>&lt;code>homestead up&lt;/code> 启动 Homestead 虚拟机开始一天的工作。
&lt;strong>第二步：&lt;/strong>&lt;code>homestead ssh&lt;/code>、&lt;code>PuTTY&lt;/code> 通过 SSH 连接到 Homestead 虚拟机，直接访问文件，并执行 artisan 命令。
**第三步：**编写优美的代码。在宿主系统中，打开你最爱的编辑器，开始写代码。
&lt;strong>可选的第四步：&lt;/strong>&lt;code>homestead halt&lt;/code> 一天的工作结束后，你可以选择关闭 Homestead 虚拟机的电源。&lt;/p>
&lt;h2 id="六个步骤开启一个全新-laravel-51-工程">
六个步骤开启一个全新 Laravel 5.1 工程
&lt;a class="heading-link" href="#%e5%85%ad%e4%b8%aa%e6%ad%a5%e9%aa%a4%e5%bc%80%e5%90%af%e4%b8%80%e4%b8%aa%e5%85%a8%e6%96%b0-laravel-51-%e5%b7%a5%e7%a8%8b">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>每次建立新的 Laravel 5.1 应用时，你都可以跟随下边的六大步骤来进行。&lt;/p>
&lt;p>比方说，我们要创建一个名叫 &lt;code>test.app&lt;/code> 的工程，项目文件夹的名字就叫 &lt;code>test&lt;/code>。&lt;/p>
&lt;h4 id="第一步-创建应用骨架">
第一步 创建应用骨架
&lt;a class="heading-link" href="#%e7%ac%ac%e4%b8%80%e6%ad%a5-%e5%88%9b%e5%bb%ba%e5%ba%94%e7%94%a8%e9%aa%a8%e6%9e%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>通过 Laravel Installer（前置章节中安装好的 &lt;code>laravel&lt;/code> 命令），可以迅速创建一个工程骨架。&lt;/p>
&lt;p>创建一个新的应用骨架&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ laravel new &lt;span style="color:#81a1c1">test&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Crafting application...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Generating optimized class loader
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Compiling common classes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Application key &lt;span style="color:#81a1c1">[&lt;/span>rzUhyDksVxzTXFjzFYiOWToqpunI2m6X&lt;span style="color:#81a1c1">]&lt;/span> &lt;span style="color:#81a1c1">set&lt;/span> successfully.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Application ready! Build something amazing.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="第二步-配置-web-服务器">
第二步 配置 Web 服务器
&lt;a class="heading-link" href="#%e7%ac%ac%e4%ba%8c%e6%ad%a5-%e9%85%8d%e7%bd%ae-web-%e6%9c%8d%e5%8a%a1%e5%99%a8">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>安装好应用骨架后，就可以在 homestead 环境中启动 Nginx 服务器，来为应用的&lt;code>public&lt;/code> 目录建立网页服务了。&lt;/p>
&lt;p>Homestead 环境中的 &lt;code>serve&lt;/code> 命令可以轻松完成这一任务。&lt;/p>
&lt;p>在 Homestead 中启动一个新的虚拟主机&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ serve test.app ~/Code/test/public
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dos2unix: converting file /vagrant/scripts/serve.sh to Unix format ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>* Restarting nginx nginx                                    &lt;span style="color:#81a1c1">[&lt;/span> OK &lt;span style="color:#81a1c1">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>php5-fpm stop/waiting
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>php5-fpm start/running, process &lt;span style="color:#b48ead">2169&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这条命令会在 &lt;code>/etc/nginx/sites-available&lt;/code> 目录下，为 &lt;strong>test.app&lt;/strong> 生成一个新的配置文件，并在 &lt;code>/etc/nginx/sites-enabled&lt;/code> 目录下生成一个指向该配置文件的软连接。&lt;/p>
&lt;p>即使虚拟机重启，该配置也会依然存在。&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>为什么不直接编辑 Homestead.yaml&lt;/strong>&lt;/p>
&lt;p>你问得对。另外一种建立 test.app 服务器的方式是 &lt;code>homestead edit&lt;/code> 命令，并在 &lt;em>sites:&lt;/em> 下边输入新的站点信息。但 serve 命令更简单，并且无需重新配置并启动 Homestead 虚拟机。&lt;/p>
&lt;p>然而，有时你可能需要经常对某个应用进行配置，你完全可以去编辑 Homestead.yaml 文件。&lt;/p>
&lt;/blockquote>
&lt;h4 id="第三步添加主机记录到-hosts-文件">
第三步 添加主机记录到 Hosts 文件
&lt;a class="heading-link" href="#%e7%ac%ac%e4%b8%89%e6%ad%a5%e6%b7%bb%e5%8a%a0%e4%b8%bb%e6%9c%ba%e8%ae%b0%e5%bd%95%e5%88%b0-hosts-%e6%96%87%e4%bb%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>截至目前，test.app 的域名还无法解析（没有任何 DNS 记录），必须向宿主系统的 hosts 文件添加一条记录。编辑 Linux 或 OS X 中的 &lt;code>/etc/hosts&lt;/code> 文件。Windows 系统中，该文件位于 &lt;code>C:\Windows\System32\drivers\etc\hosts&lt;/code>。将 &lt;code>test.app&lt;/code> 这个域名指向 &lt;code>Homestead.yaml&lt;/code> 文件中指定的 IP 地址即可。&lt;/p>
&lt;p>例如将下一行加入文件中。&lt;/p>
&lt;p>&lt;strong>Test.app 的 Host 记录&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>192.168.10.10  test.app
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>&lt;strong>Windows 系统需要管理员权限来编辑 hosts 文件&lt;/strong>&lt;/p>
&lt;p>在 Windows 中，你必须以管理员身份运行编辑器软件（例如写字板或 Sublime Text 等）。在 Linux 或 OS X 中则可以使用 &lt;code>sudo&lt;/code> 命令。&lt;/p>
&lt;/blockquote>
&lt;p>在 Linux 或 OS X 中编辑 hosts 文件&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo nano /etc/hosts
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>// or
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo vi /etc/hosts
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="第四步npm-本地安装">
第四步 NPM 本地安装
&lt;a class="heading-link" href="#%e7%ac%ac%e5%9b%9b%e6%ad%a5npm-%e6%9c%ac%e5%9c%b0%e5%ae%89%e8%a3%85">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>为了稍后可以使用 &lt;code>gulp&lt;/code>，我们需要将所需的 npm 模块都在本地安装好。&lt;/p>
&lt;p>&lt;strong>如果你用不到 gulp，那么就跳过这一步吧。&lt;/strong>&lt;/p>
&lt;p>在宿主系统的控制台中进入工程所在的目录，并执行以下命令。&lt;/p>
&lt;p>NPM 本地安装&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>$ cd Code/test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>~/Code/test% npm install
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>npm WARN package.json @ No repository field.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>v8flags@1.0.5 install /Users/chuck/Code/test/node_modules/gulp/\
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>node_modules/v8flags
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;gt; node fetch.js
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>flags for v8 3.14.5.9 cached.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[snip]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，gulp 所需的所有依赖都会安装到工程目录中的 &lt;code>node_modules&lt;/code> 文件夹了。&lt;/p>
&lt;h4 id="第五步创建应用的数据库">
第五步 创建应用的数据库
&lt;a class="heading-link" href="#%e7%ac%ac%e4%ba%94%e6%ad%a5%e5%88%9b%e5%bb%ba%e5%ba%94%e7%94%a8%e7%9a%84%e6%95%b0%e6%8d%ae%e5%ba%93">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>如果应用需要数据库，进入 Homestead 虚拟机的 &lt;strong>mysql&lt;/strong> 控制台就能轻松创建数据库了。&lt;/p>
&lt;p>在 Homestead 虚拟机中创建数据库&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ mysql --user&lt;span style="color:#81a1c1">=&lt;/span>homestead --password&lt;span style="color:#81a1c1">=&lt;/span>secret
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; create database test&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; exit&lt;span style="color:#eceff4">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>数据库创建好之后，你需要在工程的根目录中找到 &lt;code>.env&lt;/code> 文件，编辑其中的 &lt;code>DB_NAME&lt;/code> 字段为适合的值。&lt;/p>
&lt;p>修改 &lt;code>.env&lt;/code> 中的 &lt;code>DB_NAME&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-php" data-lang="php">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// Change the following line
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>DB_DATABASE&lt;span style="color:#81a1c1">=&lt;/span>homestead
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">// To the correct value
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic">&lt;/span>DB_DATABASE&lt;span style="color:#81a1c1">=&lt;/span>test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>就是这样简单。现在，你可以对数据表进行迁移和创建等操作了。之后的章节将进一步介绍这些操作。&lt;/p>
&lt;h4 id="第六步在浏览器中测试">
第六步 在浏览器中测试
&lt;a class="heading-link" href="#%e7%ac%ac%e5%85%ad%e6%ad%a5%e5%9c%a8%e6%b5%8f%e8%a7%88%e5%99%a8%e4%b8%ad%e6%b5%8b%e8%af%95">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>打开浏览器，输入 &lt;code>http://test.app&lt;/code>，你就能看到下面的画面了。&lt;/p>
&lt;p>默认的 Laravel 5.1 首页&lt;/p>
&lt;p>&lt;img src="https://leanpub.com/site_images/l5-beauty/l5-welcome-page.jpg" alt="l5-welcome-page">&lt;/p>
&lt;p>如果你看到的不是这个画面，那么之前的步骤中或许有不正确的地方。&lt;/p>
&lt;h2 id="其他-homestead-小贴士">
其他 Homestead 小贴士
&lt;a class="heading-link" href="#%e5%85%b6%e4%bb%96-homestead-%e5%b0%8f%e8%b4%b4%e5%a3%ab">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;h4 id="请在宿主系统中编写代码">
请在宿主系统中编写代码
&lt;a class="heading-link" href="#%e8%af%b7%e5%9c%a8%e5%ae%bf%e4%b8%bb%e7%b3%bb%e7%bb%9f%e4%b8%ad%e7%bc%96%e5%86%99%e4%bb%a3%e7%a0%81">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>之前的章节我们提到了这一点，这里再强调一次。请把代码编辑工作都放在宿主系统中进行。共享文件夹使用起来非常便捷，你在宿主系统的工程文件夹中进行的任何更改都会立即呈现在 Homestead 虚拟机中。&lt;/p>
&lt;h4 id="使用-homesteadaliases-文件">
使用 .homestead/aliases 文件
&lt;a class="heading-link" href="#%e4%bd%bf%e7%94%a8-homesteadaliases-%e6%96%87%e4%bb%b6">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>每当你需要使用 &lt;code>homestead up --provision&lt;/code> 或 &lt;code>homestead provision&lt;/code> 命令重新配置 Homestead 时，&lt;code>.homestead/aliases&lt;/code> 文件都会更新 Homestead 虚拟机中的各个替身。&lt;/p>
&lt;p>所以通过这个文件，我们可以轻松添加新的替身、函数甚至其他环境变量。&lt;/p>
&lt;h4 id="保持-homestead-虚拟机在最新状态">
保持 Homestead 虚拟机在最新状态
&lt;a class="heading-link" href="#%e4%bf%9d%e6%8c%81-homestead-%e8%99%9a%e6%8b%9f%e6%9c%ba%e5%9c%a8%e6%9c%80%e6%96%b0%e7%8a%b6%e6%80%81">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h4>
&lt;p>上文提到，只需两条命令就可以将 Homestead 虚拟机里的 Ubuntu 操作系统更新到最新状态。&lt;/p>
&lt;p>保持 Ubuntu 最新&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ sudo apt-get upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="本文回顾">
本文回顾
&lt;a class="heading-link" href="#%e6%9c%ac%e6%96%87%e5%9b%9e%e9%a1%be">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h2>
&lt;p>本章详细讲解了 &lt;code>homestead&lt;/code> 和 &lt;code>laravel&lt;/code> 命令，以及创建一个 Laravel 5.1 工程的简单六步。&lt;/p></description></item><item><title>解决 PDF 复制文字重复的问题</title><link>https://mogita.com/unicode-normalization-issue-with-pdf.html</link><pubDate>Mon, 16 Nov 2015 17:16:19 +0000</pubDate><guid>https://mogita.com/unicode-normalization-issue-with-pdf.html</guid><description>&lt;p>今天整理一些 pdf 格式的文稿时，为了统计字数而把文字全选并拷贝到一个 txt 文件中。但是发现拷贝出来的文字会偶尔产生重复文字，例如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>吸⽓气时缓慢放松腹部
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>注意呼吸 ⾮非常好
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>帮助缩⼩小腰围
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重复文字的位置非常多，会严重影响字数统计的结果，因此必须去重。根据 @&lt;strong>rex.&lt;/strong> 大大的&lt;a href="http://www.zhihu.com/question/20805225/answer/16273727" class="external-link" target="_blank" rel="noopener">知乎回答&lt;/a>，我尝试匹配了一下要处理的文本，结果发现这些有问题的重复字都无法被匹配。你若感兴趣可以亲自试一试，将上边例子里的重复字转换成 unicode 编码，你会发现结果是不同的。你可以使用&lt;a href="https://r12a.github.io/apps/conversion/" class="external-link" target="_blank" rel="noopener">这个在线工具&lt;/a>来转换。&lt;/p>
&lt;p>所幸得到 V2EX 的 @&lt;strong>Sylv&lt;/strong> 指点，原来这是一个「Unicode 规范化」问题（&lt;a href="https://www.v2ex.com/t/236498#r_2632245" class="external-link" target="_blank" rel="noopener">查看原帖&lt;/a>）。用 &lt;code>unicodedata.normalize()&lt;/code>函数就可以将那些字形相同但实际上编码不同的文字的编码也规范化（统一化）。这样，就可以成功进行匹配并去重了。&lt;/p>
&lt;p>Python 代码如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> &lt;span style="color:#8fbcbb">unicodedata&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> &lt;span style="color:#8fbcbb">re&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#81a1c1;font-weight:bold">import&lt;/span> &lt;span style="color:#8fbcbb">sys&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># input file name as a parameter in the command line&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>filename &lt;span style="color:#81a1c1">=&lt;/span> sys&lt;span style="color:#81a1c1">.&lt;/span>argv&lt;span style="color:#eceff4">[&lt;/span>&lt;span style="color:#b48ead">1&lt;/span>&lt;span style="color:#eceff4">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#a3be8c">&amp;#34;tokenthatneverduplicates&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># open original file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>file &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1">open&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>filename&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;r&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>raw &lt;span style="color:#81a1c1">=&lt;/span> file&lt;span style="color:#81a1c1">.&lt;/span>read&lt;span style="color:#eceff4">()&lt;/span>&lt;span style="color:#81a1c1">.&lt;/span>decode&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>file&lt;span style="color:#81a1c1">.&lt;/span>close&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># tag original double characters and letters&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>treated &lt;span style="color:#81a1c1">=&lt;/span> re&lt;span style="color:#81a1c1">.&lt;/span>sub&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">r&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;(.)\1&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">r&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;\1&amp;#39;&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> re&lt;span style="color:#81a1c1">.&lt;/span>escape&lt;span style="color:#eceff4">(&lt;/span>token&lt;span style="color:#eceff4">)&lt;/span> &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">r&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;\1&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> raw&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># do the normalization&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>normalized &lt;span style="color:#81a1c1">=&lt;/span> unicodedata&lt;span style="color:#81a1c1">.&lt;/span>normalize&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;NFKC&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> treated&lt;span style="color:#eceff4">)&lt;/span>&lt;span style="color:#81a1c1">.&lt;/span>encode&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># remove duplicated characters&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result &lt;span style="color:#81a1c1">=&lt;/span> re&lt;span style="color:#81a1c1">.&lt;/span>sub&lt;span style="color:#eceff4">(&lt;/span>&lt;span style="color:#a3be8c">r&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;([\x80-\xff]&lt;/span>&lt;span style="color:#a3be8c">{3}&lt;/span>&lt;span style="color:#a3be8c">)\1&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">r&lt;/span>&lt;span style="color:#a3be8c">&amp;#39;\1&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> normalized&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># remove tags made earlier&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>final &lt;span style="color:#81a1c1">=&lt;/span> re&lt;span style="color:#81a1c1">.&lt;/span>sub&lt;span style="color:#eceff4">(&lt;/span>token&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> result&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#616e87;font-style:italic"># save result to new file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newfile &lt;span style="color:#81a1c1">=&lt;/span> &lt;span style="color:#81a1c1">open&lt;/span>&lt;span style="color:#eceff4">(&lt;/span>filename &lt;span style="color:#81a1c1">+&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;-conv.txt&amp;#39;&lt;/span>&lt;span style="color:#eceff4">,&lt;/span> &lt;span style="color:#a3be8c">&amp;#39;w&amp;#39;&lt;/span>&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newfile&lt;span style="color:#81a1c1">.&lt;/span>write&lt;span style="color:#eceff4">(&lt;/span>final&lt;span style="color:#eceff4">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newfile&lt;span style="color:#81a1c1">.&lt;/span>close&lt;span style="color:#eceff4">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>代码中每一步均有注释，步骤非常简单。其中有个需要稍加解释的地方，由于去重的正则匹配会将所有重叠词变成单字，会导致原本正确的部分也被改掉了（如「慢慢地 -&amp;gt; 慢地」），于是用了一个小伎俩保留住它们：&lt;/p>
&lt;p>执行规范化之前，在重叠词之间添加一个独特的文本标记，然后在规范化之后将标记去掉。标记即代码开头的 token 变量。如果非常巧合，原文中有和我预设的标记相同的文字，那么修改 token 的值即可。&lt;/p>
&lt;p>代码已在 OS X 10.11 上成功执行。&lt;/p>
&lt;p>附：代码不是最优，但 python 水平实在有限，目前就确保可用吧。&lt;/p></description></item><item><title>领养面包</title><link>https://mogita.com/adopting-bread.html</link><pubDate>Fri, 23 May 2014 06:25:16 +0000</pubDate><guid>https://mogita.com/adopting-bread.html</guid><description>&lt;p>领养了一只两岁的猫，面包。&lt;/p>
&lt;p>Search for &lt;a href="https://instagram.com/explore/tags/thebreadcat/" target="_blank">#TheBreadCat&lt;/a> on Instagram.&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/the-bread-cat.jpg" alt="The Bread Cat">&lt;/p></description></item><item><title>上手 Raspberry Pi</title><link>https://mogita.com/hands-on-raspberry-pi.html</link><pubDate>Tue, 25 Mar 2014 18:20:06 +0000</pubDate><guid>https://mogita.com/hands-on-raspberry-pi.html</guid><description>&lt;p>快递找到我时还正在忙着录音，拆开塑料包裹才想起这是朝思暮想的 Raspberry Pi 到了！加上一并购买的杂七杂八的元器件，一起拎了一纸袋的东西回家。&lt;/p>
&lt;p>不知不觉已经玩了一个多星期了。RPi 的设计初衷是提起学生对电脑的兴趣，而我的 linux 技术倒是真的得到了一点长进。抽象一些的讲，一种表面上的「便利程度」很容易就成为了引起兴趣的缘由。回想起来，对 BASIC 的热情是被 PC1000 点燃的。对 linux 的兴趣，很大一部分也是基于 RPi 可以直接用电源插头控制开关机的工作方式吧。总之，开关机不是一件需要斟酌的事之后，就觉得是少了一个巨大的负担。&lt;/p>
&lt;p>具体用 RPi 做的事情，倒也不多。熟悉了那些常用的 linux 命令，学会&lt;a href="https://elinux.org/RPi_Adding_USB_Drives" target="_blank">挂载移动硬盘&lt;/a>，配置了 &lt;a href="https://users.skynet.be/hupla/raspBrian/RaspiBlog/Artikelen/2013/3/25_make_your_RPI_show_up_in_Macs_Finder_as_a_shared_drive_using_Netatalk.html" target="_blank">AFP 共享&lt;/a>给 Mac 使用，架了个 web 服务器，绑定了一个&lt;a href="https://xixitalk.github.io/blog/2013/05/29/update-ddns-with-api/" target="_blank">动态解析的域名&lt;/a>，配合之前购买的代理扩展出了一个 Wi-Fi 环境与 3G 环境都能使用的 APNP，装上了 aria2 和 yaax 实现&lt;a href="https://blog.kuroy.me/aria2-download-in-centos.html" target="_blank">远程迅雷离线下载&lt;/a>。似乎说得上来的目前就是这些。&lt;/p>
&lt;p>中间还重新刷过一次操作系统。RPi 刚到手时，在官网下载了最新版本的 Raspbian，内核为 3.10.xx。使用中发现这个版本的内核在蓝牙方面有些毛病（导致那几天心情很躁），降级内核后又出现了重启时总是内核崩溃。所以还是只能重新下载一个旧版本的 Raspbian（只会用，不懂编译内核啊）。但一直没有时间测试蓝牙。（**更新：**测试后发现蓝牙问题依然存在，并且从之前的 reboot 命令后崩溃，变成了一连接任何蓝牙设备就崩溃。&lt;a href="https://elinux.org/RPi_USB_Bluetooth_adapters" target="_blank">elinux&lt;/a> 的解释是蓝牙棒本身的问题，芯片 bug 或伪劣芯片的原因。）&lt;/p>
&lt;p>大概过两周就会搬家了。然后再去折腾那堆小器件吧。其实方案也挺傻瓜的，全都是兼容面包板的配件，玩起来大概和搭积木一样。总之这会是第一次感受用代码控制硬件嗷。&lt;/p>
&lt;p>最近一股脑来了不少事，忙得连 instaFan 都没有时间修。很对不起依然在等待、向我抱怨的用户们。。这个周末也许会好一点，希望起码能让它像样工作先。&lt;/p>
&lt;p>啊，说了这么多，不是应该摆上 RPi 的靓照一张嘛！&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/rpi/raspberry-pi-1.jpg" alt="Raspberry Pi">&lt;/p>
&lt;p>以及初次启动时忙碌地进行着 locale 设定（好像选太多了…&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/rpi/raspberry-pi-2.jpg" alt="Raspberry Pi">&lt;/p></description></item><item><title>《日本物哀》书摘</title><link>https://mogita.com/reading-mono-no-aware.html</link><pubDate>Sun, 06 Oct 2013 00:00:00 +0000</pubDate><guid>https://mogita.com/reading-mono-no-aware.html</guid><description>&lt;p>拿到了本居宣长先生的《&lt;a href="https://book.douban.com/subject/5323630/" target="_blank">日本物哀&lt;/a>》（王向远先生译本），非常喜欢。优雅地说话，一条条举例解释自己的想法，像是带着茶水逐渐泡开变色的感觉。自己也能从西方诸『格尔德斯』们的重型意见中暂时喘息一下（Google 输入法不支持单方引号）。&lt;/p>
&lt;p>书才初读几页，若干有趣的细节值得收藏和留作参考，就摘录在这里吧，不定时更新。页码依照吉林出版集团有限责任公司 2010 年 10 月版本。&lt;/p>
&lt;blockquote>
&lt;p>P19. 所谓『荒凉之处』，指的是末摘花那可怕而寂寞的家。在那种情况下读物语，之所以能够得到慰藉，是因为物语中写的事情与自己有些相似。心想和我一样遭遇的原来不止我一个人啊，于是心情就轻松了许多。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P21. 所谓『阴差阳错』的事情，指的是荒唐可笑之事。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P23. 无论如何，阅读物语，还是应以『感知物哀』为第一要义。感知物哀，首先要懂得『物之心』，而懂得『物之心』就要懂世态、通人情。（注：此处将佛儒之类的学说概称为『外国书』）&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P24.『不唠叨』，就是不惹人讨厌，所谓『唠叨』就是说了许多没有意思的废话。在《源氏物语》各卷中，在把可写的一些事情省略时，常常说：『为避免唠叨，此不多赘。』&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P25. 所谓『不着边际』，就是指那些随心所欲写下来的不可稽考的事情。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P25.『大热天』，指的是当时正值梅雨季节，由于湿热而致头发蓬乱。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P25.『笑着说道』表明这话是半开玩笑说出来的。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P26. ……一直以来那些认为《源氏物语》是为劝善惩恶而写，或是对好色的劝诫的观点，都是完全错误的。我们看这部无语，唯有『徒然心动』而已，哪有什么劝惩之意？&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P27. 窃认为，《源氏物语》中所描写的事情可以分为两种，其意趣各有不同。第一种是仿佛实有其事，其趣旨就是让人看了不由得徒然心动，归根结底就是感人心、知物哀。感知物哀，故动情于心，而完全不存在所谓劝诫之意。另一种是物语的描写叫人匪夷所思，也没有什么明确的趣旨，但其中也有一些有趣的地方。冷静阅读时觉得岂有此理，但毕竟也有可观之处。然而缺乏『物哀』之心的愚钝之人，只喜欢那些稀奇古怪的情节，而不能很好地欣赏和体味『物哀』人情。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P32. 所谓『感动』，世人通常的理解会有偏差。实际上，对于所见所闻的一切事物，觉得有趣、可笑、可怕、稀奇、可惜、可爱、伤感等一切心理活动，都是『感动』。&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>P34. 她（注：紫式部）耻于贬损人物，其心胸是非常宽厚的，与外国作家的心理状态比比看，判然有别。&lt;/p>
&lt;/blockquote></description></item><item><title>逼头：雪纳瑞·终章</title><link>https://mogita.com/beetle-the-dog-final-chapter.html</link><pubDate>Mon, 04 Feb 2013 22:20:29 +0000</pubDate><guid>https://mogita.com/beetle-the-dog-final-chapter.html</guid><description>&lt;blockquote>
&lt;p>逼头，雪纳瑞。西历 2013 年 1 月 29 日。&lt;/p>
&lt;/blockquote>
&lt;p>立春。倒春寒。天空一片铁黑，空气寒冷潮湿，距离昨天中午持续不到十分钟的一场大雨已经过去 13 个小时，永远烤不干的地面湿乎乎连成一片。寂静空旷的街道中央，确有细小的冰点飘来飘去，坐在轻骑上满脸都是小星星。这种尚未来得及形成雪花就掉落的冰点里，裹着各种工业尘埃，在接触到地面时形成一层薄薄的泥水。也是铁黑的。&lt;/p>
&lt;p>山坡上的几株翠竹此时也都默不做声地等待着鹅毛大雪，也许它们某一年会因为走错路而碰巧落下。也许是今年呢？&lt;/p>
&lt;p>竹丛生根的土里，谁会在意这种破破烂烂的地方？真的不用在意了。学不会的终究没学会，只知道过分的激动，兴奋起来就随地撒尿···真的不用再介意了。&lt;/p>
&lt;h1 id="阳光城">
阳光城
&lt;a class="heading-link" href="#%e9%98%b3%e5%85%89%e5%9f%8e">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>雨雪交加的南方是不存在暖气这一说的。就算冰天雪地也无法让人把回家和取暖直观地联系在一起。只是徒增一种想要逃离的感觉，即使是回家，也带着一幅逃难者的面容。&lt;/p>
&lt;p>反而，在那无表情的脸上是永远也读不出逃离的冲动的。他眼里只有一个人，他的存在也只为一个人，任何场所都没有特殊意义，就算对那个人来说是一个被称为家的地方，对他来说也无所谓，总之有他的地方就是家。&lt;/p>
&lt;p>泥泞的马路，嘈杂的十字路口，没有人见过这么不要命的奔跑者。世间的俗气规则保全了一个个呆滞的生命，然而没有谁能比他奔跑得更潇洒。&lt;/p>
&lt;p>就这样一直跑着，有时他也回头看看，如果那个人没有跟上，果断掉头往回跑！扑上去！汪！！&lt;/p>
&lt;h1 id="紫禁城">
紫禁城
&lt;a class="heading-link" href="#%e7%b4%ab%e7%a6%81%e5%9f%8e">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>不明就里的分离和黑暗，怎么看都像是一种惩罚。&lt;/p>
&lt;p>咬坏的只是被子的左边，就因为这个被罚关厕所。下次一定记得不能咬左边，那个人喜欢左边吧。咬中间应该没问题的！&lt;/p>
&lt;p>上次把尿撒到电视机里去了，就因为这个被罚少出门玩一次。下次一定记得那个人喜欢电视机。客厅的纱门和墙角应该没问题的！&lt;/p>
&lt;p>突然很想出门去找十三栋的那只小妞，但等了好久都没见她出来。那个人也许猜不出为什么就是蹲着却不拉粑粑。因为，会毁形象的！这是爱的便秘！要说，其实还真有那么一点点是故意耗着的。&lt;/p>
&lt;p>在万米高空中，他的思绪从头到尾地搜索了一遍记忆的书架，那些久远的过错是最让他后悔的事。总是轻率地开心，轻率地失意，轻率地表达，等到学会分辨，懂得了四方八面之情理，却发现，已经身处这万米高空了。披星戴月的旅程本身辛苦，但与那个人却仍然分隔好几个大气层，真的可以靠意志撑下去吗？&lt;/p>
&lt;p>「不管怎样，那个人一定只是惩罚我。虽然不知道是哪个错，更不知道应该如何改正，但我一定能见到那个人，多晚都可以，到时我只要用力舔他就好！」&lt;/p>
&lt;h1 id="木桶镇">
木桶镇
&lt;a class="heading-link" href="#%e6%9c%a8%e6%a1%b6%e9%95%87">
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading">&lt;/i>
&lt;span class="sr-only">Link to heading&lt;/span>
&lt;/a>
&lt;/h1>
&lt;p>既要承受这个世界的矫情，又要坚持自己认准的那种单纯，最后，自己终究还是只能被自己接受。当得知时日无多，自己也仍然在努力坚持着。&lt;/p>
&lt;p>双眼已经睁不开，好困，梦境在慢慢变得实在和凝固。我踩着六月下午的青草地，愉快地一路小跑。他在不停呼唤我的名字。他在哪？他又是谁？他是我认识的那个人。他是谁？我不认识。我不认识。我不认识。&lt;/p>
&lt;p>锵！破碎的声音！&lt;/p>
&lt;p>双眼睁开了，亲爱的，原来是你！&lt;/p></description></item><item><title>把童年插到电视机上（雅达利游戏）</title><link>https://mogita.com/childhood-jacked-in-to-the-tv.html</link><pubDate>Fri, 01 Jun 2012 09:44:38 +0000</pubDate><guid>https://mogita.com/childhood-jacked-in-to-the-tv.html</guid><description>&lt;p>&lt;img src="https://mogita.com/img/atari/atari-2600.jpg" alt="Atari 2600">&lt;/p>
&lt;p>撸了一遍今天的 RSS，其中有一条是&lt;a href="https://9to5mac.com/2012/05/31/atari-founder-on-steve-jobs/" target="_blank">雅达利创始人说&lt;/a>一部分苹果文化源于雅达利公司（乔布斯的第一份工作就是在雅达利做设计师）。&lt;/p>
&lt;p>&lt;a href="https://zh.wikipedia.org/wiki/%E9%9B%85%E9%81%94%E5%88%A9" target="_blank">雅达利&lt;/a>的诞生比我早 15 年，它的家用机里最经典的当属 Atari 2600（&lt;a href="https://v.youku.com/v_show/id_XNjY4NzY0MDg=.html" target="_blank">视频广告&lt;/a>，&lt;a href="https://www.youtube.com/watch?v=wuS58CXOWAI" target="_blank">简单评测&lt;/a>）。我当然没有福气使用这款风靡全球的游戏机，不过确实玩过雅达利的游戏。原因是老爹曾买了一台雅达利 2600 的山寨机：&lt;strong>皇冠游戏机&lt;/strong>。&lt;/p>
&lt;p>现在网上根本都找不到皇冠游戏机的图片了。不过山寨机再山寨还是跟它的原型差不离的，墨绿塑料壳机身，长二十来公分，宽十来公分，厚度不到十公分，单插卡槽，支持双手柄，可以调节游戏难度，电视机彩色或黑白模式，通过射频线接驳电视机。我记得每次开始玩游戏之前都需要疯狂地搜索信号，老电视有时能记住预选，但经常会消失，必须重新搜索所有信号。其实最高兴的时刻是画面上的雪花消失并出现一个彩色的画面的时候。&lt;/p>
&lt;p>和机器一起买回来的游戏共有 6 盒卡带，好几个卡带的印刷塑料封面已经没了，换成了老爹自己剪裁、填写游戏名称的纸质封面。我觉得那几盒卡带最神奇的设计就是卡带上有个开关（其中一盒甚至有两个开关！），拨到左边可以玩一款游戏，拨到右边就可以玩另一款游戏，都是二合一卡带，非常「划算」，而那盒两个开关的卡带自然就是四合一「超值装」。&lt;/p>
&lt;p>有些卡带已经读不出来了，包括我最喜欢的《&lt;a href="https://en.wikipedia.org/wiki/Outlaw_(video_game)#Atari_2600_Port" target="_blank">枪战&lt;/a>》。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/outlaw.png" alt="Outlaw, an Atari game">&lt;/p>
&lt;p>这是我玩过的唯一一款双人游戏，中间的障碍物包括仙人掌、会上下跑的马车等，拆完障碍物之后就是两边小人火拼了。老爹没怎么陪我玩过小霸王游戏，不过跟他对战过《枪战》可是记地真真儿的。&lt;/p>
&lt;p>其次就是无比经典的《&lt;a href="https://en.wikipedia.org/wiki/Keystone_Kapers" target="_blank">警察抓小偷&lt;/a>》，&lt;a href="https://zh.wikipedia.org/wiki/%E7%BE%8E%E5%9B%BD%E5%8A%A8%E8%A7%86" target="_blank">Activision&lt;/a> 出品。如今这个公司的名字已经如雷贯耳了。&lt;a href="https://www.youtube.com/watch?v=OYDgrXxIkJY" target="_blank">游戏视频&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/keystone-kapers.jpg" alt="Keystone Kapers, an Atari game">&lt;/p>
&lt;p>这款游戏曾让我一下午没挪窝。不过主要原因是死得勤，后边的关卡都没怎么见过（摊手）。玩家控制警察，可以左右跑、跳跃躲开电网、下蹲躲过飞机、进电梯下电梯，最后捉到一直躲避警察的小偷。可能喜欢它的原因大部分还是受到精致画面的影响，它连字体都是设计过的，而不像其他几款游戏里面那样毫无粗细变化的字体。&lt;/p>
&lt;p>然后还有《&lt;a href="https://en.wikipedia.org/wiki/Spider-Man_(Atari_2600_video_game)" target="_blank">高楼盗宝&lt;/a>》（其实应该叫蜘蛛侠）。这也算是个可玩性很强的动作类游戏了。&lt;a href="https://www.youtube.com/watch?v=hZzGaFiyezY" target="_blank">游戏视频&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/spider-man.jpg" alt="Spider Man, an Atari game">&lt;/p>
&lt;p>另一枚精致的游戏《&lt;a href="https://en.wikipedia.org/wiki/Plaque_Attack" target="_blank">挤牙膏&lt;/a>》，不得不说美工真是太让人舒畅了。&lt;a href="https://www.youtube.com/watch?v=LsAY4JgVCRM" target="_blank">游戏视频&lt;/a>（有重口图 2 秒）。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/plaque-attack.jpg" alt="Plaque Attack, an Atari game">&lt;/p>
&lt;p>看起来简单，保卫 8 颗牙，朝着不断涌入的食物射牙膏，不让它们碰到牙齿。不过到后面食物行进的路线越来越诡异，速度也越来越快，一处失守满口遭殃。哪怕最后只剩一颗牙了也没法偷懒，如果只朝这颗牙射牙膏，食物会非常突然而精准地落到牙膏碰不到的地方，干掉最后一颗牙。&lt;/p>
&lt;p>还有一款叫做《&lt;a href="https://en.wikipedia.org/wiki/River_Raid" target="_blank">开飞机&lt;/a>》（本名 River Raid，好吧这些中文名都是听老爹叫的，我也一直这样叫）。游戏性和画面都很赞。&lt;a href="https://www.youtube.com/watch?v=pmPjsBDN9Xw" target="_blank">游戏视频&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/river-raid.jpg" alt="River Raid, an Atari game">&lt;/p>
&lt;p>玩法和现代的竖版飞机几乎一样，飞机向上飞，可以控制快慢，路上有会移动的敌人，不能碰到，还有加油站，飞机油耗非常快，随时需要补充燃料。也不能碰到绿边，一碰就挂。&lt;/p>
&lt;p>最后非常喜欢的一款《&lt;a href="https://en.wikipedia.org/wiki/Seaquest_(video_game)" target="_blank">海底救人&lt;/a>》。&lt;a href="https://www.youtube.com/watch?v=A9GNDwad27E" target="_blank">游戏视频&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://mogita.com/img/atari/seaquest.jpg" alt="Seaquest, an Atari game">&lt;/p>
&lt;p>开着黄色潜水艇，向各种危险的鱼类发射子弹（并躲避鲨鱼发射的子弹 -，-），同时救下在水里逃命的人。氧气耗尽之前需要返回海面补充氧气，但每次上浮会消耗一个营救上来的人，所以如果一个人都还没有救到，浮上去是会屎的。&lt;/p>
&lt;p>其他游戏我都叫不上名字，也查不到了。比如某款赛车、一个被老爹叫做《鸡毛信》的动作游戏等。&lt;/p>
&lt;p>那么，该去吃六一的晚餐了。祝是不是儿童的儿童、生没生儿童的非儿童都儿童节快乐。&lt;/p></description></item><item><title>instaFan：将 Instagram 照片同步到饭否</title><link>https://mogita.com/instafan-getting-started.html</link><pubDate>Sun, 29 Apr 2012 08:27:31 +0000</pubDate><guid>https://mogita.com/instafan-getting-started.html</guid><description>&lt;p>2020 年 6 月更新：Fanstafou 服务已下线，感谢 1000 多位小伙伴多年的关照！与 instaFan 有关的重要饭否消息贴在了本文结尾处，留作纪念。&lt;/p>
&lt;p>2016 年 5 月更新：instaFan 服务已更名为 Fanstafou。&lt;/p>
&lt;hr>
&lt;p>&lt;a href="https://instafan.mogita.com/" target="_blank">instaFan&lt;/a> 是一枚同步服务，可以将你的 Instagram 新照片自动同步到饭否。同步内容包括：照片以及该照片的描述文字、标签、照片展示页面地址、地理位置信息。&lt;/p>
&lt;p>&lt;strong>特色&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>OAuth 认证，确保信息安全&lt;/li>
&lt;li>包含若干种同步模式随时可更改，如全部同步、只同步指定照片、暂停同步等&lt;/li>
&lt;li>自动将 Instagram 照片的标签转换成饭否格式（双“#”号）&lt;/li>
&lt;li>与 Instagram 内置的分享样式保持一致（如照片地址、默认标题等）&lt;/li>
&lt;li>支持新增的饭否地理位置显示功能&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>注意&lt;/strong>：同步服务仅对新发布的照片有效，旧照片不会被同步。所以，如果你选择了“只同步有 #fanfou 标签的照片”时，在照片评论里添加 #fanfou 标签是不会同步的，只有在发布时添加此标签才有效。&lt;/p>
&lt;p>欢迎在评论中留下你的使用感受和建议。我的 Instagram 用户名是 mogita，一起玩吧！！&lt;/p>
&lt;hr>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">It is working&amp;hellip;#&lt;a href="https://fanfou.com/q/instaFan" class="external-link" target="_blank" rel="noopener">instaFan&lt;/a># &lt;a href="http://instagr.am/p/Jk6tyauYkv/" class="external-link" target="_blank" rel="noopener">http://instagr.am/p/Jk6tyauYkv/&lt;/a>&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/Kt5hSYFrifM" class="external-link" target="_blank" rel="noopener">2012-04-19 05:55&lt;/a> 通过 &lt;a href="http://fanstafou.mogita.com/" class="external-link" target="_blank" rel="noopener">FanstaFou&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;a href="https://mogita.com/img/fanfou/mogita/zs_151459.jpg" title="It is working...#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/Jk6tyauYkv/" target="_blank">
&lt;img src="https://mogita.com/img/fanfou/mogita/zs_151459_hu17179484924247738362.jpg" alt="It is working...#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/Jk6tyauYkv/" title="It is working...#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/Jk6tyauYkv/" />
&lt;/a>
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">试用接受中！#&lt;a href="https://fanfou.com/q/instaFan" class="external-link" target="_blank" rel="noopener">instaFan&lt;/a># &lt;a href="http://instagr.am/p/J_Xl_HuYgI/" class="external-link" target="_blank" rel="noopener">http://instagr.am/p/J_Xl_HuYgI/&lt;/a>&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/MLjXa_Q6bus" class="external-link" target="_blank" rel="noopener">2012-04-29 14:08&lt;/a> 通过 &lt;a href="http://fanstafou.mogita.com/" class="external-link" target="_blank" rel="noopener">FanstaFou&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;a href="https://mogita.com/img/fanfou/mogita/0s_298563.jpg" title="试用接受中！#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/J_Xl_HuYgI/" target="_blank">
&lt;img src="https://mogita.com/img/fanfou/mogita/0s_298563_hu1462567853958200638.jpg" alt="试用接受中！#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/J_Xl_HuYgI/" title="试用接受中！#[instaFan](https://fanfou.com/q/instaFan)# http://instagr.am/p/J_Xl_HuYgI/" />
&lt;/a>
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">给 instaFan 画了个横图，简笔画水平。。哈哈。。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/wji-e1zKNHU" class="external-link" target="_blank" rel="noopener">2012-05-28 03:50&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;a href="https://mogita.com/img/fanfou/mogita/km_260120.jpg" title="给 instaFan 画了个横图，简笔画水平。。哈哈。。" target="_blank">
&lt;img src="https://mogita.com/img/fanfou/mogita/km_260120_hu12220904281050982590.jpg" alt="给 instaFan 画了个横图，简笔画水平。。哈哈。。" title="给 instaFan 画了个横图，简笔画水平。。哈哈。。" />
&lt;/a>
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">instafan 刚刚获得了开放后的第一百位用户。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/L9YWqq7pBdE" class="external-link" target="_blank" rel="noopener">2012-08-06 17:12&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">逐步添加详细的 log，发现 instagram 确实偶尔不会将用户发布新照片的消息推送到 instaFan。╮(￣▽￣&amp;quot;)╭&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/cvnuU2bC-ak" class="external-link" target="_blank" rel="noopener">2012-12-31 14:32&lt;/a> 通过 API
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">更新了一下#&lt;a href="https://fanfou.com/q/instaFan" class="external-link" target="_blank" rel="noopener">instaFan&lt;/a>#，唯一针对提升同步成功率。不知道能提升多少。。欢迎来操。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/lML9vEFhtiQ" class="external-link" target="_blank" rel="noopener">2013-02-22 22:57&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">instaFan 同步数过万了。。回家好好爱抚一番。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/6hhck1_wH64" class="external-link" target="_blank" rel="noopener">2013-03-20 10:15&lt;/a> 通过 &lt;a href="http://imach.me/gohanapp" class="external-link" target="_blank" rel="noopener">「御飯 iPad」&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">SAE 上跑 instaFan 看来希望渺茫了。要是没被 fb 收购就好了。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/PhnUd7IFu6Q" class="external-link" target="_blank" rel="noopener">2014-05-02 11:01&lt;/a> 通过 &lt;a href="http://imach.me/gohanapp" class="external-link" target="_blank" rel="noopener">「御飯 iPhone」&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">instaFan 暂定下周二（5 月 12 日）17 点到 20 点暂停服务 3 小时。（主机商说要修电源。。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/hD5EILXHXPY" class="external-link" target="_blank" rel="noopener">2015-05-05 17:16&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">今天算是把 instafan 新版服务跑起来了。长期测试，鸭的实时推送接口根本就是个坏的，有些用户发照片它永远不会给我发消息。还是特么轮询靠谱。当然，这是新版的设计，目前版本的代码不打算更新了。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/bZhIoLt7jk4" class="external-link" target="_blank" rel="noopener">2016-05-10 18:20&lt;/a> 通过 &lt;a href="http://imach.me/gohanapp" class="external-link" target="_blank" rel="noopener">御飯 iOS&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">今天下午开始迁移 instaFan 数据，为新版服务做准备。现有同步服务会暂停运行，迁移完成后服务会自动恢复。由于数据库结构升级，预计迁移过程需要 1-2 天，请各位用户见谅。#&lt;a href="https://fanfou.com/q/instafan" class="external-link" target="_blank" rel="noopener">instafan&lt;/a># #&lt;a href="https://fanfou.com/q/instagram" class="external-link" target="_blank" rel="noopener">instagram&lt;/a>#&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/BAW1DV82Zto" class="external-link" target="_blank" rel="noopener">2016-05-26 14:05&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">如果因为 instagram 的政策，#&lt;a href="https://fanfou.com/q/instafan" class="external-link" target="_blank" rel="noopener">instafan&lt;/a># 不得不改名的话，是完全换个名字还是小改避开政策就好呢？（官方不允许 ig、insta、gram、instagram 字样出现在名字里）&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/_n8B4Ex1tAw" class="external-link" target="_blank" rel="noopener">2016-05-27 14:02&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">我自己也+1 转@&lt;a href="https://fanfou.com/tenax" class="external-link" target="_blank" rel="noopener">西瓦&lt;/a> 哈哈哈被fanstafou逗笑。觉得只要朗朗上口的都行 转@&lt;a href="https://fanfou.com/mogita" class="external-link" target="_blank" rel="noopener">mogita&lt;/a> fansync？fantasync？fanstafou？syncfou？fanfoutongbu？tongbuyinsiteguranmuzhaopiandaofanfou？&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/-HLgkSYt-ks" class="external-link" target="_blank" rel="noopener">2016-05-27 14:18&lt;/a> 通过 网页 &lt;a href="https://fanfou.com/statuses/v2cCfW5fEBw" class="external-link" target="_blank" rel="noopener">转自mogita(查看)&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">Instagram 到饭否同步服务 instaFan 已改名为 FanstaFou，且刚刚完成了一次重要的机能升级，提升服务的稳定性。访问地址变更为 &lt;a href="http://fanstafou.mogita.com" class="external-link" target="_blank" rel="noopener">http://fanstafou.mogita.com&lt;/a>。老用户已自动升级为新版，无需进行任何操作。Enjoy！&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/Z3pCa0t4NFw" class="external-link" target="_blank" rel="noopener">2016-05-28 11:47&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">五月底更新后的 Fanstafou 截至现在已同步了 10104 张照片。即每个月已知用户要发送 2500 张左右的照片。平均每天发送大约 83 张照片。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/uu-KsvDgAoE" class="external-link" target="_blank" rel="noopener">2016-10-10 10:48&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">在线上运行了 7 年 6 个月零 4 天，经历了一次重写，一次服务迁移之后，Fanstafou 代码库喜提首次 git commit。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/Uj838x4odro" class="external-link" target="_blank" rel="noopener">2019-10-23 12:27&lt;/a> 通过 网页
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;a href="https://mogita.com/img/fanfou/mogita/zn_293783.png" title="在线上运行了 7 年 6 个月零 4 天，经历了一次重写，一次服务迁移之后，Fanstafou 代码库喜提首次 git commit。" target="_blank">
&lt;img src="https://mogita.com/img/fanfou/mogita/zn_293783_hu7454130301540935105.png" alt="在线上运行了 7 年 6 个月零 4 天，经历了一次重写，一次服务迁移之后，Fanstafou 代码库喜提首次 git commit。" title="在线上运行了 7 年 6 个月零 4 天，经历了一次重写，一次服务迁移之后，Fanstafou 代码库喜提首次 git commit。" />
&lt;/a>
&lt;/div>
&lt;/blockquote>
&lt;style>
.quote-fanfou {
font-family: HelveticaNeue, Helvetica Neue, Helvetica, Arial, sans-serif;
font-style: normal;
display: flex;
}
.quote-fanfou .avatar {
margin-right: 8px;
}
.quote-fanfou .avatar img {
height: 48px;
width: 48px;
}
.quote-fanfou .content {
max-width: 360px;
flex-grow: 1;
font-size: 14px;
line-height: 20px;
}
.quote-fanfou .message {
word-break: break-all;
}
.quote-fanfou .meta {
margin-top: 4px;
}
.quote-fanfou .meta, .quote-fanfou .meta a {
color: #999999;
font-size: 12px;
}
.quote-fanfou .attachment {
margin-left: 8px;
}
.quote-fanfou .attachment img {
height: 106px;
width: 106px;
padding: 2px;
border: 1px solid #cccccc;
}
&lt;/style>
&lt;blockquote class="quote-fanfou">
&lt;div class="avatar">&lt;img src="https://mogita.com/img/fanfou/mogita/avatar.jpg" alt="mogita" />&lt;/div>
&lt;div class="content">
&lt;span class="username">&lt;a href="https://fanfou.com/mogita" target="_blank">mogita&lt;/a>&lt;/span>
&lt;span class="message">Instagram API 六月底彻底淘汰，又不想接触 Facebook 平台，冗长赘余的配置和审核，取代了激情迸发的美丽时代。再见 Web 2.0，再见 FanstaFou。&lt;/span>
&lt;div class="meta">
&lt;a href="https://fanfou.com/statuses/XNnHFP89BsQ" class="external-link" target="_blank" rel="noopener">2020-06-07 03:11&lt;/a> 通过 &lt;a href="http://imach.me/gohanapp" class="external-link" target="_blank" rel="noopener">御飯 iOS&lt;/a>
&lt;/div>
&lt;/div>
&lt;div class="attachment">
&lt;/div>
&lt;/blockquote></description></item><item><title>More Words：不定长、部分符合英文单词快捷检索</title><link>https://mogita.com/more-words.html</link><pubDate>Fri, 03 Feb 2012 18:32:23 +0000</pubDate><guid>https://mogita.com/more-words.html</guid><description>&lt;p>词汇量有限是个大问题，不用谈表达，连想法都无法具体转成一种符号。在个人现有词汇量的基础上，如果只知道某个单词中的几个字母，如何找到或罗列出所有按一定顺序包含这几个字母的单词？所以适时依靠一下强大的工具吧。&lt;/p>
&lt;p>用 More Words 可以检索出 Enable2k North America 词汇表中的 173528 个单词，本意是帮助人们解决字谜游戏等。由于是字谜游戏专用的单词表，所以不包含单个字母的单词或部分专有名词。&lt;/p>
&lt;p>&lt;a href="https://www.morewords.com" class="external-link" target="_blank" rel="noopener">https://www.morewords.com&lt;/a>&lt;/p>
&lt;p>例如，如何找到尽可能多的以 “at” 结尾的单词呢？在搜索框里输入 &lt;code>*at&lt;/code> 就可以了。干脆把网站的&lt;a href="https://www.morewords.com/examples" class="external-link" target="_blank" rel="noopener">搜索示例&lt;/a>稍微翻译一下吧：&lt;/p>
&lt;ul>
&lt;li>查询确切的单词“crosswords”：&lt;code>crosswords&lt;/code>&lt;/li>
&lt;li>三个字母的单词，结尾字母是“r”：&lt;code>–r&lt;/code>&lt;/li>
&lt;li>六个字母的单词，前两个字母是“pu”，最后一个字母是“e”：&lt;code>pu–e&lt;/code>&lt;/li>
&lt;li>十个字母的单词，中间两个字母是“sw”：&lt;code>--—-sw—---&lt;/code>&lt;/li>
&lt;li>包含“sswo”这个字母顺序的任意长度单词：&lt;code>*sswo*&lt;/code>&lt;/li>
&lt;li>以“cross”开头的任意长度单词：&lt;code>cross*&lt;/code>&lt;/li>
&lt;li>以“zzle”结尾的任意长度单词：&lt;code>*zzle&lt;/code>&lt;/li>
&lt;li>以“a”开头并以“z”结尾的单词：&lt;code>a*z&lt;/code>&lt;/li>
&lt;li>以“b”开头并在中间某个位置包含“c”，且以“d”结尾的单词：&lt;code>b*c*d&lt;/code>&lt;/li>
&lt;li>以“ab”开头且不包含“e”或“o”的单词：&lt;code>ab* ^eo&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>还有其他用法，例如：&lt;/p>
&lt;ul>
&lt;li>将以“at”结尾的单词按从短到长的顺序全部排列出来：
&lt;a href="https://www.morewords.com/ends-with-by-length/at/" class="external-link" target="_blank" rel="noopener">https://www.morewords.com/ends-with-by-length/at/&lt;/a>&lt;/li>
&lt;li>或者将以“at”结尾的单词按流行度排列出来：
&lt;a href="https://www.morewords.com/most-common-ends-with/at/" class="external-link" target="_blank" rel="noopener">https://www.morewords.com/most-common-ends-with/at/&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>用法还很多，不一一列举了。值得一提的是检索速度还算快，页面也很干净。&lt;/p>
&lt;p>好吧，我在最后必须承认，这个工具有助于在写词时偶尔偷个懒。梦境才是最好的写作工具。&lt;/p>
&lt;p>Use it well.&lt;/p></description></item></channel></rss>