这是web应用的一个完整的样板工程,用到了Backbone.js & Marionette, node.js & ExpressJS,MongoDB & Moogoose, Handlebars, Grunt.js,Bower和Browserify!
我建立了一个超级基础的单页面应用程序(SPA),就是一个简单的通讯录管理器, 但麻雀虽小,五脏俱全。我写这篇文章的目的是介绍一下这个应用所用到的整个技术栈:后端,数据,前端,工具和测试。主要包括下面这些技术:
  • 后端: node.js和ExpressJS
    • 测试: Mocha,Chai, Sinon,Proxyquire
  • 数据层: MongoDB和Mongoose
  • 工具:Grunt,Bower和Browserify
  • 前端:Backbone.js和Marionette.js
    • 测试:Jasmine,Kama和PhantomJS
我会详细介绍组成上述完整技术栈的各个部分。但要说明一下,因为这是一个非常简单的应用,我不会花太多时间来讲用户界面的细节以及应用是如何工作的,因为这都相当明显(我也不会花太多时间在具体的实现上,因为这不是本应用的核心价值)。相反,我会非常详细地说明搭建应用过程中的各种工作以及使用工具的工作流程。
本文的终极目标是为将来任何的新项目提供一套可以作为初始模板的基础代码。代码应该是能够工作且功能完善,但也要易于消化吸收。最终,如果在读完本文后能够对背后所有的东西都有一个深入的理解,你就能够通过简单的克隆这个代码库来启动你自己的应用!
在本文的下面部分,你将看到:
  • Part 0:工程设置和依赖
  • Part 1:后端-一个简单的node.js和ExpressJS服务器
    • node.js - 初始设置
    • ExpressJS - 路由/控制器
    • Handlebars - 用于渲染页面、布局的模板库
    • 数据层 - Mongoose和MongoDB:
      • MongoDB - 启动服务以及如何在Express中工作
      • Mongoose - 数据模型的Schema定义
      • Seeder - 应用第一次运行时如何构造数据
    • 路由和控制器 - 前端调用的API层
    • 使用Mocha,Chai,Sinon和Proxyquire来测试
  • Part 2:开发工具(Grunt,Bower,Browserfy,TDD)
    • Grunt - 做所有的事情!
      • 初始化配置(加载grunt task)
      • 清理
      • Bower 安装
      • Browserify(vendor, shim依赖和文件)
      • Less 编译
      • Handlebars(通过Browserify进行预编译/转换)
      • Concatenation/Minification和Uglification/Copying
      • 监视器(前后端的重新构建)
      • Karma 命令/监视器
      • jshint
      • 并发任务
      • 分解命令- init:dev, build:dev, build:prod, tdd, test:client, etc.
    • Bower - 管理项目的前端依赖
      • bower.json已经很清楚了
    • Karma/Jasmine - 简单解释一下TDD vs test:client(single run)
  • Part 3: 前端 - 通讯录应用,使用Backbone和Marionette
    • Browerify - 为什么用Browserify和它的优点
    • Backbone/Underscore和Marionette
    • 通讯录应用 - 介绍及简单解释
      • 核心:
        • Marionette
        • 连接到数据层来缓存数据
        • 路由和控制器设置
        • 启动!
      • 视图:
        • Marionette.ItemView(与Backbone.View对比)
        • Handlebar预编译模板
      • 模型/集合
      • 控制器 - 调用API
    • TDD,使用Karma和Jasmine
  • Part 4: 用Heroku来部署!

Part 0: 工程设置和依赖

在开始之前,先确定你已经拥有这个工程并且可以运行它。首先,你需要一份工程的拷贝。最简单的方法是使用Git通过克隆的方式下载一份拷贝:
$ git clone git@github.com:jkat98/benm.git
如果你没有安装Git或者不熟悉这个概念,我强烈要求你读一些入门文章(我写了一篇快速入门可能会有帮助)。
一旦克隆了工程到你的本地目录,你首先要确认一些基本需求。

安装node.js和npm

如果还没有安装node.js(和npm),去http://nodejs.org安装它(npm包含在一个标准的node.js安装包中)。一定要下载最新的稳定版本(写这篇文章时版本是v0.10.24)。
安装之后,确定它能否工作的最简单的办法是打开一个终端或者命令行提示符,输入:
$ node --version
假设它工作正常,你应该能够看到输出“0.10.24”或者任何你安装的版本号。

安装MongoDB服务器

