在 「精通 MEAN MEAN 堆棧簡介」 中,咱們安裝並配置了一個 MEAN開發環境。在本文中,我將帶領您遍歷所建立的樣例 MEAN.JS 應用程序,進一步介紹 MEAN堆棧的四個關鍵部分:MongoDB、Express、AngularJS 和 Node.js。在遍歷該應用程序時,您將從服務器端到客戶端跟蹤進入的HTTP 請求。css
輸入 mongod
,啓動您的本地 MongoDB 實例。(在 UNIX® 類操做系統上,能夠輸入mongod &
,在後臺啓動進程)。接下來,咱們將對上一篇文章中建立的測試目錄執行 cd
並輸入 grunt
,啓動在 Yeoman 生成器中建立的應用程序。您將看到相似清單 1 所示的輸出。html
應用程序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 文檔的 Folder Structure 頁面,得到有關的更多信息)。github
咱們很快就會接觸到源代碼。首先,快速訪問 package.json,這是您在 上一篇文章 中看到的 Node.js 配置文件。我還將介紹它在客戶端的對應文件。這些文件都位於項目的根目錄中。web
在全部 Node.js 應用程序中,能夠將 package.json 看做是最重要的配置文件。在該文件中,您將會發現提供給 Yeoman生成器的應用程序的元數據,好比名稱、描述和做者,如清單 2 中的部分 package.json 文件所示。mongodb
{ "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
部分數據庫
"scripts": { "start": "grunt", "test": "grunt test", "postinstall": "bower install --config.interactive=false" },
您已經輸入了 grunt
來啓動應用程序。稍後,您將輸入 grunt test
來運行單元測試。postinstall
鉤(hook)是區分服務器端依賴關係和客戶端依賴關係的第一個提示。
在這個最重要的文件中,最重要的部分列出了應用程序的依賴關係,如清單 4 所示。這些 CommonJS 模塊所有運行在應用程序的服務器端。
"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)。
如今,讓咱們來關注一下客戶端。瀏覽器中加載的 JavaScript 庫在bower.json 中定義,如清單 5 所示。
{ "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 所示。
目錄結構
$ 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 目錄中查找應用程序主頁的源代碼。
清單 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 應用程序,那麼您應該瞭解它的典型工做流:
如清單 8 所示,app/routes/core.server.routes.js 文件(Express 框架的一部分)包含應用程序的關鍵進入點。
'use strict'; module.exports = function(app) { // Root routing var core = require('../../app/controllers/core'); app.route('/').get(core.index); };
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 框架的一部分)。
'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。
{% extends 'layout.server.view.html' %} {% block content %} <section data-ui-view></section> {% endblock %}
這裏沒什麼太多內容,只有清單 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 使用各類 模板引擎。一個名爲 ConsolidateJS 的CommonJS 模塊甚至能夠適應非 Express 模塊引擎,並使它們可以與
Express 兼容。本系列文章將繼續使用 Swig;您本身能夠選擇使用其餘模板庫。
您如今能夠看到一些看上去有些相似的 HTML。圍繞 title
、keywords
和description
的 {{}}
分隔符將它們標識爲 Swig佔位符,這些佔位符將被實際值所替代。Swig 是 MEAN.JS Yeoman 生成器安裝的模板引擎。
可是,若是回頭看 清單 9 中的 core
控制器,您會發現傳遞給這個模板的唯一一個值是user
。若是您懷疑其餘佔位符是配置文件中定義的默認值,那麼您的懷疑是正確的。
看一下清單 12 所示的 config/env/all.js,其中包含title
、description
和 keywords
變量。(我對目錄結構進行了搜索,查找這定義這些變量的位置 — 在瞭解 MEAN 堆棧的過程當中您可能但願將這個技巧添加到您的工具箱中)。
'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
爲了增長一些靈活性,能夠在環境變量中具體化運行時配置。在命令行中,能夠臨時設置 PORT
和NODE_ENV
等變量。這樣,在進行開發和測試時就能夠很容易地改變變量的值。(固然,您能夠將它們添加到.bash_profile,或者在 Windows® 中的 Control Panel 中設置它們,使它們具備更長的壽命)。
您可能會由於安全性而使用環境變量。在環境變量中保存用戶名、密碼和鏈接URL,而不是將它們放到容易受破壞的配置文件中(或者擴展到源控制)。這種方法也便於跨多個開發人員或生產機器部署通用的配置文件,並容許每一個機器經過本地環境變量插入唯一值或憑證。
並不僅限制使用 PORT
和 NODE_ENV
環境變量。您的 Platform as a
Service (PaaS) 提供商一般會提供若干個特定於服務的變量。
設置單獨的環境變量當然不錯,可是您可能須要對一些相關的變量進行統一修改。例如,您但願避免修改用戶名但忘記修改對應密碼之類的簡單錯誤。幸運的是,這個MEAN 應用程序支持 命名環境 的概念。(這個概念並非 MEAN 應用程序所獨有的。Rails、Grails 和許多其餘流行的
Web 框架也提供了相似的功能)。
查看清單 13 的目錄樹中的 config/env,您將在其中看到一些命名環境文件。
目錄結構
$ 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。
/** * 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。
'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 所示:
/** * 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 所示。
顯示正確的合併結果
<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 應用程序之旅。
主頁的關鍵內容(如清單 18 中的 app/views/layout.server.view.html 中定義)由 AngularJS 在客戶端填充。
<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 目錄下。
$ tree -L 1 public/ public/ |--- application.js |--- config.js |--- lib |--- modules
若是查看 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 模板。
<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 所示。
<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
內包含一個名爲 content
的block
。請記住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 所示。
'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 應用程序主頁時在瀏覽器中看到的內容相匹配。
<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 模板並將其插入到主頁面中。