【全棧開發】精通 MEAN: 瞭解一個 MEAN 應用程序

在 「精通 MEAN MEAN 堆棧簡介」 中,咱們安裝並配置了一個 MEAN開發環境。在本文中,我將帶領您遍歷所建立的樣例 MEAN.JS 應用程序,進一步介紹 MEAN堆棧的四個關鍵部分:MongoDB、Express、AngularJS 和 Node.js。在遍歷該應用程序時,您將從服務器端到客戶端跟蹤進入的HTTP 請求。css

輸入 mongod,啓動您的本地 MongoDB 實例。(在 UNIX® 類操做系統上,能夠輸入mongod &,在後臺啓動進程)。接下來,咱們將對上一篇文章中建立的測試目錄執行 cd並輸入 grunt,啓動在 Yeoman 生成器中建立的應用程序。您將看到相似清單 1 所示的輸出。html

清單 1. 啓動本地 MEAN.JS

應用程序node

$ grunt
Running "jshint:all" (jshint) task
>> 46 files lint free.

Running "csslint:all" (csslint) task
>> 2 files lint free.

Running "concurrent:default" (concurrent) task
Running "nodemon:dev" (nodemon) task
Running "watch" task
Waiting...
[nodemon] v1.0.20
[nodemon] to restart at any time, enter 'rs'
[nodemon] watching: app/views/**/*.* gruntfile.js server.js config/**/*.js app/**/*.js
[nodemon] starting 'node --debug server.js'
debugger listening on port 5858

 NODE_ENV is not defined! Using default development environment

MEAN.JS application started on port 3000

在瀏覽器中打開 http://localhost:3000,查看應用程序的主頁,如圖 1 所示。jquery

圖 1. 本地 MEAN.JS 主頁git

本地 MEAN.JS 主頁的截圖.png

接下來,咱們將查看目錄結構,查看應用程序如何開始工做(查看 MEAN.JS 文檔的 Folder Structure 頁面,得到有關的更多信息)。github

理解 Node.js 和 Bower 配置文件

咱們很快就會接觸到源代碼。首先,快速訪問 package.json,這是您在 上一篇文章 中看到的 Node.js 配置文件。我還將介紹它在客戶端的對應文件。這些文件都位於項目的根目錄中。web

package.json

在全部 Node.js 應用程序中,能夠將 package.json 看做是最重要的配置文件。在該文件中,您將會發現提供給 Yeoman生成器的應用程序的元數據,好比名稱、描述和做者,如清單 2 中的部分 package.json 文件所示。mongodb

清單 2. package.json,第 1 部分

