40行代码手撸一个静态文档生成器[译]

前言

目前有很多优秀的静态文档生成器,它们的工作原理比你想象的要简单得多。

原文: Build a static site generator in 40 lines with Node.js

作者: Douglas Matoso

译者: Simon Ma

日期:2017-09-14

为什么要造这个轮子

当我计划建立个人网站时,我的需求很简单,做一个只有几个页面的网站,放置一些关于自己的信息,我的技能和项目就够了。
毫无疑问,它应该是纯静态的(不需要后端服务,可托管在任何地方)。

我曾经使用过 Jekyll
, Hugo
Hexo
这些知名的静态文档生成器,但我认为它们有太多的功能,我不想为我的网站增加这么多的复杂性。
所以我觉得,针对我的需求,一个简单的静态文档生成器就可以满足。
嗯,手动构建一个简单的生成器,应该不会那么难。

正文

需求分析

这个生成器必须满足以下条件:

  • EJS
    模板生成 HTML
    文件。

  • 具有布局文件,所有页面都应该具有相同的页眉,页脚,导航等。
  • 允许可重用布局组件。
  • 站点的大致信息封装到一个配置文件中。
  • 从JSON文件中读取数据。
    例如:项目列表,这样我可以轻松地迭代和构建项目页面。

为什么使用 EJS 模板?
因为 EJS 很简单,它只是嵌入在 HTML 中的 JavaScript 而已。

项目结构

public/  
 src/  
   assets/  
   data/  
   pages/  
   partials/  
   layout.ejs  
 site.config.js
  • public:
    生成站点的位置。
  • src:
    源文件。
  • src/assets:
    包含 CSS, JS, 图片 等
  • src/data:
    包含 JSON 数据。
  • src/pages:
    根据其中的 EJS 生成 HTML 页面的模板文件夹。
  • src/layout.ejs:
    主要的原页面模板,包含特殊
    占位符,将插入具体的页面内容。
  • site.config.js
    : 模板中全局配置文件。

生成器

生成器代码位于 scripts/build.js
文件中,每次想重建站点时,执行 npm run build
命令即可。

实现方法是将以下脚本添加到 package.json
scripts
块中:

"build": "node ./scripts/build"

下面是完整的生成器代码:

const fse = require('fs-extra')
const path = require('path')
const { promisify } = require('util')
const ejsRenderFile = promisify(require('ejs').renderFile)
const globP = promisify(require('glob'))
const config = require('../site.config')

const srcPath = './src'
const distPath = './public'

// clear destination folder
fse.emptyDirSync(distPath)

// copy assets folder
fse.copy(`${srcPath}/assets`, `${distPath}/assets`)

// read page templates
globP('**/*.ejs', { cwd: `${srcPath}/pages` })
  .then((files) => {
    files.forEach((file) => {
      const fileData = path.parse(file)
      const destPath = path.join(distPath, fileData.dir)

      // create destination directory
      fse.mkdirs(destPath)
        .then(() => {
          // render page
          return ejsRenderFile(`${srcPath}/pages/${file}`, Object.assign({}, config))
        })
        .then((pageContents) => {
          // render layout with page contents
          return ejsRenderFile(`${srcPath}/layout.ejs`, Object.assign({}, config, { body: pageContents }))
        })
        .then((layoutContent) => {
          // save the html file
          fse.writeFile(`${destPath}/${fileData.name}.html`, layoutContent)
        })
        .catch((err) => { console.error(err) })
    })
  })
  .catch((err) => { console.error(err) })

接下来,我将解释代码中的具体组成部分。

依赖

我们只需要三个依赖项:


  • ejs

    把我们的模板编译成 HTML


  • fs-extra

    Node 文件模块的衍生版,具有更多的功能,并增加了 Promise
    的支持。


  • glob

    递归读取目录,返回包含与指定模式匹配的所有文件,类型是数组。

Promisify

我们使用 Node
提供的 util.promisify
将所有回调函数转换为基于 Promise
的函数。
它使我们的代码更短,更清晰,更易于阅读。

const { promisify } = require('util')  
const ejsRenderFile = promisify(require('ejs').renderFile)  
const globP = promisify(require('glob'))

加载配置

在顶部,我们加载站点配置文件,以稍后将其注入模板渲染中。

const config = require('../site.config')

站点配置文件本身会加载其他 JSON
数据,例如:

const projects = require('./src/data/projects')

module.exports = {  
  site: {  
    title: 'NanoGen',  
    description: 'Micro Static Site Generator in Node.js',  
    projects  
  }  
}

清空站点文件夹

我们使用 fs-extra
提供的 emptyDirSync
函数清空 生成后的站点文件夹。

fse.emptyDirSync(distPath)

拷贝静态资源

我们使用 fs-extra
提供的 copy
函数,该函数以递归方式复制静态资源 到站点文件夹。

fse.copy(`${srcPath}/assets`, `${distPath}/assets`)

编译页面模板

首先我们使用 glob
(已被 promisify)递归读取 src/pages
文件夹以查找 .ejs
文件。
它将返回一个匹配给定模式的所有文件数组。

