Appearance
应用结构
¥Application Structure
阅读本文后,你将了解:
¥After reading this article, you'll know:
Meteor 应用与其他类型应用在文件结构方面的比较。
¥How a Meteor application compares to other types of applications in terms of file structure.
如何组织小型和大型应用。
¥How to organize your application both for small and larger applications.
如何以一致且易于维护的方式格式化代码并命名应用的各个部分。
¥How to format your code and name the parts of your application in consistent and maintainable ways.
通用 JavaScript
¥Universal JavaScript
Meteor 是一个用于构建 JavaScript 应用的全栈框架。这意味着 Meteor 应用与大多数应用的不同之处在于,它们包含在客户端(Web 浏览器或 Cordova 移动应用)运行的代码、在服务器端(Node.js 容器)运行的代码以及在两种环境中运行的通用代码。Meteor 构建工具 允许你使用 ES2015 import 和 export 以及 Meteor 构建系统 默认文件加载顺序 规则的组合,指定在每个环境中运行哪些 JavaScript 代码,包括任何支持的 UI 模板、CSS 规则和静态资源。
¥Meteor is a full-stack framework for building JavaScript applications. This means Meteor applications differ from most applications in that they include code that runs on the client, inside a web browser or Cordova mobile app, code that runs on the server, inside a Node.js container, and common code that runs in both environments. The Meteor build tool allows you to specify what JavaScript code, including any supporting UI templates, CSS rules, and static assets, to run in each environment using a combination of ES2015 import and export and the Meteor build system default file load order rules.
ES2015 模块
¥ES2015 modules
从 1.3 版本开始,Meteor 完全支持 ES2015 模块。ES2015 模块标准取代了 CommonJS 和 AMD,它们是常用的 JavaScript 模块格式和加载系统。
¥As of version 1.3, Meteor ships with full support for ES2015 modules. The ES2015 module standard is the replacement for CommonJS and AMD, which are commonly used JavaScript module format and loading systems.
在 ES2015 中,你可以使用 export 关键字使变量在文件外部可用。要在其他地方使用这些变量,你必须使用指向源的路径通过 import 信号导入它们。导出某些变量的文件称为 "modules",因为它们代表一个可重用的代码单元。显式导入你使用的模块和包有助于你以模块化的方式编写代码,避免引入全局符号和 "远程操作"。
¥In ES2015, you can make variables available outside a file using the export keyword. To use the variables somewhere else, you must import them using the path to the source. Files that export some variables are called "modules", because they represent a unit of reusable code. Explicitly importing the modules and packages you use helps you write your code in a modular way, avoiding the introduction of global symbols and "action at a distance".
你可以在 modules 软件包 README 中详细了解模块系统。此包作为 ecmascript 元软件包 的一部分自动包含在每个新的 Meteor 应用中,因此大多数应用无需任何操作即可立即开始使用模块。
¥You can read about the module system in detail in the modules package README. This package is automatically included in every new Meteor app as part of the ecmascript meta-package, so most apps won't need to do anything to start using modules right away.
import 使用简介 export
¥Introduction to using import and export
Meteor 不仅允许你在应用中使用 import 来加载 JavaScript,还可以使用 import 来加载 CSS 和 HTML,从而控制加载顺序:
¥Meteor allows you to import not only JavaScript in your application, but also CSS and HTML to control load order:
js
import '../../api/lists/methods.js'; // import from relative path
import '/imports/startup/client'; // import module with index.js from absolute path
import './loading.html'; // import Blaze compiled HTML from relative path
import '/imports/ui/style.css'; // import CSS from absolute path如需了解更多导入样式的方法,请参阅 构建系统 文章。
¥For more ways to import styles, see the Build System article.
Meteor 还支持标准的 ES2015 模块 export 语法:
¥Meteor also supports the standard ES2015 modules export syntax:
js
export const listRenderHold = LaunchScreen.hold(); // named export
export { Todos }; // named export
export default Lists; // default export
export default new Collection('lists'); // default export从包中导入
¥Importing from packages
在 Meteor 中,使用 import 语法在客户端或服务器端加载 npm 包,并像访问其他模块一样访问包的导出符号,也非常简单直接。你还可以从 Meteor Atmosphere 包导入内容,但导入路径必须以 meteor/ 为前缀,以避免与 npm 包命名空间冲突。例如,要从 npm 导入 moment,从 Atmosphere 导入 HTTP:
¥In Meteor, it is also simple and straightforward to use the import syntax to load npm packages on the client or server and access the package's exported symbols as you would with any other module. You can also import from Meteor Atmosphere packages, but the import path must be prefixed with meteor/ to avoid conflict with the npm package namespace. For example, to import moment from npm and HTTP from Atmosphere:
js
import moment from 'moment'; // default import from npm
import { HTTP } from 'meteor/http'; // named import from Atmosphere有关将 imports 与包一起使用的更多详细信息,请参阅 使用软件包 教程。
¥For more details using imports with packages see Using Packages tutorial.
使用 require
¥Using require
在 Meteor 中,import 语句会编译成 CommonJS require 语法。但是,作为惯例,我们鼓励你使用 import。
¥In Meteor, import statements compile to CommonJS require syntax. However, as a convention we encourage you to use import.
也就是说,在某些情况下,你可能需要直接调用 require。一个显著的例子是从一个公共文件中引入客户端或服务器端代码。由于 import 必须位于顶层作用域,因此你不能将其放在 if 语句中,所以你需要编写类似这样的代码:
¥With that said, in some situations you may need to call out to require directly. One notable example is when requiring client or server-only code from a common file. As imports must be at the top-level scope, you may not place them within an if statement, so you'll need to write code like:
js
if (Meteor.isClient) {
require('./client-only-file.js');
}请注意,对 require() 的动态调用(其中所需的名称可以在运行时更改)无法正确分析,并且可能会导致客户端包损坏。
¥Note that dynamic calls to require() (where the name being required can change at runtime) cannot be analyzed correctly and may result in broken client bundles.
如果你需要从带有 default 导出的 ES2015 模块中获取 require,可以使用 require("package").default 获取导出。
¥If you need to require from an ES2015 module with a default export, you can grab the export with require("package").default.
使用 CoffeeScript
¥Using CoffeeScript
查看文档:CoffeeScript
¥See the Docs: CoffeeScript
cs
// lists.coffee
export Lists = new Collection 'lists'cs
import { Lists } from './lists.coffee'文件结构
¥File structure
为了充分利用模块系统并确保代码仅在需要时运行,我们建议你将所有应用代码放在 imports/ 目录中。这意味着 Meteor 构建系统仅在文件使用 import(也称为 "延迟求值或加载中")引用该文件时,才会将其打包并包含。
¥To fully use the module system and ensure that our code only runs when we ask it to, we recommend that all of your application code should be placed inside the imports/ directory. This means that the Meteor build system will only bundle and include that file if it is referenced from another file using an import (also called "lazy evaluation or loading").
Meteor 将使用 默认文件加载顺序 规则(也称为 "立即求值或加载")加载应用中名为 imports/ 的目录之外的所有文件。建议你创建两个预加载文件 client/main.js 和 server/main.js,以便为客户端和服务器分别定义明确的入口点。Meteor 确保任何目录中名为 server/ 的文件都只能在服务器上访问,同样,任何目录中名为 client/ 的文件也只能在服务器上访问。这还可以防止尝试从名为 client/ 的任何目录(即使它嵌套在 imports/ 目录中)导入文件到服务器,反之亦然,防止从 server/ 导入客户端文件。
¥Meteor will load all files outside of any directory named imports/ in the application using the default file load order rules (also called "eager evaluation or loading"). It is recommended that you create exactly two eagerly loaded files, client/main.js and server/main.js, in order to define explicit entry points for both the client and the server. Meteor ensures that any file in any directory named server/ will only be available on the server, and likewise for files in any directory named client/. This also precludes trying to import a file to be used on the server from any directory named client/ even if it is nested in an imports/ directory and vice versa for importing client files from server/.
这些 main.js 文件本身不会执行任何操作,但它们会导入一些启动模块,这些模块会在应用加载时分别在客户端和服务端立即运行。这些模块应该对应用中使用的包进行必要的配置,并导入应用的其余代码。
¥These main.js files won't do anything themselves, but they should import some startup modules which will run immediately, on client and server respectively, when the app loads. These modules should do any configuration necessary for the packages you are using in your app, and import the rest of your app's code.
目录布局示例
¥Example directory layout
首先,你可以查看提供的 示例应用 信号。这些是构建应用时可以参考的绝佳示例。
¥To start, one can have a look to the example applications provided. They are great examples to follow when structuring your app.
以下是一个示例目录结构的概述。你可以使用命令 meteor create appName --full 生成具有此结构的新应用。默认前端是 Blaze,但你可以稍后更改。或者使用 另一个创建选项
¥Below is an overview of an exemple directory structure. You can generate a new app with this structure using the command meteor create appName --full. The default frontend is Blaze, but you can change it later. Or use another create option
sh
imports/
startup/
both/
index.js # single entry point to import isomorphic modules for client and server
client/
index.js # import client startup through a single index entry point
routes.js # set up all routes in the app
server/
fixtures.js # fill the DB with example data on startup
index.js # import server startup through a single index entry point
register-api.js # dedicated file to import server code for api
api/
lists/ # a unit of domain logic
server/
publications.js # all list-related publications
publications.tests.js # tests for the list publications
lists.js # definition of the Lists collection
lists.tests.js # tests for the behavior of that collection
methods.js # methods related to lists
methods.tests.js # tests for those methods
ui/
components/ # all reusable components in the application
# can be split by domain if there are many
layouts/ # wrapper components for behaviour and visuals
pages/ # entry points for rendering used by the router
stylesheets/ # global stylesheets
private/ # to store private assets for the server
public/ # to store public assets (pictures)
client/
main.js # client entry point, imports all client code
server/
main.js # server entry point, imports all server code构建导入结构
¥Structuring imports
现在,我们已经将所有文件放入了 imports/ 目录,接下来让我们思考如何使用模块更好地组织代码。将所有在应用启动时运行的代码放在 imports/startup 目录中是合理的。另一个好方法是将数据和业务逻辑与 UI 渲染代码分离。我们建议使用名为 imports/api 和 imports/ui 的目录来进行逻辑划分。
¥Now that we have placed all files inside the imports/ directory, let's think about how best to organize our code using modules. It makes sense to put all code that runs when your app starts in an imports/startup directory. Another good idea is splitting data and business logic from UI rendering code. We suggest using directories called imports/api and imports/ui for this logical split.
在 imports/api 目录中,根据代码提供的 API 的域将代码拆分到不同的目录中也是合理的 - 通常这对应于你在应用中定义的集合。例如,在 Todos 示例应用中,我们有 imports/api/lists 和 imports/api/todos 域。在每个目录中,我们定义用于操作相关字段数据的集合、发布和方法。每个 API 文件夹通常包含不同的文件,分别用于同构代码和服务器端特定代码。为了确保良好的隔离性,服务器端代码应放在 server 文件夹中。
¥Within the imports/api directory, it's sensible to split the code into directories based on the domain that the code is providing an API for --- typically this corresponds to the collections you've defined in your app. For instance in the Todos example app, we have the imports/api/lists and imports/api/todos domains. Inside each directory we define the collections, publications and methods used to manipulate the relevant domain data. Each API folder typically has different files for isomorphic code and server-specific code. To ensure good isolation, server-specific code is put into a server folder.
注意:在更大的应用中,考虑到待办事项本身是列表的一部分,将这两个域组合到一个更大的 "list" 模块中可能更有意义。待办事项示例非常小,我们只需将其拆分出来以演示模块化即可。
¥Note: in a larger application, given that the todos themselves are a part of a list, it might make sense to group both of these domains into a single larger "list" module. The Todos example is small enough that we need to separate these only to demonstrate modularity.
在 imports/ui 目录中,通常根据文件定义的 UI 端代码类型将其分组到不同的目录中,例如顶层目录 pages、封装目录 layouts 或可重用目录 components。
¥Within the imports/ui directory it typically makes sense to group files into directories based on the type of UI side code they define, i.e. top level pages, wrapping layouts, or reusable components.
对于上面定义的每个模块,将各种辅助文件与基础 JavaScript 文件放在一起是合理的。例如,Blaze UI 组件的模板 HTML、JavaScript 逻辑和 CSS 规则应该位于同一目录中。包含业务逻辑的 JavaScript 模块应该与该模块的单元测试放在一起。
¥For each module defined above, it makes sense to co-locate the various auxiliary files with the base JavaScript file. For instance, a Blaze UI component should have its template HTML, JavaScript logic, and CSS rules in the same directory. A JavaScript module with some business logic should be co-located with the unit tests for that module.
启动文件
¥Startup files
有些代码并非业务逻辑或用户界面单元,而是一些需要在应用启动时运行的设置或配置代码。在上面的示例中,imports/startup/client/routes.js 配置了所有路由,然后导入了客户端所需的所有其他代码:
¥Some of your code isn't going to be a unit of business logic or UI, it's some setup or configuration code that needs to run in the context of the app when it starts up. In the above example, the imports/startup/client/routes.js configures all the routes and then imports all other code that is required on the client:
js
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// Import needed templates
import '../../ui/layouts/body/body.js';
import '../../ui/pages/home/home.js';
import '../../ui/pages/not-found/not-found.js';然后,我们将这两个文件导入到 imports/startup/client/index.js 中:
¥We then import both of these files in imports/startup/client/index.js:
js
import './routes.js';这样,你就可以轻松地从主客户端入口点 client/main.js 中,通过单个模块导入所有客户端启动代码:
¥This makes it easy to then import all the client startup code with a single import as a module from our main eagerly loaded client entry point client/main.js:
js
import '/imports/startup/client';在服务器端,我们使用相同的方法将所有启动代码导入到 imports/startup/server/index.js 中:
¥On the server, we use the same technique of importing all the startup code in imports/startup/server/index.js:
js
import './fixtures.js';
import './register-api.js';然后,我们的主服务器入口点 server/main.js 导入此启动模块。可以看到,这里我们实际上并没有从这些文件中导入任何变量。 - 我们导入它们是为了确保它们按此顺序执行。
¥Our main server entry point server/main.js then imports this startup module. You can see that here we don't actually import any variables from these files - we import them so that they execute in this order.
导入 Meteor "pseudo-globals"
¥Importing Meteor "pseudo-globals"
为了向后兼容,Meteor 仍然为 Meteor 核心包以及你在应用中包含的其他 Meteor 包提供 Meteor 的全局命名空间。你仍然可以像在之前的 Meteor 版本中一样,直接调用 Meteor.publish 等函数,而无需先导入它们。但是,最佳实践建议你在使用 Meteor "pseudo-globals" 之前,先使用 import { Name } from 'meteor/package' 语法加载它们。例如:
¥For backwards compatibility Meteor still provides Meteor's global namespacing for the Meteor core package as well as for other Meteor packages you include in your application. You can also still directly call functions such as Meteor.publish, as in previous versions of Meteor, without first importing them. However, it is recommended best practice that you first load all the Meteor "pseudo-globals" using the import { Name } from 'meteor/package' syntax before using them. For instance:
js
import { Meteor } from 'meteor/meteor';
import { EJSON } from 'meteor/ejson';默认文件加载顺序
¥Default file load order
尽管建议你使用 ES2015 模块和 imports/ 目录编写应用,但 Meteor 仍然支持使用这些默认加载顺序规则对文件进行预加载,以向后兼容为 Meteor 1.2 及更早版本编写的应用。有关立即求值、延迟求值和延迟加载之间的区别的说明,请参阅 Stack Overflow 上的 article 问题。
¥Even though it is recommended that you write your application to use ES2015 modules and the imports/ directory, Meteor continues to support eager evaluation of files, using these default load order rules, to provide backwards compatibility with applications written for Meteor 1.2 and earlier. For a description of the difference between eager evaluation, lazy evaluation, and lazy loading, please see this Stack Overflow article.
你可以使用 import 在单个应用中同时使用即时求值和延迟加载。所有导入语句都会按照文件中列出的顺序进行评估,并在文件加载时使用这些规则进行评估。
¥You may combine both eager evaluation and lazy loading using import in a single app. Any import statements are evaluated in the order they are listed in a file when that file is loaded and evaluated using these rules.
有几种加载顺序规则。它们将按照以下优先级顺序应用于应用中所有适用的文件:
¥There are several load order rules. They are applied sequentially to all applicable files in the application, in the priority given below:
HTML 模板文件始终优先加载
¥HTML template files are always loaded before everything else
以
main.开头的文件最后加载。¥Files beginning with
main.are loaded last任何
lib/目录下的文件将接下来加载¥Files inside any
lib/directory are loaded next路径更深的文件将接下来加载
¥Files with deeper paths are loaded next
文件将按照整个路径的字母顺序加载。
¥Files are then loaded in alphabetical order of the entire path
js
nav.html
main.html
client/lib/methods.js
client/lib/styles.js
lib/feature/styles.js
lib/collections.js
client/feature-y.js
feature-x.js
client/main.js例如,上面的文件已按正确的加载顺序排列。main.html 第二个加载,因为 HTML 模板总是先加载,即使它以 main. 开头,规则 1 的优先级高于规则 2。但是,它会在 nav.html 之后加载,因为规则 2 的优先级高于规则 5。
¥For example, the files above are arranged in the correct load order. main.html is loaded second because HTML templates are always loaded first, even if it begins with main., since rule 1 has priority over rule 2. However, it will be loaded after nav.html because rule 2 has priority over rule 5.
client/lib/styles.js 和 lib/feature/styles.js 的加载顺序在规则 4 之前完全相同;但是,由于 client 在字母顺序上位于 lib 之前,因此它会优先加载。
¥client/lib/styles.js and lib/feature/styles.js have identical load order up to rule 4; however, since client comes before lib alphabetically, it will be loaded first.
你还可以使用 Meteor.startup 来控制代码在服务器端和客户端的运行时间。
¥You can also use Meteor.startup to control when run code is run on both the server and the client.
特殊目录
¥Special directories
默认情况下,Meteor 应用文件夹中的所有 JavaScript 文件都会被打包并加载到客户端和服务端。但是,项目中的文件和目录名称会影响它们的加载顺序、加载位置以及其他一些特性。以下是 Meteor 会特殊处理的文件和目录名称列表:
¥By default, any JavaScript files in your Meteor application folder are bundled and loaded on both the client and the server. However, the names of the files and directories inside your project can affect their load order, where they are loaded, and some other characteristics. Here is a list of file and directory names that are treated specially by Meteor:
imports
任何名为
imports/的目录都不会被加载到任何地方,文件必须使用import导入。¥Any directory named
imports/is not loaded anywhere and files must be imported usingimport.node_modules
任何名为
node_modules/的目录都不会被加载到任何地方。安装到node_modules目录中的 node.js 包必须使用import导入,或者在package.js中使用Npm.depends导入。¥Any directory named
node_modules/is not loaded anywhere. node.js packages installed intonode_modulesdirectories must be imported usingimportor by usingNpm.dependsinpackage.js.client
服务器不会加载任何名为
client/的目录。类似于将你的代码封装在if (Meteor.isClient) { ... }中。在生产模式下,客户端加载的所有文件都会自动连接和压缩。在开发模式下,JavaScript 和 CSS 文件不会被压缩,以便于调试。为了保持生产环境和开发环境的一致性,CSS 文件仍然合并成一个单独的文件,因为更改 CSS 文件的 URL 会影响其中 URL 的处理方式。¥Any directory named
client/is not loaded on the server. Similar to wrapping your code inif (Meteor.isClient) { ... }. All files loaded on the client are automatically concatenated and minified when in production mode. In development mode, JavaScript and CSS files are not minified, to make debugging easier. CSS files are still combined into a single file for consistency between production and development, because changing the CSS file's URL affects how URLs in it are processed.Meteor 应用中的 HTML 文件与服务器端框架中的 HTML 文件处理方式截然不同。Meteor 会扫描目录中的所有 HTML 文件,查找以下三个顶层元素:
<head>、<body>和<template>。头部和主体部分分别连接成一个单独的头部和主体,并在页面初始加载时发送给客户端。¥HTML files in a Meteor application are treated quite a bit differently from a server-side framework. Meteor scans all the HTML files in your directory for three top-level elements:
<head>,<body>, and<template>. The head and body sections are separately concatenated into a single head and body, which are transmitted to the client on initial page load.server
客户端不会加载任何名为
server/的目录。类似于将你的代码封装在if (Meteor.isServer) { ... }中,但客户端甚至不会收到该代码。任何你不想提供给客户端的敏感代码,例如包含密码或身份验证机制的代码,都应该保存在server/目录中。¥Any directory named
server/is not loaded on the client. Similar to wrapping your code inif (Meteor.isServer) { ... }, except the client never even receives the code. Any sensitive code that you don't want served to the client, such as code containing passwords or authentication mechanisms, should be kept in theserver/directory.Meteor 会收集所有 JavaScript 文件(不包括
client、public和private子目录下的文件),并将它们加载到 Node.js 服务器实例中。在 Meteor 中,服务器代码在每个请求中以单线程运行,而不是像 Node 那样采用异步回调方式。¥Meteor gathers all your JavaScript files, excluding anything under the
client,public, andprivatesubdirectories, and loads them into a Node.js server instance. In Meteor, your server code runs in a single thread per request, not in the asynchronous callback style typical of Node.public
名为
public/的顶层目录下的所有文件都会原样提供给客户端。引用这些资源时,请勿在 URL 中包含public/,请像它们都位于顶层目录一样编写 URL。例如,将public/bg.png引用为<img src='/bg.png' />。这是存放favicon.ico、robots.txt和类似文件的最佳位置。¥All files inside a top-level directory called
public/are served as-is to the client. When referencing these assets, do not includepublic/in the URL, write the URL as if they were all in the top level. For example, referencepublic/bg.pngas<img src='/bg.png' />. This is the best place forfavicon.ico,robots.txt, and similar files.private
名为
private/的顶层目录中的所有文件只能从服务器代码访问,并且可以通过AssetsAPI 加载。这可用于私有数据文件以及项目目录中任何你不希望从外部访问的文件。¥All files inside a top-level directory called
private/are only accessible from server code and can be loaded via theAssetsAPI. This can be used for private data files and any files that are in your project directory that you don't want to be accessible from the outside.client/compatibility
此文件夹用于与依赖于将顶层使用
var声明的变量导出为全局变量的 JavaScript 库兼容。此目录中的文件将直接执行,而不会被封装在新的变量作用域中。这些文件会在其他客户端 JavaScript 文件之前执行。¥This folder is for compatibility with JavaScript libraries that rely on variables declared with var at the top level being exported as globals. Files in this directory are executed without being wrapped in a new variable scope. These files are executed before other client-side JavaScript files.
建议使用 npm 来安装第三方 JavaScript 库,并使用
import来控制文件的加载时间。¥It is recommended to use npm for 3rd party JavaScript libraries and use
importto control when files are loaded.tests
任何名为
tests/的目录都不会被加载到任何地方。对于任何需要在 Meteor 内置测试工具 之外使用测试运行器运行的测试代码,请使用此方法。¥Any directory named
tests/is not loaded anywhere. Use this for any test code you want to run using a test runner outside of Meteor's built-in test tools.
以下目录也不会作为应用代码的一部分加载:
¥The following directories are also not loaded as part of your app code:
名称以点号开头的文件/目录,例如
.meteor和.git¥Files/directories whose names start with a dot, like
.meteorand.gitpackages/:用于本地软件包¥
packages/: Used for local packagescordova-build-override/:用于 高级移动构建自定义¥
cordova-build-override/: Used for advanced mobile build customizationsprograms:出于兼容性考虑¥
programs: For legacy reasons
特殊目录之外的文件
¥Files outside special directories
特殊目录之外的所有 JavaScript 文件都会在客户端和服务器端加载。Meteor 提供变量 Meteor.isClient 和 Meteor.isServer,以便你的代码可以根据其运行在客户端还是服务器端来改变其行为。
¥All JavaScript files outside special directories are loaded on both the client and the server. Meteor provides the variables Meteor.isClient and Meteor.isServer so that your code can alter its behavior depending on whether it's running on the client or the server.
特殊目录之外的 CSS 和 HTML 文件仅在客户端加载,无法从服务器代码中使用。
¥CSS and HTML files outside special directories are loaded on the client only and cannot be used from server code.
拆分为多个应用
¥Splitting into multiple apps
如果你正在编写一个足够复杂的系统,那么将代码拆分成多个应用可能就很有意义了。例如,你可能需要为管理 UI 创建一个单独的应用(这样就无需在网站的管理部分检查所有权限,只需检查一次),或者将应用的移动版和桌面版代码分开。
¥If you are writing a sufficiently complex system, there can come a time where it makes sense to split your code up into multiple applications. For example you may want to create a separate application for the administration UI (rather than checking permissions all through the admin part of your site, you can check once), or separate the code for the mobile and desktop versions of your app.
另一个非常常见的用例是将工作进程从主应用中分离出来,这样耗时的任务就不会因为占用单个 Web 服务器而影响访客的用户体验。
¥Another very common use case is splitting a worker process away from your main application so that expensive jobs do not impact the user experience of your visitors by locking up a single web server.
以这种方式拆分应用有一些优势:
¥There are some advantages of splitting your application in this way:
如果你将特定类型用户永远不会使用的代码分离出来,你的客户端 JavaScript 包可以显著减小。
¥Your client JavaScript bundle can be significantly smaller if you separate out code that a specific type of user will never use.
你可以部署具有不同扩展配置的不同应用,并采用不同的安全措施(例如,你可以将管理应用的访问权限限制在防火墙后的用户)。
¥You can deploy the different applications with different scaling setups and secure them differently (for instance you might restrict access to your admin application to users behind a firewall).
你可以允许组织内的不同团队独立开发不同的应用。
¥You can allow different teams at your organization to work on the different applications independently.
然而,以这种方式拆分代码会带来一些挑战,在着手实现之前应该考虑这些挑战。
¥However there are some challenges to splitting your code in this way that should be considered before jumping in.
共享代码
¥Sharing code
主要挑战在于如何在你构建的不同应用之间正确共享代码。解决此问题的最简单方法是将同一应用部署在不同的 Web 服务器上,并通过不同的 settings 控制其行为。这种方法允许你部署具有不同扩展行为的不同版本,但无法享受上述大多数其他优势。
¥The primary challenge is properly sharing code between the different applications you are building. The simplest approach to deal with this issue is to deploy the same application on different web servers, controlling the behavior via different settings. This approach allows you to deploy different versions with different scaling behavior but doesn't enjoy most of the other advantages stated above.
如果你想创建具有独立代码的 Meteor 应用,则需要一些模块在它们之间共享。如果这些模块可以被更广泛的用户使用,你应该考虑使用 将其发布到包系统,根据代码是否专门针对 Meteor,可以选择 npm 或 Atmosphere。
¥If you want to create Meteor applications with separate code, you'll have some modules that you'd like to share between them. If those modules are something the wider world could use, you should consider publishing them to a package system, either npm or Atmosphere, depending on whether the code is Meteor-specific or otherwise.
如果代码是私有的,或者其他人不感兴趣,通常最好在两个应用中包含同一个模块(可以使用 私有 npm 模块)。有几种方法可以实现这一点:
¥If the code is private, or of no interest to others, it typically makes sense to include the same module in both applications (you can do this with private npm modules). There are several ways to do this:
一种直接的方法是将公共代码作为两个应用的 git 子模块 包包含进来。
¥a straightforward approach is to include the common code as a git submodule of both applications.
或者,如果你将两个应用放在同一个仓库中,则可以使用符号链接将公共模块包含到两个应用中。
¥alternatively, if you include both applications in a single repository, you can use symbolic links to include the common module inside both apps.
共享数据
¥Sharing data
另一个重要的考虑因素是如何在不同的应用之间共享数据。
¥Another important consideration is how you'll share the data between your different applications.
最简单的方法是将两个应用指向同一个 MONGO_URL,并允许它们直接读写数据库。由于 Meteor 对数据库响应式的支持,此功能运行良好。当一个应用更改 MongoDB 中的某些数据时,借助 Meteor 的实时查询功能,连接到该数据库的任何其他应用的用户都能立即看到这些更改。
¥The simplest approach is to point both applications at the same MONGO_URL and allow both applications to read and write from the database directly. This works well thanks to Meteor's support for reactivity through the database. When one app changes some data in MongoDB, users of any other app connected to the database will see the changes immediately thanks to Meteor's livequery.
但是,在某些情况下,最好允许一个应用作为主应用,并通过 API 控制其他应用对数据的访问。如果你希望按不同计划部署不同的应用,并且需要对数据变更保持谨慎,这将很有帮助。
¥However, in some cases it's better to allow one application to be the master and control access to the data for other applications via an API. This can help if you want to deploy the different applications on different schedules and need to be conservative about how the data changes.
提供服务器间 API 的最简单方法是直接使用 Meteor 内置的 DDP 协议。Meteor 客户端就是通过这种方式从服务器获取数据的,但你也可以用它来实现不同应用之间的通信。你可以使用 DDP.connect() 从 "client" 服务器连接到主服务器,然后使用返回的连接对象进行方法调用并读取发布内容。
¥The simplest way to provide a server-server API is to use Meteor's built-in DDP protocol directly. This is the same way your Meteor client gets data from your server, but you can also use it to communicate between different applications. You can use DDP.connect() to connect from a "client" server to the master server, and then use the connection object returned to make method calls and read from publications.
如果你需要更传统的 API,可以使用 Meteor 中提供的打包式 express。查看 WebApp 文档
¥If you need a more traditional API, you can use the bundled express available in Meteor. See the documentation of WebApp
js
import { Meteor } from "meteor/meteor";
import { WebApp } from "meteor/webapp";
import bodyParser from "body-parser";
const app = WebApp.handlers;
app.use(bodyParser.json());
app.use("/hello", (req, res, next) => {
res.writeHead(200);
res.end(`Hello world from: ${Meteor.release}`);
});共享账户
¥Sharing accounts
如果你有两个服务器访问同一个数据库,并且你希望经过身份验证的用户能够在这两个服务器上进行 DDP 调用,则可以使用在一个连接上设置的恢复令牌在另一个连接上登录。
¥If you have two servers that access the same database and you want authenticated users to make DDP calls across the both of them, you can use the resume token set on one connection to login on the other.
如果你的用户已连接到服务器 A,则可以使用 DDP.connect() 打开与服务器 B 的连接,并将服务器 A 的恢复令牌传递给服务器 B 进行身份验证。由于两个服务器使用相同的数据库,因此相同的服务器令牌在两种情况下都有效。身份验证代码如下:
¥If your user has connected to server A, then you can use DDP.connect() to open a connection to server B, and pass in server A's resume token to authenticate on server B. As both servers are using the same DB, the same server token will work in both cases. The code to authenticate looks like this:
js
// This is server A's token as the default `Accounts` points at our server
const token = Accounts._storedLoginToken();
// We create a *second* accounts client pointing at server B
const app2 = DDP.connect('url://of.server.b');
const accounts2 = new AccountsClient({ connection: app2 });
// Now we can login with the token. Further calls to `accounts2` will be authenticated
accounts2.loginWithToken(token);你可以在 示例仓库 中查看此架构的概念验证。
¥You can see a proof of concept of this architecture in an example repository.
另一种模式是使用另一个应用作为应用的身份提供程序。leaonline:oauth2-server 包可用于在一个 Meteor 应用中创建 OAuth2 服务器,然后其他应用可以使用标准的 accounts-oauth 包来验证用户身份。
¥Another pattern is to have another app which acts as identity provider for your apps. The leaonline:oauth2-server package can be used to create an OAuth2 server in one Meteor app, and then other apps can use the standard accounts-oauth packages to authenticate users against it.