{
  "name": "test",
  "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js",
  "version": "0.0.1",
  "author": "Scott Davis",
  "engines": {
    "node": "0.10.x",
    "npm": "1.4.x"
  },

接下來,您會看到一系列能夠輸入到命令提示符中的命令,如清單 3 所示。chrome

清單 3. package.json,第 2

部分數據庫

"scripts": {
  "start": "grunt",
  "test": "grunt test",
  "postinstall": "bower install --config.interactive=false"
},

您已經輸入了 grunt 來啓動應用程序。稍後,您將輸入 grunt test來運行單元測試。postinstall 鉤(hook)是區分服務器端依賴關係和客戶端依賴關係的第一個提示。

在這個最重要的文件中,最重要的部分列出了應用程序的依賴關係,如清單 4 所示。這些 CommonJS 模塊所有運行在應用程序的服務器端。

清單 4. package.json,第 3 部分

"dependencies": {
  "express": "~4.2.0",
  "mongoose": "~3.8.8"
},
"devDependencies": {
  "grunt-mocha-test": "~0.10.0",
  "grunt-karma": "~0.8.2",
  "karma": "~0.12.0",
  "karma-jasmine": "~0.2.1",
  "karma-coverage": "~0.2.0",
  "karma-chrome-launcher": "~0.1.2",
  "karma-firefox-launcher": "~0.1.3",
  "karma-phantomjs-launcher": "~0.1.2"
}

dependencies 代碼塊中聲明瞭運行時依賴關係(好比與路由有關的 Express,與 MongoDB 有關的Mongoose)。devDependencies 代碼塊中聲明瞭開發者和編譯時依賴關係(包括測試框架,好比Mocha、Jasmine 和 Karma)。

bower.json

如今,讓咱們來關注一下客戶端。瀏覽器中加載的 JavaScript 庫在bower.json 中定義,如清單 5 所示。

清單 5. bower.json

{
  "name": "test",
  "version": "0.0.1",
  "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js",
  "dependencies": {
    "bootstrap": "~3",
    "angular": "~1.2",
    "angular-resource": "~1.2",
    "angular-mocks": "~1.2",
    "angular-cookies": "~1.2",
    "angular-animate": "~1.2",
    "angular-touch": "~1.2",
    "angular-sanitize": "~1.2",
    "angular-bootstrap": "~0.11.0",
    "angular-ui-utils": "~0.1.1",
    "angular-ui-router": "~0.2.10"
  }
}

您能夠看到,bower.json 與 package.json 相似。它包含一些相同的元數據字段,並使用了一個dependencies 塊來定義客戶端依賴關係,好比 Bootstrap(用於感官以及響應式 Web 設計)和AngularJS(用於客戶端單頁面應用程序)。

一樣,應用程序的源代碼也被分到兩個目錄:一個用於服務器端,一個用於客戶端。

理解目錄結構

這個 MEAN 應用程序有四個主要目錄,如清單 6 所示。

清單 6. MEAN

目錄結構

$ ls -ld */
drwxr-xr-x+  7 scott  staff   238 Jun  6 14:06 app/
drwxr-xr-x+  8 scott  staff   272 Jun  6 14:06 config/
drwxr-xr-x+ 49 scott  staff  1666 Jun  6 14:07 node_modules/
drwxr-xr-x+  8 scott  staff   272 Jun  6 14:06 public/

您主要應關注 app 和 public 目錄。首先從 app 目錄中查找應用程序主頁的源代碼。

研究 MEAN 堆棧的服務器端

清單 7 顯示了 app 目錄結構。

清單 7. app(服務器端)目錄結構

$ tree app
app
|--- controllers
|�� |--- articles.server.controller.js
|�� |--- core.server.controller.js
|�� |--- users.server.controller.js
|--- models
|�� |--- article.server.model.js
|�� |--- user.server.model.js
|--- routes
|�� |--- articles.server.routes.js
|�� |--- core.server.routes.js
|�� |--- users.server.routes.js
|--- tests
|�� |--- article.server.model.test.js
|�� |--- user.server.model.test.js
|--- views
    |--- 404.server.view.html
    |--- 500.server.view.html

    |--- index.server.view.html
    |--- layout.server.view.html

若是您曾經寫過服務器端 MVC 應用程序,那麼您應該瞭解它的典型工做流:

  1. 傳入的 HTTP 請求將到達某個路由器。
  2. 路由器找到合適的控制器來處理該請求。3. 控制器從數據庫構建一個模型(或一個模型列表)並傳遞給一個視圖。4. 視圖將模型與一個模板組合在一塊兒,從而構建 HTML 頁面,而後,將完成的輸出傳遞給正在等待的 HTTP 響應。

如清單 8 所示,app/routes/core.server.routes.js 文件(Express 框架的一部分)包含應用程序的關鍵進入點。

清單 8. app/routes/core.server.routes.js

'use strict';

module.exports = function(app) {
  // Root routing
  var core = require('../../app/controllers/core');
  app.route('/').get(core.index);
};

Strict 模式

Strict 模式是 ECMAScript 5 規範的一部分,這也是最新的 JavaScript 主流版本。(有關的更多信息,請參見Mozilla Developer Network 上的文章「Strict 模式」)。Strict 模式能夠向後兼容。不能理解 'use strict'語句的早期瀏覽器版本會直接忽略它;全部新的瀏覽器將慎重處理它。所以,若是您的新版本瀏覽器中運行的代碼啓用了 strict模式,那麼它也能在舊版本瀏覽器中運行。

該路由器定義了一個單一的路徑(/),由核心控制器的 index 函數處理。注意,核心控制器是一個
CommonJS 模塊,類型爲 require

清單 8 開頭的 'use strict'; 語句會將您的 JavaScript 運行時設置爲 strict模式,這要比過去的 JavaScript 運行時的 「什麼均可以」 的語法規則更嚴格。在 strict 模式下,JavaScript運行時會將誠實的錯誤(honest mistake)處理爲語法錯誤 — 好比不當心將某個變量聲明爲global,或試圖使用以前未經定義的變量。Strict 模式搭配使用JSHint 可確保在開發階段而不是生產階段捕捉到語法錯誤。(固然,實現無 bug完美版本的關鍵在於執行單元測試時實現足夠大的代碼覆蓋範圍)。

接下來,將查看清單 9 所示的 app/controllers/core.server.controller.js(Express 框架的一部分)。

清單 9. app/controllers/core.server.controller.js

'use strict';

/**
 * Module dependencies.
 */
exports.index = function(req, res) {
  res.render('index', {
    user: req.user || null
  });
};

index 函數接受傳入的 HTTP 請求和傳出的 HTTP響應。因爲該請求不須要從數據庫獲取內容,所以沒有對任何模型進行實例化。index 模板被呈現給響應,同時還有一個變量的JSON 塊,它將取代模板中同名的佔位符。

清單 10 顯示了 app/views/index.server.view.html。

清單 10. app/views/index.server.view.html

{% extends 'layout.server.view.html' %}

{% block content %}
  <section data-ui-view></section>
{% endblock %}

這裏沒什麼太多內容,只有清單 11 所示的到app/views/layout.server.view.html 的連接。

清單 11. app/views/layout.server.view.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">

<head>
  <title>{{title}}</title>

  <!-- General META -->
  <meta charset="utf-8">
  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">

  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width,initial-scale=1">

  <!-- Semantic META -->
  <meta name="keywords" content="{{keywords}}">
  <meta name="description" content="{{description}}">

Express 模板引擎

您能夠對 Express 使用各類 模板引擎。一個名爲 ConsolidateJS 的CommonJS 模塊甚至能夠適應非 Express 模塊引擎,並使它們可以與
Express 兼容。本系列文章將繼續使用 Swig;您本身能夠選擇使用其餘模板庫。

您如今能夠看到一些看上去有些相似的 HTML。圍繞 titlekeywords
description{{}} 分隔符將它們標識爲 Swig佔位符,這些佔位符將被實際值所替代。Swig 是 MEAN.JS Yeoman 生成器安裝的模板引擎。

可是,若是回頭看 清單 9 中的 core控制器,您會發現傳遞給這個模板的唯一一個值是user。若是您懷疑其餘佔位符是配置文件中定義的默認值,那麼您的懷疑是正確的。

瞭解配置和環境

看一下清單 12 所示的 config/env/all.js,其中包含titledescriptionkeywords變量。(我對目錄結構進行了搜索,查找這定義這些變量的位置 — 在瞭解 MEAN 堆棧的過程當中您可能但願將這個技巧添加到您的工具箱中)。

清單 12. config/env/all.js

'use strict';

module.exports = {
  app: {
    title: 'Test',
    description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js',
    keywords: 'MongoDB, Express, AngularJS, Node.js'
  },
  port: process.env.PORT || 3000,
  templateEngine: 'swig',

除了模板指望的關鍵字外,該文件還包含其餘一些有趣的值,好比 port
templateEngine

環境變量

您能夠在應用程序之外的地方設置一些變量來修改應用程序的行爲,好比 PORT
NODE_ENV。例如,注意 config/env/all.js 中的 port 設置:

port: process.env.PORT || 3000,

該設置告訴應用程序 「將內部的 port 變量設置爲環境變量 PORT 的值,或在未找到PORT 的狀況下設置爲默認值 3000」。

要對這一設置進行測試,請按下 Ctrl+C 中止應用程序。要重啓應用程序,能夠嘗試使用PORT=4000 grunt,而不是使用 grunt 命令。您的應用程序如今在端口 4000
上運行。

您能夠對 Node.js 進行編碼,從而根據不一樣的運行時環境(開發、生產、準備、測試等等)表現出不一樣的行爲。與 PORT同樣,若是沒有顯式地指定運行時環境,那麼 Express 將爲 NODE_ENV 提供一個默認值 —development。這解釋了在重啓應用程序時它發出的一個警告:

NODE_ENV is not defined! Using default development environment

爲了增長一些靈活性,能夠在環境變量中具體化運行時配置。在命令行中,能夠臨時設置 PORTNODE_ENV 等變量。這樣,在進行開發和測試時就能夠很容易地改變變量的值。(固然,您能夠將它們添加到.bash_profile,或者在 Windows® 中的 Control Panel 中設置它們,使它們具備更長的壽命)。

您可能會由於安全性而使用環境變量。在環境變量中保存用戶名、密碼和鏈接URL,而不是將它們放到容易受破壞的配置文件中(或者擴展到源控制)。這種方法也便於跨多個開發人員或生產機器部署通用的配置文件,並容許每一個機器經過本地環境變量插入唯一值或憑證。

並不僅限制使用 PORTNODE_ENV 環境變量。您的 Platform as a
Service (PaaS) 提供商一般會提供若干個特定於服務的變量。

命名環境(Named environment)

設置單獨的環境變量當然不錯,可是您可能須要對一些相關的變量進行統一修改。例如,您但願避免修改用戶名但忘記修改對應密碼之類的簡單錯誤。幸運的是,這個MEAN 應用程序支持 命名環境 的概念。(這個概念並非 MEAN 應用程序所獨有的。Rails、Grails 和許多其餘流行的
Web 框架也提供了相似的功能)。

查看清單 13 的目錄樹中的 config/env,您將在其中看到一些命名環境文件。

清單 13. config

目錄結構

$ tree config/
config/
|--- config.js
|--- env
|�� |--- all.js
|�� |--- development.js
|�� |--- production.js
|�� |--- test.js
|--- express.js
|--- init.js
|--- passport.js
|--- strategies
    |--- facebook.js
    |--- google.js
    |--- linkedin.js
    |--- local.js
    |--- twitter.js
2 directories, 13 files

在 config/env、development.js、production.js 和 test.js 中,都指定了命名環境。若是您認爲 all.js包含對全部環境通用的值,那麼您的理解就是正確的。

要查看這些文件的讀取和合並位置,請查看清單 14 所示的 config/config.js。

清單 14. config/config.js

/**
 * Module dependencies.
 */
var _ = require('lodash');

/**
 * Load app configurations
 */
module.exports = _.extend(
  require('./env/all'),
  require('./env/' + process.env.NODE_ENV) || {}
);

Lo-dash 是一個 CommonJS 模塊,爲數組、對象和 JSON結構提供了方便的函數。在 清單 14 中,開發人員試圖在 all.js中設置一些基本值,並容許它們被development.js(或 production.js 或 test.js)中的值覆蓋。

您已經查看了 清單 12 中的 config/env/all.js。清單 15 顯示了
config/env/development.js。

清單 15. config/env/development.js

'use strict';

module.exports = {
  db: 'mongodb://localhost/meanjs-dev',
  app: {
    title: 'MeanJS - Development Environment'
  },

理想狀況下,lodash.extend 函數將合併兩個 JSON 塊來生成這個結果:

app: {
  title: 'MeanJS - Development Environment',
  description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js',
  keywords: 'MongoDB, Express, AngularJS, Node.js'
}

不幸的是,這並非您得到的輸出。添加一行代碼將合併後的結構輸出到 config/config.js,如清單 16 所示:

清單 16. 將實際合併後的結果合併到控制檯

/**
 * Load app configurations
 */
module.exports = _.extend(
    require('./env/all'),
    require('./env/' + process.env.NODE_ENV) || {}
);
console.log(module.exports)

輸入 PORT=4000 NODE_ENV=development grunt,返回應用程序。控制檯將顯示:

app: { title: 'MeanJS - Development Environment' }

如控制檯中所示,config/env/development.js 中的 JSON 結構覆蓋了 config/env/all.js中的結構,而不是與之合併。幸運的是,您能夠快速修改 config/config.js 以得到指望的結果。

將函數調用從 _.extend 修改成_.merge。當再次返回應用程序時,應該能夠看到指望的結果:

app: 
   { title: 'MeanJS - Development Environment',
     description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js',
     keywords: 'MongoDB, Express, AngularJS, Node.js' },

若是在瀏覽器中單擊主頁面上的 View > Source,就能夠看到 config 值已經與 HTML模板合併,如清單 17 所示。

清單 17. HTML

顯示正確的合併結果

<head>
  <title>MeanJS - Development Environment</title>

  <!-- Semantic META -->
  <meta name="keywords" content="MongoDB, Express, AngularJS, Node.js">
  <meta name="description" content="Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js">

如今,咱們將從服務器端移動到客戶端,完成這次 MEAN 應用程序之旅。

研究 MEAN 堆棧的客戶端

主頁的關鍵內容(如清單 18 中的 app/views/layout.server.view.html 中定義)由 AngularJS 在客戶端填充。

清單 18. app/views/layout.server.view.html

<body class="ng-cloak">
  <header data-ng-include="'/modules/core/views/header.client.view.html'" 
  class="navbar navbar-fixed-top navbar-inverse"></header>
  <section class="content">
    <section class="container">
      {% block content %}{% endblock %}
    </section>
  </section>

回憶一下,app 目錄包含 MEAN 應用程序的 Express 服務器端部分。從兩點能夠看出 header 在客戶端由AngularJS 管理。首先,不管什麼時候看到 HTML 屬性中有一個 ng 時,都代表它是由AngularJS 管理的。其次,更實用的一點是,包含全部服務器端代碼的 app目錄並不包含模塊目錄。排除使用服務器端做爲一種可能的解決方案後,就剩下使用 public 目錄中的客戶端源代碼。如清單 19 所示,modules目錄明顯位於 public 目錄下。

清單 19. public(客戶端)目錄結構

$ tree -L 1 public/
public/

|--- application.js
|--- config.js
|--- lib
|--- modules

若是查看 lib 目錄,就會看到一些第三方庫:

清單 20. 第三方庫的 public/lib

目錄

$ tree -L 1 public/lib
public/lib
|--- angular
|--- angular-animate
|--- angular-bootstrap
|--- angular-cookies
|--- angular-mocks
|--- angular-resource
|--- angular-sanitize
|--- angular-touch
|--- angular-ui-router
|--- angular-ui-utils
|--- bootstrap
|--- jquery

回憶一下 bower.json 中指定的庫。

可是,若是查看 modules 目錄,就會發現 app/views/layout.server.view.html 中指定了modules/core/views/header.client.view.html 模板。

清單 21. modules/core/views/header.client.view.html

<div class="container" data-ng-controller="HeaderController">
  <div class="navbar-header">
    <button class="navbar-toggle" type="button" data-ng-click="toggleCollapsibleMenu()">
      <span class="sr-only">Toggle navigation</span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </button>
    <a href="/#!/" class="navbar-brand">MeanJS</a>
  </div>

若是將 class="navbar-brand" anchor 的值從 MeanJS修改成其餘值,那麼此更改在保存文件後將會當即反映到瀏覽器中。可是到主 payload 的路徑(主頁的主要內容)更加迂迴。再次查看app/views/layout.server.view.html,如清單 22 所示。

清單 22. app/views/layout.server.view.html

<body class="ng-cloak">
  <header data-ng-include="'/modules/core/views/header.client.view.html'" 
  class="navbar navbar-fixed-top navbar-inverse"></header>
  <section class="content">
    <section class="container">
      {% block content %}{% endblock %}
    </section>
  </section>

container 內包含一個名爲 contentblock。請記住app/views/index.server.view.html:

{% extends 'layout.server.view.html' %}

{% block content %}
  <section data-ui-view></section>
{% endblock %}

這個 block content 包含一個空的部分,其中有一個 `data-ui-view屬性。該屬性用於客戶端 AngularJS 路由器。查看public/modules/core/config/core.client.routes.js,如清單 23 所示。

清單 23. app/views/index.server.view.html

'use strict';

// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
  function($stateProvider, $urlRouterProvider) {
    // Redirect to home view when route not found
    $urlRouterProvider.otherwise('/');

    // Home state routing
    $stateProvider.
    state('home', {
      url: '/',
      templateUrl: 'modules/core/views/home.client.view.html'
    });
  }
]);

當 URL 爲 / 時,客戶端路由器會將modules/core/views/home.client.view.html
模板(如清單 24 所示)插入到 app/views/index.server.view.html 部分,後者包含data-ui-view 屬性。模板的內容應當與位於 MEAN 應用程序主頁時在瀏覽器中看到的內容相匹配。

清單 24. modules/core/views/home.client.view.html

<section data-ng-controller="HomeController">
    <h1 class="text-center">THANK YOU FOR DOWNLOADING MEAN.JS</h1>
    <section>
        <p>
          Before you begin we recommend you read about the basic building 
          blocks that assemble a MEAN.JS application:
        </p>

結束語

在本文中,詳細瞭解了一個 MEAN 應用程序的全部關鍵部分。在服務器端,咱們瞭解到,HTML 請求首先從 Express 路由開始,而後調用Express 控制器函數,然後者將 JSON 數據與 Swig 模板合併,並返回到客戶機。可是整個流程並無在此終結。在客戶端,AngularJS路由得到 HTML 模板並將其插入到主頁面中。

原文出處:精通 MEAN: 瞭解一個 MEAN 應用程序

相關文章
相關標籤/搜索