接下来你要确认MongoDB服务器已经安装在你的机器上。安装过程很简单,很像node.js。你可以通过访问MongoDB官网的下载页面(http://www.mongodb.org/downloads)找到合适的下载安装文件。请一定要阅读一下安装/入门文档,因为除了安装还有一些额外的步骤(比如,创建、设置数据目录,等等)
注解:安装MongoDB不是必须的,但是这样的话,本项目中的一大部分就无法工作,除非手工做一些修改将MongoDB/Mongoose部分移除。

安装Grunt CLI

这个项目必须的最后一个工具是Grunt命令行,通过npm安装也非常容易:
$ npm install -g grunt-cli
注释:npm带有-g参数的安装将会是全局安装,意味着在你机器上安装的package是到处可用的。没有-g,package只会安装到你正在工作的本地特定工程或者代码仓库中(保存在工程中一个叫node_modules的目录中)。
Mac用户:全局安装可能需要sudo才能正确安装。

运行app!

这样,如果上面的工作都成功了,现在你可以准备启动这个web应用,然后带她去兜风了!首先你需要安装工程的所有依赖(下一节会有更详细的说明),然后用Grunt.js来初始化工程并启动服务:
$ npm install
$ grunt init:dev
$ grunt server
再一次假设一切工作正常,Grunt应该报告服务已经启动了。你应该看到大量的日志输出,包括MongoDB服务器的连接,以及一些seed数据的插入。让你的浏览器访问http://localhost:3300,app应该已经启动并运行了!随便尝试一个操作,点一个联系人来查看详细信息,添加一个联系人,等等。
不必担心上面刚刚发生的各种疯狂的事情  - 我们将覆盖所有的细节!

Part 1:后端 - 一个简单的node.js和ExpressJS服务器

我们的服务器,运行在node.js上,像其他web服务器一样有两个主要目的。第一个是它应该处理从浏览器过来的请求,然后返回各种被请求的文件。这些文件一般分为两类:静态文件(JavaScript,CSS,HTML,图片,等等)和动态文件(服务器根据模板生成的HTML)。由于我们的app是一个富客户端应用,因此服务器端的模板就不是必须的了(我们只需要返回一个静态html文件)。但是工程中还是包括了对Handlebars模板的支持,因此这仍旧是一个完整的代码基础。假设万一你决定建立并拥有一个比较传统的网站(比如,不是Backbone单页面前端应用),你只需通过在后端使用Handlebars模板就可以很容易地达到目的。
服务器应该做的第二类事情是与数据层通信将用户的session数据持久化。(比如,你与一个app进行交互,关掉浏览器,重新回来后你以前被保存的工作可以再次展现给你)这是MongoDB的价值所在 - 一个noSQL数据库,主要使用JSON来存储数据。

Package.json - 工程需要的所有东西都在这儿:

在我们准备写服务器代码之前,首先要确保服务器所需的所有东西都已经存在于开发环境之中。达到这个目的最简单的方法是将一个基本的package.json放在工程中,文件中包含一个服务端依赖的package列表。由你来决定工程需要什么,通过npm安装他们,package.json文件会随着这些新依赖的加入而逐渐变大。
让我们一起来看看这个文件并解释一下到底发生了什么:
{"name": "myapp","description": "Boilerplate web app using node, express, mongodb, backbone, marionette. Tooling includes Grunt, Bower, Browserify, etc.","version": "0.0.1","author": "Jason Krol","repository": {"type": "git","url": "https://github.com/jkat98/benm.git"},
1. 上面最开始的部分包含了标准的项目描述。非常的不言自明。
  "engines": {"node": "0.10.x"},
2.engines项列出了运行工程所必须的运行时,很明显这里是node.js和它的版本
  "scripts": {"start": "node server.js"},
3.scripts说明了用“run“来启动工程、服务器、应用或者不管是什么,真正运行的是什么命令
  "dependencies": {"express": "latest","mongoose": "~3.8.3","handlebars-runtime": "~1.0.12","express3-handlebars": "~0.5.0","MD5": "~1.2.0"},
4.dependencies列举了工程要正确运行所需的最小依赖。没有这些,服务器不会运行。
  "devDependencies": {"bower": "~1.2.8","grunt": "~0.4.1","grunt-contrib-concat": "~0.3.0","grunt-contrib-jshint": "~0.7.2","grunt-contrib-uglify": "~0.2.7","grunt-bower-task": "~0.3.4","grunt-nodemon": "~0.1.2","karma-script-launcher": "~0.1.0","karma-chrome-launcher": "~0.1.1","karma-html2js-preprocessor": "~0.1.0","karma-firefox-launcher": "~0.1.2","karma-jasmine": "~0.1.4","karma-requirejs": "~0.2.0","karma-coffee-preprocessor": "~0.1.1","karma-phantomjs-launcher": "~0.1.1","karma": "~0.10.8","grunt-contrib-copy": "~0.4.1","grunt-contrib-clean": "~0.5.0","browserify": "2.36.1","grunt-browserify": "~1.3.0","load-grunt-tasks": "~0.2.0","time-grunt": "~0.2.3","grunt-contrib-watch": "~0.5.3","grunt-concurrent": "~0.4.2","grunt-karma": "~0.6.2","grunt-contrib-less": "~0.8.3","grunt-contrib-handlebars": "~0.6.0","grunt-contrib-cssmin": "~0.7.0","hbsfy": "~1.0.0","grunt-shell-spawn": "~0.3.0","chai": "~1.9.0","sinon": "~1.8.1","sinon-chai": "~2.5.0","grunt-simple-mocha": "~0.4.0","proxyquire": "~0.5.2"}
}
5.devDependencies是项目在开发过程中的依赖列表。这个列表很大,大部分是Grunt的插件(稍后会解释),还包括Bower,Browserify,Karma,等等。就是说,这是一个在项目开发时会用到的所有东西的列表,而不是工程运行时需要的。
这样,将package.json配置好后,下一步就是执行一个简单的npm安装命令来安装好所有依赖并准备好与我们的服务器共舞:
$ npm install
因为要下载相当多的模块,所以这可能会花几分钟的时间。说明一下,你有可能在Part 0让工程启动并运行时已经完成了这一步。
如果你是开始一个新项目,配置package.json最简单的方式是执行一个npm init命令,它将问你几个简单问题然后帮你生成一个package.json文件:
$ npm init
或许可以在每个提示后直接按回车键,前提是你对一个基本是空的但完整的package.json文件还满意的话。

用装有ExpressJS和Mongoose的node.js作为服务器:

让我们看看作为主服务器的node.js的一些非常基本的代码。我们让ExpressJS做它擅长的事情,来处理底层很多不太需要关心的事情,这样我们就可以聚焦于配置和建立API路由给前端app来用。
根目录下的大部分文件夹属于node.js服务器,包括app、controllers、views和views/layouts。全部前端代码都在client目录下,public目录用于提供对外服务。通常,任何在public或者views目录下的内容都是浏览器可见的,其它都不可见。
---- app
---- controllers
---- views
-------- layouts
---- public
-------- js
-------- css
---- client
-------- requires
-------- spec
-------- src
-------- styles
-------- templates
---- spec
-------- app
-------- controllers
最核心的服务器文件叫做server.js,它包含了一堆node.js代码(至少包括启动代码)。我们来看一下这个文件:
var express = require('express'),http = require('http'),path = require('path'),routes = require('./app/routes'),exphbs = require('express3-handlebars'),mongoose = require('mongoose'),seeder = require('./app/seeder'),app = express();
在这里我们通过node的require方法引入了一堆模块。传递给require()的模块,如果没有./或者../,表示是通过已安装的或者node.js内置的模块被加载的(例如:http是node.js内核的一部分,express是通过npm安装的)。requires()中包含./或者../的是应用程序自己的模块,我们会稍微说明一下。
现在我们有一堆可以使用的模块了,那我们就看一下启动服务器所需的ExpressJS基本配置:
app.set('port', process.env.PORT || 3300);
// ...app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(express.cookieParser('some-secret-value-here'));
app.use(app.router);
app.use('/', express.static(path.join(__dirname, 'public')));// development only
if ('development' == app.get('env')) {app.use(express.errorHandler());
}
上面的代码是很标准的ExressJS配置。每一个app.use都是加载一种ExpressJS中间件,它们都是基本插件,用于处理一些不需要我们操心的事情!最后一个app.use()(在//developmentonly注释之前)是设置public目录为静态目录,意味着Express将原封不动地提供这些文件(不会对这些文件做任何修改就返回给客户端)。

用于提供动态HTML页面的Handlebars模板

app.set('views', __dirname + '/views');
app.engine('handlebars', exphbs({defaultLayout: 'main',layoutsDir: app.get('views') + '/layouts'
}));
app.set('view engine', 'handlebars');
我们在用Handlebars初始化视图引擎并将它指向views目录。很有可能你会碰到在这里用Jade的情况,因为它是一个很流行的模板引擎,我很喜欢Jade(特别是精炼的HTML语法让它看起来像Zen代码一样)。但是,这里我想用Handlebars,因为这样可以保持前后端的模板语言的统一。服务器端的Handlebars模板保存在views目录中,对应的布局保存在layouts目录中。在后端使用Handlebars的语法跟前端的情况非常类似。

使用Mongoose和MongoDB作为数据层:

目前为止,一切正常,要做的最后一件事情是包含一个数据库服务器连接。这个项目我们用的数据库是MongoDB - 一个noSQL、基于JSON的数据存储系统。在我看来,像这样一个全部基于JavaScript的项目使用MongoDB几乎就是不需要过脑子的事情,非常明显的选择。当然这因人而异。
MongoDB入门
如果你不熟悉MongoDB,下面就是一个速成教程。假设你已经在电脑上安装了MongoDB,你可以在任何终端或者命令行简单敲击mongod命令来启动服务:
$mongod
在另一个终端或者命令行中,使用mongo命令进入MongoDB shell:
$mongo
进入了MongoDB shell后,命令行提示符会改变。几个常用命令:
show dbs - 显示系统中可用的数据库列表
use databasename - 切换到已经存在的数据库(或者如果不存在就新建它)
db.things.find() - 列出当前活动数据库(用上面‘use’设置的)中“things”collection中的所有记
db.things.insert({a: 1, b: 2 c: 3}) - 将参数对象插入到“things”collection中作为一条新记录
db.things.find({a: 1}) - 返回一个含有属性a且其值为‘1’的记录列表
//connect to the db server:
mongoose.connect('mongodb://localhost/MyApp');
mongoose.connection.on('open', function() {console.log("Connected to Mongoose...");// check if the db is empty, if so seed it with some contacts:seeder.check();
});
我们用于连接MongoDB的node.js驱动是Mongoose。有许多类似的用于MongoDB的node.js驱动,但我喜欢Mongoose,因为它有可以用来与model一起工作的schema。(从一个有“代码优先”的.Net程序员的背景来看,这是我学习node时最直接的一个比较)在server.js文件中我们只需要包含一小段代码来使用Mongoose驱动连接MongoDB。注意,用于连接MongoDB的url是“mongodb://localhost/”,后面跟着要连接的数据库名。MongoDB的一个好处是如果要连接的数据库不存在,它会自动创建一个!
一旦连接建立起来,我们会执行一个简单的seeder check函数来检查是否是第一次运行app - 如果是的话,它会将一些记录扔进Contacts collection中(就是“表”),所以第一次运行的时候app也不是完全没记录的。这不是必须的,但会给初次使用带来好的第一印象。

Seeder模块:

seeder对象真是node.js的一个伟大之举。如果回到server.js最开头的部分,你会回忆起我们包含了一堆require()语句。在这里我们将模块引入了node.js,不管是node本身的,或者用npm安装的,甚或就是我们自己的,引入后就可以在代码中像普通JavaScript变量一样来使用它们。我们一起来看一下seeder模块都做了些什么:
var mongoose = require('mongoose'),models = require('./models'),md5 = require('MD5');module.exports = {check: function() {// ...
这里我们又一次包含了几个require(),但这次少了一些,只是由于这是一个特别的模块,只需做它分内的事情,有较少的依赖;换句话说,只有Mongoose、数据模型和MD5(MD5只用来在Gravatar处理图像时生成一个hash值)最重要的是module.exports这一行- 导出了一个JavaScript对象,其中含有一个名叫‘check’的属性方法。Check()会检查Contacts collection中是否含有任何记录,如果没有,它会首先插入一些模拟数据。

API路由和控制器:

//routes list:
routes.initialize(app);//finally boot up the server:
http.createServer(app).listen(app.get('port'), function() {console.log('Server up: http://localhost:' + app.get('port'));
});
我们的server code现在终于配置完成了,并且连接到了数据库服务器,要做的最后一件事是配好路由然后启动服务!
var home = require('../controllers/home'),contacts = require('../controllers/contacts');module.exports.initialize = function(app) {app.get('/', home.index);app.get('/api/contacts', contacts.index);app.get('/api/contacts/:id', contacts.getById);app.post('/api/contacts', contacts.add);app.put('/api/contacts', contacts.update);
};
module.exports = {index: function(req, res) {res.render('index');}
};
这个工程中我们在路由和控制器之间用了1:1的关系,所以如果你看一下app目录中的route.js文件,你会看到里面非常简单。字面上仅仅定义了API层的访问点,用于响应从前端过来的请求。每一条路由的真正实现定义在控制器中,可以在controller/*.js中找到。此外,注意route.js文件最上面的require(),通过引用../controllers/file(没有.js)引入了我们自己的模块。要注意的一点是我们的home控制器只返回一个含有index属性的对象,这个属性会对Handlebars模板进行渲染。contacts控制器返回纯JSON结果- 根据URL请求的不同,结果也会不一样。
最重要的控制器是contacts控制器(controllers/contacts.js),它用于前端获取初始联系人列表,返回每个联系人的详细信息,以及添加新联系人,更新已经存在的联系人。contact.js中的每一个方法主要是通过Mongoose来对MongoDB进行插入、修改、查询的。

数据模型:

服务器代码最后一部分是数据模型。我们的数据模型是定义了数据schema的对象。如果你看一下app/models.js文件,你会在最上头看到一些requires()(尤其是Mongoose,Schema和ObjectId - 都与Mongoose有关)。我们定义了Contact模型,也就是一个新的数据schema和新定义的属性(以及类型),最终导出一个对象,包含有Contact的模型定义。这种方式可以定义项目的所有数据schema,然后通过主“models”模块返回。
假设除了联系人之外,我们还想保存一些喜欢的书籍。在model.js中,我们要声明另一个变量Book,让它=new Schema({}),在这个对象内部定义每本书的属性(例如,标题,作者,描述,ISBN,等等)。然后在moduel.exports里Contact:行之后增加一个逗号然后添加Book:mongoose.model('Book',Book);这样,所有包含有var models=require('./models')的地方将能够立刻得到Book模型。你可以不必这样在一个文件/模块中包含所有的模型 - 这只是我在这个工程的做法(可能对大多数项目也可以这样,除非你有大量的模型)。
var mongoose = require('mongoose'),Schema = mongoose.Schema,ObjectId = Schema.ObjectId;var Contact = new Schema({email:      { type: String },name: {first:  { type: String },last:   { type: String }},phone:      { type: String },gravatar:   { type: String }
});module.exports = {Contact: mongoose.model('Contact', Contact)
};
我们的整个服务端代码只包含6个小js文件,大多数里面只有几行代码。他们一起支撑起一个全功能的,具有数据库连接和CRUD逻辑的web服务器。

用Mocha,Chai,Sinon和Proxyquire测试node

在服务器端我们用了几种不同的工具来处理node代码的测试问题。主要是用Mocha,结合Grunt mocha runner来执行测试。测试例本身是用Chai写的,其本身就是断言语言, Sinon用于监视和打桩,如果需要在node世界中造一点”假”的话,Proxyquire会大有用武之地。
要运行测试例,执行下面的命令:
$grunt test:server
这会执行mocha runner,在spec目录里的所有测试都会被跑到。在终端中你应该能够看到每一个测试例的输出,希望列表中都是绿色的对勾。
测试例按照与app本身相似的结构进行组织,有一个app和controllers目录。
要想了解更多有关node.js测试的内容,参阅我的另一篇博文,里面有更多细节。

Part2: 开发工具(Bower,Grunt,Browserify)

现在服务器部分已经讲完了,在我们继续讲前端app之前,我想重温一下开发过程中用到的工具套件。

Bower - 前端依赖管理

Bower是一个伟大的小工具!相当于node中的npm。使用Bower你可以快速方便地安装工程所需的前端依赖。通常,在过去,当我们要用到像jQuery,underscore,Backboen.js这些类库时,总是去他们的网站或者GitHub仓库(千篇一律的),找到下载链接,保存一份.js文件的拷贝到工程中的某处,然后在主布局文件中增加一行对那个文件的引用。有了Bower,整个过程简化了许多。
像node那样,Bower依赖于它自己的bower.json文件,这非常类似于已有的package.json(上面讲过了)文件。
{"name": "myapp","version": "0.0.1","private": true,"dependencies": {"backbone.marionette": "~1.4.1","jquery": "~1.10.2","underscore": "~1.5.2","backbone": "~1.1.0"},"devDependencies": {"jasmine": "~1.3.1"},"exportsOverride": {"jquery": {"js": "jquery.js"},"underscore": {"js": "underscore.js"},"backbone": {"js": "backbone.js"},"backbone.marionette": {"js": "lib/backbone.marionette.js"},"jasmine": {}}
}
dependencies和devDepandencies两部分都有定义。有了这些定义,你可以通过简单执行‘bower install’,就像‘npm install’那样,它将会把在bower.json文件中预定义的文件下载下来(不需要挨个手工安装)。
exportsOverride部分简单定义了当从‘bower_components’安装到‘client/requires’目录时,如何组织这些文件。注意到这里没有Jasmine的定义,说明它在客户端require阶段不会被拷贝。这是由于app运行在前端,而客户端不需要加载Jasmine库,它只是在本地开发阶段做测试用的。
如果你要从头开始一个项目并且想在工程中引入jQuery,通过Bower能够很方便地安装:
$ bower install jquery
同时,假设你也需要Backbone.js和underscore.js:
$ bower install backbone
$ bower install upderscore
然后,你需要做的就是在你的主布局文件中增加一个到bower_components/jquery/jquery.js的引用!
当然,如果能再简单一些就更好了...

Grunt.js - 全能的任务执行者

Grunt.js非常狗屎,它可用插件的总量十分庞大。在这种情况下,如果你没有明确为什么要用这个插件,那理解和使用Grunt会非常令人困惑。
让我们后退一步来看一下如果没有自动化任务(类似于Grunt)来协助的话,典型的开发流程会是什么样:
你用自己喜欢的方式将前端文件都组织好了。可能由于各种原因你有非常多的文件 - Backbone模型、视图、集合、路由、控制器,等等。此外,你决定使用LESS来写CSS - 因为你很潮而且大家都在用它。作为一名优秀的程序员,你最终希望工程中所有.js文件都合并到一个单独的文件,最好压缩过(并进行丑化,这样别人就无法搞懂你的源码也就不能偷走你的工作了)。很明显,LESS文件不能原样地返回给浏览器- 它们也需要转换为普通的.css文件。此外,你践行TDD,希望在每次应用的代码或者测试代码本身发生变化时能定期测试前端代码。不仅如此,你需要真正启动node.js服务器这样你才能在本地浏览器上看到你的app - 每次你修改node代码时,你需要重启服务器。最后一点 - 上面这些只是为了执行app的一个独立测试,你将需要在每次修改代码时都重复上面大多数步骤。
哦噢,有太多烦心事了!这就是Grunt的用武之地。用Grunt你可以创建一个自动化”脚本“,他会在每次你编辑文件或者做任何修改时自动执行。将这个流程自动化后,执行”grunt server“这样的命令会做下列的事情:
  • 用Bower安装任何前端依赖
  • 合并所有的.js文件到一个单独的文件(包括那些Bower依赖)
  • 压缩(最小化)这个单独的.js文件并丑化它
  • 编译LESS文件为单独的.css文件
  • 复制这两个文件到一个public目录,以便你应用的index.html文件可以引用到它们
  • 启动Karma并执行测试套
  • 启动node服务器
  • 每次在任何有关的文件变化时做上述事情
    • 修改.js文件 - 重新执行合并、压缩和复制。测试也要重新执行。
    • 修改.LESS文件 - 重新执行编译和复制。
    • 修改服务器相关的node.js文件 - 重启node服务器
  • 此外,你也可以运行任何其他的便捷任务,比如jsHint,这样就能够立刻检查所有JavaScript文件的语法错误了。
现在,一个典型的开发流程看起来应该是这样:
  • 启动”grunt server“。
  • 开始工作。
  • 刷新浏览器*
*注:你甚至可以更有想象力一些,使用Live reload插件,这样就不需要最后一步了!
很了不起吧?!你担心的所有乱七八糟的事情都可以抛到窗户外面并忘记了,所有的这些让网站开发走了一条非同寻常的路!
我们看一下Gruntfile.js文件。提示 - 它很大,所以我们把它拆成一块一块来分析。
module.exports = function(grunt) {require('time-grunt')(grunt);require('load-grunt-tasks')(grunt);grunt.initConfig({pkg: grunt.file.readJSON('package.json'),
这是Gruntfile最基础的部分。它基本就是用了node的module.exports来返回一个Grunt函数,然后你可以传入一个配置对象给initConfig来启动它。前面两行require了两个很方便的Grunt插件,最重要的一个是load-grunt-tasks。通常对于Gruntfile,你需要手工指定使用一个插件要完成什么任务。这个方便的小插件通过读取package.json文件并查找和加载在dependencies及devDependencies列表中以“grunt-”开头的插件来帮你自动实现这个目标!这让配置文件的很大一部分是不必要的了。
下一步,我们运行Grunt的initConfig函数并配置整个脚本。第一行读取主package.json文件以便后面使用 - 具体说,我们会从package文件中拿到工程的名称,用它来命名在构建过程中生成的一些文件。

Bower安装

去读bower.json文件并安装任何前端依赖。
bower: {install: {options: {targetDir: 'client/requires',layout: 'byComponent'}}
},

Clean

在每次构建之前都要清空我们维护的任何临时构建目录(这样不会到处都是残留文件)。‘build’命令清空整个‘build’目录。‘dev’命令清空指定的一系列文件(例如,因为通常不会变化而不必删除vendor.js文件)。最后,‘prod’命令会清空一个叫做‘dist’的目录,在app准备发布时这个目录用于将其分发。(这个文件夹通常只包含2个关键文件,myapp.js和myapp.css - 再加上必要的图片文件)
clean: {build: ['build'],dev: {src: ['build/app.js', 'build/<%= pkg.name %>.css', 'build/<%= pkg.name %>.js']},prod: ['dist']
},

Browserify

这可是个大头 - 不可小觑。Browserify是一个很神奇的工具,基本上它能让你在前端代码中就像在后端的node.js中一样使用模块。这意味着你可以在整个技术栈使用相同的编程习惯和风格。Browserify允许你使用模块化的方式搭建前端代码,只在你需要的时候引入依赖。此外,Grunt的Browserify插件会将app和第三方文件分别合并到一个单独的文件。(这样,如果你有5个第三方文件和20个app文件,你将只会得到一个vendor.js文件和一个app.js文件。)
Browserify的配置可以拆分为3个主要部分:vendor,app和test。Vendor部分从Bower中得到所有的前端依赖,Browserify他们然后将其合并为一个单独的vendor.js文件。App部分为你核心的app文件做同样的事情。注意到在app部分,我们只定义了main.js(并不是我们所有的.js文件)。这是由Browserify的工作方式决定的,它通常会遍历整个app,在每次发现require()的时候加载必需的依赖。node.js几乎以相同的方式工作。
另外,你可以shim第三方类库,定义他们自己的依赖。因此,例如,对于Marionette,你可以定义它依赖jQuery、Backbone和underscore。这样,在你前端的app.js文件里,你只需要require('backbone.marionette'),它会自动包含jQuery、underscore和Backbone,也不需要你手工require它们。
最后,Browserify会自动搜索前端Handlebars模板中的所有requires()并自动将其编译为JavaScript函数。这会非常显著地为用户加速前端的渲染速度,因为视图模板不必在运行时渲染了(而这正是Handlebars通常的工作方式)。
browserify: {vendor: {src: ['client/requires/**/*.js'],dest: 'build/vendor.js',options: {shim: {jquery: {path: 'client/requires/jquery/js/jquery.js',exports: '$'},underscore: {path: 'client/requires/underscore/js/underscore.js',exports: '_'},backbone: {path: 'client/requires/backbone/js/backbone.js',exports: 'Backbone',depends: {underscore: 'underscore'}},'backbone.marionette': {path: 'client/requires/backbone.marionette/js/backbone.marionette.js',exports: 'Marionette',depends: {jquery: '$',backbone: 'Backbone',underscore: '_'}}}}},app: {files: {'build/app.js': ['client/src/main.js']},options: {transform: ['hbsfy'],external: ['jquery', 'underscore', 'backbone', 'backbone.marionette']}},test: {files: {'build/tests.js': ['client/spec/**/*.test.js']},options: {transform: ['hbsfy'],external: ['jquery', 'underscore', 'backbone', 'backbone.marionette']}}
},

LESS

将所有.less文件编译为.css文件并将.css文件命名为'myapp'.css放到构建目录中。这个文件不仅包含所有.less文件,而且还有其它存在并被第三方类库(jQueryUI,reset.css等等)依赖的.css文件。
less: {transpile: {files: {'build/<%= pkg.name %>.css': ['client/styles/reset.css','client/requires/*/css/*','client/styles/less/main.less']}}
},

concat

将所有.js文件合并为一个单独的文件。拿到vendor.js和app.js文件(都由Browserify生成)并合并为一个单独的'myapp'.js文件,将最终生成的这个文件放到构建目录中。
concat: {'build/<%= pkg.name %>.js': ['build/vendor.js', 'build/app.js']
},

copy

当我们在开发模式下运行Grunt服务时,从构建目录复制文件到它们应该在的目录,这样前端app才能够看到它们,后端服务器也能够用其提供服务。.js、.css和图片文件分别放在不同的目录中(看一下server/views/layout/里面的主.handlebars布局文件,你会看到最终的.js和.css文件放在布局文件的什么地方)
copy: {dev: {files: [{src: 'build/<%= pkg.name %>.js',dest: 'server/public/js/<%= pkg.name %>.js'}, {src: 'build/<%= pkg.name %>.css',dest: 'server/public/css/<%= pkg.name %>.css'}, {src: 'client/img/*',dest: 'server/public/img/'}]},prod: {files: [{src: ['client/img/*'],dest: 'dist/img/'}]}
},

cssmin

从.css文件中删除所有的空白字符并尽可能地压缩。默认的,这只在生产环境中设置(因为在开发时,最好还是能够直接在浏览器中查看源码来检阅最终的.css文件)。
cssmin: {minify: {src: ['build/<%= pkg.name %>.css'],dest: 'dist/css/<%= pkg.name %>.css'}
},

uglify

类似的对于我们的主.js文件,删除任何空白字符,修剪变量名称和注释。在能正常工作的前提下让文件尽量小。再一次,这只是用于生产环境(因为在开发时,当在浏览器中调试时,我们仍旧希望能够读取.js文件)。
uglify: {compile: {options: {compress: true,verbose: true},files: [{src: 'build/<%= pkg.name %>.js',dest: 'dist/js/<%= pkg.name %>.js'}]}
},

watch

随时监控文件变化。当它们变化时,执行预定义好的Grunt任务(上面大都讲过了)。被监控的client/src/*.js文件会重新运行Browserify任务,被监控的.less文件会重新运行编译步骤,然后进行必要的复制,在每个文件被修改时重复上述步骤。
watch: {scripts: {files: ['client/templates/*.hbs', 'client/src/**/*.js'],tasks: ['clean:dev', 'browserify:app', 'concat', 'copy:dev']},less: {files: ['client/styles/**/*.less'],tasks: ['less:transpile', 'copy:dev']},test: {files: ['build/app.js', 'client/spec/**/*.test.js'],tasks: ['browserify:test']},karma: {files: ['build/tests.js'],tasks: ['jshint:test', 'karma:watcher:run']}
},

nodemon

像watch一样,除了几个相关的.js文件,每次服务器端node.js文件变化时,重启服务器,这样最新版本就能得到执行。
nodemon: {dev: {options: {file: 'server/server.js',nodeArgs: ['--debug'],watchedFolders: ['server/controllers', 'server/app'],env: {PORT: '3300'}}}
}

shell

这只是简单执行一个命令行。具体点,就是每次在主服务器启动时执行‘mongod’命令(因为app要正常工作它们两个必须同时运行)。
shell: {mongo: {command: 'mongod',options: {async: true}}
},

concurrent

并发意味着你能够同时异步地执行多个“阻塞”任务。对于开发环境,这些任务包括监控服务器的nodemon,数据库的mongod和监控前端脚本、less和测试的watcher。没有并发,那就由于同时只能执行一个任务而不得不排队等待,这导致Grunt在执行所有其它必须同时并行运行的任务之前被挂起、阻塞。
concurrent: {dev: {tasks: ['nodemon:dev', 'shell:mongo', 'watch:scripts', 'watch:less', 'watch:test'],options: {logConcurrentOutput: true}},test: {tasks: ['watch:karma'],options: {logConcurrentOutput: true}}
},

karma

具体执行Karma测试的runner和watcher的任务。后面我们将会再多讲一点。
karma: {options: {configFile: 'karma.conf.js'},watcher: {background: true,singleRun: false},test: {singleRun: true}
},

jsHint

在所有必需的.js文件上运行jsHint语法检查器。
jshint: {all: ['Gruntfile.js', 'client/src/**/*.js', 'client/spec/**/*.js'],dev: ['client/src/**/*.js'],test: ['client/spec/**/*.js']
}
这部分是Gruntfile.js文件中initConfig的最后一部分!前面讲了很多,但是通常如果你想让所有的事情都自动化并让自己过得舒服些,它可以变得更长。在最开始你配置Gruntfile花费的时间能够为你节省在项目开发过程中10倍于它自己的时间。更别提它让你的的大脑得到解放,不必担心其它乱七八糟的事情。
现在,所有都不再是问题了,你只需要配置一些简单的命令行命令,在启动Grunt时可以用来干具体的活!
grunt.registerTask('init:dev', ['clean', 'bower', 'browserify:vendor']);
grunt.registerTask('build:dev', ['clean:dev', 'browserify:app', 'browserify:test','jshint:dev', 'less:transpile', 'concat', 'copy:dev']);
grunt.registerTask('build:prod', ['clean:prod', 'browserify:vendor', 'browserify:app','jshint:all', 'less:transpile', 'concat', 'cssmin', 'uglify', 'copy:prod']);
grunt.registerTask('server', ['build:dev', 'concurrent:dev']);
grunt.registerTask('test:client', ['karma:test']);
grunt.registerTask('tdd', ['karma:watcher:start', 'concurrent:test']);
每个‘注册’的任务都很不言自明,基本上你需要先给它一个名字,然后这样执行它
$grunt mytask
‘mytask’可以是你想要的任何名字。在registerTask()里跟在名字后面的是你想要执行的任务列表,就是你在initConfig中定义的那些任务。
init:dev - 在你要开始新项目时首先要执行的任务。它会进行Bower安装,复制那些文件到client_requires目录,然后给第三方文件执行Browserify。这个任务只需要在开发开始的时候执行一次,因为你的Bower依赖不会总是变化,所以没有必要在你每次修改app文件时重新执行Bower安装。
build:dev - 这个会做大部分工作。它会在你每次运行grunt服务时被执行,负责处理每次你修改文件需要重新构建时的任务。
grunt server - 操作的大脑。这个是你最经常使用的命令,它执行一个build:dev命令,然后是concurrent:dev来启动所有的watcher和服务器。
最后是test:clienttdd,二者都会启动Karma并运行测试例,唯一的不同是‘test:client’只执行一次,‘tdd’在自动模式下每次都会运行 - 每次在app.js和test.js文件被修改时重新执行测试例。

使用Jasmine、Karma和PhantomJS来做TDD

目前我在前面已经断断续续得提到过一些关于Karma和TDD的事情,但是并没有进行详细解释。这是因为我已经写过一篇相当详尽的文章来讲它们,你可以在这里阅读!只要说TDD(测试驱动开发)非常重要就够了。它背后的基本思想是你用一套测试例反过来验证真正的代码。假如你在调试或者修改核心代码时不小心break了什么,你的测试例能够马上警告你!如果你不经常做TDD,我强烈建议你考虑一下,至少通读一下我的其他文章。

Part 3: 使用Backbone.js和Marionette.js的前端应用

现在该来详细讲一下如何真正搭建前端应用了。这个app本身是一个很原始很简单的通讯录管理应用。app将联系人以带有名字、email和头像的小卡片的形式呈现。点击卡片会打开联系人的详细信息视图。你也可以添加联系人或者更新、删除已有联系人。它不是一个“To Do”应用,而真正实现了上述功能。
有关Browserify的注释
像上面在Gruntfile.js配置部分讲述的那样,我们在应用的前端部分严重依赖Browserify。这只是因为我真的非常喜欢Browserify的工作方式,我喜爱它将前端编程变得像后端开发一样。以前,我写过一篇文章讨论在Backbone.js中使用require.js-但是为了现在这篇文章,我决定做些改变,尝试一些不同的东西。其实相比于以前用require.js的经验,我个人更喜欢使用Browserify。可以说,如果你以前从来没有用过node.js或者任何前端模块化开发框架,它们看起来都会很让人困惑。我建议你读一下我以前的文章,require.js和Browserify的主题和概念说起来很类似。

Main.js和App.js - 启动和大脑

var App = require('./app');
var myapp = new App();
myapp.start();
Main.js就像它听起来那样 - 是主JavaScript文件。这个文件很小,但是它将启动所有东西!它做的第一件事情是,使用Browserify,require我们的主app对象(位于app.js)。有了这个对象我们可以创建一个新实例并正式启动这个app。
app.js也像它听起来那样 - 它基本上可以说是我们的整个app。它就是位于中央的大脑,协调app的全部可动部分。我们来将它拆开,看一下究竟做了些什么。
var Marionette = require('backbone.marionette'),Controller = require('./controller'),Router = require('./router'),ContactModel = require('./models/contact'),ContactsCollection = require('./collections/contacts');
看起来很熟悉,对吗?这里我们声明app.js的依赖,声明了与Marionette、控制器、路由、联系人模型和联系人集合相对应的变量。每一个模块(除了Marionette)都是我们自己的文件,后面会有更多解释。注意Marionette依赖于jQuery、underscore和Backbone,但是我们不需要require它们,因为这已经在Gruntfile.js的Browserify的配置中通过依赖shim被实现了。
下面,创建了一个空函数并通过模块系统导出。即便是空的,它会立即在原型中增加了一个“start”的函数,这个函数将做所有工作。
module.exports = App = function App() {};App.prototype.start = function(){App.core = new Marionette.Application();
在App.start()中发生了4件重要的事情:
  • 创建了一个新的Marionette.Application()
  • 绑定事件到initialize:before
  • 绑定事件到app:start
  • 启动Marionette应用
绑定的两个事件不会真正被触发直到启动Marionette应用(这就是为什么最后我会调用App.core.start())。现在我们分别看一下每个事件:

initialize:before

App.core.on("initialize:before", function (options) {App.core.vent.trigger('app:log', 'App: Initializing');App.views = {};App.data = {};// load up some initial data:var contacts = new ContactsCollection();contacts.fetch({success: function() {App.data.contacts = contacts;App.core.vent.trigger('app:start');}});
});
这个事件在app真正启动之前先一步发生。你可以参考Marionette文档来弄清有哪些可用的事件以及它们发生的顺序。这里initialize:before发生在app:start之前 - 我们这样定义在app启动之前想要处理的事情。具体说,我们新建了App的一些缓存对象(视图和数据),然后从服务器得到联系人数据。一旦数据获取完成,我们真正触发app:start。

app:start

App.core.vent.bind('app:start', function(options){App.core.vent.trigger('app:log', 'App: Starting');if (Backbone.history) {App.controller = new Controller();App.router = new Router({ controller: App.controller });App.core.vent.trigger('app:log', 'App: Backbone.history starting');Backbone.history.start();}//new up and views and render for base app here...App.core.vent.trigger('app:log', 'App: Done starting and running!');
});
// ...
App.core.start();
在app:start里面,我们新建了一个controller的实例和一个router的实例,router将controller作为它构造函数一部分。二者都是Marionette对象(会再解释)。在本项目中,我们的前端路由和控制器的工作方式几乎跟后端node.js的路由和控制器一样,唯一的不同是node.js路由管理了服务器端可以访问的URL - Marionette路由管理着前端app运行时可以被访问的URL。将Backbone或者Marionette路由看作是与DOM事件类似的东西,但是DOM元素是window.location地址栏。如果浏览器地址栏中的URL改变了,触发应用中一个路由事件。(不像普通URL那样加载一个新的页面。)

Marionette路由和控制器

像你在app.js中看到的那样,我们在app:start事件中定义了一个路由和控制器的新实例。我也提到他们几乎就像node.js中的对手那样工作。

route.js

var Marionette = require('backbone.marionette');module.exports = Router = Marionette.AppRouter.extend({appRoutes: {'#'  : 'home','details/:id' : 'details','add' : 'add'}
});
router.js文件非常简单 - 新建一个Marionette AppRouter对象并将其赋值为一个appRoutes的集合。这些appRoutes将与Controller对象中的同名函数1:1对应。这里我们将app的根URL'#'指向控制器中的'home'函数。然后'details/:id'是得到联系人详细信息视图的URL,指向控制器的'detail'函数。最后我们将'add'URL指向控制器中的'add'函数。

controller.js

home: function() {var view = window.App.views.contactsView;this.renderView(view);window.App.router.navigate('#');
},
控制器是路由背后真正逻辑的拥有者。像我们前面提到的那样,路由和控制器有1:1的关系,意味着每一个定义在路由表中的路由都对应于一个定义在控制器中的函数。控制器中的每一个函数负责相应路由的屏幕渲染。用'home'函数举例来说,我们建立了一个视图,如果我们正在看详细信息视图的话还包括一个模型,然后通过调用控制器的renderView函数来渲染它。renderView函数首先会摧毁已经存在的视图,如果这个视图已经被渲染的话。这里要小心事件处理并保证不要有僵尸视图和/或者事件处理器留存。

Models和Collections

var Backbone = require('backbone');module.exports = ContactModel = Backbone.Model.extend({idAttribute: '_id'
});var Backbone = require('backbone'),ContactModel = require('../models/contact');module.exports = ContactsCollection = Backbone.Collection.extend({model:  ContactModel,url: '/api/contacts'
});
这个简单的app有一个最基本的模型 - 联系人,另外还有一个联系人集合。二者都定义在‘src’目录中的各自文件夹里。你能看到集合的url已经被设置为我们的API。另外,因为我们用mongoDB,所以我们手工将模型的id属性指向_id字段,MongoDB默认用这个字段作为唯一标识。

Views

这个应用有3个主要的视图,一个小的联系人“卡片”,它在主页中显示为列表,一个联系人详细信息页面以及一个新增联系人表单。

views/contacts.js

联系人视图实际上包含了2个视图,一个Marionette ItemView作为单独的联系人“卡片”,然后是一个Marionette CollectionView,它是“卡片”视图的集合,作为联系人列表。联系人CollectionView监听任何集合变更事件,如果有变化,集合视图会被重新渲染。这就是为什么任何新的联系人添加后主列表会被重新渲染。
var Marionette = require('backbone.marionette');var itemView = Marionette.ItemView.extend({template: require('../../templates/contact_small.hbs'),initialize: function() {this.listenTo(this.model, 'change', this.render);},events: {'click': 'showDetails'},showDetails: function() {window.App.core.vent.trigger('app:log', 'Contacts View: showDetails hit.');window.App.controller.details(this.model.id);}
});module.exports = CollectionView = Marionette.CollectionView.extend({initialize: function() {this.listenTo(this.collection, 'change', this.render);},itemView: itemView
});
ItemView本身就含有一个会触发控制器中‘detail’函数的事件。ItemView还有一个对它的模型的监听器,这样如果联系人详细信息发生变化,视图也会被重新渲染。注意到ItemView有一个Handlebars模板,通过require()被引用进来。Browserify会在构建的时候用一个预编译好的JavaScript函数来替换这一行,这会让视图渲染相比于在浏览器中视图每次渲染时才编译要快得多。

views/contact_details.js

联系人的详细信息视图相当简单 - 只有一个去“<<Back”的事件处理器和一个Handlebars模板。

views/add.js

最后,我们有一个用于在插入新联系人时渲染表单的视图,最主要的功能是save函数:
events: {'click a.save-button': 'save'
},save: function(e) {e.preventDefault();var newContact = {name: {first: this.$el.find('#name_first').val(),last: this.$el.find('#name_last').val()},email: this.$el.find('#email').val(),phone: this.$el.find('#phone').val()};window.App.data.contacts.create(newContact);window.App.core.vent.trigger('app:log', 'Add View: Saved new contact!');window.App.controller.home();
}
这里我们定义了一个新的联系人对象,这只是一个普通的JavaScript对象,对应于在MongoDB侧我们的数据对象的样子。然后我们简单将它传递给Backbone的collection.create()函数 - 它将用Backbone的默认实现,将定义好的JSON对象变量传递给一个对API URL(在前面的集合中定义的)的POST请求。回到服务器侧,node.js路由监听对‘api/contacts’的POST请求,它会调用(node.js)控制器的‘add’函数。服务器端联系人控制器中的‘add’函数会用Mongoose创建一个新的联系人模型然后保存到MongoDB服务器。

server/controllers/contacts.js

add: function(req, res) {var newContact = new models.Contact(req.body);newContact.gravatar = md5(newContact.email);newContact.save(function(err, contact) {if (err)res.json({});console.log('successfully inserted contact: ' + contact._id);res.json(contact);});
},
有关验证的重要提示:
无论前端还是后端都没有进行任何验证,认识到这一点很重要。很明显这不好,但是我没有这样做只是希望让代码更简洁和易于消化。这是在真正的app中你需要实现的。(为安全起见,在前端和后端同时进行验证是一个好主意!)

用Handlebars作为视图模板

像我在讲述后端时提到的那样,我们决定用于前端和后端的模板引擎是Handlebars。我这样做既想保持一致又因为我是Handlebars的粉丝。你能够在‘client/templates’目录中找到视图模板,每一个基本上都是一个带有.hbs后缀的HTML文档。

Part 4: 用Heroku部署!

从这开始,我们已经有了一个功能完整的app,尽管它有点简单。测试它最好的办法,最理智的下一步,是将它真正部署到因特网上!一个免费并且超级简单的神奇服务是Heroku!Heroku基本上是一个可扩展的云主机解决方案,它有很多令人惊讶的特性并支持插件。对像我们这样一个app来说,她是主机托管服务的理想选择。让我们一起看一下启动app的步骤。

在Heroku.com上注册一个免费账户

如果你没有Heroku的账户,现在就去免费注册一个。只要两秒...

下载Heroku Toolbelt

有了账户之后,确保下载并安装Heroku Toolbelt,这是让建立app并推送代码到Heroku变得很容易的一个命令行工具!

建立Heroku app,安装插件,适时推送你的代码

现在你有了Heroku账户并且安装了Toolbelt,你准备好了在你的账户下建立一个app,准备这个app并适时推送代码。在此过程中也可以安装一些插件。
提示:下面的所有命令都应该在工程的根目录下执行。
$ heroku login
登录后,你可能会被提示添加SSH key。按照提示进行操作。
下一步,建立一个名为‘procfile’的文件,然后将下面一行添加进去:
web: node server.js
在你的Heroku账号下建立一个app:
$ heroku create
下面我们需要安装第一个插件,MongoHQ。MongoHQ是另外一个特别针对MongoDB的云主机服务,与Heroku配合得非常好。像Heroku一样,MongoHQ有一个免费的沙箱服务!
$ heroku addons: add mongohq
提示:为了真正使用插件,你需要确保你的账户有完整的计费信息。这只是因为大多数服务是可扩展的并有限制条款。一旦超出那些条款,计费就会介入。
下一步,去Heroku网站并访问你的dashboard/Apps。点击刚刚创建的app,然后点击MongoHQ插件。进入Collections,为你的app添加一个集合(例如,“contacts”)。然后去Admin并选择Users tab。创建一个新用户来访问数据库(起任意你想要的用户名/密码)。返回Overview tab并复制Mongo URI。
编辑server.js,连接mongodb服务器的那行需要修改为刚刚复制的Mongo URI。修改<user>和<password>为你刚刚创建的MongoHQ用户和密码。
现在我们可以推送代码到Heroku:
提示:在你推送代码到Heroku之前,因为需要Git来完成这件事,你要确保到目前为止做的任何修改都已经staged并committed(例如你刚刚为MongoHQ连接URI而修改了server.js)。做这个最简单的方式是用下面的命令:
$ git add .
$ git commit -m "Updates for Heroku"
$ git push heroku master
到目前为止,app配置好了,插件安装了,代码推送上去了,我们需要Heroku真正的启动服务器。要做到这一点,Heroku用了所谓的dyno,它读取Procfile并且执行这个命令:
$ heroku ps:scale web=1
最后,在浏览器中启动你的新Heroku应用:
$ heroku open

结论

对于一个看起来似乎很小的app来说,仍旧有很多内容需要讲述。这个样板项目对于未来任何想快速启动运行的app来说都是一个很好的基础。不需要操心一个标准web项目的方方面面的细节,你的时间可以被用来关心如何更好地制作app!
如果你对本项目由任何疑问,请随时给我留一条评论或者在Twitter上找到我。同时,非常欢迎任何的意见和建议!感谢阅读本文,希望你发现这里的代码对你有用 - 现在去创建些东西吧!


原文链接:http://kroltech.com/2013/12/boilerplate-web-app-using-backbone-js-expressjs-node-js-mongodb
编后语:
这篇文章在一年前刚开始学习node.js时就看到了,当时还摸不到node.js的门,这篇文章给了我很大的帮助,算是迈出了第一步。当时就曾经想翻译本文,但是实在有点长,一直没有下定决心。大概一个月以前,主动申请做公司产品的前端开发,一个偶然的机会又搜到了这篇文章,终于决心把它翻译过来。
断断续续地翻译了两三个星期,中间因为evernote网页版的问题还把整个翻译好的part 2丢了。还好终于赶在农历大年之前翻译完了,如果不是因为昨天抢了一天的红包,可能还会提前一天:),也算对自己有一个交代吧。
希望本文能对大家有些帮助,水平有限,错误难免,发现人家那些翻译整本书的还真得有一定的定力和水平。
祝大家羊年快乐,幸福吉祥!

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 用node.js和express.js和jade搭建轻型cms系统

    http://cnodejs.org/topic/4f16442ccae1f4aa270010bf前言:我们主要做的是iphone/ipad程序,但关注node.js很久,因为我们多少总是要做网站,做后台。node.js就像一个非常快的ruby。对于我们而言,其实学习node.js起来还是很简单,网上资料很多,但没有看到一些比较完整的例子。…...

    2024/4/24 13:06:09
  2. Node.js服务端的javascript脚本

    Node.js是一套用来编写高性能网络服务器的JavaScript工具包,一系列的变化由此开始。比较独特的是,Node.js会假设你是在POSIX环境下运行它Linux 或 Mac OS X。如果你是在Windows下,那就需要安装MinGW以获得一个仿POSIX的环境。在Node中,Http是首要的。Node为创建http服务器作…...

    2024/4/24 13:06:07
  3. 如何使用Visual Studio Code调试Node.js

    转载自:http://blog.darkthread.net/post-2016-08-06-debug-nodejs-with-vscode.aspx 如有侵犯,请来信:oiken@qq.com如何使用Visual Studio Code偵錯Node.js?小木頭去上電腦課,一回家,想當然爾程式魔人老爸立即展開偵訊:學什麼語言?用什麼開發工具?做了什麼練習?小子…...

    2024/4/24 13:06:06
  4. Mac中Node.js版本升级

    > 第一步,先查看本机node.js版本: > $ node -v > > 第二步,清除node.js的cache: > $ sudo npm cache clean -f > > 第三步,安装 npm 工具 > $ sudo npm install -g n > > 第四步,安装最新版本的nod…...

    2024/5/1 7:19:55
  5. Edge.js增加了对 Linux和Mac OS X的支持,所以C#和Node.js可以在任意环境中进行开发...

    Edge.js项目添加了对Linux和Mac OS X环境的支持。它与.NET CLR集成,并结合Node.js,提供在JavaScript中运行C#代码的能力。应用程序可以使用.NET代码来在不阻断Node.js事件循环的情况下,处理进程密集型的任务。当单独运行于微软平台时,这意味着开发者不必使用C/C++就能编写W…...

    2024/4/15 4:14:00
  6. docker之mac下部署nodejs项目

    docker之mac下部署nodejs项目mac下docker安装初始化一个Node.js项目构建项目镜像运行nodejs项目镜像将本地镜像推送到docker hub上 mac下docker安装 1.官网下载2. 安装教程可参照官方安装教程 3.安装成功可以看到此界面:4.检验安装结果。 docker -v结果为:初始化一个Node.js项…...

    2024/4/15 4:13:56
  7. npm is known not to run on Node.js vue启动报错

    其实很简单,就是你的node版本out了,官网升级下就好了,我是因为重装了系统,node没注意是很早之前的一个文件了,搞得宝宝差点怀疑猿生了,后来无意间上了官网看了下版本,好吧,自己蠢到家了,实力踩坑,升级就好,就这么简单,官网下载重新安装 听到这里你会不会有种想砍人…...

    2024/4/29 2:12:20
  8. Mac系统下如何在终端进入Node.js的REPL环境

    如果还没有安装 Node.js,Windows 系统的请看这篇文章,macOS 系统的请看这篇文章 REPL 的全称为:read-eval-print-loop(交互式解析器)如何进入打开「终端」,输入 node,回车即可进入如何退出按两次 control + c 或者是输入 .exit,然后回车即可退出...

    2024/4/29 1:35:49
  9. 超好用的工具网站

    脚本之家: 各种破解版软件的下载网站 萝卜工坊:非常牛的摸鱼神器,不想手写的话,可以直接把Word文件转为手写图片,还可以选择字体(字体还可以自己上传,在网上很可能可以找到与自己相近的字体)。还可以设置行距,字距,混乱程度,字体粗细。总之就是强烈推荐啦! processo…...

    2024/4/29 1:32:01
  10. node.js学习之node的安装

    安装Node.js下面分别介绍在Mac、Ubuntu、Centos及Windows下安装Node.js。 Mac在Mac下,如果你喜欢用homebrew,那么只用一行就可以装好:1 brew install node 否则,只能考虑手工安装了,步骤如下:1. 安装Xcode2. 安装Git3 .运行下面的命令行编译node.js2 git clone git://gith…...

    2024/4/28 6:56:07
  11. 2018最新Node.Js从基础到实战六阶段系统

    第1章:Node.js基础1.认识 Node.js 2.Node.js 与 JavaScript 的关系3.Node.js 的特点4.Node.js 开发环境介绍5.Mac OS X 下 Node.js 开发环境搭建6.Windows 下 Node.js 开发环境搭建7.开发工具的使用)8.NPM 与调试工具的使用9.常用 Linux 命令介绍10.HTTP 基础11.应用程序简介…...

    2024/4/28 17:26:01
  12. Node.js、Express框架获取客户端IP地址

    Node.js//传入请求HttpRequest function getClientIp(req) {return req.headers[x-forwarded-for] ||req.connection.remoteAddress ||req.socket.remoteAddress ||req.connection.socket.remoteAddress; }Express//express框架则简单许多 req.ip意外收获发现这两种方式获取的i…...

    2024/4/29 1:15:14
  13. 2019Node.Js从基础到实战六阶段系统(最新最全)

    第1章:Node.js基础1.认识 Node.js 2.Node.js 与 JavaScript 的关系 3.Node.js 的特点 4.Node.js 开发环境介绍 5.Mac OS X 下 Node.js 开发环境搭建 6.Windows 下 Node.js 开发环境搭建 7.开发工具的使用) 8.NPM 与调试工具的使用 9.常用 Linux 命令介绍 10.HTTP 基础 11.应…...

    2024/4/28 21:26:22
  14. node.js项目中常量的配置

    在项目中,我们常将一些常量信息做成配置项,如,数据库的链接配置,业务错误代码配资等等。我们通过两种方式可以解决该问题。 系统环境变量的方式 配置文件的方式 下边,将以这两方面进行展开。 1. 系统环境变量 Node.js 中通过process.env来访问当前的环境变量信息 $vim tes…...

    2024/4/28 0:20:51
  15. 前端开发:Mac电脑安装vue.js的步骤

    最近转行前端开发,进入前端开发的第一件事就是安装开发环境,前端开发的环境有很多个,这里只来介绍Visual Studio Code开发运行vue的方法,然后再介绍一下Mac电脑通过下载运行node.js进行vue.js的安装步骤。具体的vue.js安装步骤如下所示。1、打开浏览器去node.js官网,找到n…...

    2024/4/28 8:47:53
  16. 构建微服务开发环境5————安装Node.js

    【内容指引】 下载Node.js; Mac下安装Node.js; Windows下安装Node.js; 查看node和npm的版本。 一、下载Node.js 访问Node.js官网:https://nodejs.org/en/download/  二、Mac下安装Node.js 1.傻瓜啥安装,看图:        2.安装完后查看node版本,打开终端,输…...

    2024/4/28 15:59:08
  17. Linux环境下node.js环境的搭建以及配置,使用node.js编写工具与简化less编译

    由于我的服务器是买的云服务器,因此整个是连接ssh进行配置的LNMP由于云服务器刚使用不久,此前一直是只安装了centos 6.5版本的裸机,因此这次顺道配置了LNMP,整个LNMP环境的安装和LAMP环境的搭建基本一致。附本人参照的配置博客:http://www.cnblogs.com/xiaoit/p/3991037.h…...

    2024/4/28 12:01:30
  18. macOS下搭建Node.js环境

    1.安装Homebrew$:ruby -e "$(curl -fsSl http://raw.githubusercontent.com/Homebred/install/master/install);"2.安装Node$:brew install node需要稍微等待一会,看到如下界面即安装成功3.查看Node版本:$:node -v测试:在桌面新建一个test.js的文件$:cd Desktop/ $:t…...

    2024/4/28 16:09:23
  19. Node.js软肋之CPU密集型任务

    Node.js在官网上是这样定义的:“一个搭建在Chrome JavaScript运行时上的平台,用于构建高速、可伸缩的网络程序。Node.js采用的事件驱动、非阻塞I/O模型使它既轻量又高效,是构建运行在分布式设备上的数据密集型实时程序的完美选择。”Web站点早已不仅限于内容的呈现,很多交互…...

    2024/4/28 19:55:29
  20. 【Node.js】用IntelliJ IDEA创建Node.js项目

    项目一:Hello World程序 step1. 在webapp目录写一个启动文件helloworld.js。 var http = require("http"); http.createServer(function(request, response) {response.writeHead(200, {"Content-Type": "text/plain"});response.write("H…...

    2024/4/28 18:44:39

最新文章

  1. 【再探】设计模式—抽象工厂及建造者模式

    抽象工厂模式和建造者模式都属于创建型模式。两者都能创建对应的对象&#xff0c;而创建者模式更侧重于创建复杂对象&#xff0c;将对象的创建过程封装起来&#xff0c;让客户端不需要知道对象的内部细节。 1 抽象工厂模式 需求&#xff1a; 在使用工厂方法模式时&#xff0…...

    2024/5/1 7:40:54
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 竞赛 GRU的 电影评论情感分析 - python 深度学习 情感分类

    1 前言 &#x1f525;学长分享优质竞赛项目&#xff0c;今天要分享的是 &#x1f6a9; GRU的 电影评论情感分析 - python 深度学习 情感分类 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 这…...

    2024/4/29 13:03:41
  4. 微服务demo(二)nacos服务注册

    一、服务注册 1、pom&#xff1a; 移步spring官网https://spring.io&#xff0c;查看集成Nacos所需依赖 找到对应版本点击进入查看集成说明 然后再里面找到集成配置样例&#xff0c;这里只截一张&#xff0c;其他集成内容继续向下找 2、配置文件 &#xff08;1&#xff09;有…...

    2024/5/1 7:13:50
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/29 23:16:47
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57