diff options
Diffstat (limited to 'files/zh-cn/learn/server-side/express_nodejs')
29 files changed, 6311 insertions, 0 deletions
diff --git a/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html new file mode 100644 index 0000000000..b4802ca3c5 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/deployment/index.html @@ -0,0 +1,526 @@ +--- +title: 'Express 教程 7: 部署到生产环境' +slug: learn/Server-side/Express_Nodejs/deployment +tags: + - Express + - Learn + - Node + - 初学者 + - 部署 +translation_of: Learn/Server-side/Express_Nodejs/deployment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">现在你已经创建(并测试)了一个不错的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆</a> 网站了,你打算把它发布到一个公共网络服务器,这样图书馆职工和网络上的其他成员就可以访问它了。这篇文章总结了你可以怎样找到一台主机部署你的网站,以及你需要为站点准备到生产环境做什么。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>完成前面所有的指南主题,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express Tutorial Part 6: Working with forms</a>.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习你可以怎样以及在哪里部署一个Express 应用到生产环境。</td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>一旦您的站点完成(或完成“足够”以开始公共测试),您将需要将其托管在比您的个人开发计算机,更公开和可访问的地方。</p> + +<p>到目前为止,您一直在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">开发环境</a>中工作,使用 Express / Node 作为Web服务器,将您的站点共享到本地浏览器/网络,并使用(不安全的)开发设置运行您的网站,以显示调试和其他私人信息。在您可以在外部托管网站之前,您首先必须:</p> + +<ul> + <li>选择托管Express 应用程序的环境。</li> + <li>对项目设置进行一些更改。</li> + <li>设置生产级别的基础架构,以服务您的网站。</li> +</ul> + +<p>本教程提供了,有关选择托管站点的选项的一些指导,简要概述了为使您的Express 应用程序准备好生产,所需执行的操作,以及如何将LocalLibrary 网站安装到 <a href="https://www.heroku.com/">Heroku</a>云托管上的工作示例服务。</p> + +<p>请记住,您不必使用Heroku - 还有其他托管服务可用。我们还提供了一个单独的教程,以展示如何在 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry </a>上安装LocalLibrary。</p> + +<h2 id="什么是生产环境?">什么是生产环境?</h2> + +<p>生产环境是服务器计算机提供的环境,您可以在其中运行网站,以供外部使用。环境包括:</p> + +<ul> + <li>网站运行的计算机硬件。</li> + <li>操作系统(例如Linux或Windows)。</li> + <li>编程语言运行库和框架库,在其上编写您的网站。</li> + <li>Web 服务器基础结构,可能包含Web服务器,反向代理,负载平衡器等。</li> + <li>您的网站所依赖的数据库。</li> +</ul> + +<p>服务器计算机,可以位于您的场所,并通过快速链接,连接到 Internet,但使用 “托管在云上” 的计算机更为常见。这实际上意味着,您的代码运行在托管公司的数据中心的某台远程计算机(或可能是“虚拟”计算机)。远程服务器,通常会以特定价格提供互联网连接,和一些保证级别的计算资源(例如CPU,RAM,存储器等)。</p> + +<p>这种可远程访问的计算/网络硬件,称为基础架构即服务(IaaS)。许多IaaS 供应商,提供预安装特定操作系统的选项,您必须在其上,安装生产环境的其他组件。其他供应商,允许您选择功能更全面的环境,可能包括完整的 node 设置。</p> + +<div class="note"> +<p><strong>注意:</strong> 预构建环境,可以使您的网站设置变得非常简单,因为它们会减少配置,但可用选项可能会限制您使用不熟悉的服务器(或其他组件),并且可能基于较旧版本的操作系统。通常最好自己安装组件,以便获得所需的组件,并且当您需要升级系统的某些部分时,您可以知道从哪里开始!</p> +</div> + +<p>其他托管服务提供商,支持 Express 作为平台即服务(PaaS)产品的一部分。使用此类托管时,您无需担心大多数生产环境(服务器,负载平衡器等),因为主机平台会为您处理这些问题。这使得部署非常简单,因为您只需要专注于 Web 应用程序,而不是任何其他服务器基础结构。</p> + +<p>一些开发人员选择 IaaS ,相对于 PaaS ,IaaS 提供更高灵活性,而其他开发人员偏好 PaaS 的降低维护开销,和更轻松的扩展性。当您在一开始使用时,在 PaaS 系统上设置您的网站,要容易得多,因此我们将在本教程中使用 PaaS。</p> + +<div class="note"> +<p><strong>提示:</strong> 如果您选择Node/Express友好的托管服务提供商,他们应该提供,有关如何使用Web服务器,应用程序服务器,反向代理等不同配置,来设置 Express 网站的说明。例如,在<a href="https://www.digitalocean.com/community/tutorials?q=node">数字海洋node社区文档</a>中,有许多各种配置的手把手指南。</p> +</div> + +<h2 id="选择一个主机供应商">选择一个主机供应商</h2> + +<p>众所周知,众多托管服务提供商,都积极支持或与Node(和Express)合作。这些供应商提供不同类型的环境(IaaS,PaaS),以及不同价格的不同级别的计算和网络资源。</p> + +<div class="note"> +<p><strong>提示:</strong> 有很多托管解决方案,他们的服务和定价,可能会随着时间而改变。虽然我们在下面介绍几个选项,但在选择托管服务提供商之前,有必要自己进行互联网搜索。</p> +</div> + +<p>选择主机时需要考虑的一些事项:</p> + +<ul> + <li>您的网站可能有多忙,以及满足该需求所需的数据,和计算资源的成本。</li> + <li>水平扩展(添加更多机器)和垂直扩展(升级到更强大的机器)的支持级别,以及这样做的成本。</li> + <li>供应商有数据中心的地方,因此访问可能是最快的。</li> + <li>主机正常运行时间和停机时间的历史表现。</li> + <li>用于管理站点的工具 - 易于使用且安全(例如 SFTP 与 FTP)。</li> + <li>用于监控服务器的内置框架。</li> + <li>已知限制。有些主机会故意阻止某些服务(例如电子邮件)。其他在某些价格层中,仅提供一定数小时的 “实时时间”,或者仅提供少量存储空间。</li> + <li>额外的好处。一些提供商将提供免费域名和SSL证书支持,否则您将不得不为此另外支付费用。</li> + <li>您所依赖的“免费”等级,是否会随着时间的推移而过期,以及迁移到更昂贵等级的成本,是否意味着您最好在一开始就使用其他服务!</li> +</ul> + +<p>当你刚开始时,好消息是有很多网站提供“免费”的计算环境,尽管有一些条件。例如, <a href="https://www.heroku.com/">Heroku </a>“永远” 提供免费但资源有限的PaaS 环境,而 <a href="http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-free-tier.html">Amazon Web Services</a>, <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/">Microsoft Azure </a>和开源选项 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Installing_on_PWS_Cloud_Foundry">PWS/Cloud Foundry </a>在您第一次加入时,提供免费信用额度。</p> + +<p>许多提供商还拥有“基本”层,可提供更多有用的计算能力,和更少的限制。举例来说, <a href="https://www.digitalocean.com/">Digital Ocean</a> 是一个流行的托管服务提供商,它提供了一个相对便宜的基本计算层(在本教程写作时,是每月5美元的较低范围)。</p> + +<div class="note"> +<p><strong>注意:</strong> 请记住,价格不是唯一的选择标准。如果您的网站成功,可能会发现可扩展性是最重要的考虑因素。</p> +</div> + +<h2 id="准备好发布你的网站">准备好发布你的网站</h2> + +<p>发布网站时,要考虑的主要问题是网络安全性和性能。至少,您需要删除开发期间,错误页面上包含的堆栈跟踪,整理日志记录,并设置适当的标头,以避免许多常见的安全威胁。</p> + +<p>在以下小节中,我们概述了您应该对应用进行的、最重要的更改。</p> + +<div class="note"> +<p><strong>提示:</strong> Express文档中还有其他有用的提示 - 请参阅“<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生产最佳实践:性能和可靠性</a>”,以及“<a href="https://expressjs.com/en/advanced/best-practice-security.html">生产最佳实践:安全性</a>”。</p> +</div> + +<h3 id="设置_NODE_ENV_为_'production'">设置 NODE_ENV 为 'production'</h3> + +<p>我们可以通过将 <code>NODE_ENV</code> 环境变量,设置为 production ,来删除错误页面中的堆栈跟踪(默认设置为 “development” )。除了生成较为不详细的错误消息之外,还要将变量设置为生产缓存视图模板,和从CSS扩展生成的CSS文件。测试表明,将<code>NODE_ENV</code>设置为生产,可以将应用程序性能提高三倍!</p> + +<p>可以使用导出或环境文件,或使用OS初始化系统,以进行此更改。</p> + +<div class="note"> +<p><strong>注意:</strong> 这实际上是在环境设置,而不是应用中所做的更改,但重要的是,要注意这里!我们将在下面,展示如何为我们的托管示例设置。</p> +</div> + +<h3 id="Log_appropriately">Log appropriately</h3> + +<p>记录呼叫会对高流量网站产生影响。在生产环境中,您可能需要记录网站活动(例如,跟踪流量,或记录API调用),但您应尝试最小化为调试目的而添加的日志记录量。</p> + +<p>在生产环境中,最小化“调试”日志记录的一种方法,是使用类似<a href="https://www.npmjs.com/package/debug">调试debug </a>的模块,允许您通过设置环境变量,来控制执行的日志记录。例如,下面的代码片段,显示了如何设置“author”日志记录。调试变量使用名称“author”声明,并且将自动显示,来自此对象的所有日志的前缀“author”。</p> + +<pre class="brush: js"><strong>var debug = require('debug')('author');</strong> + +// Display Author update form on GET +exports.author_update_get = function(req, res, next) { + + req.sanitize('id').escape().trim(); + Author.findById(req.params.id, function(err, author) { + if (err) { +<strong> debug('update error:' + err);</strong> + return next(err); + } + //On success + res.render('author_form', { title: 'Update Author', author: author }); + }); + +};</pre> + +<p>然后,您可以通过在<code>DEBUG</code>环境变量中,将它们指定为逗号分隔列表,来启用特定日志集。您可以设置显示作者和书籍日志的变量,如图所示(也支持通配符)。</p> + +<pre class="brush: bash">#Windows +set DEBUG=author,book + +#Linux +export DEBUG="author,book" +</pre> + +<div class="note"> +<p><strong>挑战:</strong> 调用<code>debug</code>可以替换您以前使用<code>console.log()</code>或<code>console.error()</code>执行的日志记录。通过调试模块<a href="https://www.npmjs.com/package/debug">debug</a>进行日志记录,替换代码中的所有<code>console.log()</code>调用。通过设置 DEBUG 变量,并在其中记录对日志记录的影响,在开发环境中,打开和关闭日志记录。</p> +</div> + +<p>如果您需要记录网站活动,可以使用 Winston 或 Bunyan 等日志库。有关此主题的更多信息,请参阅:<a href="https://expressjs.com/en/advanced/best-practice-performance.html">生产最佳实践:性能和可靠性</a>。</p> + +<h3 id="使用_gzipdeflate_压缩响应文件">使用 gzip/deflate 压缩响应文件</h3> + +<p>Web服务器,通常可以压缩发送回客户端的 HTTP 响应,从而显着减少客户端获取和加载页面所需的时间。使用的压缩方法,取决于客户端在请求中支持的解压缩方法(如果不支持压缩方法,则响应将以未压缩的方式发送)。</p> + +<p>您可以使用压缩中间件 <a href="https://www.npmjs.com/package/compression">compression</a>,将其添加到您的站点。通过在项目的根目录下,运行以下命令,将其安装到项目中。</p> + +<pre class="brush: bash">npm install compression</pre> + +<p>打开<strong>./app.js</strong>,并导入压缩库,如图所示。使用<code>use()</code>方法,将压缩库添加到中间件链(这应该出现在您想要压缩的任何路由之前 - 在本教程这种情况下,全部都是!)</p> + +<pre class="brush: js">var catalogRouter = require('./routes/catalog'); //Import routes for "catalog" area of site +<strong>var compression = require('compression');</strong> + +// Create the Express application object +var app = express(); + +... + +<strong>app.use(compression()); //Compress all routes</strong> + +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', indexRouter); +app.use('/users', usersRouter); +app.use('/catalog', catalogRouter); // Add catalog routes to middleware chain. + +... +</pre> + +<div class="note"> +<p><strong>注意</strong>: 对于生产中流量较大的网站,您不会使用此中间件。相反,你会使用像 Nginx 这样的反向代理。</p> +</div> + +<h3 id="使用_Helmet_避免被常见漏洞侵袭">使用 Helmet 避免被常见漏洞侵袭</h3> + +<p><a href="https://www.npmjs.com/package/helmet">Helmet</a> 是一个中间件包,可以通过设置适当的 HTTP 标头,来帮助保护您的应用,免受一些众所周知的 Web 漏洞的影响(有关它设置的标头/防护漏洞的详细信息,请参阅<a href="https://helmetjs.github.io/docs/">文档</a>)。</p> + +<p>通过在项目的根目录下,运行以下命令,将其安装到项目中。</p> + +<pre class="brush: bash">npm install helmet +</pre> + +<p>打开<strong>./app.js</strong>,并导入如图所示的<em> helmet</em> 库。然后使用<code>use()</code>方法将模块添加到中间件链。</p> + +<pre class="brush: js">var compression = require('compression'); +<strong>var helmet = require('helmet'); +</strong> +// Create the Express application object +var app = express(); + +<strong>app.use(helmet())</strong>; +...</pre> + +<div class="note"> +<p id="production-best-practices-performance-and-reliability"><strong>注意:</strong> 上面的命令,添加了对大多数站点有意义的可用标头子集。您可以按照<a href="https://www.npmjs.com/package/helmet">npm</a>上的说明,根据需要添加/禁用特定标头。</p> +</div> + +<h2 id="例子:在_Heroku_上安装一个本地图书馆">例子:在 Heroku 上安装一个本地图书馆</h2> + +<p>本节提供了如何在<a href="http://heroku.com">Heroku PaaS cloud</a>云上安装LocalLibrary的实际演示。</p> + +<h3 id="为什么选择_Heroku">为什么选择 Heroku?</h3> + +<p>Heroku 是运行时间最长,且最受欢迎的基于云的 PaaS 服务之一。它最初只支持 Ruby 应用程序,但现在可用于托管来自许多编程环境的应用程序,包括Node(以及Express)!</p> + +<p>我们选择使用 Heroku 有以下几个原因: </p> + +<ul> + <li>Heroku 有一个<a href="https://www.heroku.com/pricing">免费套餐</a>(尽管有一些限制)。</li> + <li>作为PaaS,Heroku为我们提供了大量的 Web 基础架构。这使得入门更加容易,因为您不必担心服务器,负载平衡器,反向代理,崩溃时重新启动网站,或者 Heroku 为我们提供的任何其他 Web 基础结构。</li> + <li>虽然它确实有一些限制,但这些不会影响这个特定的应用程序。例如: + <ul> + <li>Heroku只提供短期存储,因此用户上传的文件无法安全地存储在Heroku本身。</li> + <li>如果半小时内没有请求,免费套餐将使不活动的网络应用程序进入睡眠。然后,该网站可能需要几秒钟才能被唤醒。</li> + <li>免费套餐将您网站运行的时间,限制为每月一定的小时数(不包括网站“睡着”的时间)。这对于低使用/演示站点来说很好,但如果需要100%的正常运行时间,则不适用。</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Heroku 官方文档</a>中列出的其他限制。</li> + </ul> + </li> + <li>大多数情况下它只是可以工作,如果你最终喜欢它,并希望升级,那么扩展你的应用程序非常容易。</li> +</ul> + +<p>虽然 Heroku 非常适合举办此演示,但它可能并不适合您的真实网站。 Heroku可以轻松设置和扩展,但代价是灵活性较低,而且一旦退出免费套餐,可能会花费更多。</p> + +<h3 id="Heroku_如何工作?"> Heroku 如何工作?</h3> + +<p>Heroku在一个或多个“<a href="https://devcenter.heroku.com/articles/dynos">Dynos</a>”中运行网站,这些“Dynos”是独立的虚拟化Unix容器,提供运行应用程序所需的环境。 Dynos 是完全隔离的,并且有一个短暂的文件系统(一个短暂的文件系统,每次dyno重新启动时都会清理/清空)。 dynos 默认共享的唯一内容,是应用程序<a href="https://devcenter.heroku.com/articles/config-vars">配置变量</a>。 Heroku内部使用负载均衡器,将Web流量分配给所有“web”dynos。由于它们之间没有任何共享,Heroku可以通过添加更多dynos,来水平扩展应用程序(当然,您可能还需要扩展数据库,以接受其他连接)。</p> + +<p>由于文件系统是短暂的,因此无法直接安装应用程序所需的服务(例如数据库,队列,缓存系统,存储,电子邮件服务等)。相反,Heroku Web应用程序使用 Heroku 或第三方作为独立“附加组件”提供的支持服务。连接到Web应用程序后,可以通过环境变量,在Web应用程序中访问附加服务。</p> + +<p>为了执行您的应用程序,Heroku需要能够设置适当的环境和依赖关系,并了解它是如何启动的。对于Node应用程序,它所需的所有信息都是从<strong>package.json</strong>文件中获取的。</p> + +<p>开发人员使用特殊的客户端应用程序/终端,与Heroku交互,这很像Unix bash脚本。这允许您上传存储在git存储库中的代码,检查正在运行的进程,查看日志,设置配置变量等等!</p> + +<p>为了让我们的应用程序在Heroku上工作,我们需要将我们的Express Web应用程序放入git存储库,并对 package.json 进行一些小的更改。完成后,我们可以设置Heroku帐户,获取Heroku客户端,并使用它来安装我们的网站。</p> + +<p>这是您开始教程所需的全部概述(有关更全面的指南,请参阅<a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">带有Node.js的Heroku入门</a>)。</p> + +<h3 id="在_Github_上创建一个应用仓库">在 Github 上创建一个应用仓库</h3> + +<p>Heroku 与 <strong>git </strong>源代码版本控制系统紧密集成,使用它来上传/同步您对实时运行系统所做的任何更改。它通过添加一个名为 heroku 的新 Heroku“远程”存储库,来指向您在Heroku云上的源存储库。在开发期间,您使用 git 在“主”存储库 master 中存储更改。如果要部署站点,请将更改同步到 Heroku 存储库。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果您习惯于遵循良好的软件开发实践,那么您可能已经在使用git或其他一些SCM系统。如果您已有git存储库,则可以跳过此步骤。</p> +</div> + +<p>有很多方法可以使用git,但最简单的方法之一,是首先在<a href="https://github.com/">GitHub</a>上建立一个帐户,在那里创建存储库,然后在本地同步它:</p> + +<ol> + <li>访问 <a href="https://github.com/">https://github.com/</a> 并创建一个帐户。</li> + <li>登录后,单击顶部工具栏中的<strong> +</strong> 号链接,然后选择新建存储库<strong>New repository</strong>。</li> + <li>填写此表单上的所有字段。虽然这些不是强制性的,但强烈建议使用它们。 + <ul> + <li>输入新的存储库名称(例如,express-locallibrary-tutorial)和描述(例如 “以Express(node)编写的本地图书馆网站”)。</li> + <li>在 Add .gitignore 选择列表中选择 <strong>Node</strong>。</li> + <li>在添加许可证 <em>Add license</em> 选择列表中,选择您偏好的许可证。</li> + <li>点选 <strong>使用自述文件初始化此存储库 </strong>“<strong>Initialize this repository with a README</strong>”</li> + </ul> + </li> + <li>按 <strong>Create repository</strong>.</li> + <li>单击新仓库页面上的绿色“克隆或下载”按钮 "<strong>Clone or download</strong>"。</li> + <li>从显示的对话框的文本字段,复制URL值(它应该类似于:<strong>https://github.com/<em><your_git_user_id></em>/express-locallibrary-tutorial.git</strong>)。</li> +</ol> + +<p>现在创建了存储库(“repo”),我们将要在本地计算机上克隆它:</p> + +<ol> + <li>为您的本地计算机安装git(您可以在<a href="https://git-scm.com/downloads">此处</a>找到不同平台的版本)。</li> + <li>打开命令提示符/终端,并使用您在上面复制的 URL ,克隆clone存储库: + <pre class="brush: bash">git clone https://github.com/<strong><em><your_git_user_id></em></strong>/express-locallibrary-tutorial.git +</pre> + 这将在当前时间点之后,创建存储库。</li> + <li>到新的仓库。 + <pre class="brush: bash">cd express-locallibrary-tutorial</pre> + </li> +</ol> + +<p>最后一步,是复制你的应用程序,然后使用 git ,将文件添加到你的仓库:</p> + +<ol> + <li>将Express应用程序,复制到此文件夹中(不包括<strong>/node_modules</strong>,其中包含您应根据需要,从NPM获取的依赖项文件)。</li> + <li>打开命令提示符/终端,并使用<code>add</code>命令,将所有文件添加到 git。</li> + <li> + <pre class="brush: bash">git add -A +</pre> + </li> + <li>使用 status 命令,检查要添加的所有文件是否正确(您希望包含源文件,而不是二进制文件,临时文件等)。它应该看起来有点像下面的列表。 + <pre>> git status +On branch master +Your branch is up-to-date with 'origin/master'. +Changes to be committed: + (use "git reset HEAD <file>..." to unstage) + + new file: ...</pre> + </li> + <li>如果您满意,请将文件提交到本地存储库: + <pre class="brush: bash">git commit -m "First version of application moved into github"</pre> + </li> + <li>然后使用以下内容,将本地存储库同步到Github网站: + <pre>git push origin master</pre> + </li> +</ol> + +<p>完成此操作后,您应该可以返回创建存储库的Github上的页面,刷新页面,并查看您的整个应用程序现已上传。使用此添加/提交/推送循环,您可以在文件更改时,继续更新存储库。</p> + +<div class="note"> +<p><strong>提示:</strong> 这是备份你的“vanilla”项目的好时机 - 虽然我们将在以下部分中进行的一些更改,可能对任何平台(或开发)上的部署有用,而一些其他的更改可能没有用。</p> + +<p>执行此操作的最佳方法,是使用git来管理您的修订。使用git,您不仅可以回到特定的旧版本,而且可以在生产变更的单独“分支”中进行维护,并选择在生产和开发分支之间移动的任何更改。<a href="https://help.github.com/articles/good-resources-for-learning-git-and-github/">学习Git</a>非常值得,但超出了本主题的范围。</p> + +<p>最简单的方法,是将文件复制到另一个位置。使用最符合您对 git 了解的方法!</p> +</div> + +<h3 id="更新Heroku的应用程序">更新Heroku的应用程序</h3> + +<p>本节介绍了您需要对 LocalLibrary 应用程序进行的更改,以使其在Heroku上运行。</p> + +<h4 id="设置_node_版本">设置 node 版本</h4> + +<p><strong>package.json</strong>包含解决应用程序依赖项所需的所有内容,以及启动站点时,应启动的文件。 Heroku检测到此文件的存在,并将使用它来配置您的应用程序环境。</p> + +<p>我们当前的<strong>package.json</strong>中,缺少的唯一有用信息,是 node 的版本。我们可以通过输入命令,找到我们用于开发的 node 版本:</p> + +<pre class="brush: bash">>node --version +v8.9.1</pre> + +<p>打开<strong>package.json</strong>,并将此信息添加为<strong>engines > node</strong> 部分,如图所示(使用系统的版本号)。</p> + +<pre class="brush: json">{ + "name": "express-locallibrary-tutorial", + "version": "0.0.0", +<strong> "engines": { + "node": "8.9.1" + },</strong> + "private": true, + ... +</pre> + +<h4 id="数据库配置">数据库配置</h4> + +<p>到目前为止,在本教程中,我们使用了一个硬编码到<strong>app.js</strong>的单个数据库。通常我们希望,能够为生产和开发创建不同的数据库,接下来我们将修改 LocalLibrary 网站,以从OS环境获取数据库URI(如果已定义),否则使用我们的开发数据库。</p> + +<p>打开<strong>app.js</strong>,并找到设置mongoDB连接变量的行。它看起来像这样:</p> + +<pre class="brush: js">var mongoDB = 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library';</pre> + +<p>使用以下代码替换该行,该代码使用<code>process.env.MONGODB_URI</code>从名为<code>MONGODB_URI</code>的环境变量中,获取连接字符串(如果已设置)(使用您自己的数据库URL,而不是下面的占位符。)</p> + +<pre class="brush: js">var mongoDB = <strong>process.env.MONGODB_URI</strong> || 'mongodb://your_user_id:your_password@ds119748.mlab.com:19748/local_library'; +</pre> + +<h4 id="安装依赖并重新测试">安装依赖并重新测试</h4> + +<p>在我们继续之前,让我们再次测试该网站,并确保它不受我们的任何更改的影响。</p> + +<p>首先,我们需要获取我们的依赖项(你会记得,我们没有将 <strong>node_modules</strong>文件夹,复制到我们的 git 树中)。您可以通过在项目根目录的终端中,运行以下命令来执行此操作:</p> + +<pre class="brush: bash">npm install +</pre> + +<p>现在运行该站点(请参阅<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">测试路由</a>的相关命令),并检查该站点,是否仍按预期运行。</p> + +<h4 id="将更改保存到_Github">将更改保存到 Github</h4> + +<p>接下来,让我们将所有更改保存到 Github。在终端中(在我们的存储库中),输入以下命令:</p> + +<pre class="brush: bash">git add -A +git commit -m "Added files and changes required for deployment to heroku" +git push origin master</pre> + +<p>我们现在应该准备开始在 Heroku 上,部署 LocalLibrary。</p> + +<h3 id="获取一个_Heroku_账户">获取一个 Heroku 账户</h3> + +<p>要开始使用Heroku,您首先需要创建一个帐户(如果您已经拥有一个帐户,并安装了Heroku客户端,请跳过创建并上传网站):</p> + +<ul> + <li>访问 <a href="https://www.heroku.com/">www.heroku.com</a> ,并单击免费注册按钮 <strong>SIGN UP FOR FREE</strong> 。</li> + <li>输入您的详细信息,然后按<strong>CREATE FREE ACCOUNT</strong>。系统会要求您,检查帐户中是否有注册电子邮件。</li> + <li>单击注册电子邮件中的帐户激活链接。您将在网络浏览器上收回您的帐户。</li> + <li>输入您的密码,然后单击 <strong>SET PASSWORD AND LOGIN</strong>.</li> + <li>然后,您将登录并进入Heroku仪表板:<a href="https://dashboard.heroku.com/apps">https://dashboard.heroku.com/apps</a>.</li> +</ul> + +<h3 id="安装客户端">安装客户端</h3> + +<p>按照 <a href="https://devcenter.heroku.com/articles/getting-started-with-python#set-up">Heroku上的说明</a>,下载并安装Heroku客户端。</p> + +<p>安装客户端后,您将能够运行命令。例如,要获得客户端的帮助说明:</p> + +<pre class="brush: bash">heroku help +</pre> + +<h3 id="创建并上传网站">创建并上传网站</h3> + +<p>要创建应用程序,我们在存储库的根目录中,运行“create”命令。这将在我们的本地git环境中,创建一个名为 heroku 的 git remote(“指向远程存储库的指针”)。</p> + +<pre class="brush: bash">heroku create</pre> + +<div class="note"> +<p><strong>注意:</strong> 如果您愿意,可以在“创建”create 之后指定远程存储库的命名。如果你不这样做,你会得到一个随机的名字。该名称用于默认URL。</p> +</div> + +<p>然后,我们可以将我们的应用程序,推送到Heroku存储库,如下所示。这将上传应用程序,获取所有依赖项,将其打包到dyno中,然后启动该站点。</p> + +<pre class="brush: bash">git push heroku master</pre> + +<p>如果我们很幸运,该应用程序现在正在网站上“运行”。要打开浏览器并运行新网站,请使用以下命令:</p> + +<pre class="brush: bash">heroku open</pre> + +<div class="note"> +<p><strong>注意</strong>: 该站点将使用我们的开发数据库运行。创建一些书本和其他对象,并检查该网站是否按预期运行。在下一节中,我们将其设置为使用我们的新数据库。</p> +</div> + +<h3 id="设定配置变量">设定配置变量</h3> + +<p>您将从前一节回忆起,我们需要将NODE_ENV设置为'production',以便提高性能,并生成更简洁的错误消息。我们通过输入以下命令,来完成此操作:</p> + +<pre class="brush: bash">>heroku config:set NODE_ENV='production' +Setting NODE_ENV and restarting limitless-tor-18923... done, v13 +NODE_ENV: production +</pre> + +<p>我们还应该使用单独的数据库进行生产,在<strong>MONGODB_URI</strong>环境变量中,设置其URI。您可以完全按照<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose#Setting_up_the_MongoDB_database">我们原来的方式</a>,设置新数据库和数据库用户,并获取其URI。您可以如下图所示设置URI(显然,要使用您自己的URI!)</p> + +<pre class="brush: bash">>heroku config:set <strong>MONGODB_URI</strong>='mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production' +Setting MONGODB_URI and restarting limitless-tor-18923... done, v13 +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +</pre> + +<p>您可以使用<code>heroku config</code>命令,随时检查配置变量 - 立即尝试:</p> + +<pre class="brush: bash">>heroku config +=== limitless-tor-18923 Config Vars +MONGODB_URI: mongodb://your_user:your_password@ds139278.mlab.com:39278/local_library_production +NODE_ENV: production +</pre> + +<p>Heroku会在更新变量时,重新启动应用程序。如果您现在检查主页,它应该显示对象计数的零值,因为上面的更改,意味着我们现在正在使用新的(空)数据库。</p> + +<h3 id="管理附加组件">管理附加组件</h3> + +<p>Heroku 使用独立的附加组件,为应用程序提供支持服务 - 例如电子邮件或数据库服务。我们不在本网站中使用任何插件,但它们是使用Heroku的重要部分,因此您可能需要查看主题<a href="https://devcenter.heroku.com/articles/managing-add-ons">管理插件</a>(Heroku docs)。</p> + +<h3 id="调试">调试</h3> + +<p>Heroku客户端提供了一些调试工具:</p> + +<pre class="brush: bash">heroku logs # Show current logs +heroku logs --tail # Show current logs and keep updating with any new results +heroku ps #Display dyno status +</pre> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>本教程介绍在生产环境中,如何配置Express 应用。是Express系列教程的最后一个。我们希望你觉得这些教程有用。你可以在<a href="https://github.com/mdn/express-locallibrary-tutorial">Github上取得完整的源码</a>。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li id="production-best-practices-performance-and-reliability"><a href="https://expressjs.com/en/advanced/best-practice-performance.html">Production best practices: performance and reliability</a> (Express docs)</li> + <li><a href="https://expressjs.com/en/advanced/best-practice-security.html">Production Best Practices: Security</a> (Express docs)</li> + <li>Heroku + <ul> + <li><a href="https://devcenter.heroku.com/articles/getting-started-with-nodejs">Getting Started on Heroku with Node.js</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/deploying-nodejs">Deploying Node.js Applications on Heroku</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/nodejs-support">Heroku Node.js Support</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/node-concurrency">Optimizing Node.js Application Concurrency</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/how-heroku-works">How Heroku works</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/dynos">Dynos and the Dyno Manager</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/config-vars">Configuration and Config Vars</a> (Heroku docs)</li> + <li><a href="https://devcenter.heroku.com/articles/limits">Limits</a> (Heroku docs)</li> + </ul> + </li> + <li>Digital Ocean + <ul> + <li><a href="https://www.digitalocean.com/community/tutorials?q=express">Express</a> tutorials</li> + <li><a href="https://www.digitalocean.com/community/tutorials?q=node.js">Node.js</a> tutorials </li> + </ul> + </li> +</ul> + +<p>{{PreviousMenu("Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + +<p> </p> + +<h2 id="本教程链接">本教程链接</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li><a href="/en-US/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html new file mode 100644 index 0000000000..9a5e9ac7de --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/development_environment/index.html @@ -0,0 +1,390 @@ +--- +title: 设置 Node 开发环境 +slug: learn/Server-side/Express_Nodejs/development_environment +tags: + - Express + - Node + - node.js + - npm + - 初学者 + - 学习 + - 开发环境 + - 服务器端 +translation_of: Learn/Server-side/Express_Nodejs/development_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">你已经了解了 Express 的用途,接下来将在 Windows、Linux(Ubuntu)和 Mac OS X 下搭建 Node/Express 开发环境。本节将介绍主流操作系统下开发 Express 程序的必备知识。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">预备知识:</th> + <td>会打开终端 / 命令行。会为开发用操作系统安装软件包。</td> + </tr> + <tr> + <th scope="row">学习目标:</th> + <td>在电脑上搭建 Express 开发环境。</td> + </tr> + </tbody> +</table> + +<h2 id="Express_开发环境概述">Express 开发环境概述</h2> + +<p>使用 Node 和 Express 搭建 web 应用程序开发环境非常简便。这一章节将简述所需的工具,在主流操作系统(Ubuntu、macOS 和 Windows)上安装 Node 的步骤,以及测试安装是否成功的方法。</p> + +<h3 id="什么是_Express_开发环境?">什么是 Express 开发环境?</h3> + +<p>完整的 Express 本地开发环境包括 Nodejs、NPM 包管理器和 <strong>Express 应用生成器</strong>(可选)。</p> + +<p>Node 和 NPM 包管理器可以用二进制包、安装程序或系统包管理器一并安装(下文将介绍)。然后在开发每个 Express web 应用时,由 NPM 针对当前应用将 Express(以及模板引擎、数据库驱动程序、身份验证中间件、静态文件托管中间件等其它库)作为依赖项进行安装。</p> + +<p>NPM 也可以安装(全局的)<strong>Express 应用生成器</strong>,可用于创建遵循 <a href="/zh-CN/docs/Web/Apps/Fundamentals/Modern_web_app_architecture/MVC_architecture">MVC模式</a> 的 Express 应用框架。它不是必备的,因为无需这个工具就可以创建 Express 应用(或相同架构布局或依赖的 Express 应用)。但我们还是会使用它,因为它更容易上手,还有助于应用结构的模块化管理。</p> + +<div class="note"> +<p><strong>注:</strong> 与某些其他Web框架不同,开发环境不包含单独的开发Web服务器。在Node / Express中,Web应用程序将创建并运行自己的Web服务器!</p> +</div> + +<p>典型的开发环境中还需要一些外围工具,包括用于编写代码的 <a href="zh-CN/docs/Learn/Common_questions/实用文本编辑器">文本编辑器</a> 或 IDE ,用于代码控制管理的工具(比如代码版本控制工具 <a href="https://git-scm.com/">Git</a>)。这里假定你已经安装了这些工具(尤其是文本编辑器)。</p> + +<h3 id="支持哪些操作系统?">支持哪些操作系统?</h3> + +<p>Node 可以在 Windows、macOS、Linux 的诸多发行版本或 Docker 等环境运行(完整列表见 Node <a href="https://nodejs.org/zh-cn/download/">下载页面</a>)。几乎所有的个人电脑都具备 Node 开发所需性能。Express 运行在 Node 环境中,因此可运行 Node 的平台均可运行 Express。</p> + +<p>本文将介绍 Windows、macOS 和 Ubuntu Linux 上的安装步骤。</p> + +<h3 id="应该选择_NodeExpress_的哪个版本?">应该选择 Node/Express 的哪个版本?</h3> + +<p>Node 有许多 <a href="https://nodejs.org/zh-cn/blog/release/">发行版本</a>,新版包含 bug 修复、对最新版本 ECMAScript 标准的支持,以及 API 的改进。</p> + +<p>通常应该选择最新的 LTS(Long-term supported,长期支持版)发行版,因为它比当前发布版(current)更稳定。当前发布版包含最新的特性(维护中),如果需要 LTS 版本中没有提供的特征,那么可以选择它。</p> + +<p>Express 应选用最新版本。</p> + +<h3 id="数据库和其它依赖该如何选择?">数据库和其它依赖该如何选择?</h3> + +<p>其它依赖(例如数据库驱动程序、模板引擎、身份认证引擎等)是应用的一部分,使用 NPM 将它们引入到应用环境中。稍后进行讨论。</p> + +<h2 id="安装_Node">安装 Node</h2> + +<p>先在操作系统上安装 Node.js 和 NPM 后才可使用 Express。接下来将介绍如何最简便地在 Ubuntu 18.04、macOS Mojave 以及 Windows 10 上安装 Node.js 最新的 LTS 版本。.</p> + +<div class="note"> +<p><strong>提示:</strong>以下内容将介绍在上述三种 OS 上安装 Node 和 NPM 的最简便方法。对于其它操作系统,以及更多的安装方法,可以参考 <a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org).</p> +</div> + +<h3 id="Windows_和_macOS">Windows 和 macOS</h3> + +<p>在 Windows 和 macOS 上安装 Node 和 NPM 非常简单明了,使用现成的安装包就行了:</p> + +<ol> + <li>下载安装包: + <ol> + <li>访问 <a href="https://nodejs.org/zh-cn/">https://nodejs.org/zh-cn/</a></li> + <li>左侧按钮上写着“推荐多数用户使用(LTS)”,点击下载。</li> + </ol> + </li> + <li>双击下载的安装包,按照提示即可安装。</li> +</ol> + +<h3 id="Ubuntu_18.04">Ubuntu 18.04</h3> + +<p>安装 Node 最新的 LTS 版本的最简便方法就是使用 <a href="https://nodejs.org/zh-cn/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages">包管理器</a>,可以直接从 Ubuntu 二进制发行仓库中下载。通过在终端运行以下两行简单的命令就可以做到:</p> + +<pre class="brush: bash"><code>curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - +sudo apt-get install -y nodejs</code> +</pre> + +<div class="warning"> +<p><strong>警告:</strong>直接从 Ubuntu 默认仓库中下载的 Node 是 8.x 版本的。</p> +</div> + +<ol> +</ol> + +<h3 id="测试_Node.js_和_NPM_是否安装成功">测试 Node.js 和 NPM 是否安装成功</h3> + +<p>检查 Node 是否成功安装的最简单方法就是在终端(或命令行)中运行 "<code>version</code>" 命令,看是否返回版本号字符串:</p> + +<pre class="brush: bash">$ node -v +v10.15.0</pre> + +<p>NPM 应该与 Node.js 一同成功安装,可以使用同样的方法来测试一下:</p> + +<pre class="brush: bash">$ npm -v +6.7.0</pre> + +<p>下面的测试也许会带来小小激动:创建一个非常基础的“纯 Node”服务器,在浏览器中访问正确的 URL 地址时将直接打印 "Hello world":</p> + +<ol> + <li>以下代码使用了纯 Node 的特性(与 Express 无关)和一些 ES6 的语法,把它复制到 <strong>hellonode.js</strong> 文件中: + + <pre class="brush: js">// 加载 HTTP 模块 +const http = require("http"); +const hostname = '127.0.0.1'; +const port = 3000; + +// 创建 HTTP 服务器 +const server = http.createServer((req, res) => { + + // 用 HTTP 状态码和内容类型(Content-Type)设置 HTTP 响应头 + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + + // 发送响应体 + res.end('Hello World\n'); +}); + +// 监听 3000 端口的请求,注册一个回调函数记录监听开始 +server.listen(port, hostname, () => { + console.log(`服务器运行于 http://${hostname}:${port}/`); +}); +</pre> + + <p>代码导入了 <code>"http"</code> 模块,并用它(<code>createServer()</code>)创建了一个服务器来监听 3000 端口的 HTTP 请求。随后在控制台打印一条信息,提示测试服务器的正确 URL。<code>createServer()</code> 函数接受一个回调函数作为参数,并在接收 HTTP 请求后进行回调。直接返回了 HTTP 状态码 200 ("<code>OK</code>"),以及纯文本信息 "Hello World"。</p> + + <div class="note"> + <p><strong>注:</strong>现在看不懂这些代码请不要担心,开始使用 Express 后候会进行更加详细的解释。</p> + </div> + </li> + <li>在命令行工具中进入 hellonode.js 文件所在的目录,输入“node + 文件名”并运行,服务器就启动了: + <pre class="brush: bash">$ node hellonode.js +服务器运行于 http://127.0.0.1:3000/ +</pre> + </li> + <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:3000/</a>),如果一切正常,浏览器会直接显示出 "Hello world" 字符串。</li> +</ol> + +<h2 id="使用_NPM">使用 NPM</h2> + +<p>构建 Node 应用过程中,<a href="https://docs.npmjs.com/">NPM</a> 是除了 Node 本身之外最重要的工具。可用于获取应用开发、测试以及生产所需的所有包(JavaScript 库)。也可运行开发过程中使用的测试单元和工具。</p> + +<div class="note"> +<p><strong>注:</strong>以 Node 的角度来看,Express 只是一个用 NPM 安装、供人使用的包而已。</p> +</div> + +<p>可以用 NPM 手动逐个安装所需包。但通常可用 <a href="https://docs.npmjs.com/files/package.json">package.json</a> 文件来管理依赖。把每个<font><font>依赖以一个</font></font> JavaScript “包”的形式(其中<font><font>包括名称、版本、描述,初始执行文件、生产依赖,开发依赖、支持的 </font></font><em><font><font>Node </font></font></em><font><font>版本,等等</font></font>)罗<font><font>列在这个文件中。package.json 文件包含 NPM 获取和运行应用程序所需的所有内容(在编写可重用的库时,可以用它把包上传到 NPM 仓库中供其他用户使用)。</font></font></p> + +<h3 id="添加依赖项">添加依赖项</h3> + +<p>下面介绍用 NPM 下载包、将包保存进工程依赖树,以及在 Node 应用中调用包的方法和步骤。</p> + +<div class="note"> +<p><strong>注:</strong>现在来讲解获取和安装 Express 包的步骤。稍后解释为什么可以直接对 Express 包(乃至其它包)使用 <strong>Express 应用生成器</strong>。这段对理解 NPM 的工作原理和应用生成器的工作机制有一定的帮助。</p> +</div> + +<ol> + <li>首先为新应用创建一个目录,并进入它: + <pre class="brush: bash">$ mkdir myapp +$ cd myapp</pre> + </li> + <li>然后,使用 NPM 的 init 命令为应用创建一个 <strong>package.json</strong> 文件。这个命令将请求一系列的信息,包括应用的名称和版本,程序初始进入点的文件名(默认为 <strong>index.js</strong>)。现在先接受默认信息即可: + <pre class="brush: bash">$ npm init</pre> + + <p><strong>package.json</strong> 文件中保存了所接受的默认信息,最后一条是许可证信息:</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} +</pre> + </li> + <li>接下来在 <strong>myapp</strong> 目录中安装 Express,用下面的命令将 Express 保存在 <strong>package.json</strong> 文件中的依赖表里: + <pre class="brush: bash">$ npm install express</pre> + + <p>此时 <strong>package.json</strong> 文件的底部会出现依赖列表("dependencies"),其中包含 Express:</p> + + <pre class="brush: json">{ + "name": "myapp", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", +<strong> "dependencies": { + "express": "^4.16.4" + }</strong> +} +</pre> + </li> + <li>可以调用 <code>require()</code> 函数来使用库: + <pre><code><strong>const express = require('express'); +</strong>const app = express(); + +app.get('/', (req, res) => { + res.send('Hello World!') +}); + +app.listen(8000, () => { + console.log('示例程序正在监听 8000 端口!') +});</code> +</pre> + + <p>以上代码展示了一个最简单的 "HelloWorld" Express 应用。它导入了 "express" 模块并用它创建了一个服务器(app)来监听 8000 端口,并且在控制台打印了一条信息以提示测试服务器的正确 URL。<code>app.get()</code> 函数只响应对特定路径(<code>'/'</code>)的 HTTP <code>GET</code> 请求,此处的响应就是发送 "Hello World!"。<br> + <br> + 在 myapp 应用的根目录下新建一个 <strong>index.js</strong> 文件,将上述代码粘贴进来并保存。</p> + </li> + <li>在命令行输入 node + 文件名 即可启动服务器: + <pre class="brush: bash">$ node index.js +<code>示例程序正在监听 8000 端口!</code> +</pre> + </li> + <li>在浏览器中访问这个 URL(<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>),如果一切正常,浏览器会直接显示出 "Hello world!" 字符串。</li> +</ol> + +<h3 id="开发依赖(Development_dependencies)">开发依赖(Development dependencies)</h3> + +<p>如果一个依赖只在开发过程中用到,应该将其保存为“开发依赖”(这样,包的用户便无需在生产环境中安装它们)。比如,如果要使用 <a href="http://eslint.org/">eslint</a>(一款流行的 JavaScript lint 工具)可以这样调用 NPM:</p> + +<pre class="brush: bash"><code>$ npm install eslint --save-dev</code></pre> + +<p>当前应用的 <strong>package.json </strong>文件中将自动添加以下项目:</p> + +<pre class="brush: js"> "devDependencies": { + "eslint": "^5.12.0" + } +</pre> + +<div class="note"> +<p><strong>注:</strong>"<a href="https://zh.wikipedia.org/wiki/Lint">lint</a>" 是用于对软件进行静态分析的工具,可以发现并报告软件是否遵循某些最佳编程惯例。</p> +</div> + +<h3 id="运行任务">运行任务</h3> + +<p>在 <strong>package.json</strong> 中,除了定义和获取依赖,还可以定义脚本,然后通过 NPM 的 <a href="https://docs.npmjs.com/cli/run-script">run-script</a> 命令来运行。这个用法普遍用于自动运行测试单元或部分应用,也可用于构建工具链(比如运行工具来压缩 JavaScript 文件或图片,lint 或分析代码,等等)。</p> + +<div class="note"> +<p><strong>注:</strong><a href="http://gulpjs.com/">Gulp</a> 和 <a href="http://gruntjs.com/">Grunt</a> 等任务运行器可用于运行测试单元或其它外部工具。</p> +</div> + +<p>比如,可以在 <strong>package.json</strong> 文件中添加以下内容来定义一个脚本,从而对上文的代码运行 eslint(假设应用代码在 /src/js 文件夹下):</p> + +<pre class="brush: js">"scripts": { + ... + "lint": "eslint src/js" + ... +} +</pre> + +<p>深入解释一下,eslint src/js 命令可以在终端/命令行对应用目录下的 src/js 目录中的 JavaScript 文件运行 eslint。把上面一段脚本添加进应用的 package.json 中还可以为此命令提供一个快捷方式—— lint。</p> + +<p>然后就可以用 NPM 这样运行 eslint 了:</p> + +<pre class="brush: bash"><code>$ npm run-script lint</code></pre> + +<p>或使用别名:</p> + +<pre class="brush: bash"><code>$ npm run lint</code></pre> + +<p>这个示例看上去并没有让原始命令简洁多少,但在 NPM 脚本中可以加入更长的命令,甚至是多命令链。比如可以让单一的 NPM 脚本来一次运行所有的测试单元。</p> + +<h2 id="安装_Express_应用生成器">安装 Express 应用生成器</h2> + +<p><a href="https://expressjs.com/en/starter/generator.html">Express 应用生成器</a> 工具可以生成一个 Express 应用的“框架”。可以用 NPM 这样安装它(-g 参数可以把该工具全局安装,那样就可以在任意应用中使用了):</p> + +<pre class="brush: bash"><code>$ npm install express-generator -g</code></pre> + +<p>进入应用目录,运行以下命令,即可创建一个名为 "helloworld" 的 Express 应用:</p> + +<pre class="brush: bash">$ express helloworld</pre> + +<div class="note"> +<p><strong>注:</strong>也可以指定模板库来使用其它丰富的设置。可通过 help 命令来查看所有选项:</p> + +<pre class="brush: bash">$ express --help +</pre> +</div> + +<p>NPM 将在当前位置的子目录中创建新的 Express 应用,可以在控制台看到构建的过程。在完成时,NPM 会提示你需要安装哪些 Node 依赖,以及如何开启应用。</p> + +<div class="note"> +<p>新应用的根目录有一个 <strong>package.json</strong> 文件。可以打开它看看都安装了哪些依赖,其中可以看到 Express 和 Jade 模板库:</p> + +<pre class="brush: js">{ + "name": "helloworld", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "cookie-parser": "~1.4.3", + "debug": "~2.6.9", + "express": "~4.16.0", + "http-errors": "~1.6.2", + "jade": "~1.11.0", + "morgan": "~1.9.0" + } +}</pre> +</div> + +<p>用下列命令可为 helloworld 应用安装所有依赖:</p> + +<pre class="brush: bash">$ cd helloworld +$ npm install +</pre> + +<p>然后运行这个应用(Windows 环境):</p> + +<pre class="brush: bash">> SET DEBUG=helloworld:* & npm start +</pre> + +<p>(Linux/macOS 环境):</p> + +<pre class="brush: bash">$ DEBUG=helloworld:* npm start</pre> + +<p>DEBUG 命令可以展示应用运行时返回的有用的日志信息,如下所示:</p> + +<p><img alt="设置 DEBUG 命令显示的日志信息" src="https://mdn.mozillademos.org/files/16404/debug.png"></p> + +<p>打开浏览器并访问 <a href="http://127.0.0.1:3000/">http://127.0.0.1:3000/</a> 将看到 Express 的默认欢迎页面。</p> + +<p><img alt="生成应用的默认主页" src="https://mdn.mozillademos.org/files/16405/express.png"></p> + +<p>稍后在创建应用框架一节中将讨论生成应用的具体细节。</p> + +<ul> +</ul> + +<h2 id="小结">小结</h2> + +<p>现在 Node 开发环境已经配置好了,可以用于创建 Express 应用了。你还了解了用 NPM 导入 Express 的步骤,以及如何创建(使用 Express 应用生成器)和运行 web 应用。</p> + +<p>下一节将开始用上述的环境和工具通过实战逐步搭建一个完整的 web 应用。</p> + +<h2 id="另请参阅">另请参阅</h2> + +<ul> + <li><a href="https://nodejs.org/zh-cn/download/">Node.js 下载页面</a> (nodejs.org 官方中文页面)</li> + <li><a href="https://nodejs.org/zh-cn/download/package-manager/">通过包管理器方式安装 Node.js</a> (nodejs.org 官方中文页面)</li> + <li><a href="http://www.expressjs.com.cn/starter/installing.html">安装 Express</a> (expressjs.com.cn 中文镜像页面)</li> + <li><a href="http://www.expressjs.com.cn/starter/generator.html">Express 应用程序生成器</a> (expressjs.com.cn 中文镜像页面)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Introduction", "Learn/Server-side/Express_Nodejs/Tutorial_local_library_website", "Learn/Server-side/Express_Nodejs")}}</p> + +<h2 id="本章目录">本章目录</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">设置 Node(Express)开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程:本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2:创建站点框架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3:使用数据库(Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4:路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5:显示图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6:使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7:部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html new file mode 100644 index 0000000000..c62c5fbbec --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_detail_page/index.html @@ -0,0 +1,89 @@ +--- +title: 作者细节页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page +--- +<p>作者细节页面需要呈现指定作者<code>Author</code>的信息,使用 <code>_id</code> 字段的值(自动产生)识别,接着是这个作者的所有书本物件<code>Book</code>的列表。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/authorController.js</strong>。</p> + +<p>在档案最上方,加入底下几行,引入 async 和 Book 模组(作者细节页面需要它们)。</p> + +<pre class="brush: js">var async = require('async'); +var Book = require('../models/book');</pre> + +<p>找到 exported <code>author_detail()</code> 控制器方法,并用底下代码置换。</p> + +<pre class="brush: js">// Display detail page for a specific Author. +exports.author_detail = function(req, res, next) { + +<strong> async.parallel({ + author: function(callback) { + Author.findById(req.params.id) + .exec(callback) + }, + authors_books: function(callback) { + Book.find({ 'author': req.params.id },'title summary') + .exec(callback) + }, + }, function(err, results) { + if (err) { return next(err); } // Error in API usage. + if (results.author==null) { // No results. + var err = new Error('Author not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('author_detail', { title: 'Author Detail', author: results.author, author_books: results.authors_books } ); + });</strong> + +}; +</pre> + +<p>此处的控制器方法使用 <code>async.parallel()</code>,用平行的方式,查询作者 <code>Author</code>和相应的书本实例,并附加上绘制本页面的回调,如果 2 个要求都成功完成,就运行回调。这个方式,就跟前面的种类细节页面所说明的完全相同。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/author_detail.pug</strong> ,並複制貼上底下的文字。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 Author: #{author.name}</strong> + p #{author.date_of_birth} - #{author.date_of_death} + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in author_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This author has no books. +</pre> + +<p>本模板里的所有事物,都在先前的章节演示过了。</p> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择All Authors 连结,然后选择一个作者。如果每个东西都设定正确了,你的网站看起来应该会像底下的截图。</p> + +<p><img alt="Author Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14466/LocalLibary_Express_Author_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 422px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 作者的出生与死亡日期的外观很丑!我们将在本文最后的自我挑战处理它。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下一个部分 : <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge"> 书本实例细节页面和自我挑战 </a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html new file mode 100644 index 0000000000..055ae91e1c --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/author_list_page/index.html @@ -0,0 +1,89 @@ +--- +title: 作者清单面页、分类清单页面、与自我挑战 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page +--- +<p>作者列表页面,需要呈现数据库中所有作者的列表,有每位作者的名字,并连结到作者详细内容页面。出生与死亡日期应该在名字后面,并且在同一列。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>作者列表控制器函数,需要获取所有作者实例的列表,然后将这些实例传递给模板进行渲染。</p> + +<p>打开<strong>/controllers/authorController.js</strong>。在文件顶部附近,找到导出的<code>author_list()</code> 控制器方法,并将其替换为以下代码(更改后的代码以粗体显示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Authors.</span> +exports<span class="punctuation token">.</span>author_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Author<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">sort</span><span class="punctuation token">(</span><span class="punctuation token">[</span><span class="punctuation token">[</span><span class="string token">'family_name'</span><span class="punctuation token">,</span> <span class="string token">'ascending'</span><span class="punctuation token">]</span><span class="punctuation token">]</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_authors<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'author_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Author List'</span><span class="punctuation token">,</span> author_list<span class="punctuation token">:</span> list_authors <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>该方法使用模型的 <code>find()</code>, <code>sort()</code> 和 <code>exec()</code> 函数,以返回所有<code>Author</code>对象,并按<code>family_name</code>的字母顺排列。传递给<code>exec()</code>方法的回调被调用,并将传入任何错误(或<code>null</code>)作为第一个参数,或者成功时,传入所有作者列表。如果出现错误,则调用带有错误值的下一个中间件函数,如果没有错误,则呈现<strong> author_list</strong>(.pug)模板,传递页面标题<code>title,</code>和作者列表(<code>author_list</code>)。</p> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>打开 <strong>/views/author_list.pug </strong>,用底下文字取代它的内容。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each author <span class="keyword token">in</span> author_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>author<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_birth<span class="punctuation token">}</span> <span class="operator token">-</span> #<span class="punctuation token">{</span>author<span class="punctuation token">.</span>date_of_death<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no authors<span class="punctuation token">.</span></code></pre> + +<p>如同我们其它的模板,上面视图也依照着同样的模式。</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a> 。然后选择所有作者 All authors 连结。如果每个东西都设定正确了,页面看起来应该像底下的截图。</p> + +<p><img alt="Author List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14468/LocalLibary_Express_Author_List.png" style="display: block; height: 453px; margin: 0px auto; width: 1200px;"></p> + +<div class="note"> +<p><strong>注意:</strong> 作者生命日期的外观是丑陋的!您可以使用我们用于<code>BookInstance</code> 列表的<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data#date_formatting">相同方法</a>(将生命周期的虚拟属性,添加到 <code>Author</code> 模型),来改进此方法。</p> + +<p>但是,这次缺少日期,除非严格模式生效,否则将忽略对不存在的属性的引用。<code>moment()</code>返回当前时间,并且您不希望将缺少的日期格式化为就像今天一样。</p> + +<p>解决此问题的一种方法,是定义返回格式化日期的函数内容,以便返回空字符串,除非日期实际存在。例如:</p> + +<p><code>return this.date_of_birth ? moment(this.date_of_birth).format('YYYY-MM-DD') : '';</code></p> +</div> + +<h2 id="种类列表页面—自我挑战!Edit">种类列表页面—自我挑战!<a class="button section-edit only-icon" href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data$edit#Genre_list_page—challenge!" rel="nofollow, noindex"><span>Edit</span></a></h2> + +<p>在这个部分,你应该实作你自己的种类列表页面。该页面应显示数据库中所有种类的列表,每个种类都链接到其关联的详细信息页面。预期结果的屏幕截图如下所示。</p> + +<p><img alt="Genre List - Express Local Library site" src="https://mdn.mozillademos.org/files/14460/LocalLibary_Express_Genre_List.png" style="border-style: solid; border-width: 1px; display: block; height: 346px; margin: 0px auto; width: 600px;"></p> + +<p>种类列表控制器功能,需要获取所有种类实例的列表,然后将这些实例传递给模板进行渲染。</p> + +<ol> + <li>您需要在 <strong>/controllers/genreController.js</strong> 中编辑<code>genre_list()</code>。</li> + <li>实现方式几乎与<code>author_list()</code>控制器完全相同。 + <ul> + <li>按名称以上升顺序,对结果进行排序。</li> + </ul> + </li> + <li>要呈现的模板,应命名为 <strong>genre_list.pug</strong>。</li> + <li>要呈现的模板应该传递变量<code>title</code>('Genre List')和种类列表<code>genre_list</code>(从<code>Genre.find()</code>回调返回)。</li> + <li>该视图应与上面的屏幕截图/要求相匹配(这应该与作者列表视图具有非常相似的结构/格式,除了种类没有日期)。</li> +</ol> + +<h2 id="下一步">下一步</h2> + +<p>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></p> + +<p>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">种类细节页面</a></p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html new file mode 100644 index 0000000000..775bcd387f --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_detail_page/index.html @@ -0,0 +1,112 @@ +--- +title: 书本详细信息页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page +--- +<p>书本细节页面需要呈现一本指定书本(<code>Book</code>)的信息, 使用它的 <code>_id</code> 字段值(自动产生)做为识别,接着是图书馆中书本实例(<code>BookInstance</code>)的信息。无论我们在哪里呈现一个作者、种类、或书本实例,都应该连结到它的细节页面。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/bookController.js</strong>. ,找到 exported <code>book_detail()</code> 控制器方法,用底下的代码置换。</p> + +<pre class="brush: js">// Display detail page for a specific book. +exports.book_detail = function(req, res, next) { + +<strong> async.parallel({ + book: function(callback) { + + Book.findById(req.params.id) + .populate('author') + .populate('genre') + .exec(callback); + }, + book_instance: function(callback) { + + BookInstance.find({ 'book': req.params.id }) + .exec(callback); + }, + }, function(err, results) { + if (err) { return next(err); } + if (results.book==null) { // No results. + var err = new Error('Book not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('book_detail', { title: 'Title', book: results.book, book_instances: results.book_instance } ); + });</strong> + +}; + +</pre> + +<div class="note"> +<p><strong>注意:</strong> 我们不需要用 require 导入 async 和 BookInstance,当我们实作主页面控制器的时候,我们就已经引入这些模组。</p> +</div> + +<p>此处的控制器方法使用 <code>async.parallel()</code>,用平行的方式找到 <code>Book</code> 以及它的相应复本 (<code>BookInstances</code>) 。这样的处理方式,就跟上面的 种类细节页面 所说明的完全相同。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/book_detail.pug</strong> 并加入底下文字。</p> + +<pre class="brush: js">extends layout + +block content + h1 #{title}: #{book.title} + + p #[strong Author:] + a(href=book.author.url) #{book.author.name} + p #[strong Summary:] #{book.summary} + p #[strong ISBN:] #{book.isbn} + p #[strong Genre:]&nbsp; + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, + + div(style='margin-left:20px;margin-top:20px') + h4 Copies + + each val in book_instances + hr + if val.status=='Available' + <strong>p.text-success</strong> #{val.status} + else if val.status=='Maintenance' + p.text-danger #{val.status} + else + p.text-warning #{val.status} + p #[strong Imprint:] #{val.imprint} + if val.status!='Available' + p #[strong Due back:] #{val.due_back} + p #[strong Id:]&nbsp; + a(href=val.url) #{val._id} + + else + p There are no copies of this book in the library. +</pre> + +<p>在这个模板里,几乎每个东西都在先前的章节演示过了。</p> + +<div class="note"> +<p><strong>注意:</strong> 与该书相关的種類列表,在模板中的实作,如以下代碼。除了最后一本书之外,在与本书相关的每个种類之后,都会添加一个逗号。</p> + +<pre> p #[strong Genre:] + each val, index in book.genre + a(href=val.url) #{val.name} + if index < book.genre.length - 1 + |, </pre> +</div> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All books 连结,然后选择其中一本书。如果每个东西都设定正确了,你的页面看起来应该像是底下的截图。</p> + +<p><img alt="Book Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14470/LocalLibary_Express_Book_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 616px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者细节页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html new file mode 100644 index 0000000000..bb76f61d50 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/book_list_page/index.html @@ -0,0 +1,70 @@ +--- +title: 书本列表页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page +--- +<p>接下做我们将实作书本列表页面。这个页面需要呈现数据库中所有书本的列表,包含每本书的作者、标题,标题将成为一个超连结,连到书本详细内容页面。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>书本列表控制器函数,需要获取数据库中所有<code>Book</code>对象的列表,然后将这些对象传给模板进行呈现。</p> + +<p>打开 <strong>/controllers/bookController.js</strong>. 找到导出的 <code>book_list()</code> 控制器方法,并替换為下面的代码。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all Books.</span> +exports<span class="punctuation token">.</span>book_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + Book<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">{</span><span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="string token">'title author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'author'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_books<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">//Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'book_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book List'</span><span class="punctuation token">,</span> book_list<span class="punctuation token">:</span> list_books <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>该方法使用模型的<code>find()</code>函数返回所有<code>Book</code>对象,选择仅返回标题<code>title</code>和作者<code>author</code>,因为我们不需要其他字段(它也会返回<code>_id</code>和虚拟字段)。这里我们还调用<code>Book</code>上的<code>populate()</code> ,指定作者<code>author</code>字段 — 这将用完整的作者信息,替换存储的书本作者 id。</p> + +<p>成功时,传递给查询的回调,将呈现<strong> book_list</strong>(.pug) 模板,将标题<code>title</code>和<code>book_list</code>(包含作者的書本列表)作为变量传递。</p> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>创建 <strong>/views/book_list.pug </strong>并复制底下的文字。</p> + +<p> </p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each book <span class="keyword token">in</span> book_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>book<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> + <span class="operator token">|</span> <span class="punctuation token">(</span>#<span class="punctuation token">{</span>book<span class="punctuation token">.</span>author<span class="punctuation token">.</span>name<span class="punctuation token">}</span><span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no books<span class="punctuation token">.</span></code></pre> + +<p>這个视图扩展了 <strong>layout.pug</strong> 基本模板,并覆盖了名为 '<strong>content</strong>' 的區块 <code>block</code> 。它显示我们从控制器传入的标题<code>title</code>(通过<code>render()</code>方法),然后使用<code>each</code>-<code>in</code>-<code>else</code>语法,遍历<code>book_list</code>变量。为每本图书创建一个列表项,以显示书名,并作为书的详细信息页面的链接,后面跟着作者姓名。如果<code>book_list</code>中没有书,则执行<code>else</code>子句,并显示文字 “没有书” 'There are no books.'。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们使用 <code>book.url</code> ,为每本书提供详细记录链接(我们已经实现了此路由,但尚未实现此页面)。这是 <code>Book </code>模型的一个虚拟属性,它使用模型实例的 <code>_id </code>字段,生成唯一的URL路径。</p> +</div> + +<p>在这里,我們感兴趣的是,每本书被定义为两行,第二行使用管道(上面高亮显示)。这种方法是必要的,因为如果作者姓名位于上一行,那么它将成为超链接的一部分。</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用 (参见 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes#Testing_the_routes">测试路由</a> 有相关的命令) ,并打开你的浏览器,访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。然后选择 所有书本 连结。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</p> + +<p><img alt="Book List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14464/LocalLibary_Express_Book_List.png" style="border-style: solid; border-width: 1px; display: block; height: 387px; margin: 0px auto; width: 918px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例列表页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html new file mode 100644 index 0000000000..cba25ab30d --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_detail_page_and_challenge/index.html @@ -0,0 +1,91 @@ +--- +title: 书本实例细节页面、与自我挑战 +slug: >- + learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +translation_of: >- + Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge +--- +<h2 id="书本实例细节页面">书本实例细节页面</h2> + +<p><code>BookInstance</code>细节页面,需要呈现每一个<code>BookInstance</code>的信息,用 <code>_id</code> 字段值(自动产生)做识别。它包含了 <code>Book</code> 名称 (也是一个连结,连到 书本细节页面),接着是纪录中的其它的信息。</p> + +<h3 id="控制器">控制器</h3> + +<p>打开 <strong>/controllers/bookinstanceController.js</strong>. ,找到exported <code>bookinstance_detail()</code> 控制器方法,并替换以下代码。</p> + +<pre class="brush: js">// Display detail page for a specific BookInstance. +exports.bookinstance_detail = function(req, res, next) { + +<strong> BookInstance.findById(req.params.id) + .populate('book') + .exec(function (err, bookinstance) { + if (err) { return next(err); } + if (bookinstance==null) { // No results. + var err = new Error('Book copy not found'); + err.status = 404; + return next(err); + } + // Successful, so render. + res.render('bookinstance_detail', { title: 'Book:', bookinstance: bookinstance}); + })</strong> + +}; +</pre> + +<p>该方法使用从URL(使用路由)中提取的特定书本实例的ID,调用<code>BookInstance.findById()</code>,并通过请求参数(<code style="font-style: normal; font-weight: normal;">req.params.id</code>),在控制器中访问。然后调用<code>populate()</code>来获取相关<code>Book</code>的详细信息。</p> + +<h3 id="视图">视图</h3> + +<p>创建 <strong>/views/bookinstance_detail.pug</strong>,并复制到下面的内容中。</p> + +<pre class="brush: js">extends layout + +block content + +<strong> h1 ID: #{bookinstance._id}</strong> + + p #[strong Title:] + a(href=bookinstance.book.url) #{bookinstance.book.title} + p #[strong Imprint:] #{bookinstance.imprint} + + p #[strong Status:] + if bookinstance.status=='Available' + span.text-success #{bookinstance.status} + else if bookinstance.status=='Maintenance' + span.text-danger #{bookinstance.status} + else + span.text-warning #{bookinstance.status} + + if bookinstance.status!='Available' + p #[strong Due back:] #{bookinstance.due_back} +</pre> + +<p>本模组中的所有东西,都在先前的章节演示过了。</p> + +<h3 id="它看起來像是">它看起來像是?</h3> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All book-instances 连结,然后选择其中一本。如果每个东西都设定正确了,你的网站看起来应该像是底下的截图。</p> + +<p><img alt="BookInstance Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14472/LocalLibary_Express_BookInstance_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 362px; margin: 0px auto; width: 1000px;"></p> + +<h2 id="自我挑战">自我挑战</h2> + +<p>目前,我们网站上显示的大多数日期,都使用默认的 JavaScript 格式(例如 <em>Tue Dec 06 2016 15:49:58 GMT+1100</em>(AUS东部夏令时间)。本文的挑战,是改善作者<code>Author</code>生命周期日期显示的外观信息(死亡/出生日期)和BookInstance详细信息页面,使用格式:December 6th, 2016。</p> + +<div class="note"> +<p><strong>注意:</strong> 您可以使用与我们用于 Book Instance List 的相同方法(将生命周期的虚拟属性,添加到<code>Author</code>模型,并使用<a href="https://www.npmjs.com/package/moment">moment</a>来设置日期字符串的格式)。</p> +</div> + +<p>这一挑战的要求:</p> + +<ol> + <li>用 BookInstance 详细信息页面中的 <code>due_back_formatted</code> 替换 <code>due_back</code>。</li> + <li>更新作者模块以添加寿命虚拟属性。寿命应該有两个值: <em>date_of_birth - date_of_death,這</em>两个值的格式与 <code>BookInstance.due_back_formatted</code>的日期格式相同。</li> + <li>在当前使用<code>date_of_birth</code> 和 <code>date_of_death</code>的所有视图中,使用 <code>Author.lifespan</code> 。</li> +</ol> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html new file mode 100644 index 0000000000..1e8403f0a5 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/bookinstance_list_page/index.html @@ -0,0 +1,69 @@ +--- +title: 书本实例列表页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page +--- +<p>接下来,我们将实作图书馆中所有书本实例 (<code>BookInstance</code>) 的列表页面。这个页面需要包含与每个 <code>BookInstance</code> (链接到其详细信息页面) 关联的书本 <code>Book</code> 标题,以及<code>BookInstance</code>模型中的其他信息,包含每个副本的状态,印记和唯一ID。唯一ID的文字,应该链接到 <code>BookInstance</code> 详细信息页面。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p><code>BookInstance</code>列表控制器函数,需要获取所有书本实例的列表,填充关联的书本信息,然后将列表传递给模板以进行呈现。</p> + +<p>打开 <strong>/controllers/bookinstanceController.js</strong>。找到导出的 <code>bookinstance_list()</code> 控制器方法,并用以下代码替换它(更改后的代码以粗体显示)。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// Display list of all BookInstances.</span> +exports<span class="punctuation token">.</span>bookinstance_list <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">,</span> next<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + BookInstance<span class="punctuation token">.</span><span class="function token">find</span><span class="punctuation token">(</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">populate</span><span class="punctuation token">(</span><span class="string token">'book'</span><span class="punctuation token">)</span> + <span class="punctuation token">.</span><span class="function token">exec</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> list_bookinstances<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span>err<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="keyword token">return</span> <span class="function token">next</span><span class="punctuation token">(</span>err<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="punctuation token">}</span> + <span class="comment token">// Successful, so render</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'bookinstance_list'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Book Instance List'</span><span class="punctuation token">,</span> bookinstance_list<span class="punctuation token">:</span> list_bookinstances <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p>此方法使用模型的<code>find()</code>函数,返回所有<code>BookInstance</code>对象。然后它将一个调用,以菊花链方式连接到<code>populate()</code>,附加书本<code>book</code>字段,这将使用完整的<code>Book</code>文档,替换每个<code>BookInstance</code>存储的书本ID。</p> + +<p>成功时,传递给查询的回调,会呈现 <strong>bookinstance_list</strong> (.pug)模板,并将标题<code>title</code>和书籍实例列表<code>bookinstance_list</code>作为变量传递。</p> + +<h2 class="highlight-spanned" id="视图"><span class="highlight-span">视图</span></h2> + +<p>创建 <strong>/views/bookinstance_list.pug</strong> ,並複制貼上底下的文字。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + + ul + each val <span class="keyword token">in</span> bookinstance_list + li + <span class="function token">a</span><span class="punctuation token">(</span>href<span class="operator token">=</span>val<span class="punctuation token">.</span>url<span class="punctuation token">)</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>book<span class="punctuation token">.</span>title<span class="punctuation token">}</span> <span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>imprint<span class="punctuation token">}</span> <span class="operator token">-</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Available'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>success #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">==</span><span class="string token">'Maintenance'</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>danger #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">else</span> + span<span class="punctuation token">.</span>text<span class="operator token">-</span>warning #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>status<span class="punctuation token">}</span> + <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back<span class="punctuation token">}</span> <span class="punctuation token">)</span> + + <span class="keyword token">else</span> + li There are no book copies <span class="keyword token">in</span> <span class="keyword token">this</span> library<span class="punctuation token">.</span></code></pre> + +<p>这个視图与其他視图非常相似。它扩展了布局,替换内容區块,显示从控制器传入的标题<code>title</code>,并遍历<code>bookinstance_list</code> 中的所有书籍副本。对于每个副本,我们都会显示它的状态(用颜色编码),如果书本不可用,则显示其预期返回日期。這裡引入了一个新功能 — 我们可以在标签之后使用点符号表示法,來指定一個类別。因此,<code>span.text-success</code> 将被编译为 <code><span class="text-success"></code> (也可以用 Pug 编写为 <code>span(class="text-success")</code>.</p> + +<h2 class="highlight-spanned" id="它看起來像是"><span class="highlight-span">它看起來像是?</span></h2> + +<p>运行本应用,打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>,然后选择 All book-instances 连结。假如每个东西都设定正确了,你的网站看起来应该像是底下的截图。</p> + +<p><img alt="BookInstance List Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14474/LocalLibary_Express_BookInstance_List.png" style="border-style: solid; border-width: 1px; display: block; height: 322px; margin: 0px auto; width: 1200px;"></p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化与使用 moment</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html new file mode 100644 index 0000000000..8abbf8d290 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/date_formatting_using_moment/index.html @@ -0,0 +1,58 @@ +--- +title: 使用 moment 做日期格式化 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment +--- +<p>我们模型的日期预设呈现很难看: <em>Tue Dec 06 2016 15:49:58 GMT+1100 (AUS Eastern Daylight Time)</em>。在本节中,我们将展示如何更新上一节中的 書本實例 BookInstance 列表页面,以更友好的格式显示<code>due_date</code>字段:December 6th, 2016。</p> + +<p>我们将使用的方法,是在我们的<code>BookInstance</code>模型中,创建一个返回格式化日期的虚拟屬性。我们将使用<a class="external external-icon" href="https://www.npmjs.com/package/moment" rel="noopener">moment</a> 来做实际的格式化,这是一个轻量级JavaScript日期库,用于解析,验证,操作和格式化日期。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们可以直接在 Pug 模板中,使用 <em>moment </em>格式化字符串,或者可以在许多其它地方格式化字符串。使用虚拟属性,可以使我们获得格式化的日期,這与我们当前获取 <code>due_date</code> 的方式完全相同。</p> +</div> + +<h2 class="highlight-spanned" id="安装_moment">安装<span class="highlight-span"> moment</span></h2> + +<p>在项目的根目录,输入下列命令</p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install moment</code></pre> + +<h2 class="highlight-spanned" id="创建虚拟属性">创建虚拟属性</h2> + +<ol> + <li>打开<strong> ./models/bookinstance.js</strong>.</li> + <li>在此页面最上方,引用 moment + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> moment <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'moment'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> +</ol> + +<p>在 url 属性后面,加入虚拟属性 <code>due_back_formatted</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">BookInstanceSchema +<span class="punctuation token">.</span><span class="function token">virtual</span><span class="punctuation token">(</span><span class="string token">'due_back_formatted'</span><span class="punctuation token">)</span> +<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="keyword token">function</span> <span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">return</span> <span class="function token">moment</span><span class="punctuation token">(</span><span class="keyword token">this</span><span class="punctuation token">.</span>due_back<span class="punctuation token">)</span><span class="punctuation token">.</span><span class="function token">format</span><span class="punctuation token">(</span><span class="string token">'MMMM Do, YYYY'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>注意:</strong> 格式化方法可以使用几乎任何模式显示日期。<a class="external external-icon" href="http://momentjs.com/docs/#/displaying/" rel="noopener">moment文档</a>中,可以找到表示不同日期组件的语法。</p> +</div> + +<h2 class="highlight-spanned" id="更新视图"><span class="highlight-span">更新视图</span></h2> + +<p>打开 <strong>/views/bookinstance_list.pug</strong> ,然后用 <code>due_back_formatted</code> 取代 <code>due_back</code> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"> <span class="keyword token">if</span> val<span class="punctuation token">.</span>status<span class="operator token">!=</span><span class="string token">'Available'</span> + <span class="comment token">//span (Due: #{val.due_back} )</span> + span <span class="function token"> </span><span class="punctuation token">(</span>Due<span class="punctuation token">:</span> #<span class="punctuation token">{</span>val<span class="punctuation token">.</span>due_back_formatted<span class="punctuation token">}</span> <span class="punctuation token">)</span> </code></pre> + +<p>这就是本章节的全部了。如果你访问侧边栏的 All book-instances ,你应该会看到所有的归还日期都更吸引人了!</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下一個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者列表页面、种类列表页面、与自我挑战</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html new file mode 100644 index 0000000000..f720812c50 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/flow_control_using_async/index.html @@ -0,0 +1,139 @@ +--- +title: 使用 async 进行非同步流控制 +slug: learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async +--- +<p>有些本地图书馆网页的控制器代码,会依赖多重非同步要求的结果,可能会需要以某种特定次序运行,或者以平行方式运行。为了管理流控制,并在我们所有需要用到的信息,都已经可以取用的时候,再绘制网页,我们将使用许多人采用的 node <a class="external external-icon" href="https://www.npmjs.com/package/async" rel="noopener">async</a> 模组。</p> + +<div class="note"> +<p><strong>注意:</strong> 在 JavaScript 中有许多其他方法,可以管理异步行为和流控制,包括相对较新的 JavaScript 语言功能,如 <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Techniques/Promises">Promises</a>。</p> +</div> + +<p>Async 有很多有用的方法(请查看<a href="http://caolan.github.io/async/docs.html">文档</a>)。一些最重要的功能是:</p> + +<ul> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code> 执行必须并行执行的任何操作。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code> 用于当需要确保异步操作是序列执行的。</li> + <li><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code> 用于必须序列运行的操作,每个操作取决于前面操作的结果。</li> +</ul> + +<h2 class="highlight-spanned" id="为什么需要这么做">为什么需要这么做?</h2> + +<p>我们在 Express 中使用的大多数方法,都是异步的 - 您指定要执行的操作,传递回调。该方法立即返回,并在请求的操作完成时,调用回调。按照 Express 中的惯例,回调函数将错误值作为第一个参数传递(或成功时为 <code>null</code>),并将函数的结果(如果有的话)作为第二个参数传递。</p> + +<p>如果控制器只需要执行<strong>一个</strong>异步操作,来获取呈现页面所需的信息,那么实现很简单 - 我们只需在回调中呈现模板。下面的代码片段,显示了一个函数,该函数呈现模型 <code>SomeModel</code> 的计数(使用Mongoose <code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code>方法):</p> + +<pre class="brush: js"><code>exports.some_model_count = function(req, res, next) { + +</code> SomeModel.count({ a_model_field: 'match_value' }, function (err, count) { + // ... do something if there is an err + + // On success, render the result by passing count into the render function (here, as the variable 'data'). + res.render('the_template', { data: count } ); + }); +<code>}</code> +</pre> + +<p>但是,如果您需要进行<strong>多个</strong>异步查询,并且在完成所有操作之前,无法呈现页面,该怎么办?一个单纯的实现可以用 “菊花链” 连接请求,在先前请求的回调中,启动后续请求,并在最终回调中呈现响应。这种方法的问题,是我们的请求必须串行运行,即使并行运行它们可能更有效。这也可能导致复杂的嵌套代码,通常称为<a href="http://callbackhell.com/">回调地狱</a>。</p> + +<p>一个更好的解决方案,是并行执行所有请求,然后在所有查询完成后执行单个回调。这是 Async 模块简化的流操作!</p> + +<h2 class="highlight-spanned" id="平行的非同步操作"><span class="highlight-span">平行的非同步操作</span></h2> + +<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#parallel" rel="noopener">async.parallel()</a></code>用于并行运行多个异步操作。</p> + +<p><code>async.parallel()</code> 的第一个参数,是要运行的异步函数的集合(数组,对象或其他可迭代的)。每个函数都传递一个回调函数<code>callback(err, result)</code> ,它必须在完成时调用错误<code>err</code>(可以为<code>null</code>)和可选的结果值。</p> + +<p><code>async.parallel()</code>的可选第二个参数是一个回调,它将在第一个参数中的所有函数完成时运行。回调的调用,是使用错误参数和包含各个异步操作结果的结果集合。结果集合与第一个参数的类型相同(即,如果传递异步函数数组,则将使用结果数组,调用最终回调)。如果任何并行函数报告错误,则提前调用回调(具有错误值)。</p> + +<p>下面的示例,显示了当我们将对象作为第一个参数传递时它是如何工作的。如您所见,结果将返回到一个对象中,该对象具有与传入的原始函数相同的属性名称。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equal to: {one: 1, two: 2, ..., something_else: some_value}</span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>如果您将一组函数,作为第一个参数传递,则结果将是一个数组(数组顺序结果,将与声明函数的原始顺序匹配 - 而不是它们完成的顺序)。</p> + +<h2 class="highlight-spanned" id="序列的非同步操作"><span class="highlight-span">序列的非同步操作</span></h2> + +<p><code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#series" rel="noopener">async.series()</a></code>方法用于按顺序运行多个异步操作,后续函数不依赖于先前函数的输出。它本质上是声明的,并且行为与<code>async.parallel()</code>.相同。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + one<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + two<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> + something_else<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> <span class="punctuation token">.</span><span class="punctuation token">.</span><span class="punctuation token">.</span> <span class="punctuation token">}</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback after the last asynchronous function completes.</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// 'results' is now equals to: {one: 1, two: 2, ..., something_else: some_value} </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<div class="note"> +<p><strong>注意:</strong> ECMAScript(JavaScript)语言规范指出,对象的枚举顺序是未定义的,因此可能不会按照在所有平台上指定它们的顺序,调用这些函数。如果顺序真的很重要,那么你应该传递一个数组而不是一个对象,如下所示。</p> +</div> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">series</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some stuff ...</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// do some more stuff ... </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="punctuation token">]</span><span class="punctuation token">,</span> + <span class="comment token">// optional callback</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// results is now equal to ['one', 'two'] </span> + <span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="依赖序列的非同步操作">依赖序列的非同步操作</h2> + +<p>方法<code><a class="external external-icon" href="http://caolan.github.io/async/docs.html#waterfall" rel="noopener">async.waterfall()</a></code>用于在每个操作依赖于前一个操作的结果时,依次运行多个异步操作。</p> + +<p>每个异步函数调用的回调,包含第一个参数的<code>null</code>,与后续参数里的结果。该序列中的每个函数,都将前一个回调的结果参数,作为第一个参数,然后是回调函数。</p> + +<p>完成所有操作后,将使用上一操作的结果,调用最终回调。当您参考下面的代码片段时,这种工作方式会更加明确(此示例来自 <em>async</em> 文档):</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">waterfall</span><span class="punctuation token">(</span><span class="punctuation token">[</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'one'</span><span class="punctuation token">,</span> <span class="string token">'two'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> arg2<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'one' and arg2 now equals 'two' </span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'three'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="keyword token">function</span><span class="punctuation token">(</span>arg1<span class="punctuation token">,</span> callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// arg1 now equals 'three'</span> + <span class="function token">callback</span><span class="punctuation token">(</span><span class="keyword token">null</span><span class="punctuation token">,</span> <span class="string token">'done'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> +<span class="punctuation token">]</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> result<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// result now equals 'done'</span> +<span class="punctuation token">}</span> +<span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<h2 class="highlight-spanned" id="安装_async">安装<span class="highlight-span"> async</span></h2> + +<p>使用 NPM 包管理器安装 async 模块,以便我们可以在代码中使用它。您可以常规方式执行此操作,在 LocalLibrary 项目的根目录中,打开命令提示并输入以下命令:</p> + +<p> </p> + +<pre class="brush: bash line-numbers language-bash"><code class="language-bash">npm install async</code></pre> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模板入门</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html new file mode 100644 index 0000000000..825474b9ab --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/genre_detail_page/index.html @@ -0,0 +1,120 @@ +--- +title: 种类细节页面 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page +--- +<p>种类细节页面,需要利用<code>_id</code> 字段值 (自动生成) ,以呈现特定种类实例的信息。此页面应该呈现种类名称,各个种类的所有书本列表(每本书都连结到书本的细节页面)。</p> + +<h2 id="控制器">控制器</h2> + +<p>打开 <strong>/controllers/genreController.js</strong> ,并在档案最上方引用 async 和 Book 模组。</p> + +<pre class="brush: js">var Book = require('../models/book'); +var async = require('async'); +</pre> + +<p>找到导出的<code>genre_detail</code><code>()</code>控制器方法,并将其替换为以下代码。</p> + +<pre class="brush: js">// Display detail page for a specific Genre. +exports.genre_detail = function(req, res, next) { + +<strong> async.parallel({ + genre: function(callback) { + Genre.findById(req.params.id) + .exec(callback); + }, + + genre_books: function(callback) { + Book.find({ 'genre': req.params.id }) + .exec(callback); + }, + + }, function(err, results) { + if (err) { return next(err); } + if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); + } + // Successful, so render + res.render('genre_detail', { title: 'Genre Detail', genre: results.genre, genre_books: results.genre_books } ); + });</strong> + +}; +</pre> + +<p>该方法使用<code>async.parallel()</code>,并行查询类型名称及其相关联的书本,并在(如果)两个请求成功完成时,回调呈现页面。</p> + +<p>所需种类记录的 ID ,在 URL 的末尾编码,并根据路由定义(<strong>/genre/:id</strong>)自动提取。通过请求参数(<code style="font-style: normal; font-weight: normal;">req.params.id</code><code style="font-style: normal; font-weight: normal;">)</code>在控制器内访问 ID。它在<code style="font-style: normal; font-weight: normal;">Genre.findById()</code>中用于获取当前种类。它还用于获取符合当前种类的所有<code>Book</code>对象,就是在种类字段中具有种类ID的那些 <code>Book.find({ 'genre': req.params.id })</code>。</p> + +<div class="note"> +<p><strong>注意:</strong> 如果数据库中不存在该类型(即它可能已被删除),则<code>findById()</code>将成功返回,但没有结果。在这种情况下,我们想要显示一个“未找到”页面,因此我们创建一个<code>Error</code>对象,并将其传递给链中的下一个中间件函数<code>next</code>。</p> + +<pre class="brush: js"><strong>if (results.genre==null) { // No results. + var err = new Error('Genre not found'); + err.status = 404; + return next(err); +}</strong> +</pre> + +<p>然后,此消息将传播给我们的错误处理代码(这是在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website#error_handling">生成应用程序框架</a>时设置的 - 有关更多信息,请参阅<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction#Handling_errors">处理错误</a>)。</p> +</div> + +<p>渲染的视图是 <strong>genre_detail</strong>,它传递了该类型的标题<code>title</code>,种类<code>genre</code>和书本列表的变量(<code>genre_books</code>)。</p> + +<h2 id="视图">视图</h2> + +<p>创建 <strong>/views/genre_detail.pug</strong> ,并填写底下文字:</p> + +<pre class="brush: js">extends layout + +block content + + <strong>h1 Genre: #{genre.name}</strong> + + div(style='margin-left:20px;margin-top:20px') + + h4 Books + + dl + each book in genre_books + dt + a(href=book.url) #{book.title} + dd #{book.summary} + + else + p This genre has no books +</pre> + +<p>这个视图跟我们其它的模板非常相似。主要的差别在于,我们不使用 <code>title</code> 传送第一个标题 (虽然它还是用在底层的 <strong>layout.pug</strong> 模板,设定页面的标题)。</p> + +<h2 id="它看起來像是">它看起來像是?</h2> + +<p>运行本应用,并打开浏览器访问 <a href="http://localhost:3000/">http://localhost:3000/</a>。选择 All genres 连结,然后选择其中一个种类 (例如,"Fantasy")。如果每样东西都设定正确了,你的页面看起来应该像底下的截图。</p> + +<p><img alt="Genre Detail Page - Express Local Library site" src="https://mdn.mozillademos.org/files/14462/LocalLibary_Express_Genre_Detail.png" style="border-style: solid; border-width: 1px; display: block; height: 523px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p>您可能会收到与此类似的错误:</p> + +<pre class="brush: bash">Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" +</pre> + +<p>这是来自 <strong>req.params.id</strong> 的 mongoose 错误。要解决这个问题,首先需要在<strong> genreController.js</strong> 页面上要求mongoose,如下所示:</p> + +<pre class="brush: js"> var mongoose = require('mongoose'); +</pre> +然后使用 <strong>mongoose.Types.ObjectId()</strong>将 id 转换为可以使用的。例如: + +<pre class="brush: js">exports.genre_detail = function(req, res, next) { + var id = mongoose.Types.ObjectId(req.params.id); + ... +</pre> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下一个部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本细节页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html new file mode 100644 index 0000000000..27dbdb5788 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/home_page/index.html @@ -0,0 +1,134 @@ +--- +title: 主页 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Home_page +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Home_page +--- +<p>我们创建的第一个页面,是网站的主页面,可以从网站的根目录 (<code>'/'</code>) ,或者 catalog 的根目录 (<code>catalog/</code>) 访问。这将呈现一些网站的静态文字描述,以及动态计算数据库中不同记录类型的“计数”。</p> + +<p>我们已经为主页创建了一个路由。为了完成页面,我们需要更新控制器函数,以从数据库中提取记录的“计数”,并创建一个可用于呈现页面的视图(模板)。</p> + +<h2 class="highlight-spanned" id="路由"><span class="highlight-span">路由</span></h2> + +<p>在 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes">前面的教程</a>,我们创建 index 页面路由。此处要提醒的是,所有的路由函式,都定义在 <strong>/routes/catalog.js</strong>:</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// GET catalog home page.</span> +router<span class="punctuation token">.</span><span class="keyword token">get</span><span class="punctuation token">(</span><span class="string token">'/'</span><span class="punctuation token">,</span> book_controller<span class="punctuation token">.</span>index<span class="punctuation token">)</span><span class="punctuation token">;</span> <span class="comment token">//This actually maps to /catalog/ because we import the route with a /catalog prefix</span></code></pre> + +<p>在 <strong>/controllers/bookController.js</strong> 中,定义回调函数参数(<code>book_controller.index</code>) :</p> + +<pre class="brush: js"><code>exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</code> +</pre> + +<p>我们扩展这个控制器函数,以从我们的模型获取信息,然后使用模板(视图)渲染它。</p> + +<h2 class="highlight-spanned" id="控制器"><span class="highlight-span">控制器</span></h2> + +<p>索引控制器函数需要获取以下有关信息,即数据库中有多少<code>Book</code>,<code>BookInstance</code>,可用的<code>BookInstance</code>,<code>Author</code>和<code>Genre</code>记录,将这些数据渲染到模板中,以创建HTML页面,然后将其返回到HTTP响应中。</p> + +<div class="note"> +<p><strong>Note:</strong> 我们使用<code><a class="external external-icon" href="http://mongoosejs.com/docs/api.html#model_Model.count" rel="noopener">count()</a></code> 方法来获取每个模型的实例数量。这在具有一组可选条件的模型上进行调用,以匹配第一个参数,而回调放在第二个参数(如<a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">使用数据库</a><a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">(Mongoose)</a>)中讨论的那样,并且还可以返回 <code>Query</code> ,然后稍后以回调执行它。当数据库返回计数时,将返回该回调,并将错误值(或空值<code>null</code>)作为第一个参数,并将记录计数(如果存在错误,则返回null)作为第二个参数。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js">SomeModel<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span> a_model_field<span class="punctuation token">:</span> <span class="string token">'match_value'</span> <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span> <span class="punctuation token">(</span>err<span class="punctuation token">,</span> count<span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// ... do something if there is an err</span> + <span class="comment token">// ... do something with the count if there was no error</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> +</div> + +<p>打开 <strong>/controllers/bookController.js</strong>. 在文件顶部附近,您应该看到导出的 <code>index()</code> 函数。</p> + +<pre class="brush: python line-numbers language-python"><code class="language-python">var Book = require('../models/book') + +exports.index = function(req, res, next) { + res.send('NOT IMPLEMENTED: Site Home Page'); +}</code></pre> + +<p>用以下代码片段替换上面的所有代码。这要做的第一件事,是导入(<code>require()</code>)所有模型(以粗体突出高亮显示)。我们需要这样做,是因为我们将使用它们来获取记录的计数。然后它会导入异步模块<em> async</em> 。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> Book <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/book'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> Author <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/author'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> Genre <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/genre'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="keyword token">var</span> BookInstance <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'../models/bookinstance'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +<span class="keyword token">var</span> <span class="keyword token">async</span> <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'async'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + +exports<span class="punctuation token">.</span>index <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span>req<span class="punctuation token">,</span> res<span class="punctuation token">)</span> <span class="punctuation token">{</span> + + <span class="keyword token">async</span><span class="punctuation token">.</span><span class="function token">parallel</span><span class="punctuation token">(</span><span class="punctuation token">{</span> + book_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Book<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">); // Pass an empty object as match condition to find all documents of this collection</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + book_instance_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + BookInstance<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + book_instance_available_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + BookInstance<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">(</span><span class="punctuation token">{</span>status<span class="punctuation token">:</span><span class="string token">'Available'</span><span class="punctuation token">}</span><span class="punctuation token">,</span> callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + author_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Author<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + genre_count<span class="punctuation token">:</span> <span class="keyword token">function</span><span class="punctuation token">(</span>callback<span class="punctuation token">)</span> <span class="punctuation token">{</span> + Genre<span class="punctuation token">.</span><span class="function token">count</span><span class="punctuation token">({}, </span>callback<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> + <span class="punctuation token">}</span><span class="punctuation token">,</span> <span class="keyword token">function</span><span class="punctuation token">(</span>err<span class="punctuation token">,</span> results<span class="punctuation token">)</span> <span class="punctuation token">{</span> + res<span class="punctuation token">.</span><span class="function token">render</span><span class="punctuation token">(</span><span class="string token">'index'</span><span class="punctuation token">,</span> <span class="punctuation token">{</span> title<span class="punctuation token">:</span> <span class="string token">'Local Library Home'</span><span class="punctuation token">,</span> error<span class="punctuation token">:</span> err<span class="punctuation token">,</span> data<span class="punctuation token">:</span> results <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">;</span></code></pre> + +<p><code>async.parallel()</code> 方法传递一个对象,其中包含用于获取每个模型计数的函数。这些函数都是在同一时间开始的。当这些函数全部完成时,最终回调将与结果参数中的计数(或错误)一起被调用。</p> + +<p>成功时,回调函数调用 <code><a class="external external-icon" href="http://expressjs.com/en/4x/api.html#res.render" rel="noopener">res.render()</a></code>,指定名为 '<strong>index</strong>' 的视图(模板),以及一个对象包含了要插入其中的数据 (这包括我们模型计数的结果对象)。数据以键值对的形式提供,可以使用键在模板中访问。</p> + +<div class="note"> +<p><strong>注意:</strong> 上面的<code>async.parallel()</code>裡的回调函数有点不寻常,因为不管是否出现错误,我们都会渲染页面(通常您可能使用单独的执行路径来处理错误的显示)。</p> +</div> + +<h2 class="highlight-spanned" id="视图">视图</h2> + +<p>打开 <strong>/views/index.pug</strong> ,并用底下文字取代它的内容。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">extends</span> <span class="class-name token">layout</span> + +block content + h1<span class="operator token">=</span> title + p Welcome to #<span class="punctuation token">[</span>em LocalLibrary<span class="punctuation token">]</span><span class="punctuation token">,</span> a very basic Express website developed <span class="keyword token">as</span> a tutorial example on the Mozilla Developer Network<span class="punctuation token">.</span> + + h1 Dynamic content + + <span class="keyword token">if</span> error + p Error getting dynamic content<span class="punctuation token">.</span> + <span class="keyword token">else</span> + p The library has the following record counts<span class="punctuation token">:</span> + + ul + li #<span class="punctuation token">[</span>strong Books<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Copies<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Copies available<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>book_instance_available_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Authors<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>author_count<span class="punctuation token">}</span> + li #<span class="punctuation token">[</span>strong Genres<span class="punctuation token">:</span><span class="punctuation token">]</span> <span class="operator token">!</span><span class="punctuation token">{</span>data<span class="punctuation token">.</span>genre_count<span class="punctuation token">}</span></code></pre> + +<p>这个视图很简单。我们扩展了 <strong>layout.pug</strong> 基本模板,覆盖了名为 '<strong>content</strong>' 的模块 <code>block</code>。第一个<code>h1</code>标题,将是传递给<code>render()</code>函数的<code>title</code> 变量的转义文本 — 请注意 '<code>h1=</code>' 的使用方式,将使得接下來的文本,被视为 JavaScript 表达式。然后我们放入一个介绍本地图书馆的段落。</p> + +<p>在动态内容标题下,我们检查从<code>render()</code>函数传入的错误变量,是否已定义。如果是这样,我们列出这个错误。如果不是,我们从<code>data</code>变量中,获取并列出每个模型的副本数量。</p> + +<div class="note"> +<p><strong>注意:</strong> 我们没有转义计数值 (i.e. 我们使用 <code>!{}</code> 语法) ,因为计数值已经被计算过了。如果信息是由终端用户提供的,那么我们就会转义該变量,以用于显示。</p> +</div> + +<h2 class="highlight-spanned" id="它看起来像是">它看起来像是?</h2> + +<p>此处,我们应该已经创建了呈现index页面,所需要的每样东西。运行本地图书馆应用,并打开浏览器访问 <a class="external external-icon" href="http://localhost:3000/" rel="noopener">http://localhost:3000/</a>。如果每样东西都设定正确了,你的网站看起来应该像底下的截图。</p> + +<p><img alt="Home page - Express Local Library site" src="https://mdn.mozillademos.org/files/14458/LocalLibary_Express_Home.png" style="display: block; height: 440px; margin: 0px auto; width: 1000px;"></p> + +<div class="note"> +<p><strong>注意: </strong>您将无法使用侧边栏链接,因为这些网页的网址,视图和模板尚未定义。例如,如果您尝试,取决于您点击的链接,您将获取“尚未实作:图书清单”等错误。在“控制器”文件中的不同控制器中,會指定这些字符串文字(将被合适的数据替换)。</p> +</div> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本列表页面</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html new file mode 100644 index 0000000000..b59601e248 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/index.html @@ -0,0 +1,87 @@ +--- +title: 'Express 教程 5: 呈现图书馆数据' +slug: learn/Server-side/Express_Nodejs/Displaying_data +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</div> + +<p class="summary">我们现在准备好要新增网页,以显示<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">本地图书馆网站</a>的书本与其它资料。这些网页将包含一个主页 ,显示我们拥有的每个模型的记录数,以及所有模型的清单和详细信息页面。借此,我们将获得从数据库获取记录、以及使用模板的实战经验。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前置条件:</th> + <td>完成先前教程主题 (包含 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 Part 4: 路由与控制器</a>)。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td> + <p>了解如何使用异步模组与Pug 样版语言,以及如何从我们的控制器函数中的URL取得信息。</p> + </td> + </tr> + </tbody> +</table> + +<h2 id="概览">概览</h2> + +<p>在我们先前的教程中,定义了可以用来跟资料库互动的 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/mongoose">Mongoose models</a> ,并创建了一些初始的图书馆记录。我们接着<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/routes">创建本地图书馆网站需要的所有路由</a> ,但仅使用"空壳控制器" 函数(这些是骨架控制器函数,当一个网页被存取时,只回传一个"未实现" 信息)。</p> + +<p>下一步,是为这些显示图书馆信息的网页,提供适当的实现(我们将在后面的文章,聚焦网页表单的实现,像是创建、更新、删除信息)。这包含了更新控制器函数,以利用我们的模型获取记录,并定义模板,为用户显示这些信息。</p> + +<p>我们在一开始,提供概述/入门主题,解释在控制器函数中,如何管理异步操作,以及如何使用Pug编写模板。然后我们将为每一个主要的 "只读" 页面提供实现步骤,并且在使用到任何特别的、新的特性时附上简短的解释说明。</p> + +<p>本教程的最后,你对路由、异步函数、视图、模型如何实际运作,应该有了更好的理解。</p> + +<h2 id="本教程的章节">本教程的章节</h2> + +<p>本教程分为下列章节,讲解了为了显示图书馆网站需求的页面而新增各种特性的过程 。在进入下一个教程之前,你需要阅读并逐一实现下列章节。</p> + +<ol> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/flow_control_using_async">使用 async 进行异步流控制</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer">模版入门</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template">本地图书馆基础样版</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_list_page">书本清单页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_list_page">书本实例清单页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Date_formatting_using_moment">日期格式化-使用 moment</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">作者清单页面、分类清单页面</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">、</a><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_list_page">与自我挑战</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Genre_detail_page">分类详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Book_detail_page">书本详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Author_detail_page">作者详情页面</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/BookInstance_detail_page_and_challenge">书本实例详情页面、与自我挑战</a></li> +</ol> + +<h2 id="总结">总结</h2> + +<p>我们现在已经为我们的网站,创建了所有 "只读" 的页面: 一个主页,可以显示每一个模组的实例数量,书本的列表与详细信息页面,书本的实例、作者、分类。沿着目前的学习路径,我们学到了许多基本知识,有控制器、在异步操作时管理流控制、使用Pug创建视图模板、使用模型查询数据库、如何从视图传送信息到模板、如何创建并扩展模板。而完成挑战的人,还会学到如何用moment处理日期。</p> + +<p>在下一篇文章,我们将依据目前为止学到的知识,创建HTML 表单以及表单管理代码,开始修改储存在网站中的资料。</p> + +<h2 id="参见">参见</h2> + +<ul> + <li><a href="http://caolan.github.io/async/docs.html">Async </a>模组 (Async 模组官方文件)</li> + <li><a href="https://expressjs.com/en/guide/using-template-engines.html">在Express中使用模板引擎</a> (Express 官方文件)</li> + <li><a href="https://pugjs.org/api/getting-started.html">Pug</a> (Pug 官方文件)</li> + <li><a href="http://momentjs.com/docs/">Moment</a> (Moment 官方文件)</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/routes", "Learn/Server-side/Express_Nodejs/forms", "Learn/Server-side/Express_Nodejs")}}</p> + + + +<h2 id="本教程文章列表">本教程文章列表</h2> + +<ul> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 新建网站骨架</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由和控制器</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li> + <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署至生产环境</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html new file mode 100644 index 0000000000..41d851e7d7 --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/locallibrary_base_template/index.html @@ -0,0 +1,69 @@ +--- +title: 本地图书馆基础模板 +slug: learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/LocalLibrary_base_template +--- +<p>现在我们了解如何使用Pug拓展模板,让我们开始项目,创建一个基础模板。这个模板会有一个侧边栏,连结到本教程中将要创建的各个页面(例如,呈现并创建书本、种类、作者等等),以及一个主要内容区域,我们将在每个页面中进行覆写。</p> + +<p>开启 <strong>/views/layout.pug</strong> ,并以下列代码置换其内容。</p> + +<pre class="brush: html line-numbers language-html notranslate"><code class="language-html">doctype html +html(lang='en') + head + title= title + meta(charset='utf-8') + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css') + script(src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js') + script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js') + link(rel='stylesheet', href='/stylesheets/style.css') + body + div(class='container-fluid') + div(class='row') + div(class='col-sm-2') + block sidebar + ul(class='sidebar-nav') + li + a(href='/catalog') Home + li + a(href='/catalog/books') All books + li + a(href='/catalog/authors') All authors + li + a(href='/catalog/genres') All genres + li + a(href='/catalog/bookinstances') All book-instances + li + hr + li + a(href='/catalog/author/create') Create new author + li + a(href='/catalog/genre/create') Create new genre + li + a(href='/catalog/book/create') Create new book + li + a(href='/catalog/bookinstance/create') Create new book instance (copy) + + div(class='col-sm-10') + block content</code></pre> + +<p>此模板使用(并包含)来自 <a class="external external-icon" href="http://getbootstrap.com/" rel="noopener">Bootstrap</a> 的 JavaScript 和 CSS ,以改进HTML页面的布局和呈现方式。使用Bootstrap 或其它客户端网页框架,是一种快速的方式,可以创建吸引人的网页,能够良好地适应不同的浏览器尺寸,并且允许我们处理页面的呈现,而不需要纠缠于任何不同尺寸的细节—此处我们只想专注于伺服端代码!</p> + +<p>布局的安排应该相当明白,假如你已经阅读了之前的 <a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data#Template_primer">模板入门</a>。注意,使用 <code>block content</code> 当做定位符号,放到页面内容将要放置的地方。</p> + +<p>基础模板也参考了一个本地 css 档 (<strong>style.css</strong>) ,此档提供了一些额外的样式。打开 <strong>/public/stylesheets/style.css</strong> ,并用底下的 CSS 代码,取代它的内容:</p> + +<pre class="brush: css line-numbers language-css notranslate"><code class="language-css"><span class="selector token"><span class="class token">.sidebar-nav</span> </span><span class="punctuation token">{</span> + <span class="property token">margin-top</span><span class="punctuation token">:</span> <span class="number token">20</span>px<span class="punctuation token">;</span> + <span class="property token">padding</span><span class="punctuation token">:</span> <span class="number token">0</span><span class="punctuation token">;</span> + <span class="property token">list-style</span><span class="punctuation token">:</span> none<span class="punctuation token">;</span> +<span class="punctuation token">}</span></code></pre> + +<p>当我们开始运行网站时,我们应该看到侧边栏出现!在本教程的下个部分,我们将使用以上的布局,来定义各个页面。</p> + +<h2 id="下一步">下一步</h2> + +<ul> + <li>回到 <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li> + <li>继续教程 5 的下個部分: <a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data/Home_page">主页</a></li> +</ul> diff --git a/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html new file mode 100644 index 0000000000..374957bb1b --- /dev/null +++ b/files/zh-cn/learn/server-side/express_nodejs/displaying_data/template_primer/index.html @@ -0,0 +1,149 @@ +--- +title: 模板入门 +slug: learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +translation_of: Learn/Server-side/Express_Nodejs/Displaying_data/Template_primer +--- +<p>模板是一个文字档,定义了一个输出档的结构或者排版,使用定位符号表示,当模板被绘制时,资料将插入到何处(在Express,模板被称为视图)。</p> + +<h2 id="Express_模板选择">Express 模板选择</h2> + +<p>Express 可以与许多不同的<a href="https://expressjs.com/en/guide/using-template-engines.html">模板渲染引擎</a>一起使用。在本教程中,我们使用<a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>(以前称为Jade)作为模板。这是最流行的 Node 模板语言,并且官方将自身描述为 “用于编写HTML,语法干净且空格敏感,受 <a class="external external-icon" href="http://haml.info/" rel="noopener">Haml</a>影响很大”。</p> + +<p>不同的模板语言使用不同的方法,来定义布局和标记数据的占位符 — 一些使用 HTML 来定义布局,而另一些则使用可以编译为 HTML 的不同标记格式。Pug 是第二种类型;它使用 HTML 的表示形式,其中任何行中的第一个单词,通常表示HTML元素,后续行中的缩进,用于表示嵌套在这些元素中的任何内容。结果是一个页面定义直接转换为 HTML,但可以说更简洁,更容易阅读。</p> + +<div class="note"> +<p><strong>注意:</strong> 使用 Pug 的缺点,是它对缩进和空格敏感(如果在错误的位置添加额外的空格,可能会得到没什么帮助的错误代码)。但是,一旦您的模板到位,它们就很容易阅读和维护。</p> +</div> + +<h2 class="highlight-spanned" id="模板组态"><span class="highlight-span">模板</span>组态</h2> + +<p>在我们<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">创建骨架网站</a>时,LocalLibrary 配置为使用 <a class="external external-icon" href="https://pugjs.org/api/getting-started.html" rel="noopener">Pug</a>。您应该看到 Pug 模块作为依赖项,包含在网站的 <strong>package.json</strong>文件中,以及 <strong>app.js</strong>文件中的以下配置设置。设置告诉我们,使用 Pug 作为视图引擎,Express 应该在<strong> /views</strong>子目录中搜索模板。</p> + +<pre class="brush: js line-numbers language-js"><code class="language-js"><span class="comment token">// View engine setup.</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'views'</span><span class="punctuation token">,</span> path<span class="punctuation token">.</span><span class="function token">join</span><span class="punctuation token">(</span>__dirname<span class="punctuation token">,</span> <span class="string token">'views'</span><span class="punctuation token">)</span><span class="punctuation token">)</span><span class="punctuation token">;</span> +app<span class="punctuation token">.</span><span class="keyword token">set</span><span class="punctuation token">(</span><span class="string token">'view engine'</span><span class="punctuation token">,</span> <span class="string token">'pug'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>如果查看 views 目录,您将看到项目默认视图的 .pug 文件。这包括需要用自己的内容替换的主页(<strong>index.pug</strong>)和基本模板(<strong>layout.pug</strong>)的视图。</p> + +<pre><code>/express-locallibrary-tutorial //the project root + /views + error.pug + <strong>index.pug</strong> + layout.pug</code> +</pre> + +<h2 class="highlight-spanned" id="模板语法">模板语法</h2> + +<p>下面的示例模板文件,展示了许多 Pug 最有用的功能。</p> + +<p>首先要注意的是,该文件映射典型 HTML 文件的结构,其中(几乎)每一行中的第一个单词是 HTML 元素,并且缩进用于指示嵌套元素。因此,例如,<code>body</code> 本文元素位于 <code>html</code> 元素内,而段落元素(<code>p</code>)位于 <code>body</code> 元素内等。非嵌套元素(例如,各个段落)位于不同的行上。</p> + +<pre class="brush: html line-numbers language-html"><code class="language-html">doctype html +html(lang="en") + head + title= title + script(type='text/javascript'). + body + h1= title + + p This is a line with #[em some emphasis] and #[strong strong text] markup. + p This line has un-escaped data: !{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em</span><span class="punctuation token">></span></span>'} and escaped data: #{'<span class="tag token"><span class="tag token"><span class="punctuation token"><</span>em</span><span class="punctuation token">></span></span> is not emphasised<span class="tag token"><span class="tag token"><span class="punctuation token"></</span>em |