globP('**/*.ejs', { cwd: `${srcPath}/pages` })  
  .then((files) => {

对于找到的每个模板文件,我们使用 Node
path.parse
函数来分隔文件路径的各个组成部分(例如目录,名称和扩展名)。

然后,我们在站点目录中使用 fs-extra
提供的 mkdirs
函数创建与之对应的文件夹。

files.forEach((file) => {  
  const fileData = path.parse(file)  
  const destPath = path.join(distPath, fileData.dir)

 // create destination directory  
  fse.mkdirs(destPath)

然后,我们使用 EJS
编译文件,并将配置数据作为数据参数。

由于我们使用的是已 promisify 的 ejs.renderFile
函数,因此我们可以返回调用结果,并在下一个 promise
链中处理结果。

.then(() => {  
  // render page  
  return ejsRenderFile(`${srcPath}/pages/${file}`, Object.assign({}, config))  
})

在下一个 then
块中,我们得到了已编译好的页面内容。

现在,我们编译布局文件,将页面内容作为 body
属性传递进去。

.then((pageContents) => {  
  // render layout with page contents  
  return ejsRenderFile(`${srcPath}/layout.ejs`, Object.assign({}, config, { body: pageContents }))  
})

最后,我们得到了生成好的编译结果(布局+页面内容的 HTML),然后将其保存到对应的 HTML
文件中。

.then((layoutContent) => {  
  // save the html file  
  fse.writeFile(`${destPath}/${fileData.name}.html`, layoutContent)  
})

调试服务器

为了使查看结果更容易,我们在 package.json
scripts
中添加一个简单的静态服务器。

"serve": "serve ./public"

运行 npm run serve
命令,打开 http://localhost:5000
就看到结果了。

进一步探索

Markdown

大多数静态文档生成器都支持以 Markdown
格式编写内容。

并且,它们还支持以 YAML
格式在顶部添加一些元数据,如下所示:

---  
title: Hello World  
date: 2013/7/13 20:46:25  
---

只需要一些修改,我们就可以支持相同的功能了。
首先,我们必须增加两个依赖:

然后,我们将 glob
的匹配模式更新为包括 .md
文件,并保留 .ejs
,以支持渲染复杂页面。

如果想要部署一些纯 HTML 页面,还需包含 .html

globP('**/*.@(md|ejs|html)', { cwd: `${srcPath}/pages` })

对于每个文件,我们都必须加载文件内容,以便可以在顶部提取到元数据。

.then(() => {  
  // read page file  
  return fse.readFile(`${srcPath}/pages/${file}`, 'utf-8')  
})

我们将加载后的内容传递给 front-matter

它将返回一个对象,其中 attribute
属性便是提取后的元数据。
然后,我们使用此数据扩充站点配置。

.then((data) => {  
  // extract front matter  
  const pageData = frontMatter(data)  
  const templateConfig = Object.assign({}, config, { page: pageData.attributes })

现在,我们根据文件扩展名将页面内容编译为 HTML。

如果是 .md
,则利用 marked
函数编译;

如果是 .ejs
,我们继续使用 EJS
编译;

如果是 .html
,便无需编译。

let pageContent  

switch (fileData.ext) {  
  case '.md':  
    pageContent = marked(pageData.body)  
    break  
  case '.ejs':  
    pageContent = ejs.render(pageData.body, templateConfig)  
    break  
  default:  
    pageContent = pageData.body  
}

最后,我们像以前一样渲染布局。
增加元数据,最明显的一个意义是,我们可以为每个页面设置单独的标题,如下所示:

---  
title: Another Page  
---

并让布局动态地渲染这些数据:

如此一来,每个页面将具有唯一的 </code><br /> 标签。 </p> <h4>多种布局的支持</h4> <p> 另一个有趣的探索是,在特定的页面中使用不同的布局。<br /> 比如专门为站点首页设置一个独一无二的布局:</p> <pre>--- layout: minimal ---</pre> <p>我们需要有单独的布局文件,我将它们放在 <code>src/layouts</code><br /> 文件夹中: </p> <pre>src/layouts/ default.ejs mininal.ejs</pre> <p>如果 <code>front matter</code><br /> 出现了布局属性,我们将利用 <code>layouts</code><br /> 文件夹中同名模板文件进行渲染; 如果未设置,则利用默认模板渲染。 </p> <pre>const layout = pageData.attributes.layout || 'default' return ejsRenderFile(`${srcPath}/layouts/${layout}.ejs`, Object.assign({}, templateConfig, { body: pageContent }) )</pre> <p>即使添加了这些新特性,构建脚本也才只有 <code>60</code><br /> 行。 </p> <h3>下一步</h3> <p> 如果你想更进一步,可以添加一些不难的附加功能:</p> <ul> <li> 可热重载的调试服务器</p> <p>你可以使用像 <a href="https://www.npmjs.com/package/live-server" rel="nofollow,noindex" target="_blank"><br /> <strong>live-server</strong><br /> </a><br /> (内置自动重新加载) 或 <a href="https://www.npmjs.com/package/chokidar" rel="nofollow,noindex" target="_blank"><br /> <strong>chokidar</strong><br /> </a><br /> (观察文件修改以自动触发构建脚本)这样的模块去完成。 </li> <li> 自动部署</p> <p>添加脚本以将站点部署到 <code>GitHub Pages</code><br /> 等常见的托管服务,或仅通过 <code>SSH</code><br /> (使用 <code>scp</code><br /> 或 <code>rsync</code><br /> 等命令)将文件上传到你自己的服务器上。 </li> <li> 支持 CSS/JS 预处理器<br /> 在静态文件被复制到站点文件前,增加一些预处理器(SASS 编译为 CSS,ES6 编译为 ES5 等)。 </li> <li> 更好的日志打印</p> <p>添加一些 <code>console.log</code><br /> 日志输出 来更好地分析发生了什么。 </p> <p>你可以使用 <code>chalk</code><br /> 包来完善这件事。 </li> </ul> <p>反馈? 有什么建议吗? 请随时发表评论或与我联系!</p> <h2>结束语</h2> <p> 这个文章的完整示例可以在这里找到:https://github.com/doug2k1/nanogen/tree/legacy。</p> <p>一段时间后,我决定将项目转换为 <code>CLI</code><br /> 模块,以使其更易于使用,它位于上面链接的 <code>master</code><br /> 分支中。<br /> 译者:</p> <p>今日本想写一篇 <a href="https://github.com/panjf2000/ants/" rel="nofollow,noindex" target="_blank">ants</a><br /> (一个高性能的 <code>goroutine</code><br /> 池)源码解析,奈何环境太吵,静不下心,遂罢。 </p> <p>这是一篇我前些日子无意间看到的文章,虽然是 <code>17</code><br /> 年的文章,在读完之后仍对我产生了一些思考。<br /> 希望这篇文章对你有所帮助。</p> <p>转载本站文章请注明作者和出处 <a href="http://tomotoes.com" rel="nofollow,noindex" target="_blank">一个坏掉的番茄</a><br /> ,请勿用于任何商业用途。 </div> <!-- Start Tags --> <div class="tags"></div> <!-- End Tags --> </div><!-- End Content --> <!-- Start Related Posts --> <div class="related-posts"><div class="postauthor-top"><h3>Related Posts</h3></div> <article class="post excerpt "> <a href="https://www.go2live.cn/nocate/%e6%9e%84%e5%bb%ba%e7%94%9f%e4%ba%a7%e5%8f%af%e7%94%a8%e7%9a%84%e6%8e%a8%e8%8d%90%e7%b3%bb%e7%bb%9f%e9%9c%80%e8%a6%81%e6%b3%a8%e6%84%8f%e5%93%aa%e4%ba%9b%e9%97%ae%e9%a2%98%ef%bc%9f.html" title="构建生产可用的推荐系统需要注意哪些问题?" id="featured-thumbnail"> <header> <h4 class="title front-view-title">构建生产可用的推荐系统需要注意哪些问题?</h4> </header> </a> </article><!--.post.excerpt--> <article class="post excerpt "> <a href="https://www.go2live.cn/nocate/%e7%a5%9e%e5%86%9c%e7%8f%ad%e5%92%8c%e6%88%91%e7%9a%842020%e5%b9%b4%e6%80%bb%e7%bb%93.html" title="神农班和我的2020年总结" id="featured-thumbnail"> <header> <h4 class="title front-view-title">神农班和我的2020年总结</h4> </header> </a> </article><!--.post.excerpt--> <article class="post excerpt last"> <a href="https://www.go2live.cn/nocate/%e7%9c%8b-android-%e7%9a%84%e5%8f%91%e5%b1%95%e5%8e%86%e7%a8%8b-%e8%b0%88%e4%b8%80%e4%b8%8b%e5%bd%93%e4%b8%8b%e6%9c%80%e5%90%88%e9%80%82%e7%9a%84%e6%9e%b6%e6%9e%84.html" title="看 Android 的发展历程-谈一下当下最合适的架构" id="featured-thumbnail"> <header> <h4 class="title front-view-title">看 Android 的发展历程-谈一下当下最合适的架构</h4> </header> </a> </article><!--.post.excerpt--> </div> <!-- End Related Posts --> <!-- Start Author Box --> <div class="postauthor"> <h4>About The Author</h4> <img alt='' src='https://secure.gravatar.com/avatar/4273d06b59287bcad055d0fa0d5bb02a?s=85&d=monsterid&r=g' srcset='https://secure.gravatar.com/avatar/4273d06b59287bcad055d0fa0d5bb02a?s=170&d=monsterid&r=g 2x' class='avatar avatar-85 photo' height='85' width='85' /> <h5>fenny</h5> <p></p> </div> <!-- End Author Box --> <!-- You can start editing here. --> </div> </div> </article> <!-- End Article --> <!-- Start Sidebar --> <aside class="sidebar c-4-12"> <div id="sidebars" class="sidebar"> <div class="sidebar_list"> <div id="search-2" class="widget widget_search"><form method="get" id="searchform" class="search-form" action="https://www.go2live.cn" _lpchecked="1"> <fieldset> <input type="text" name="s" id="s" value="Search this site..." onblur="if (this.value == '') {this.value = 'Search this site...';}" onfocus="if (this.value == 'Search this site...') {this.value = '';}" > <input type="submit" value="Search" /> </fieldset> </form> </div><div id="linkcat-115" class="widget widget_links"><h3 class="widget-title"><span>友情链接</span></h3> <ul class='xoxo blogroll'> <li><a href="http://mooc.guokr.com/course/" target="_blank">mooc学院-综合学习</a></li> <li><a href="http://www.guanyuwu.com" rel="acquaintance sweetheart" target="_blank">创意手工分享</a></li> <li><a href="http://www.tuijiankan.com/" rel="friend" target="_blank">向阳博客</a></li> <li><a href="http://blog.devtang.com/" target="_blank">唐巧的技术博客-研发</a></li> <li><a href="http://www.liaoxuefeng.com/" target="_blank">廖雪峰官方网站-研发</a></li> <li><a href="http://weappdev.com/" target="_blank">微信小程序开发论坛</a></li> <li><a href="https://guanyuwu.taobao.com">直男送礼好去处</a></li> <li><a href="http://coolshell.cn/">酷壳-研发</a></li> <li><a href="http://www.ruanyifeng.com/blog/" target="_blank">阮一峰博客-研发-前端</a></li> <li><a href="http://www.laruence.com/" target="_blank">鸟哥博客-php专家</a></li> <li><a href="http://gongfuxiang.com/" target="_blank">龚福祥的博客-研发</a></li> </ul> </div> <div id="pages-10" class="widget widget_pages"><h3 class="widget-title"><span>页面</span></h3> <ul> <li class="page_item page-item-86"><a href="https://www.go2live.cn/it%e4%bc%81%e4%b8%9a">互联网it企业一览</a></li> <li class="page_item page-item-135484"><a href="https://www.go2live.cn/%e5%85%b3%e4%ba%8e">关于</a></li> <li class="page_item page-item-396503 page_item_has_children"><a href="https://www.go2live.cn/%e5%8f%82%e8%80%83%e6%89%8b%e5%86%8c">参考手册</a> <ul class='children'> <li class="page_item page-item-346526"><a href="https://www.go2live.cn/%e5%8f%82%e8%80%83%e6%89%8b%e5%86%8c/go%e6%8a%80%e6%9c%af%e9%80%89%e5%9e%8b">go技术选型</a></li> <li class="page_item page-item-346528 page_item_has_children"><a href="https://www.go2live.cn/%e5%8f%82%e8%80%83%e6%89%8b%e5%86%8c/go%e8%af%ad%e8%a8%80%e5%8f%82%e8%80%83">go语言参考</a> <ul class='children'> <li class="page_item page-item-392240"><a href="https://www.go2live.cn/%e5%8f%82%e8%80%83%e6%89%8b%e5%86%8c/go%e8%af%ad%e8%a8%80%e5%8f%82%e8%80%83/%e6%8e%a8%e8%8d%90%e4%b9%a6%e7%9b%ae">推荐书目</a></li> </ul> </li> <li class="page_item page-item-396501"><a href="https://www.go2live.cn/%e5%8f%82%e8%80%83%e6%89%8b%e5%86%8c/ssh%e4%bd%bf%e7%94%a8%e4%bb%8b%e7%bb%8d">ssh使用介绍</a></li> </ul> </li> <li class="page_item page-item-4972"><a href="https://www.go2live.cn/%e6%bc%94%e9%81%93%e5%85%ac%e4%bc%97%e5%8f%b7%e8%ae%a2%e9%98%85%e8%af%b4%e6%98%8e">演道公众号订阅说明</a></li> <li class="page_item page-item-72"><a href="https://www.go2live.cn/%e8%8b%b1%e6%96%87%e5%9c%b0%e5%9d%80%e5%86%99%e6%b3%95">英文地址写法</a></li> </ul> </div><div id="calendar-5" class="widget widget_calendar"><div id="calendar_wrap" class="calendar_wrap"><table id="wp-calendar"> <caption>2021年七月</caption> <thead> <tr> <th scope="col" title="星期一">M</th> <th scope="col" title="星期二">T</th> <th scope="col" title="星期三">W</th> <th scope="col" title="星期四">T</th> <th scope="col" title="星期五">F</th> <th scope="col" title="星期六">S</th> <th scope="col" title="星期日">S</th> </tr> </thead> <tfoot> <tr> <td colspan="3" id="prev"><a href="https://www.go2live.cn/2021/04">« Apr</a></td> <td class="pad"> </td> <td colspan="3" id="next" class="pad"> </td> </tr> </tfoot> <tbody> <tr> <td colspan="3" class="pad"> </td><td>1</td><td>2</td><td>3</td><td>4</td> </tr> <tr> <td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td> </tr> <tr> <td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td> </tr> <tr> <td>19</td><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td> </tr> <tr> <td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td id="today">31</td> <td class="pad" colspan="1"> </td> </tr> </tbody> </table></div></div><div id="categories-3" class="widget widget_categories"><h3 class="widget-title"><span>分类目录</span></h3> <ul> <li class="cat-item cat-item-16"><a href="https://www.go2live.cn/category/program/android" title="android的四种启动模式,与五大布局,五种存储模式,四大组件::::::演道网Andriod专栏提供Andriod基础概念,具体代码演示,方便大家深入理解和运用Android。大部分代码可以直接使用。">Android</a> </li> <li class="cat-item cat-item-269"><a href="https://www.go2live.cn/category/program/go" title="go,golang,go编程语言,golang编程, go web, go 分布式, go 并发">go</a> </li> <li class="cat-item cat-item-4"><a href="https://www.go2live.cn/category/program/ios" title="iOS开发入门和提高::::::演道网IOS专栏提供一线IOS研发人员在工作学习过程中的经验总结。让大家少走弯路。">iOS</a> </li> <li class="cat-item cat-item-80"><a href="https://www.go2live.cn/category/program/java" title="java入门与提高::::::演道网java专栏提供一线java研发人员在学习工作中的经验,减少大家走的弯路,大量源码可以直接使用。">java</a> </li> <li class="cat-item cat-item-6"><a href="https://www.go2live.cn/category/basic/linux" >linux学习</a> </li> <li class="cat-item cat-item-79"><a href="https://www.go2live.cn/category/program/php" title="php入门与提高::::::演道网php专栏提供一线php研发人员在学习工作中的经验,减少大家走的弯路,大量源码可以直接使用。">php</a> </li> <li class="cat-item cat-item-81"><a href="https://www.go2live.cn/category/program/python" title="python入门与提高::::::演道网python专栏提供一线python研发人员在学习工作中的经验,减少大家走的弯路,大量源码可以直接使用。">python</a> </li> <li class="cat-item cat-item-82"><a href="https://www.go2live.cn/category/practice/shell" >shell</a> </li> <li class="cat-item cat-item-105"><a href="https://www.go2live.cn/category/prd/design" >UI设计</a> </li> <li class="cat-item cat-item-11"><a href="https://www.go2live.cn/category/dev/web" >web</a> </li> <li class="cat-item cat-item-22"><a href="https://www.go2live.cn/category/dev" title="w3cschool,HTML,CSS,PHP,DOM,JavaScript,jQuery,XML,AJAX,ASP.NET,W3C,MySQL,SQL,jquery mobile,bootstrap,Python,jquery easyui,jquery ui,angularjs::::演道网的w3cschool,HTML,CSS,PHP,DOM,JavaScript,jQuery,XML,AJAX,ASP.NET,W3C,MySQL,SQL,jquery mobile,bootstrap,Python,jquery easyui,jquery ui,angularjs等技术都在这里。">产品研发</a> </li> <li class="cat-item cat-item-73"><a href="https://www.go2live.cn/category/prd" title="互联网产品、设计、运营案例分析,方法论指导::::::演道网互联网产品设计专栏提供网上优秀的产品运营设计相关的知识和文章。">产品设计</a> </li> <li class="cat-item cat-item-104"><a href="https://www.go2live.cn/category/prd/product" >产品需求</a> </li> <li class="cat-item cat-item-23"><a href="https://www.go2live.cn/category/dev/%e5%85%b6%e5%ae%83" >其它</a> </li> <li class="cat-item cat-item-277"><a href="https://www.go2live.cn/category/reference" >参考手册</a> </li> <li class="cat-item cat-item-276"><a href="https://www.go2live.cn/category/%e5%bf%83%e7%81%b5%e5%b0%8f%e6%86%a9" title="心灵小憩">心灵小憩</a> </li> <li class="cat-item cat-item-84"><a href="https://www.go2live.cn/category/dev/db" title="数据库入门与提高::::::演道网数据库专栏提供一线数据库研发人员在学习工作中的经验,减少大家走的弯路,大量源码可以直接使用。">数据库</a> </li> <li class="cat-item cat-item-1"><a href="https://www.go2live.cn/category/nocate" >未分类</a> </li> <li class="cat-item cat-item-3"><a href="https://www.go2live.cn/category/life" >生活随感</a> </li> <li class="cat-item cat-item-266"><a href="https://www.go2live.cn/category/basic/algorithm" >算法</a> </li> <li class="cat-item cat-item-75"><a href="https://www.go2live.cn/category/grow" >观念更新</a> </li> <li class="cat-item cat-item-270"><a href="https://www.go2live.cn/category/practice/keng" >踩坑</a> </li> <li class="cat-item cat-item-83"><a href="https://www.go2live.cn/category/practice/devops" >运维</a> </li> <li class="cat-item cat-item-106"><a href="https://www.go2live.cn/category/prd/market" >运营推广</a> </li> </ul> </div><div id="tag_cloud-12" class="widget widget_tag_cloud"><h3 class="widget-title"><span>标签</span></h3><div class="tagcloud"><a href="https://www.go2live.cn/tag/android" class="tag-cloud-link tag-link-39 tag-link-position-1" style="font-size: 18.980392156863pt;" aria-label="Android (47 items)">Android</a> <a href="https://www.go2live.cn/tag/android%e5%bc%80%e5%8f%91%e6%8a%80%e6%9c%af%e5%91%a8%e6%8a%a5" class="tag-cloud-link tag-link-258 tag-link-position-2" style="font-size: 14.862745098039pt;" aria-label="Android开发技术周报 (16 items)">Android开发技术周报</a> <a href="https://www.go2live.cn/tag/angularjs" class="tag-cloud-link tag-link-119 tag-link-position-3" style="font-size: 12.300653594771pt;" aria-label="AngularJS (8 items)">AngularJS</a> <a href="https://www.go2live.cn/tag/django" class="tag-cloud-link tag-link-134 tag-link-position-4" style="font-size: 16.052287581699pt;" aria-label="django (22 items)">django</a> <a href="https://www.go2live.cn/tag/flask" class="tag-cloud-link tag-link-259 tag-link-position-5" style="font-size: 8pt;" aria-label="flask (2 items)">flask</a> <a href="https://www.go2live.cn/tag/http" class="tag-cloud-link tag-link-45 tag-link-position-6" style="font-size: 17.516339869281pt;" aria-label="http (32 items)">http</a> <a href="https://www.go2live.cn/tag/jquery" class="tag-cloud-link tag-link-121 tag-link-position-7" style="font-size: 10.013071895425pt;" aria-label="jQuery (4 items)">jQuery</a> <a href="https://www.go2live.cn/tag/nginx" class="tag-cloud-link tag-link-77 tag-link-position-8" style="font-size: 11.843137254902pt;" aria-label="nginx (7 items)">nginx</a> <a href="https://www.go2live.cn/tag/nodejs" class="tag-cloud-link tag-link-120 tag-link-position-9" style="font-size: 13.490196078431pt;" aria-label="NodeJS (11 items)">NodeJS</a> <a href="https://www.go2live.cn/tag/securecrt" class="tag-cloud-link tag-link-103 tag-link-position-10" style="font-size: 19.895424836601pt;" aria-label="securecrt (59 items)">securecrt</a> <a href="https://www.go2live.cn/tag/shell" class="tag-cloud-link tag-link-71 tag-link-position-11" style="font-size: 18.522875816993pt;" aria-label="shell (42 items)">shell</a> <a href="https://www.go2live.cn/tag/view" class="tag-cloud-link tag-link-29 tag-link-position-12" style="font-size: 12.758169934641pt;" aria-label="View (9 items)">View</a> <a href="https://www.go2live.cn/tag/vim" class="tag-cloud-link tag-link-61 tag-link-position-13" style="font-size: 18.248366013072pt;" aria-label="vim (39 items)">vim</a> <a href="https://www.go2live.cn/tag/wordpress%e4%bc%98%e5%8c%96" class="tag-cloud-link tag-link-109 tag-link-position-14" style="font-size: 10.745098039216pt;" aria-label="wordpress优化 (5 items)">wordpress优化</a> <a href="https://www.go2live.cn/tag/%e4%bb%a3%e7%a0%81%e7%a4%ba%e8%8c%83" class="tag-cloud-link tag-link-43 tag-link-position-15" style="font-size: 17.241830065359pt;" aria-label="代码示范 (30 items)">代码示范</a> <a href="https://www.go2live.cn/tag/%e5%89%8d%e7%ab%af%e5%bc%80%e5%8f%91" class="tag-cloud-link tag-link-118 tag-link-position-16" style="font-size: 15.503267973856pt;" aria-label="前端开发 (19 items)">前端开发</a> <a href="https://www.go2live.cn/tag/%e5%8e%9f%e5%88%9b" class="tag-cloud-link tag-link-12 tag-link-position-17" style="font-size: 21.542483660131pt;" aria-label="原创 (91 items)">原创</a> <a href="https://www.go2live.cn/tag/%e5%9f%ba%e7%a1%80%e6%a6%82%e5%bf%b5" class="tag-cloud-link tag-link-30 tag-link-position-18" style="font-size: 16.235294117647pt;" aria-label="基础概念 (23 items)">基础概念</a> <a href="https://www.go2live.cn/tag/%e5%a4%9a%e7%ba%bf%e7%a8%8b" class="tag-cloud-link tag-link-49 tag-link-position-19" style="font-size: 10.013071895425pt;" aria-label="多线程 (4 items)">多线程</a> <a href="https://www.go2live.cn/tag/%e5%ad%a6%e4%bc%9a%e6%8f%90%e9%97%ae" class="tag-cloud-link tag-link-129 tag-link-position-20" style="font-size: 15.137254901961pt;" aria-label="学会提问 (17 items)">学会提问</a> <a href="https://www.go2live.cn/tag/%e5%b7%a5%e5%85%b7" class="tag-cloud-link tag-link-28 tag-link-position-21" style="font-size: 9.0980392156863pt;" aria-label="工具 (3 items)">工具</a> <a href="https://www.go2live.cn/tag/%e5%b7%a5%e5%85%b7%e5%ad%a6%e4%b9%a0" class="tag-cloud-link tag-link-17 tag-link-position-22" style="font-size: 10.745098039216pt;" aria-label="工具学习 (5 items)">工具学习</a> <a href="https://www.go2live.cn/tag/%e5%b7%a5%e7%a8%8b%e5%b8%88%e6%97%a5%e5%b8%b8" class="tag-cloud-link tag-link-218 tag-link-position-23" style="font-size: 18.614379084967pt;" aria-label="工程师日常 (43 items)">工程师日常</a> <a href="https://www.go2live.cn/tag/%e5%bc%80%e5%8f%91%e8%80%85%e5%a4%b4%e6%9d%a1" class="tag-cloud-link tag-link-170 tag-link-position-24" style="font-size: 22pt;" aria-label="开发者头条 (102 items)">开发者头条</a> <a href="https://www.go2live.cn/tag/%e5%be%ae%e4%bf%a1%e5%b0%8f%e7%a8%8b%e5%ba%8f" class="tag-cloud-link tag-link-116 tag-link-position-25" style="font-size: 14.588235294118pt;" aria-label="微信小程序 (15 items)">微信小程序</a> <a href="https://www.go2live.cn/tag/%e5%be%ae%e4%bf%a1%e5%ba%94%e7%94%a8%e5%8f%b7" class="tag-cloud-link tag-link-117 tag-link-position-26" style="font-size: 14.40522875817pt;" aria-label="微信应用号 (14 items)">微信应用号</a> <a href="https://www.go2live.cn/tag/%e5%bf%83%e6%99%ba" class="tag-cloud-link tag-link-35 tag-link-position-27" style="font-size: 12.758169934641pt;" aria-label="心智 (9 items)">心智</a> <a href="https://www.go2live.cn/tag/%e6%8a%80%e6%9c%af%e6%9e%b6%e6%9e%84" class="tag-cloud-link tag-link-127 tag-link-position-28" style="font-size: 16.967320261438pt;" aria-label="技术架构 (28 items)">技术架构</a> <a href="https://www.go2live.cn/tag/%e6%8e%a8%e8%8d%90" class="tag-cloud-link tag-link-32 tag-link-position-29" style="font-size: 8pt;" aria-label="推荐 (2 items)">推荐</a> <a href="https://www.go2live.cn/tag/%e6%95%88%e7%8e%87" class="tag-cloud-link tag-link-37 tag-link-position-30" style="font-size: 11.385620915033pt;" aria-label="效率 (6 items)">效率</a> <a href="https://www.go2live.cn/tag/%e6%a0%b8%e5%bf%83%e6%a6%82%e5%bf%b5" class="tag-cloud-link tag-link-34 tag-link-position-31" style="font-size: 10.013071895425pt;" aria-label="核心概念 (4 items)">核心概念</a> <a href="https://www.go2live.cn/tag/%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" class="tag-cloud-link tag-link-33 tag-link-position-32" style="font-size: 10.745098039216pt;" aria-label="源码分析 (5 items)">源码分析</a> <a href="https://www.go2live.cn/tag/%e7%9f%a5%e8%af%86%e6%80%bb%e7%bb%93" class="tag-cloud-link tag-link-97 tag-link-position-33" style="font-size: 9.0980392156863pt;" aria-label="知识总结 (3 items)">知识总结</a> <a href="https://www.go2live.cn/tag/%e7%a0%81%e5%86%9c%e5%91%a8%e5%88%8a" class="tag-cloud-link tag-link-257 tag-link-position-34" style="font-size: 15.320261437908pt;" aria-label="码农周刊 (18 items)">码农周刊</a> <a href="https://www.go2live.cn/tag/%e7%a7%af%e7%b4%af" class="tag-cloud-link tag-link-25 tag-link-position-35" style="font-size: 10.745098039216pt;" aria-label="积累 (5 items)">积累</a> <a href="https://www.go2live.cn/tag/%e7%ae%97%e6%b3%95" class="tag-cloud-link tag-link-69 tag-link-position-36" style="font-size: 13.124183006536pt;" aria-label="算法 (10 items)">算法</a> <a href="https://www.go2live.cn/tag/%e7%ba%bf%e7%a8%8b" class="tag-cloud-link tag-link-51 tag-link-position-37" style="font-size: 16.692810457516pt;" aria-label="线程 (26 items)">线程</a> <a href="https://www.go2live.cn/tag/%e7%bf%bb%e8%af%91" class="tag-cloud-link tag-link-31 tag-link-position-38" style="font-size: 9.0980392156863pt;" aria-label="翻译 (3 items)">翻译</a> <a href="https://www.go2live.cn/tag/%e8%a7%82%e7%82%b9" class="tag-cloud-link tag-link-59 tag-link-position-39" style="font-size: 9.0980392156863pt;" aria-label="观点 (3 items)">观点</a> <a href="https://www.go2live.cn/tag/%e8%ae%a8%e8%ae%ba" class="tag-cloud-link tag-link-46 tag-link-position-40" style="font-size: 14.130718954248pt;" aria-label="讨论 (13 items)">讨论</a> <a href="https://www.go2live.cn/tag/%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0" class="tag-cloud-link tag-link-36 tag-link-position-41" style="font-size: 12.300653594771pt;" aria-label="读书笔记 (8 items)">读书笔记</a> <a href="https://www.go2live.cn/tag/%e8%bd%ac%e8%bd%bd" class="tag-cloud-link tag-link-14 tag-link-position-42" style="font-size: 14.588235294118pt;" aria-label="转载 (15 items)">转载</a> <a href="https://www.go2live.cn/tag/%e8%bf%90%e7%bb%b4" class="tag-cloud-link tag-link-18 tag-link-position-43" style="font-size: 11.843137254902pt;" aria-label="运维 (7 items)">运维</a> <a href="https://www.go2live.cn/tag/%e9%9d%a2%e8%af%95" class="tag-cloud-link tag-link-38 tag-link-position-44" style="font-size: 18.980392156863pt;" aria-label="面试 (47 items)">面试</a> <a href="https://www.go2live.cn/tag/%e9%b8%a1%e6%b1%a4%e6%96%87" class="tag-cloud-link tag-link-94 tag-link-position-45" style="font-size: 10.013071895425pt;" aria-label="鸡汤文 (4 items)">鸡汤文</a></div> </div><div id="recent-comments-4" class="widget widget_recent_comments"><h3 class="widget-title"><span>近期评论</span></h3><ul id="recentcomments"><li class="recentcomments"><span class="comment-author-link"><a href='https://personalloans2.com/' rel='external nofollow' class='url'>Spotloan</a></span>发表在《<a href="https://www.go2live.cn/basic/linux/linux-useradd%e5%91%bd%e4%bb%a4%e6%b7%bb%e5%8a%a0%e6%96%b0%e7%94%a8%e6%88%b7%e5%9b%be%e6%96%87%e8%af%a6%e8%a7%a3-%e6%bc%94%e9%81%93%e7%bd%91.html/comment-page-7#comment-100220">Linux useradd命令添加新用户图文详解-演道网</a>》</li><li class="recentcomments"><span class="comment-author-link"><a href='https://paydayloans3.com/' rel='external nofollow' class='url'>Speedycash</a></span>发表在《<a href="https://www.go2live.cn/basic/linux/linux-useradd%e5%91%bd%e4%bb%a4%e6%b7%bb%e5%8a%a0%e6%96%b0%e7%94%a8%e6%88%b7%e5%9b%be%e6%96%87%e8%af%a6%e8%a7%a3-%e6%bc%94%e9%81%93%e7%bd%91.html/comment-page-7#comment-100219">Linux useradd命令添加新用户图文详解-演道网</a>》</li><li class="recentcomments"><span class="comment-author-link"><a href='http://viagra200.com/' rel='external nofollow' class='url'>JudyDak</a></span>发表在《<a href="https://www.go2live.cn/basic/linux/linux-useradd%e5%91%bd%e4%bb%a4%e6%b7%bb%e5%8a%a0%e6%96%b0%e7%94%a8%e6%88%b7%e5%9b%be%e6%96%87%e8%af%a6%e8%a7%a3-%e6%bc%94%e9%81%93%e7%bd%91.html/comment-page-7#comment-100218">Linux useradd命令添加新用户图文详解-演道网</a>》</li><li class="recentcomments"><span class="comment-author-link"><a href='https://nolvadex10.com/' rel='external nofollow' class='url'>SamDak</a></span>发表在《<a href="https://www.go2live.cn/basic/linux/linux-useradd%e5%91%bd%e4%bb%a4%e6%b7%bb%e5%8a%a0%e6%96%b0%e7%94%a8%e6%88%b7%e5%9b%be%e6%96%87%e8%af%a6%e8%a7%a3-%e6%bc%94%e9%81%93%e7%bd%91.html/comment-page-7#comment-100217">Linux useradd命令添加新用户图文详解-演道网</a>》</li><li class="recentcomments"><span class="comment-author-link"><a href='http://hydrochlorothiazide125.com/' rel='external nofollow' class='url'>AlanDak</a></span>发表在《<a href="https://www.go2live.cn/basic/linux/linux-useradd%e5%91%bd%e4%bb%a4%e6%b7%bb%e5%8a%a0%e6%96%b0%e7%94%a8%e6%88%b7%e5%9b%be%e6%96%87%e8%af%a6%e8%a7%a3-%e6%bc%94%e9%81%93%e7%bd%91.html/comment-page-7#comment-100216">Linux useradd命令添加新用户图文详解-演道网</a>》</li></ul></div> </div> </div><!--sidebars--> </aside> <!-- End Sidebar --> </div> </div> <footer id="site-footer" role="contentinfo"> <!--start copyrights--> <div class="copyrights"> <div class="container"> <div class="row" id="copyright-note"> <span> © 2021 演道网 <span class="footer-info-right"> | WordPress Theme by <a href="https://superbthemes.com/"> Superb WordPress Themes</a> </span> <div class="top"> <a href="#top" class="toplink">Back to Top ↑</a> </div> </div> </div> </div> <!--end copyrights--> </footer><!-- #site-footer --> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Emojify.js/js/emojify.min.js?ver=9.9.9'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/ClipBoard/clipboard.min.js?ver=2.0.1'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/components/prism-core.min.js?ver=1.15.0'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/plugins/autoloader/prism-autoloader.min.js?ver=1.15.0'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/plugins/toolbar/prism-toolbar.min.js?ver=1.15.0'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/plugins/show-language/prism-show-language.min.js?ver=1.15.0'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js?ver=1.15.0'></script> <script type='text/javascript' src='https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Turndown/turndown.js?ver=5.0.1'></script> <script type='text/javascript' src='https://www.go2live.cn/wp-content/themes/feather-magazine/js/customscripts.js?ver=4.9.8'></script> <script type='text/javascript' src='https://www.go2live.cn/wp-includes/js/wp-embed.min.js?ver=4.9.8'></script> <script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=default&ver=1.3.10'></script> <script type="text/javascript"> window.onload = function () { emojify.setConfig({ img_dir: "https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Emojify.js/images/basic",//前端emoji资源地址 blacklist: { 'ids': [], 'classes': ['no-emojify'], 'elements': ['^script$', '^textarea$', '^pre$', '^code$'] } }); emojify.run(); } </script> <script type="text/javascript"> Prism.plugins.autoloader.languages_path = "https://cdn.jsdelivr.net/wp/wp-editormd/tags/9.9.9/assets/Prism.js/components/"; </script> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?7c0353d5197094ff9721e67666d9b6b4"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> </body> </html>