Merge remote-tracking branch 'guide/master' into feature/merge-guide
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<!--
|
||||
🙌 Thanks for making this PR 😃
|
||||
-->
|
||||
|
||||
TODO:
|
||||
|
||||
- [ ] If this is a significant change, update [CHANGELOG.md](https://github.com/meteor/guide/blob/master/CHANGELOG.md)
|
||||
- [ ] Use `<h2 id="foo">` instead of `## Foo` for headers
|
||||
- [ ] Leave a blank line after each header
|
||||
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
.idea/
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "site/themes/meteor"]
|
||||
path = site/themes/meteor
|
||||
url = https://github.com/meteor/hexo-theme-meteor.git
|
||||
39
CHANGELOG.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Changelog
|
||||
order: 1001
|
||||
description: A log of significant changes to the Meteor Guide.
|
||||
---
|
||||
- 2021/07/21: Added "Apollo" article
|
||||
- 2021/07/20: Tweaked VueJS navigation structure and updated some information. Mention Picker in server-side routing.
|
||||
- 2020/09/13: Removed the section about crosswalk from the Cordova guide
|
||||
- 2020/08/08: Added "Hot Code Push" guide
|
||||
- 2020/04/26: Added "React Native" section to build, and renamed "Mobile" to "Cordova"
|
||||
- 2020/02/03: Added "Preventing unnecessary data retrieval" section to Accounts
|
||||
- 2018/10/23: Added VueJS SSR Rendering for Meteor guide
|
||||
- 2018/10/14: Added VueJS Integration guide
|
||||
- 2018/03/03: Added HTTP Headers to the production security section and made Helmet the official recommendation. Update Mobile section to refer to HTTP header section for CSP instead of Browser Policy package. [#750](https://github.com/meteor/guide/pull/750)
|
||||
- 2017/10/28: Removed mention of `react-addons-pure-render-mixin` package from "Using Meteor's data system" section as it is no longer needed.
|
||||
- 2017/09/08: Updated "Using Meteor's data system" section to describe the new `withTracker` function as it now replaces `createContainer`.
|
||||
- 2017/03/22: Added Docker section within Deployment and Monitoring.
|
||||
- 2017/03/05: Updated "Testing" to use the replacement `dispatch:mocha` package instead of the previous suggestions from `dispatch:*`. [PR#618](https://github.com/meteor/guide/pull/618) [PR#614](https://github.com/meteor/guide/pull/614)
|
||||
- 2017/02/08: Updated MongoDb hosting services with more details and recommendations. [PR#609](https://github.com/meteor/guide/pull/609)
|
||||
- 2017/01/19: Updated recommendations for forcing SSL to avoid the `force-ssl` package when possible.
|
||||
- 2017/01/07: Created new section "TypeScript".
|
||||
- 2017/01/04: Changed "Testing" section to reference `dburles:factory` in the same spirit as the `meteor/todos` app [PR #598](https://github.com/meteor/guide/pull/598)
|
||||
- 2016/07/02: Created new section in ui-ux on use of i18n with React.
|
||||
- 2016/05/28: Created new section "A simple React unit test" [PR #466](https://github.com/meteor/guide/pull/466).
|
||||
- 2016/05/22: Created new section "Testing publications" for separated `publication-collector` package (as [discussed here](https://github.com/meteor/todos/issues/119)).
|
||||
- 2016/05/05: Changed Build Section organization to separate Atmosphere and npm. [Discussed here](https://github.com/meteor/guide/pull/390#issuecomment-212577341). [PR #410](https://github.com/meteor/guide/pull/410)
|
||||
- 2016/04/16: Switch order of Code Style and Application structure sections. [PR #383](https://github.com/meteor/guide/pull/383)
|
||||
- 2016/04/16: Added [Writing Packages - Creating an npm package](https://guide.meteor.com/writing-packages.html#creating-npm) and [Using Packages - Overriding packages - npm](https://guide.meteor.com/using-packages.html#npm-overriding). [PR #381](https://github.com/meteor/guide/pull/381)
|
||||
- 2016/04/16: Added "Writing Packages - Creating an npm package" and "Using Packages - Overriding packages - npm". [PR #381](https://github.com/meteor/guide/pull/381)
|
||||
Fixed old changelog PR reference
|
||||
- 2016/04/07: Add more examples and details on application structure using imports. [PR #356](https://github.com/meteor/guide/pull/356)
|
||||
- 2016/04/04: Add more content on writing and publishing Atmosphere packages. [PR #339](https://github.com/meteor/guide/pull/339)
|
||||
- 2016/04/03: Add back in build tool default loading order rules. [PR #340](https://github.com/meteor/guide/pull/340)
|
||||
- 2016/04/01: Added CoffeeScript exports syntax. [PR #328](https://github.com/meteor/guide/pull/328)
|
||||
- 2016/04/01: Changed Mocha test code snippets to use function expressions instead of arrow functions, after the discussion on [Issue #318](https://github.com/meteor/guide/issues/318). [PR #323](https://github.com/meteor/guide/pull/323)
|
||||
- 2016/04/01: Added `gadicc:blaze-react-component` in a new "Blaze in React" section of the React article. [PR #325](https://github.com/meteor/guide/pull/325)
|
||||
- 2016/03/31: Added Chromatic demo video and React Storybook to User Interfaces article. [PR #320](https://github.com/meteor/guide/pull/320)
|
||||
|
||||
Changelog is only tracked since the Meteor 1.3 release.
|
||||
67
CONTRIBUTING.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Contribution Guidelines
|
||||
order: 1000
|
||||
description: Tips for contributing to the Meteor Guide.
|
||||
---
|
||||
|
||||
Please submit clarifications and improvements to the Guide! If it's just a small fix, go ahead and open a PR. If it's something more major, please file an issue for discussion first.
|
||||
|
||||
### Using the change log
|
||||
|
||||
If you are adding significant new content, please take a moment to include an update to the [changelog](changelog.html) in your PR.
|
||||
|
||||
### Writing tips
|
||||
|
||||
Things to be aware of:
|
||||
|
||||
#### Always use specific IDs on headers so that we can change them later:
|
||||
|
||||
```markdown
|
||||
// bad
|
||||
## Using schemas with collections
|
||||
|
||||
// good
|
||||
<h2 id="schemas-with-collections">Using schemas with collections</h2>
|
||||
```
|
||||
|
||||
#### Titles and headers
|
||||
|
||||
Article titles are `Title Case`, and headers are `Sentence case`.
|
||||
|
||||
#### Always put a blank line after each header
|
||||
|
||||
Otherwise, the following paragraph isn't parsed correctly.
|
||||
|
||||
```markdown
|
||||
// bad
|
||||
<h2 id="schemas-with-collections">Using schemas with collections</h2>
|
||||
This is some text
|
||||
|
||||
// good
|
||||
<h2 id="schemas-with-collections">Using schemas with collections</h2>
|
||||
|
||||
This is some text
|
||||
```
|
||||
|
||||
#### Escape handlebars syntax inside inline code snippets
|
||||
|
||||
Note: you don't need to escape things in fenced/multiline code snippets, only in inline ones.
|
||||
|
||||
```markdown
|
||||
// will break
|
||||
Render multiple items in your template with `{{#each}}`
|
||||
|
||||
// good
|
||||
Render multiple items in your template with `{% raw %}{{#each}}{% endraw %}`
|
||||
```
|
||||
|
||||
### Running the static site generator locally
|
||||
|
||||
The site is built using hexo, a static site generator. To run it locally, perform the following steps:
|
||||
|
||||
```shell
|
||||
git submodule update --init --recursive
|
||||
cd site
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
18
LICENSE.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
========================================
|
||||
Meteor is licensed under the MIT License
|
||||
========================================
|
||||
|
||||
Copyright (C) 2011-present Meteor Development Group
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
====================================================================
|
||||
This license applies to all code in Meteor that is not an externally
|
||||
maintained library. Externally maintained libraries have their own
|
||||
licenses, included in the LICENSES directory.
|
||||
====================================================================
|
||||
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Meteor Guide
|
||||
|
||||
[Read the guide!!](http://guide.meteor.com/)
|
||||
64
content/1.10-migration.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.10
|
||||
description: How to migrate your application to Meteor 1.10.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.10 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, some breaking changes to note and migration steps for a bug that you might encounter.
|
||||
|
||||
<h3 id="mongo-exit-62">Unexpected mongo exit code 62</h3>
|
||||
|
||||
If you get `Unexpected mongo exit code 62. Restarting.` when starting your local
|
||||
MongoDB, you can either reset your project (`meteor reset`)
|
||||
(if you don't care about your local data)
|
||||
or you will need to update the feature compatibility version of your local MongoDB:
|
||||
|
||||
1. Downgrade your app to earlier version of Meteor `meteor update --release 1.9.2`
|
||||
2. Start your application
|
||||
3. While your application is running open a new terminal window, navigate to the
|
||||
app directory and open `mongo` shell: `meteor mongo`
|
||||
4. Use: `db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })` to
|
||||
check the current feature compatibility.
|
||||
5. If the returned version is less than 4.0 update like this:
|
||||
`db.adminCommand({ setFeatureCompatibilityVersion: "4.2" })`
|
||||
6. You can now stop your app and update to Meteor 1.10.
|
||||
|
||||
For more information about this, check out [MongoDB documentation](https://docs.mongodb.com/manual/release-notes/4.2-upgrade-standalone/).
|
||||
|
||||
<h3 id="cordova-update">Cordova upgrade</h3>
|
||||
|
||||
Cordova has been updated from version 7 to 9. We recommend that you test
|
||||
your features that are taking advantage of Cordova plugins to be sure
|
||||
they are still working as expected.
|
||||
|
||||
<h3 id="WKWebViewOnly">WKWebViewOnly</h3>
|
||||
|
||||
WKWebViewOnly is set by default now as true so if you are relying on
|
||||
UIWebView or plugins that are using UIWebView APIs you probably want to
|
||||
set it as false, you can do this by calling
|
||||
`App.setPreference('WKWebViewOnly', false);` in your mobile-config.js. But we
|
||||
don't recommend turning this into false because
|
||||
[Apple have said](https://developer.apple.com/news/?id=12232019b) they are
|
||||
going to reject apps using UIWebView.
|
||||
|
||||
<h3 id="windows-32bit-drop">Windows 32-bit support dropped</h3>
|
||||
|
||||
Because MongoDB since 3.4 no longer supports 32-bit Windows, Meteor 1.10 has
|
||||
also dropped support for 32-bit Windows. In other words, Meteor 1.10 supports
|
||||
64-bit Mac, Windows 64-bit, and Linux 64-bit.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.9.3?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.9.3, there may be important considerations not listed in this guide (which specifically covers 1.9.3 to 1.10). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
44
content/1.10.2-migration.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.10.2
|
||||
description: How to migrate your application to Meteor 1.10.2.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.10.2 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is a breaking change for those using the Flow syntax.
|
||||
|
||||
<h3 id="flow-unsupported">Flow syntax not supported</h3>
|
||||
|
||||
The `babel-compiler` package, used by both `ecmascript` and
|
||||
`typescript`, no longer supports stripping [Flow](https://flow.org/)
|
||||
type annotations by default, which may be a breaking change if your
|
||||
application (or Meteor package) relied on Flow syntax.
|
||||
|
||||
If you still need Babel's Flow plugins, you can install them with npm
|
||||
and then enable them with a custom `.babelrc` file in your application's
|
||||
(or package's) root directory:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-flow",
|
||||
"@babel/plugin-transform-flow-strip-types"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.10?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.10, there may be important considerations not listed in this guide (which specifically covers 1.10 to 1.10.2). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
36
content/1.11-migration.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.11
|
||||
description: How to migrate your application to Meteor 1.11.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.11 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there some breaking changes to note and migration steps for a bug that you might encounter.
|
||||
|
||||
<h3 id="eamil-dns">Email DNS lookup</h3>
|
||||
|
||||
`email` package dependencies have been update and package version has been bumped to 2.0.0
|
||||
There is a potential breaking change as the underlying package started to use `dns.resolve()`
|
||||
instead of `dns.lookup()` which might be breaking on some environments.
|
||||
See [nodemailer changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) for more information.
|
||||
|
||||
<h3 id="cordova-git-url">Cordova now working with Git urls</h3>
|
||||
|
||||
Cordova add plugin is not working with plugin name in the git URL when the plugin id was different than the name in the config.xml.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.10.2?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.10.2, there may be important considerations not listed in this guide (which specifically covers 1.10.2 to 1.11). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
42
content/1.12-migration.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.12
|
||||
description: How to migrate your application to Meteor 1.12.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.12 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are some breaking changes to note.
|
||||
|
||||
<h3 id="types-imports">Importing types</h3>
|
||||
|
||||
When importing types in Typescript, you might need to use the "type" qualifier, like so:
|
||||
```js
|
||||
import { Point } from 'react-easy-crop/types';
|
||||
```
|
||||
to
|
||||
```ts
|
||||
import type { Point } from 'react-easy-crop/types';
|
||||
```
|
||||
Because now `emitDecoratorsMetadata` is enabled.
|
||||
|
||||
<h3 id="typescript-upgrade">Typescript upgraded to 4.1.2</h3>
|
||||
|
||||
Refer to typescript breaking changes before migrating your existing project, from 3.7.6 to 4.1.2: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.11?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.11, there may be important considerations not listed in this guide (which specifically covers 1.11 to 1.12). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
238
content/1.3-migration.md
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.3
|
||||
description: How to migrate your application to use recommended best practice as of Meteor 1.3.
|
||||
discourseTopicId: 20190
|
||||
---
|
||||
|
||||
<h2 id="breaking-changes">Breaking changes</h2>
|
||||
|
||||
These are all the *breaking changes* -- that is changes that you absolutely have to worry about if you are updating your app from 1.2.x to 1.3. However, we recommend that you also consider the *recommended* changes listed in the other sections below.
|
||||
|
||||
- Ensure that your project has a [`package.json`](https://docs.npmjs.com/files/package.json) file, which will be the foundation for npm package installs. You can create this by running:
|
||||
```
|
||||
meteor npm init -y
|
||||
```
|
||||
- Files in a directory named `imports/` will no longer load eagerly. (You should probably rename such a directory as it is the basis of our new [module system](#modules)).
|
||||
|
||||
- Files within your app named `*.test[s].*`, `*.app-test[s].*`, `*.spec[s].*` and `*.app-spec[s].*` will no longer load eagerly (you should probably rename such a file if it doesn't contain tests, as it will be eagerly loaded by our new [app testing modes](#testing)).
|
||||
|
||||
- If you are using React you will now need to install a set of React npm packages in your app. See the [recommendations for React](#react) below for more details.
|
||||
|
||||
<h3 id="breaking-changes-mobile">Mobile</h3>
|
||||
|
||||
- iOS apps now require iOS 8 or higher, and building for iOS requires Xcode 7.2 or higher to be installed.
|
||||
|
||||
- Building for Android now requires Android SDK 23 to be installed. You may also need to create a new AVD for the emulator.
|
||||
|
||||
- Cordova has been upgraded to the most recent versions (Cordova 6.0.0, Cordova iOS 4.0.1 and Cordova Android 5.1.0). This may require you to upgrade your plugin versions. We pin core Cordova plugins to versions known to be compatible and warn about this during build, but you may encounter compile time or runtime errors with third party plugins. Upgrading to newer versions of these plugins may help if they have been updated to work with recent versions of Cordova.
|
||||
|
||||
- The plugin used to serve your app's files and support hot code push has been completely rewritten. As a result, files are now served from `localhost` instead of `meteor.local`, with a fixed port number derived from your `appId`. You may have to update OAuth redirect URLs to point to the new local domain and port.
|
||||
|
||||
<h2 id="modules">Recommendations: modules</h2>
|
||||
|
||||
The biggest new feature in Meteor 1.3 is support for [ES2015 modules](https://developer.mozilla.org/en/docs/web/javascript/reference/statements/import) on the client and the server. Using modules you can declare dependencies between files, control load order, and use npm packages on the client and server.
|
||||
|
||||
- You should load all Meteor "pseudo-globals" using the `import { Name } from 'meteor/package'` syntax. For instance:
|
||||
|
||||
```js
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { EJSON } from 'meteor/ejson';
|
||||
```
|
||||
|
||||
- You should consider installing the `meteor-node-stubs` npm package to allow using npm packages written for `node` on the browser:
|
||||
|
||||
```bash
|
||||
meteor npm install --save meteor-node-stubs
|
||||
```
|
||||
|
||||
- If you are using app-local packages to control load order and write unit tests for your application, we recommend you switch to using modules:
|
||||
- Remove code related to the [Package API](http://docs.meteor.com/#/full/packagejs) from the `package.js` files and rename them to `index.js`,
|
||||
- Move your local packages to the `imports/` directory.
|
||||
- Add the necessary `import` statements to each of the modules in your packages.
|
||||
- Add `export` statements to each of your packages exports.
|
||||
|
||||
```js
|
||||
api.addFiles('file.js');
|
||||
// For files that are not imported elsewhere, this turns into
|
||||
import './file.js';
|
||||
|
||||
// Remove from package.js
|
||||
api.export('Foo');
|
||||
|
||||
// localPackage/foo.js
|
||||
// Foo must be explicitly exported
|
||||
export default Foo;
|
||||
|
||||
// client/main.js
|
||||
import '/imports/localPackage';
|
||||
```
|
||||
- You can read about our recommended structure for applications and modules in the [Application Structure article](structure.html) of the Meteor Guide, and how to test them in the [Testing article](testing.html).
|
||||
|
||||
- If you are using Atmosphere packages which wrap npm packages, both on the client and server, it is now recommended that you install them using npm. Run `npm init` to initialize your `package.json` and install packages with `npm install --save` (or `npm install --save-dev` if it's a development dependency for testing etc.). We have [some tips](using-packages.html#async-callbacks) about how to use npm packages written in an asynchronous style.
|
||||
|
||||
Also, you should no longer need to use the [`meteorhacks:npm`](https://atmospherejs.com/meteorhacks/npm) package. To migrate, follow the following steps:
|
||||
|
||||
1. Remove packages from your app: `meteor remove meteorhacks:npm npm-container`.
|
||||
2. Remove the generated `npm-container` package: `rm -r packages/npm-container`.
|
||||
3. Move the contents of `packages.json` to the `dependencies` section of your `package.json` (you may need to create one with `meteor npm init`).
|
||||
4. Use [`import`](structure.html#intro-to-import-export) instead of `Npm.require()`.
|
||||
|
||||
<h2 id="packages">Recommendations: package authors</h2>
|
||||
|
||||
Package authors are recommended to:
|
||||
|
||||
- No longer publish wrapper packages that do no more than include an npm package / client side lib. If your package adds significant wrappers around the npm package, it might make sense however.
|
||||
|
||||
- Publish to npm when appropriate, especially if your package can be used by the wider JS community!
|
||||
|
||||
- Use [`api.mainModule()`](http://1.3-docs.meteorapp.com/#/full/modularpackagestructure) and `export` from your main module rather than `api.exports()` in Atmosphere packages.
|
||||
|
||||
- If you depend (directly or transitively) on a client side npm package that is large or problematic if installed twice (e.g. React), use [`tmeasday:check-npm-versions`](https://github.com/tmeasday/check-npm-versions) to declare "peer" dependencies. If the client side npm package you depend on is `angular`, you can support both Meteor 1.2 and 1.3 using [this solution](#angular-meteor-packages). Read more about this in the [Writing Packages article](writing-packages.html#peer-npm-dependencies).
|
||||
|
||||
<h2 id="testing">Recommendations: Testing</h2>
|
||||
|
||||
Meteor 1.3 includes a new command `meteor test`, which can be used to run tests against your app, in two modalities. You can read about these features in much more detail in the [Testing Guide Article](testing.html).
|
||||
|
||||
<h3 id="full-app-testing">Full app testing</h3>
|
||||
|
||||
If you were previously using [Velocity](http://xolv.io/velocity-announcement/) to run tests against your running Meteor app, the full app test mode should allow you to run your tests against 1.3, with some small changes.
|
||||
|
||||
- To convert tests, you'll need to change or upgrade your test driver package to a 1.3 compatible package (as of this writing there is only one choice [`practicalmeteor:mocha`](https://atmospherejs.com/practicalmeteor/mocha) but we expect more to exist in the future). You should name your test files in the pattern `*.app-test[s].*` and place them *outside* of `tests/` directories. To run the tests you can run `meteor test --full-app --driver-package <driver-package>`
|
||||
|
||||
- Note that full app test mode does not run the test reporter in a separate application to the app under test, and does not amalgamate results from multiple testing systems, as Velocity does. This effectively means if you are using more than one testing system, you will need to run `meteor test --full-app` multiple times.
|
||||
|
||||
- Also, it means certain types of tests are better off written as [*acceptance tests*](testing.html#acceptance-tests) outside of the Meteor tool.
|
||||
|
||||
<h3 id="module-testing">Module testing</h3>
|
||||
|
||||
If you were previously using in-app packages in order to unit test your app, you should switch to a [modules-based approach](#modules) and test them using the normal test mode.
|
||||
|
||||
- To convert your unit tests to run against the app, first upgrade your test driver (see [above](#full-app-testing)) and then place your test files alongside the modules they are testing with a name matching `*.tests.*`. Such files will automatically be added to your "test app" when you run `meteor test --driver-package <driver-package>`. You can `import` the modules that you need to test against within each test file.
|
||||
|
||||
- Some example tests can be seen the [Todos example app](https://github.com/meteor/todos)
|
||||
|
||||
<h2 id="mobile">Recommendations: Mobile</h2>
|
||||
|
||||
Alongside some of the breaking mobile changes [listed above](#breaking-changes-mobile), there are some changes in the way the mobile integration works that you should consider:
|
||||
|
||||
- Some low resolution app icon and launch images sizes for now unsupported devices have been deprecated. To avoid a deprecation warning during build, please remove the entries from your `mobile-config.js`. (You probably also want to remove the image files from your project.)
|
||||
|
||||
- The plugin now allows for local file access on both iOS and Android. You can construct file system URLs manually (`http://localhost:<port>/local-filesystem/<path>`) or use `WebAppLocalServer.localFileSystemUrl()` to convert a `file://` URL.
|
||||
|
||||
<h2 id="react">Install React from npm</h2>
|
||||
|
||||
In Meteor 1.3, we recommend installing `react` and `react-dom` [into your app using npm](react.html#using-with-meteor), and importing them from your app code:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
```
|
||||
|
||||
As mentioned in the [breaking changes](#breaking-changes), the `react` Atmosphere package still works, but it now expects you to install the React npm packages it uses in your application (read the [Using Packages](using-packages.html) article for more details about how to manage your npm dependencies):
|
||||
|
||||
```
|
||||
npm install --save react react-dom react-addons-transition-group \
|
||||
react-addons-css-transition-group react-addons-linked-state-mixin \
|
||||
react-addons-create-fragment react-addons-update react-addons-pure-render-mixin \
|
||||
react-addons-test-utils react-addons-perf
|
||||
```
|
||||
|
||||
**However**, we recommend that you should stop using the `react` or `react-runtime` Atmosphere packages and instead install React directly from npm (for more detail, see the [React article](react.html) of the guide). To make this change in an existing app, you can run:
|
||||
|
||||
```
|
||||
meteor remove react
|
||||
|
||||
# if you are using our data integration
|
||||
meteor add react-meteor-data
|
||||
|
||||
npm install --save react react-dom react-addons-pure-render-mixin
|
||||
```
|
||||
|
||||
Then, in your application, you should import React directly rather than [relying on a global React symbol](#modules):
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
```
|
||||
|
||||
If you are using a package that depends on the `react` or `react-runtime` Atmosphere packages, you will still need to install the full list of npm React packages above, so we encourage package authors to update their packages to import React directly from npm.
|
||||
|
||||
<h3 id="react-meteor-data">Loading data with React</h3>
|
||||
|
||||
The `react-meteor-data` has a [new `createContainer` syntax](react.html#data) for combining Meteor's data system with React in an idiomatic way. We encourage you to use containers to separate your data loading concerns from your presentational components!
|
||||
|
||||
<h2 id="react">Install Angular from npm</h2>
|
||||
|
||||
With an Angular Meteor app you can safely update to Meteor 1.3 without any changes to your code.
|
||||
You need to make sure you are using the latest `angular` Atmosphere package `1.3.9_2`.
|
||||
|
||||
But, in Meteor 1.3, we recommend installing `angular` and `angular-meteor` into your app using npm:
|
||||
```
|
||||
npm install --save angular angular-meteor
|
||||
```
|
||||
and importing them from your app code:
|
||||
```js
|
||||
import angular from 'angular';
|
||||
import angular-meteor from 'angular-meteor';
|
||||
```
|
||||
|
||||
Read the [Using Packages](using-packages.html) article for more details about how to manage your npm dependencies.
|
||||
|
||||
If you already using the Atmosphere packages and want to move to the npm packages, you will need to remove the Atmosphere packages first but keep the angular-templates Atmosphere package:
|
||||
|
||||
```
|
||||
meteor remove angular
|
||||
meteor add angular-templates
|
||||
|
||||
npm install --save angular angular-meteor
|
||||
```
|
||||
|
||||
Then, in your application, you should import angular directly rather than [relying on global angular](#modules):
|
||||
|
||||
```js
|
||||
import angular from 'angular';
|
||||
import angular-meteor from 'angular-meteor';
|
||||
```
|
||||
|
||||
<h3 id="angular-meteor-packages">Existing Angular Atmosphere packages</h3>
|
||||
|
||||
If you are a package author that depends on the `angular:angular` Atmosphere package, you can support both Meteor 1.2 and 1.3 so your users will have an unbreaking update process:
|
||||
|
||||
Change your `angular:angular` dependency into a weak dependency:
|
||||
```js
|
||||
api.use('angular:angular@1.5.3', 'client', { weak: true });
|
||||
```
|
||||
and then add a dependency check for both Meteor 1.2 and 1.3 before initializing your angular module:
|
||||
```js
|
||||
if (!window.angular) {
|
||||
try {
|
||||
if (Package['modules-runtime']) {
|
||||
var require = Package['modules-runtime'].meteorInstall();
|
||||
require('angular');
|
||||
}
|
||||
} catch(e) {
|
||||
throw new Error('angular package is missing');
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('your.module', []);
|
||||
```
|
||||
|
||||
<h2 id="guide">New guide articles</h2>
|
||||
|
||||
As part of the 1.3 release, we have some new guide articles and updated sections of existing articles:
|
||||
|
||||
- There's a [Application Structure](structure.html) article which explains how to structure your files and use the module system.
|
||||
|
||||
- There's a [Code Style](code-style.html) article that makes recommendations about how to ensure consistent formatting for your code.
|
||||
|
||||
- There's a [Testing](testing.html) article which covers how to do various kinds of testing in Meteor.
|
||||
|
||||
- There's a [React](react.html) article which explains how to best use React with Meteor
|
||||
|
||||
- There's a [Mobile](mobile.html) article which covers how to best use our Cordova integration.
|
||||
|
||||
- There's a [Using Packages](using-packages.html) article which explains how best to use both npm and Atmosphere packages in your app.
|
||||
|
||||
- There's a [Writing Packages](writing-packages.html) article which explains practice for writing Atmosphere packages and using all kinds of dependencies within them.
|
||||
|
||||
- The UI/UX article has been updated to explain how to do [i18n](ui-ux.html#i18n) in Meteor applications.
|
||||
89
content/1.4-migration.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.4
|
||||
description: How to migrate your application to use recommended best practice as of Meteor 1.4.
|
||||
discourseTopicId: 26998
|
||||
---
|
||||
|
||||
<h2 id="breaking-changes">Breaking changes</h2>
|
||||
|
||||
These are all the *breaking changes* — that is, changes you absolutely have to worry about if you are updating your app from 1.3.x to 1.4. However, we recommend that you also consider the *recommended* changes [listed below](#recommendations).
|
||||
|
||||
### The `babel-runtime` npm is usually required
|
||||
<a name="babel-runtime-required"></a>
|
||||
|
||||
The `babel-runtime` npm package is generally required as a dependency since the Meteor `babel-runtime` package no longer attempts to provide custom implementations of Babel helper functions. To install the `babel-runtime` npm, run the following command in any Meteor application:
|
||||
```
|
||||
meteor npm install --save babel-runtime
|
||||
```
|
||||
New projects created with `meteor create <app-name>` will automatically have this package added to their `package.json`'s `dependencies`.
|
||||
|
||||
<h3 id="binary-packages-require-build-toolchain">Binary Packages require a Build Toolchain</h3>
|
||||
|
||||
The headline feature of Meteor 1.4 is the upgrade to Node version 4. Node 4 includes a changed ABI (application binary interface), which means that *binary npm packages* that your application uses will need to be recompiled.
|
||||
|
||||
Some very common binary packages (such as `npm-bcrypt`) will already have been republished for the Node 4 platform, so if you are using a limited set of packages, this may not affect you; however if you are using less common dependencies, this may be an issue.
|
||||
|
||||
If you have binary npm packages in your application `node_modules` directory, you should run `meteor npm rebuild` (after `meteor update`) in your application directory to recompile those packages.
|
||||
|
||||
Meteor will automatically recompile any binary npm dependencies of Meteor packages, if they were not already compiled with the correct ABI. This will typically happen the first time you start your application after updating to 1.4, but it may also happen when you `meteor add some:package` that was published using a different version of Meteor and/or Node.
|
||||
|
||||
In order for this rebuilding to work, you will need to install a basic compiler toolchain on your development machine. Specifically,
|
||||
|
||||
- OS X users should install the [commandline tools](http://railsapps.github.io/xcode-command-line-tools.html) (in short, run `xcode-select --install`).
|
||||
|
||||
- Windows users should install the [MS Build Tools](https://www.microsoft.com/en-us/download/details.aspx?id=48159).
|
||||
|
||||
- Linux users should ensure they have Python 2.7, `make` and a C compiler (g++) installed.
|
||||
|
||||
To test that your compiler toolchain is installed and working properly, try installing any binary npm package in your application using `meteor npm`. For example, run `meteor npm install bcrypt` then `meteor node`, then try calling `require("bcrypt")` from the Node shell.
|
||||
|
||||
<h3 id="update-from-mongo-2_4">Update from MongoDB 2.4</h3>
|
||||
|
||||
Meteor has been updated to use version 2.2.4 of the node MongoDB driver. This means Meteor now ships with full support for MongoDB 3.2 (the latest stable version) and the WiredTiger storage engine. [We recommend](#update-to-mongo-3_2) you update your application to MongoDB 3.2.
|
||||
|
||||
If you are currently using MongoDB 2.4, please note that the version has reached [end-of-life](https://www.mongodb.com/support-policy) and you should at the least update to version 2.6. Version 2.6 is the minimum version supported by Meteor 1.4.
|
||||
|
||||
Updating your database to 2.6 is generally pretty painless. Please consult the [MongoDB documentation](https://docs.mongodb.com/manual/release-notes/2.6-upgrade/) for details about how to do so.
|
||||
|
||||
> As of 1.4, you must ensure your `MONGO_OPLOG_URL` contains a `replicaSet` argument (see [the changelog](https://github.com/meteor/meteor/blob/devel/History.md#v14) and [the oplog documentation](https://github.com/meteor/docs/blob/master/long-form/oplog-observe-driver.md#oplogobservedriver-in-production)).
|
||||
|
||||
> NOTE: Some MongoDB hosting providers may have a deployment setup that doesn't require you to use a `replicaSet` argument. For example, [Compose.io](https://www.compose.io/) has two types of deployments, MongoDB Classic and MongoDB+. The new MongoDB+ offering is a sharded setup and not a true replica set (despite the shard being implemented as a replica set) so it does not require the `replicaSet` parameter and Meteor will throw an error if you add it to your connection strings.
|
||||
|
||||
> If you see a failed authentication you may need to upgrade to [SCRAM-SHA-1](https://docs.mongodb.com/manual/release-notes/3.0-scram/#upgrade-mongodb-cr-to-scram), essentially: `use admin, db.adminCommand({authSchemaUpgrade: 1});`. You may need to delete and re-add your oplog reader user.
|
||||
|
||||
<h3 id="debugger">Remove debugger statements</h3>
|
||||
Due to changes in Node 4, if you have `debugger` statements in your code they will now hit the breakpoint even without a debugger attached. This also means you can now debug without using the `--debug-brk` option.
|
||||
|
||||
<h3 id="tokens-expire">Password reset and enrollment tokens now expire</h3>
|
||||
Password Reset tokens now expire (after 3 days by default -- can be modified via Accounts.config({ passwordResetTokenExpirationInDays: ...}). [PR #7534](https://github.com/meteor/meteor/pull/7534)
|
||||
|
||||
See [PR #7794](https://github.com/meteor/meteor/issues/7794) for infomation about splitting reset
|
||||
vs enrollment tokens and allowing different expiration times.
|
||||
|
||||
<h2 id="recommendations">Recommendations</h2>
|
||||
|
||||
<h3 id="update-to-1_3_5_1-first">Update to Meteor 1.3.5.1 first</h3>
|
||||
|
||||
Though not mandatory, it may be helpful to update your apps to Meteor 1.3.5.1 before updating to 1.4, since 1.3.5.1 is the most recent release before 1.4, and contains much of the same code as 1.4. To update an app to 1.3.5.1, run `meteor update --release 1.3.5.1` in the app directory. When you are confident the app is working correctly, `meteor update` will take you all the way to Meteor 1.4.
|
||||
|
||||
<h3 id="update-to-mongo-3_2">Update to MongoDB 3.2</h3>
|
||||
|
||||
Although Meteor 1.4 supports MongoDB 2.6 and up, as well as the older MMAPv1 storage engine, we recommend you update your database to use the new WiredTiger storage engine and use MongoDB 3.2.
|
||||
|
||||
To update your production database to version 3.2 you should follow the steps listed in the [MongoDB documentation](https://docs.mongodb.com/manual/release-notes/3.2-upgrade/). To update your storage engine, you should ensure you follow the ["Change Storage Engine to WiredTiger"](https://docs.mongodb.com/v3.0/release-notes/3.0-upgrade/#change-storage-engine-to-wiredtiger) instructions in the 3.0 upgrade documentation.
|
||||
|
||||
If you are using OS X or 64bit Linux, you can update your development database in a similar way (if you are running `meteor` as usual, you can connect to the development database at `localhost:3001/meteor`). However, if you are not concerned about the data in your development database, the easiest thing to do is to remove all local data (including your development database) with `meteor reset`. When you next start `meteor`, the database will be recreated with a 3.2 WiredTiger engine.
|
||||
|
||||
If you are using Windows or 32bit Linux, you can update your development database to 3.2, however it will continue to use the MMAPv1 storage engine, as the 32bit MongoDB binary does not support WiredTiger.
|
||||
|
||||
<h3 id="nested-imports">Use Nested Imports</h3>
|
||||
|
||||
Thanks to the use of the [reify](https://www.npmjs.com/package/reify) library, Meteor now fully supports nested `import` declarations in both application and package modules, whereas previously they were only allowed in application code:
|
||||
|
||||
```js
|
||||
if (Meteor.isClient) {
|
||||
import { symbol } from './client-only/file';
|
||||
}
|
||||
```
|
||||
|
||||
One place this is particularly useful is in [test files that are only intended to run on the client or the server](https://github.com/meteor/todos/commit/3963a65d96cd7ef235a95d5e3a331d6f0606f70f) — you can now use `import` wherever you like, without having to organize your tests in `client` or `server` directories.
|
||||
28
content/1.5-migration.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.5
|
||||
description: How to migrate your application to Meteor 1.5.
|
||||
discourseTopicId: 37099
|
||||
---
|
||||
|
||||
This guide is quite short and we think you'll find the upgrade from 1.4 to 1.5 quite painless. We encourage reading [full history](http://docs.meteor.com/changelog.html) and comparing the full differences between the versions you are upgrading from and to.
|
||||
|
||||
> If you find details which are not covered here, please discuss it using the "Discuss" button above, or if you have something super important, open a pull-request to this article using the "Edit on GitHub" button above!
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.4?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.4, there may be important considerations not listed in this guide (which specifically covers 1.4 to 1.5). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
|
||||
<h3 id="mail-url">`MAIL_URL` should be reviewed</h3>
|
||||
|
||||
Due to an upgrade in the underlying dependency for the [`email` package](http://docs.meteor.com/api/email.html), it is necessary to check that your `MAIL_URL` is using the correct scheme (e.g. `smtps://` or `smtp://`).
|
||||
|
||||
Previously, Meteor would automatically assume that any `MAIL_URL` using port 465 was to be encrypted and automatically changed `smtp://` to `smtps://`. However, this is not always desired, and not always a safe assumption for Meteor.
|
||||
|
||||
If your `MAIL_URL` is TLS/SSL-only (and does not need [`STARTTLS`](https://en.wikipedia.org/wiki/Opportunistic_TLS)), be sure that the `MAIL_URL` starts with `smtps://` and not `smtp://`.
|
||||
|
||||
Again, generally speaking, this applies to applications whose `MAIL_URL` already includes `:465`. If an application's mail provider supports `STARTTLS` (i.e. if the `MAIL_URL` uses `:587` and _sometimes_ `:25`), the application can continue to use `smtp://` (without the `s`) and the TLS/SSL upgrade will be made by the mail server, if supported.
|
||||
|
||||
Unfortunately, the e-mail ecosystem is [confusing](http://busylog.net/smtp-tls-ssl-25-465-587/). More information can be found in the [Nodemailer docs](https://nodemailer.com/smtp/).
|
||||
32
content/1.6-migration.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.6
|
||||
description: How to migrate your application to Meteor 1.6.
|
||||
discourseTopicId: 40314
|
||||
---
|
||||
|
||||
Most changes in Meteor 1.6 are related to the underlying Node.js upgrade . We encourage reading [full history](http://docs.meteor.com/changelog.html) and comparing the full differences between the versions you are upgrading from and to.
|
||||
|
||||
> If you find details which are not covered here, please discuss it using the "Discuss" button above. If you find any important details which are not included here, please open a pull-request to this article using the "Edit on GitHub" button above to help other members of the community!
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.5?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.5, there may be important considerations not listed in this guide (which specifically covers 1.5 to 1.6). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
|
||||
<h3 id="node-breaking-changes">Node.js Breaking Changes</h3>
|
||||
|
||||
The most significant update in Meteor 1.6 is the upgrade of the underlying Node.js version which Meteor relies on. While Meteor itself has made the appropriate changes, any core Node.js module usage within applications is subject to the breaking changes outlined by the Node.js change logs below which, when combined, cover the transition from Node.js 4 to 8:
|
||||
|
||||
* [Breaking changes between v4 and v6](https://github.com/nodejs/node/wiki/Breaking-changes-between-v4-LTS-and-v6-LTS).
|
||||
* [Breaking changes between v6 and v7](https://github.com/nodejs/node/wiki/Breaking-changes-between-v6-and-v7).
|
||||
* [Changelog for Node 8](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md).
|
||||
> At the time of writing, the official "_Breaking changes between v6 and v8_" was not yet available from the Node.js Foundation. The "Notable changes" section within this changelog is the best alternative resource.
|
||||
|
||||
<h4 id="node-notable">Node.js Notable Changes</h4>
|
||||
|
||||
While the Node.js change-logs are quite extensive, it is our experience so far that the most common change are the deprecations of the `new Buffer()` and `Buffer()` constructors. See the Node.js [`Buffer` documentation](https://nodejs.org/dist/latest-v8.x/docs/api/buffer.html#buffer_class_buffer) for more information on the correct replacements.
|
||||
|
||||
When reviewing the changelog, pay close attention to any items which are marked as "removed".
|
||||
35
content/1.7-migration.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.7
|
||||
description: How to migrate your application to Meteor 1.7.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.7 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are a few items that require additional attention, which are outlined below.
|
||||
|
||||
<h3 id="babel-updates">Babel / `meteor-node-stubs` updates</h3>
|
||||
|
||||
After updating to Meteor 1.7 or 1.7.0.1, you should update the `@babel/runtime` npm package (as well as other Babel-related packages), and the `meteor-node-stubs` package, to their latest versions.
|
||||
|
||||
```sh
|
||||
meteor npm install @babel/runtime@latest meteor-node-stubs@latest
|
||||
```
|
||||
|
||||
<h3 id="mongo-3.6">Mongo 3.6</h3>
|
||||
|
||||
Mongo has been upgraded to version 3.6.4 for 64-bit systems, and 3.2.19 for 32-bit systems.
|
||||
|
||||
After upgrading an application to use Mongo 3.6.4, it has been observed that attempting to run that application with an older version of Meteor (via `meteor --release X`), that uses an older version of Mongo, can prevent the application from starting. This can be fixed by either running `meteor reset` (**WARNING:** will wipe your local DB), or by repairing the Mongo database. To repair the database, find the mongod binary on your system that lines up with the Meteor release you are jumping back to, and run `mongodb --dbpath your-apps-db --repair`. For example:
|
||||
|
||||
```sh
|
||||
~/.meteor/packages/meteor-tool/1.6.0_1/mt-os.osx.x86_64/dev_bundle/mongodb/bin/mongod --dbpath /my-app/.meteor/local/db --repair
|
||||
```
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.6?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.6, there may be important considerations not listed in this guide (which specifically covers 1.6 to 1.7). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
69
content/1.8-migration.md
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.8
|
||||
description: How to migrate your application to Meteor 1.8.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.8 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is one required migration step and few things that you should note.
|
||||
|
||||
<h3 id="babel-update">Update the `@babel/runtime`</h3>
|
||||
|
||||
Update the `@babel/runtime` npm package to version 7.0.0 or later:
|
||||
|
||||
```sh
|
||||
meteor npm install @babel/runtime@latest
|
||||
```
|
||||
|
||||
<h3 id="legacy-bundle">web.browser.legacy</h3>
|
||||
|
||||
Meteor 1.7 introduced a new client bundle called `web.browser.legacy` in
|
||||
addition to the `web.browser` (modern) and `web.cordova` bundles.
|
||||
Naturally, this extra bundle increased client (re)build times. Since
|
||||
developers spend most of their time testing the modern bundle in
|
||||
development, and the legacy bundle mostly provides a safe fallback in
|
||||
production, Meteor 1.8 cleverly postpones building the legacy bundle
|
||||
until just after the development server restarts, so that development
|
||||
can continue as soon as the modern bundle has finished building. Since
|
||||
the legacy build happens during a time when the build process would
|
||||
otherwise be completely idle, the impact of the legacy build on server
|
||||
performance is minimal. Nevertheless, the legacy bundle still gets
|
||||
rebuilt regularly, so any legacy build errors will be surfaced in a
|
||||
timely fashion, and legacy clients can test the new legacy bundle by
|
||||
waiting a bit longer than modern clients. Applications using the
|
||||
`autoupdate` or `hot-code-push` packages will reload modern and legacy
|
||||
clients independently, once each new bundle becomes available.
|
||||
|
||||
<h3 id="forced-package-version">Overriding package version</h3>
|
||||
|
||||
The `.meteor/packages` file supports a new syntax for overriding
|
||||
problematic version constraints from packages you do not control.
|
||||
|
||||
If a package version constraint in `.meteor/packages` ends with a `!`
|
||||
character, any other (non-`!`) constraints on that package elsewhere in
|
||||
the application will be _weakened_ to allow any version greater than or
|
||||
equal to the constraint, even if the major/minor versions do not match.
|
||||
|
||||
For example, using both CoffeeScript 2 and `practicalmeteor:mocha` used
|
||||
to be impossible (or at least very difficult) because of this
|
||||
[`api.versionsFrom("1.3")`](https://github.com/practicalmeteor/meteor-mocha/blob/3a2658070a920f8846df48bb8d8c7b678b8c6870/package.js#L28)
|
||||
statement, which unfortunately constrained the `coffeescript` package to
|
||||
version 1.x. In Meteor 1.8, if you want to update `coffeescript` to
|
||||
2.x, you can relax the `practicalmeteor:mocha` constraint by putting
|
||||
```
|
||||
coffeescript@2.2.1_1! # note the !
|
||||
```
|
||||
in your `.meteor/packages` file. The `coffeescript` version still needs
|
||||
to be at least 1.x, so that `practicalmeteor:mocha` can count on that
|
||||
minimum. However, `practicalmeteor:mocha` will no longer constrain the
|
||||
major version of `coffeescript`, so `coffeescript@2.2.1_1` will work.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.7?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.7, there may be important considerations not listed in this guide (which specifically covers 1.7 to 1.8). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
59
content/1.8.2-migration.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.8.2
|
||||
description: How to migrate your application to Meteor 1.8.2.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.8.2 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are required migration steps that you should perform for this release to run smoothly.
|
||||
|
||||
<h3 id="babel-update">Update the `@babel/runtime`</h3>
|
||||
|
||||
Be sure to update the `@babel/runtime` npm package to its latest version
|
||||
(currently 7.7.2):
|
||||
|
||||
```sh
|
||||
meteor npm install @babel/runtime@latest
|
||||
```
|
||||
|
||||
<h3 id="meteor-node-stubs">Meteor Node Stubs</h3>
|
||||
|
||||
New Meteor applications now depend on `meteor-node-stubs@1.0.0`, so it
|
||||
may be a good idea to update to the same major version:
|
||||
|
||||
```sh
|
||||
meteor npm install meteor-node-stubs@next
|
||||
```
|
||||
|
||||
<h3 id="packages-republish">Packages should be re-published</h3>
|
||||
|
||||
If you are the author of any Meteor packages, and you encounter errors
|
||||
when using those packages in a Meteor 1.8.2 application (for example,
|
||||
`module.watch` being undefined), we recommend that you bump the minor
|
||||
version of your package and republish it using Meteor 1.8.2, so
|
||||
Meteor 1.8.2 applications will automatically use the new version of the
|
||||
package, as compiled by Meteor 1.8.2:
|
||||
|
||||
```sh
|
||||
cd path/to/your/package
|
||||
# Add api.versionsFrom("1.8.2") to Package.onUse in package.js...
|
||||
meteor --release 1.8.2 publish
|
||||
```
|
||||
|
||||
This may not be necessary for all packages, especially those that have
|
||||
been recently republished using Meteor 1.8.1, or local packages in the
|
||||
`packages/` directory (which are always recompiled from source).
|
||||
However, republishing packages is a general solution to a wide variety
|
||||
of package versioning and compilation problems, and package authors can
|
||||
make their users' lives easier by handling these issues proactively.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.8?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.8, there may be important considerations not listed in this guide (which specifically covers 1.8 to 1.8.2). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
35
content/1.8.3-migration.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.8.3
|
||||
description: How to migrate your application to Meteor 1.8.3.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.8.3 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is a required migration steps for those that use Blaze or jQuery.
|
||||
|
||||
<h3 id="npm-jquery">Use NPM jQuery</h3>
|
||||
|
||||
If your application uses `blaze-html-templates`, the Meteor `jquery`
|
||||
package will be automatically installed in your `.meteor/packages` file
|
||||
when you update to Meteor 1.8.3. However, this new version of the Meteor
|
||||
`jquery` package no longer bundles its own copy of the `jquery` npm
|
||||
implementation, so you may need to install `jquery` from npm by running
|
||||
|
||||
```sh
|
||||
meteor npm i jquery
|
||||
```
|
||||
|
||||
in your application directory. Symptoms of not installing jquery include
|
||||
a blank browser window, with helpful error messages in the console.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.8.2?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.8.2, there may be important considerations not listed in this guide (which specifically covers 1.8.2 to 1.8.3). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
27
content/1.9-migration.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.9
|
||||
description: How to migrate your application to Meteor 1.9.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.9 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is a major breaking change that you should note.
|
||||
|
||||
<h3 id="no-32bit-version">Discontinuation of 32-bit Unix versions</h3>
|
||||
|
||||
Because Node.js 12 no longer supports 32-bit Linux, Meteor 1.9 has also
|
||||
dropped support for 32-bit Linux. In other words, Meteor 1.9 supports
|
||||
64-bit Mac, Windows, and Linux, as well as 32-bit Windows.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.8.3?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.8.3, there may be important considerations not listed in this guide (which specifically covers 1.8.3 to 1.9). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
26
content/1.9.3-migration.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Migrating to Meteor 1.9.3
|
||||
description: How to migrate your application to Meteor 1.9.3.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 1.9.3 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is a fix to an error that you might get to note.
|
||||
|
||||
<h3 id="mongo-retry-writers">MongoError unsupported retryable writes</h3>
|
||||
|
||||
If you get the error `MongoError: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.`, append `retryWrites=false` to your MongoDB connection string.
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.9?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.9, there may be important considerations not listed in this guide (which specifically covers 1.9 to 1.9.3). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.8.2](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
37
content/2.0-migration.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Migrating to Meteor 2.0
|
||||
description: How to migrate your application to Meteor 2.0.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 2.0 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there is a thing to note.
|
||||
|
||||
<h3 id="hmr">Hot Module Replacement</h3>
|
||||
|
||||
Updates the javascript modules in a running app that were modified during a rebuild. Reduces the feedback cycle while developing so you can view and test changes quicker (it even updates the app before the build has finished). Enabled by adding the `hot-module-replacement` package to an app. React components are automatically updated by default using React Fast Refresh. Integrations with other libraries and view layers can be provided by third party packages. Support for Blaze is coming soon. This first version supports app code in the modern web architecture. ([docs](https://guide.meteor.com/build-tool.html#hot-module-replacement)) [#11117](https://github.com/meteor/meteor/pull/11117)
|
||||
|
||||
<h3 id="free-cloud">Free tier for Meteor Cloud is back</h3>
|
||||
|
||||
Free deploy on [Cloud](https://www.meteor.com/cloud): Deploy for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy))
|
||||
|
||||
Deploy including MongoDB on [Cloud](https://www.meteor.com/cloud): Deploy including MongoDB in a shared instance for free to Cloud with one command: `meteor deploy myapp.meteorapp.com --free --mongo`. ([docs](https://docs.meteor.com/commandline.html#meteordeploy))
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 1.12?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 1.12, there may be important considerations not listed in this guide (which specifically covers 1.12 to 2.0). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11)
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
44
content/2.2-migration.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Migrating to Meteor 2.2
|
||||
description: How to migrate your application to Meteor 2.2.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 2.2 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are a few breaking changes that you might need to apply migration for.
|
||||
|
||||
<h3 id="mongodb-windows">Running MongoDB on Windows</h3>
|
||||
|
||||
`meteor-tool` has been updated and you might need to install the new Visual C++ Redistributable for Visual Studio 2019 to run MongoDB 4.4.4 on Windows. [read more](https://docs.meteor.com/windows.html)
|
||||
|
||||
<h3 id="mongodb-useUnifiedTopology">MongoDB `useUnifiedTopology`</h3>
|
||||
|
||||
`mongo` package is now using `useUnifiedTopology` as `true` by default otherwise the new driver was producing a warning (see details below). It's important to test your app with this change.
|
||||
|
||||
<h3 id="cordova-10">Cordova 10</h3>
|
||||
|
||||
`cordova` plugins and main libraries were updated from 9 to 10. It's important to test your app with these changes.
|
||||
|
||||
<h3 id="typescript-4.2.2">Typescript 4.2.2</h3>
|
||||
|
||||
`typescript` was updated to 4.2.2, make sure your read the [breaking changes](https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/#breaking-changes).
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 2.0?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 2.0, there may be important considerations not listed in this guide (which specifically covers 2.0 to 2.2). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12)
|
||||
* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11)
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
91
content/2.3-migration.md
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Migrating to Meteor 2.3
|
||||
description: How to migrate your application to Meteor 2.3.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 2.3 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are a few breaking changes that you might need to apply migration for.
|
||||
|
||||
<h3 id="node-14">Node.js v14</h3>
|
||||
|
||||
As Node.js version was upgraded to a new major version we recommend that you review if your npm dependencies are compatible with Node.js 14.
|
||||
|
||||
- If we receive reports from breaking changes we are going to list them here but so far we are not aware of any.
|
||||
- We recommend that you read Node.js [release notes](https://nodejs.org/en/blog/release/v14.0.0/) though.
|
||||
- We recommend that you remove your `node_modules` folder (`rm -rf node_modules`) and run `meteor npm i` to be sure you compile all the binary dependencies again using the new Node.js version.
|
||||
- Maybe you also want to recreate your lock file.
|
||||
- If you get an error try `meteor reset` which will clear caches, beware that this will also remove your local DB for your app.
|
||||
|
||||
<h3 id="packages-deprecated-flag">`deprecated` option for packages</h3>
|
||||
|
||||
In `Package.description`, there is a new `deprecated` option. If set to `true` it will inform user when installing that the package has been deprecated. Additionally you can provide a string that will be displayed, where you can direct the users where to go next.
|
||||
|
||||
All official packages that have been deprecated have now the deprecated flag and will inform you about that if you install or update them.
|
||||
|
||||
<h3 id="removed-deprecated-package-methods">Removal of deprecated package API</h3>
|
||||
|
||||
Old API for packages definitions has been removed. The old underscore method names (`Package.on_use`, `Package.on_test`, `Package._transitional_registerBuildPlugin` and `api.add_files`) have been removed and will no longer work, please use the camel case method names (e.g. `api.addFiles()`).
|
||||
|
||||
<h3 id="accounts-2.0">Accounts 2.0</h3>
|
||||
|
||||
* `accounts-base@2.0.0`
|
||||
- Deprecated backward compatibility function `logoutOtherClients` has been removed.
|
||||
|
||||
* `accounts-password@2.0.0`
|
||||
- Deprecated backward compatibility functionality for `SRP` passwords from pre-Meteor 1.0 days has been removed.
|
||||
- Enroll account workflow has been separated from reset password workflow (the enrollment token records are now stored in a separate db field `services.password.enroll`).
|
||||
|
||||
* `oauth@2.0.0`
|
||||
- Removed deprecated `OAuth.initiateLogin` and other functionality like the addition of `?close` in return URI for deprecated OAuth flow pre Meteor 1.0
|
||||
|
||||
If you are maintaining a package that depends on one of the accounts packages which had a major version bump you will either need to set the new version manually or set `api.versionsFrom('2.3')`.
|
||||
You can also have it reference its current version and 2.3 like this: `api.versionsFrom(['1.12', '2.3'])`, for specific package it can be like this: `api.use('accounts-base@1.0.1 || 2.0.0')`.
|
||||
|
||||
<h3 id="http-2">HTTP v2</h3>
|
||||
|
||||
Internally `http` package has replaced the use of http for [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), should still work as previous version, but edge cases might be different. This is to aid you in transition to fetch. Note that this means that the `npmRequestOptions` parameter to `HTTP.call` has been removed, as `request` is no longer used internally. You should migrate to the use of `fetch`. You can install polyfill package via:
|
||||
|
||||
```sh
|
||||
meteor add fetch
|
||||
```
|
||||
|
||||
<h3 id="removal-of-deprecated-api">Removal of deprecated APIs</h3>
|
||||
|
||||
In addition to the above mentioned removal of deprecated package API, other long deprecated APIs have been removed and will no longer work.
|
||||
|
||||
* Removed deprecated `mobile-port` flag
|
||||
* Removed deprecated `raw` name from `isobuild`
|
||||
* `ddp-client@2.5.0`
|
||||
- Removed deprecated backward compatibility method names for Meteor before 1.0
|
||||
* `ddp-server@2.4.0`
|
||||
- Removed deprecated backward compatibility method names for Meteor before 1.0
|
||||
* `meteor-base@1.5.0`
|
||||
- Removed `livedata` dependency which was there for packages build for 0.9.0
|
||||
* `minimongo@1.7.0`
|
||||
- Removed the `rewind` method that was noop for compatibility with Meteor 0.8.1
|
||||
* `mongo@1.12.0`
|
||||
- Removed the `rewind` method that was noop for compatibility with Meteor 0.8.1
|
||||
* `socket-stream-client@0.4.0`
|
||||
- Remove IE8 checks
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 2.2?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 2.2, there may be important considerations not listed in this guide (which specifically covers 2.2 to 2.3). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 2.2](2.2-migration.html) (from 2.0)
|
||||
* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12)
|
||||
* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11)
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
49
content/2.4-migration.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Migrating to Meteor 2.4
|
||||
description: How to migrate your application to Meteor 2.4.
|
||||
---
|
||||
|
||||
Most of the new features in Meteor 2.4 are either applied directly behind the scenes (in a backwards compatible manner) or are opt-in. For a complete breakdown of the changes, please refer to the [changelog](http://docs.meteor.com/changelog.html).
|
||||
|
||||
The above being said, there are a few items that you should implement to have easier time in the future.
|
||||
|
||||
<h3 id="createIndex">createIndex</h3>
|
||||
|
||||
Previously undocumented `_ensureIndex` has been aligned with MongoDB breaking change in naming and is now usable as `createIndex`. Use of `_ensureIndex` is now deprecated and will throw a warning in development for you.
|
||||
|
||||
<h3 id="email22">Email 2.2</h3>
|
||||
|
||||
The `email` package had a feature update. You can now override the sending functionality completely with `Email.customTransport` or if you are using [known services](https://nodemailer.com/smtp/well-known/) you can now ditch the `MAIL_URL` environment variable and set it in your `settings.json` file, like so:
|
||||
```json
|
||||
{
|
||||
"packages": {
|
||||
"email": {
|
||||
"service": "Mailgun",
|
||||
"user": "postmaster@meteor.com",
|
||||
"password": "superDuperPassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="older-versions">Migrating from a version older than 2.3?</h2>
|
||||
|
||||
If you're migrating from a version of Meteor older than Meteor 2.3, there may be important considerations not listed in this guide (which specifically covers 2.2 to 2.3). Please review the older migration guides for details:
|
||||
|
||||
* [Migrating to Meteor 2.3](2.3-migration.html) (from 2.2)
|
||||
* [Migrating to Meteor 2.2](2.2-migration.html) (from 2.0)
|
||||
* [Migrating to Meteor 2.0](2.0-migration.html) (from 1.12)
|
||||
* [Migrating to Meteor 1.12](1.12-migration.html) (from 1.11)
|
||||
* [Migrating to Meteor 1.11](1.11-migration.html) (from 1.10.2)
|
||||
* [Migrating to Meteor 1.10.2](1.10.2-migration.html) (from 1.10)
|
||||
* [Migrating to Meteor 1.10](1.10-migration.html) (from 1.9.3)
|
||||
* [Migrating to Meteor 1.9.3](1.9.3-migration.html) (from 1.9)
|
||||
* [Migrating to Meteor 1.9](1.9-migration.html) (from 1.8.3)
|
||||
* [Migrating to Meteor 1.8.3](1.8.3-migration.html) (from 1.8.2)
|
||||
* [Migrating to Meteor 1.8.2](1.8.2-migration.html) (from 1.8)
|
||||
* [Migrating to Meteor 1.8](1.8-migration.html) (from 1.7)
|
||||
* [Migrating to Meteor 1.7](1.7-migration.html) (from 1.6)
|
||||
* [Migrating to Meteor 1.6](1.6-migration.html) (from 1.5)
|
||||
* [Migrating to Meteor 1.5](1.5-migration.html) (from 1.4)
|
||||
* [Migrating to Meteor 1.4](1.4-migration.html) (from 1.3)
|
||||
* [Migrating to Meteor 1.3](1.3-migration.html) (from 1.2)
|
||||
1
content/CHANGELOG.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../CHANGELOG.md
|
||||
1
content/CONTRIBUTING.md
Symbolic link
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
766
content/accounts.md
Normal file
@@ -0,0 +1,766 @@
|
||||
---
|
||||
title: Users and Accounts
|
||||
description: How to build user login functionality into a Meteor app. Let your users log in with passwords, Facebook, Google, GitHub, and more.
|
||||
discourseTopicId: 19664
|
||||
---
|
||||
|
||||
After reading this article, you'll know:
|
||||
|
||||
1. What features in core Meteor enable user accounts
|
||||
1. How to use accounts-ui for a quick prototype
|
||||
1. How to use the useraccounts family of packages to build your login UI
|
||||
1. How to build a fully-featured password login experience
|
||||
1. How to enable login through OAuth providers like Facebook
|
||||
1. How to add custom data to Meteor's users collection
|
||||
1. How to manage user roles and permissions
|
||||
|
||||
<h2 id="core-meteor">Features in core Meteor</h2>
|
||||
|
||||
Before we get into all of the different user-facing accounts functionality you can add with Meteor, let's go over some of the features built into the Meteor DDP protocol and `accounts-base` package. These are the parts of Meteor that you'll definitely need to be aware of if you have any user accounts in your app; most of everything else is optional and added/removed via packages.
|
||||
|
||||
<h3 id="userid-ddp">userId in DDP</h3>
|
||||
|
||||
DDP is Meteor's built-in pub/sub and RPC protocol. You can read about how to use it in the [Data Loading](data-loading.html) and [Methods](methods.html) articles. In addition to the concepts of data loading and method calls, DDP has one more feature built in - the idea of a `userId` field on a connection. This is the place where login state is tracked, regardless of which accounts UI package or login service you are using.
|
||||
|
||||
This built-in feature means that you always get `this.userId` inside Methods and Publications, and can access the user ID on the client. This is a great starting point for building your own custom accounts system, but most developers won't need to worry about the mechanics, since you'll mostly be interacting with the `accounts-base` package instead.
|
||||
|
||||
<h3 id="accounts-base">`accounts-base`</h3>
|
||||
|
||||
This package is the core of Meteor's developer-facing user accounts functionality. This includes:
|
||||
|
||||
1. A users collection with a standard schema, accessed through [`Meteor.users`](http://docs.meteor.com/#/full/meteor_users), and the client-side singletons [`Meteor.userId()`](http://docs.meteor.com/#/full/meteor_userid) and [`Meteor.user()`](http://docs.meteor.com/#/full/meteor_user), which represent the login state on the client.
|
||||
2. A variety of helpful other generic methods to keep track of login state, log out, validate users, etc. Visit the [Accounts section of the docs](http://docs.meteor.com/#/full/accounts_api) to find a complete list.
|
||||
3. An API for registering new login handlers, which is used by all of the other accounts packages to integrate with the accounts system. There isn't any official documentation for this API, but you can [read more about it in a blog post](https://dev.to/storytellercz/extending-meteor-accounts-login-system-5h5g).
|
||||
|
||||
Usually, you don't need to include `accounts-base` yourself since it's added for you if you use `accounts-password` or similar, but it's good to be aware of what is what.
|
||||
|
||||
<h2 id="accounts-ui">Fast prototyping with `accounts-ui`</h2>
|
||||
|
||||
Often, a complicated accounts system is not the first thing you want to build when you're starting out with a new app, so it's useful to have something you can drop in quickly. This is where `accounts-ui` comes in - it's one line that you drop into your app to get an accounts system. To add it:
|
||||
|
||||
```js
|
||||
meteor add accounts-ui
|
||||
```
|
||||
|
||||
Then include it anywhere in a Blaze template:
|
||||
|
||||
```html
|
||||
{{> loginButtons}}
|
||||
```
|
||||
|
||||
Then, make sure to pick a login provider; they will automatically integrate with `accounts-ui`:
|
||||
|
||||
```sh
|
||||
# pick one or more of the below
|
||||
meteor add accounts-password
|
||||
meteor add accounts-facebook
|
||||
meteor add accounts-google
|
||||
meteor add accounts-github
|
||||
meteor add accounts-twitter
|
||||
meteor add accounts-meetup
|
||||
meteor add accounts-meteor-developer
|
||||
```
|
||||
|
||||
Now open your app, follow the configuration steps, and you're good to go - if you've done one of our [Meteor tutorials](https://www.meteor.com/developers/tutorials), you've already seen this in action. Of course, in a production application, you probably want a more custom user interface and some logic to have a more tailored UX, but that's why we have the rest of this guide.
|
||||
|
||||
Here are a couple of screenshots of `accounts-ui` so you know what to expect:
|
||||
|
||||
<img src="images/accounts-ui.png">
|
||||
|
||||
<h2 id="useraccounts">Customizable UI: useraccounts</h2>
|
||||
|
||||
Once you've gotten your initial prototype up and running with `accounts-ui`, you'll want to move to something more powerful and configurable so that you can better integrate your login flow with the rest of your app. The [`useraccounts` family of packages](https://github.com/meteor-useraccounts/core/blob/master/Guide.md) is the most powerful set of accounts management UI controls available for Meteor today. If you need even more customization, you can also roll your own system, but it's worth trying `useraccounts` first.
|
||||
|
||||
<h3 id="useraccounts-flexibility">Use any router or UI framework</h3>
|
||||
|
||||
The first thing to understand about `useraccounts` is that the core accounts management logic is independent of the HTML templates and routing packages. This means you can use [`useraccounts:core`](https://atmospherejs.com/useraccounts/core) to build your own set of login templates. Generally, you'll want to pick one login template package and one login routing package. The options for templates include:
|
||||
|
||||
- [`useraccounts:unstyled`](https://atmospherejs.com/useraccounts/unstyled) which lets you bring your own CSS; this one is used in the Todos example app to make the login UI blend seamlessly with the rest of the app.
|
||||
- Pre-built templates for [Bootstrap, Semantic UI, Materialize, and more](https://github.com/meteor-useraccounts/core/blob/master/Guide.md#available-versions). These templates don't come with the actual CSS framework, so you can pick your favorite Bootstrap package, for example.
|
||||
|
||||
While it's optional and the basic functionality will work without it, it's also a good idea to pick a router integration:
|
||||
|
||||
- [Flow Router](https://atmospherejs.com/useraccounts/flow-routing), the router [recommended in this guide](routing.html).
|
||||
- [Iron Router](https://atmospherejs.com/useraccounts/iron-routing), another popular router in the Meteor community.
|
||||
|
||||
In the example app we are using the Flow Router integration with great success. Some of the later sections will cover how to customize the routes and templates to fit your app better.
|
||||
|
||||
<h3 id="useraccounts-drop-in">Drop-in UI without routing</h3>
|
||||
|
||||
If you don't want to configure routing for your login flow, you can drop in a self-managing accounts screen. Wherever you want the accounts UI template to render, include the `atForm` template, like so:
|
||||
|
||||
```html
|
||||
{{> atForm}}
|
||||
```
|
||||
|
||||
Once you configure routing according to [the section below](#useraccounts-customizing-routes), you'll want to remove this inclusion.
|
||||
|
||||
<h3 id="useraccounts-customizing-templates">Customizing templates</h3>
|
||||
|
||||
For some apps, the off-the-shelf login templates provided by the various `useraccounts` UI packages will work as-is, but most apps will want to customize some of the presentation. There's a way to do that using the template replacement functionality of the `aldeed:template-extension` package.
|
||||
|
||||
First, figure out which template you want to replace by looking at the source code of the package. For example, in the `useraccounts:unstyled` package, the templates are listed [in this directory on GitHub](https://github.com/meteor-useraccounts/unstyled/tree/master/lib). By squinting at the file names and looking for some of the HTML strings, we can figure out that we might be interested in replacing the `atPwdFormBtn` template. Let's take a look at the original template:
|
||||
|
||||
```html
|
||||
<template name="atPwdFormBtn">
|
||||
<button type="submit" class="at-btn submit {{submitDisabled}}" id="at-btn">
|
||||
{{buttonText}}
|
||||
</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
Once you've identified which template you need to replace, define a new template. In this case, we want to modify the class on the button to work with the CSS for the rest of the app. There are a few things to keep in mind when overriding a template:
|
||||
|
||||
1. Render the helpers in the same way the previous template did. In this case we are using `buttonText`.
|
||||
2. Keep any `id` attributes, like `at-btn`, since those are used for event handling.
|
||||
|
||||
Here's what our new override template looks like:
|
||||
|
||||
```html
|
||||
<template name="override-atPwdFormBtn">
|
||||
<button type="submit" class="btn-primary" id="at-btn">
|
||||
{{buttonText}}
|
||||
</button>
|
||||
</template>
|
||||
```
|
||||
|
||||
Then, use the `replaces` function on the template to override the existing template from `useraccounts`:
|
||||
|
||||
```js
|
||||
Template['override-atPwdFormBtn'].replaces('atPwdFormBtn');
|
||||
```
|
||||
|
||||
<h3 id="useraccounts-customizing-routes">Customizing routes</h3>
|
||||
|
||||
In addition to having control over the templates, you'll want to be able to control the routing and URLs for the different views offered by `useraccounts`. Since Flow Router is the officially recommended routing option for Meteor, we'll go over that in particular.
|
||||
|
||||
First, we need to configure the layout we want to use when rendering the accounts templates:
|
||||
|
||||
```js
|
||||
AccountsTemplates.configure({
|
||||
defaultTemplate: 'Auth_page',
|
||||
defaultLayout: 'App_body',
|
||||
defaultContentRegion: 'main',
|
||||
defaultLayoutRegions: {}
|
||||
});
|
||||
```
|
||||
|
||||
In this case, we want to use the `App_body` layout template for all of the accounts-related pages. This template has a content region called `main`. Now, let's configure some routes:
|
||||
|
||||
```js
|
||||
// Define these routes in a file loaded on both client and server
|
||||
AccountsTemplates.configureRoute('signIn', {
|
||||
name: 'signin',
|
||||
path: '/signin'
|
||||
});
|
||||
|
||||
AccountsTemplates.configureRoute('signUp', {
|
||||
name: 'join',
|
||||
path: '/join'
|
||||
});
|
||||
|
||||
AccountsTemplates.configureRoute('forgotPwd');
|
||||
|
||||
AccountsTemplates.configureRoute('resetPwd', {
|
||||
name: 'resetPwd',
|
||||
path: '/reset-password'
|
||||
});
|
||||
```
|
||||
|
||||
Note that we have specified a password reset route. Normally, we would have to configure Meteor's accounts system to send this route in password reset emails, but the `useraccounts:flow-routing` package does it for us. [Read more about configuring email flows below.](#email-flows)
|
||||
|
||||
Now that the routes are setup on the server, they can be accessed from the browser (e.g. `example.com/reset-password`). To create links to these routes in a template, it's best to use a helper method provided by the router. For Flow Router, the [`ostrio:flow-router-extra`](https://atmospherejs.com/ostrio/flow-router-extra/) package provides a `pathFor` helper for just this purpose. Once installed, the following is possible in a template:
|
||||
|
||||
```html
|
||||
<div class="btns-group">
|
||||
<a href="{{pathFor 'signin'}}" class="btn-secondary">Sign In</a>
|
||||
<a href="{{pathFor 'join'}}" class="btn-secondary">Join</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
You can find a complete list of different available routes in the [documentation the `useraccounts:flow-routing`](https://github.com/meteor-useraccounts/flow-routing#routes).
|
||||
|
||||
<h3 id="useraccounts-further-customization">Further customization</h3>
|
||||
|
||||
`useraccounts` offers many other customization options beyond templates and routing. Read the [`useraccounts` guide](https://github.com/meteor-useraccounts/core/blob/master/Guide.md) to learn about all of the other options.
|
||||
|
||||
<h2 id="accounts-password">Password login</h2>
|
||||
|
||||
Meteor comes with a secure and fully-featured password login system out of the box. To use it, add the package:
|
||||
|
||||
```sh
|
||||
meteor add accounts-password
|
||||
```
|
||||
|
||||
To see what options are available to you, read the complete description of the [`accounts-password` API in the Meteor docs](http://docs.meteor.com/#/full/accounts_passwords).
|
||||
|
||||
<h3 id="requiring-username-email">Requiring username or email</h3>
|
||||
|
||||
> Note: You don't have to do this if you're using `useraccounts`. It disables the regular Meteor client-side account creation functions for you and does custom validation.
|
||||
|
||||
By default, the `Accounts.createUser` function provided by `accounts-password` allows you to create an account with a username, email, or both. Most apps expect a specific combination of the two, so you will certainly want to validate the new user creation:
|
||||
|
||||
```js
|
||||
// Ensuring every user has an email address, should be in server-side code
|
||||
Accounts.validateNewUser((user) => {
|
||||
new SimpleSchema({
|
||||
_id: { type: String },
|
||||
emails: { type: Array },
|
||||
'emails.$': { type: Object },
|
||||
'emails.$.address': { type: String },
|
||||
'emails.$.verified': { type: Boolean },
|
||||
createdAt: { type: Date },
|
||||
services: { type: Object, blackbox: true }
|
||||
}).validate(user);
|
||||
|
||||
// Return true to allow user creation to proceed
|
||||
return true;
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="multiple-emails">Multiple emails</h3>
|
||||
|
||||
Often, users might want to associate multiple email addresses with the same account. `accounts-password` addresses this case by storing the email addresses as an array in the user collection. There are some handy API methods to deal with [adding](http://docs.meteor.com/api/passwords.html#Accounts-addEmail), [removing](http://docs.meteor.com/api/passwords.html#Accounts-removeEmail), and [verifying](http://docs.meteor.com/api/passwords.html#Accounts-verifyEmail) emails.
|
||||
|
||||
One useful thing to add for your app can be the concept of a "primary" email address. This way, if the user has added multiple emails, you know where to send confirmation emails and similar.
|
||||
|
||||
<h3 id="case-sensitivity">Case sensitivity</h3>
|
||||
|
||||
Before Meteor 1.2, all email addresses and usernames in the database were considered to be case-sensitive. This meant that if you registered an account as `AdaLovelace@example.com`, and then tried to log in with `adalovelace@example.com`, you'd see an error indicating that no user with that email exists. Of course, this can be quite confusing, so we decided to improve things in Meteor 1.2. But the situation was not as simple as it seemed; since MongoDB doesn't have a concept of case-insensitive indexes, it was impossible to guarantee unique emails at the database level. For this reason, we have some special APIs for querying and updating users which manage the case-sensitivity problem at the application level.
|
||||
|
||||
<h4 id="case-sensitivity-in-my-app">What does this mean for my app?</h4>
|
||||
|
||||
Follow one rule: don't query the database by `username` or `email` directly. Instead, use the [`Accounts.findUserByUsername`](http://docs.meteor.com/api/passwords.html#Accounts-findUserByUsername) and [`Accounts.findUserByEmail`](http://docs.meteor.com/api/passwords.html#Accounts-findUserByEmail) methods provided by Meteor. This will run a query for you that is case-insensitive, so you will always find the user you are looking for.
|
||||
|
||||
<h3 id="email-flows">Email flows</h3>
|
||||
|
||||
When you have a login system for your app based on user emails, that opens up the possibility for email-based account flows. The common thing between all of these workflows is that they involve sending a unique link to the user's email address, which does something special when it is clicked. Let's look at some common examples that Meteor's `accounts-password` package supports out of the box:
|
||||
|
||||
1. **Password reset.** When the user clicks the link in their email, they are taken to a page where they can enter a new password for their account.
|
||||
1. **User enrollment.** A new user is created by an administrator, but no password is set. When the user clicks the link in their email, they are taken to a page where they can set a new password for their account. Very similar to password reset.
|
||||
1. **Email verification.** When the user clicks the link in their email, the application records that this email does indeed belong to the correct user.
|
||||
|
||||
Here, we'll talk about how to manage the whole process manually from start to finish.
|
||||
|
||||
<h4 id="default-email-flow">Email works out of the box with accounts UI packages</h4>
|
||||
|
||||
If you want something that works out of the box, you can use `accounts-ui` or `useraccounts` which basically do everything for you. Only follow the directions below if you definitely want to build all parts of the email flow yourself.
|
||||
|
||||
<h4 id="sending-email">Sending the email</h4>
|
||||
|
||||
`accounts-password` comes with handy functions that you can call from the server to send an email. They are named for exactly what they do:
|
||||
|
||||
1. [`Accounts.sendResetPasswordEmail`](http://docs.meteor.com/#/full/accounts_sendresetpasswordemail)
|
||||
2. [`Accounts.sendEnrollmentEmail`](http://docs.meteor.com/#/full/accounts_sendenrollmentemail)
|
||||
3. [`Accounts.sendVerificationEmail`](http://docs.meteor.com/#/full/accounts_sendverificationemail)
|
||||
|
||||
The email is generated using the email templates from [Accounts.emailTemplates](http://docs.meteor.com/#/full/accounts_emailtemplates), and include links generated with `Accounts.urls`. We'll go into more detail about customizing the email content and URL later.
|
||||
|
||||
<h4 id="identifying-link-click">Identifying when the link is clicked</h4>
|
||||
|
||||
When the user receives the email and clicks the link inside, their web browser will take them to your app. Now, you need to be able to identify these special links and act appropriately. If you haven't customized the link URL, then you can use some built-in callbacks to identify when the app is in the middle of an email flow.
|
||||
|
||||
Normally, when the Meteor client connects to the server, the first thing it does is pass the _login resume token_ to re-establish a previous login. However, when these callbacks from the email flow are triggered, the resume token is not sent until your code signals that it has finished handling the request by calling the `done` function that is passed into the registered callback. This means that if you were previously logged in as user A, and then you clicked the reset password link for user B, but then you cancelled the password reset flow by calling `done()`, the client would log in as A again.
|
||||
|
||||
1. [`Accounts.onResetPasswordLink`](http://docs.meteor.com/#/full/Accounts-onResetPasswordLink)
|
||||
2. [`Accounts.onEnrollmentLink`](http://docs.meteor.com/#/full/Accounts-onEnrollmentLink)
|
||||
3. [`Accounts.onEmailVerificationLink`](http://docs.meteor.com/#/full/Accounts-onEmailVerificationLink)
|
||||
|
||||
Here's how you would use one of these functions:
|
||||
|
||||
```js
|
||||
Accounts.onResetPasswordLink((token, done) => {
|
||||
// Display the password reset UI, get the new password...
|
||||
|
||||
Accounts.resetPassword(token, newPassword, (err) => {
|
||||
if (err) {
|
||||
// Display error
|
||||
} else {
|
||||
// Resume normal operation
|
||||
done();
|
||||
}
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
If you want a different URL for your reset password page, you need to customize it using the `Accounts.urls` option:
|
||||
|
||||
```js
|
||||
Accounts.urls.resetPassword = (token) => {
|
||||
return Meteor.absoluteUrl(`reset-password/${token}`);
|
||||
};
|
||||
```
|
||||
|
||||
If you have customized the URL, you will need to add a new route to your router that handles the URL you have specified, and the default `Accounts.onResetPasswordLink` and friends won't work for you.
|
||||
|
||||
<h4 id="completing-email-flow">Displaying an appropriate UI and completing the process</h4>
|
||||
|
||||
Now that you know that the user is attempting to reset their password, set an initial password, or verify their email, you should display an appropriate UI to allow them to do so. For example, you might want to show a page with a form for the user to enter their new password.
|
||||
|
||||
When the user submits the form, you need to call the appropriate function to commit their change to the database. Each of these functions takes the new value and the token you got from the event in the previous step.
|
||||
|
||||
1. [`Accounts.resetPassword`](http://docs.meteor.com/#/full/accounts_resetpassword) - this one should be used both for resetting the password, and enrolling a new user; it accepts both kinds of tokens.
|
||||
2. [`Accounts.verifyEmail`](http://docs.meteor.com/#/full/accounts_verifyemail)
|
||||
|
||||
After you have called one of the two functions above or the user has cancelled the process, call the `done` function you got in the link callback. This will tell Meteor to get out of the special state it enters when you're doing one of the email account flows.
|
||||
|
||||
<h3 id="customizing-emails">Customizing accounts emails</h3>
|
||||
|
||||
You will probably want to customize the emails `accounts-password` will send on your behalf. This can be done through the [`Accounts.emailTemplates` API](http://docs.meteor.com/#/full/accounts_emailtemplates). Below is some example code from the Todos app:
|
||||
|
||||
```js
|
||||
Accounts.emailTemplates.siteName = "Meteor Guide Todos Example";
|
||||
Accounts.emailTemplates.from = "Meteor Todos Accounts <accounts@example.com>";
|
||||
|
||||
Accounts.emailTemplates.resetPassword = {
|
||||
subject(user) {
|
||||
return "Reset your password on Meteor Todos";
|
||||
},
|
||||
text(user, url) {
|
||||
return `Hello!
|
||||
Click the link below to reset your password on Meteor Todos.
|
||||
${url}
|
||||
If you didn't request this email, please ignore it.
|
||||
Thanks,
|
||||
The Meteor Todos team
|
||||
`
|
||||
},
|
||||
html(user, url) {
|
||||
// This is where HTML email content would go.
|
||||
// See the section about html emails below.
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
As you can see, we can use the ES2015 template string functionality to generate a multi-line string that includes the password reset URL. We can also set a custom `from` address and email subject.
|
||||
|
||||
<h4 id="html-emails">HTML emails</h4>
|
||||
|
||||
If you've ever needed to deal with sending pretty HTML emails from an app, you know that it can quickly become a nightmare. Compatibility of popular email clients with basic HTML features like CSS is notoriously spotty, so it is hard to author something that works at all. Start with a [responsive email template](https://github.com/leemunroe/responsive-html-email-template) or [framework](https://get.foundation/emails), and then use a tool to convert your email content into something that is compatible with all email clients. [This blog post by Mailgun covers some of the main issues with HTML email.](http://blog.mailgun.com/transactional-html-email-templates/) In theory, a community package could extend Meteor's build system to do the email compilation for you, but at the time of writing we were not aware of any such packages.
|
||||
|
||||
<h2 id="oauth">OAuth login</h2>
|
||||
|
||||
In the distant past, it could have been a huge headache to get Facebook or Google login to work with your app. Thankfully, most popular login providers have standardized around some version of [OAuth](https://en.wikipedia.org/wiki/OAuth), and Meteor supports some of the most popular login services out of the box.
|
||||
|
||||
<h3 id="supported-login-services">Facebook, Google, and more</h3>
|
||||
|
||||
Here's a complete list of login providers for which Meteor actively maintains core packages:
|
||||
|
||||
1. Facebook with `accounts-facebook`
|
||||
2. Google with `accounts-google`
|
||||
3. GitHub with `accounts-github`
|
||||
4. Twitter with `accounts-twitter`
|
||||
5. Meetup with `accounts-meetup`
|
||||
6. Meteor Developer Accounts with `accounts-meteor-developer`
|
||||
|
||||
There is a package for logging in with Weibo, but it is no longer being actively maintained.
|
||||
|
||||
<h3 id="oauth-logging-in">Logging in</h3>
|
||||
|
||||
If you are using an off-the-shelf login UI like `accounts-ui` or `useraccounts`, you don't need to write any code after adding the relevant package from the list above. If you are building a login experience from scratch, you can log in programmatically using the [`Meteor.loginWith<Service>`](http://docs.meteor.com/#/full/meteor_loginwithexternalservice) function. It looks like this:
|
||||
|
||||
```js
|
||||
Meteor.loginWithFacebook({
|
||||
requestPermissions: ['user_friends', 'public_profile', 'email']
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
// handle error
|
||||
} else {
|
||||
// successful login!
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="oauth-configuration">Configuring OAuth</h3>
|
||||
|
||||
There are a few points to know about configuring OAuth login:
|
||||
|
||||
1. **Client ID and secret.** It's best to keep your OAuth secret keys outside of your source code, and pass them in through Meteor.settings. Read how in the [Security article](security.html#api-keys-oauth).
|
||||
2. **Redirect URL.** On the OAuth provider's side, you'll need to specify a _redirect URL_. The URL will look like: `https://www.example.com/_oauth/facebook`. Replace `facebook` with the name of the service you are using. Note that you will need to configure two URLs - one for your production app, and one for your development environment, where the URL might be something like `http://localhost:3000/_oauth/facebook`.
|
||||
3. **Permissions.** Each login service provider should have documentation about which permissions are available. For example, [here is the page for Facebook](https://developers.facebook.com/docs/facebook-login/permissions). If you want additional permissions to the user's data when they log in, pass some of these strings in the `requestPermissions` option to `Meteor.loginWithFacebook` or [`Accounts.ui.config`](http://docs.meteor.com/#/full/accounts_ui_config). In the next section we'll talk about how to retrieve that data.
|
||||
|
||||
<h3 id="oauth-calling-api">Calling service API for more data</h3>
|
||||
|
||||
If your app supports or even requires login with an external service such as Facebook, it's natural to also want to use that service's API to request additional data about that user. For example, you might want to get a list of a Facebook user's photos.
|
||||
|
||||
First, you'll need to request the relevant permissions when logging in the user. See the [section above](#oauth-configuration) for how to pass those options.
|
||||
|
||||
Then, you need to get the user's access token. You can find this token in the `Meteor.users` collection under the `services` field. For example, if you wanted to get a particular user's Facebook access token:
|
||||
|
||||
```js
|
||||
// Given a userId, get the user's Facebook access token
|
||||
const user = Meteor.users.findOne(userId);
|
||||
const fbAccessToken = user.services.facebook.accessToken;
|
||||
```
|
||||
|
||||
For more details about the data stored in the user database, read the section below about accessing user data.
|
||||
|
||||
Now that you have the access token, you need to actually make a request to the appropriate API. Here you have two options:
|
||||
|
||||
1. Use the [`http` package](http://docs.meteor.com/#/full/http) to access the service's API directly. You'll probably need to pass the access token from above in a header. For details you'll need to search the API documentation for the service.
|
||||
2. Use a package from Atmosphere or npm that wraps the API into a nice JavaScript interface. For example, if you're trying to load data from Facebook you could use the [fbgraph](https://www.npmjs.com/package/fbgraph) npm package. Read more about how to use npm with your app in the [Build System article](build-tool.html#npm).
|
||||
|
||||
<h2 id="displaying-user-data">Loading and displaying user data</h2>
|
||||
|
||||
Meteor's accounts system, as implemented in `accounts-base`, also includes a database collection and generic functions for getting data about users.
|
||||
|
||||
<h3 id="current-user">Currently logged in user</h3>
|
||||
|
||||
Once a user is logged into your app with one of the methods described above, it is useful to be able to identify which user is logged in, and get the data provided during the registration process.
|
||||
|
||||
<h4 id="current-user-client">On the client: Meteor.userId()</h4>
|
||||
|
||||
For code that runs on the client, the global `Meteor.userId()` reactive function will give you the ID of the currently logged in user.
|
||||
|
||||
In addition to that core API, there are some helpful shorthand helpers: `Meteor.user()`, which is exactly equal to calling `Meteor.users.findOne(Meteor.userId())`, and the `{% raw %}{{currentUser}}{% endraw %}` Blaze helper that returns the value of `Meteor.user()`.
|
||||
|
||||
Note that there is a benefit to restricting the places you access the current user to make your UI more testable and modular. Read more about this in the [UI article](ui-ux.html#global-stores).
|
||||
|
||||
<h4 id="current-user-server">On the server: this.userId</h4>
|
||||
|
||||
On the server, each connection has a different logged in user, so there is no global logged-in user state by definition. Since Meteor tracks the environment for each Method call, you can still use the `Meteor.userId()` global, which returns a different value depending on which Method you call it from, but you can run into edge cases when dealing with asynchronous code.
|
||||
|
||||
We suggest using the `this.userId` property on the context of Methods and publications instead, and passing that around through function arguments to wherever you need it.
|
||||
|
||||
```js
|
||||
// Accessing this.userId inside a publication
|
||||
Meteor.publish('lists.private', function() {
|
||||
if (!this.userId) {
|
||||
return this.ready();
|
||||
}
|
||||
|
||||
return Lists.find({
|
||||
userId: this.userId
|
||||
}, {
|
||||
fields: Lists.publicFields
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// Accessing this.userId inside a Method
|
||||
Meteor.methods({
|
||||
'todos.updateText'({ todoId, newText }) {
|
||||
new SimpleSchema({
|
||||
todoId: { type: String },
|
||||
newText: { type: String }
|
||||
}).validate({ todoId, newText }),
|
||||
|
||||
const todo = Todos.findOne(todoId);
|
||||
|
||||
if (!todo.editableBy(this.userId)) {
|
||||
throw new Meteor.Error('todos.updateText.unauthorized',
|
||||
'Cannot edit todos in a private list that is not yours');
|
||||
}
|
||||
|
||||
Todos.update(todoId, {
|
||||
$set: { text: newText }
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="meteor-users-collection">The Meteor.users collection</h3>
|
||||
|
||||
Meteor comes with a default MongoDB collection for user data. It's stored in the database under the name `users`, and is accessible in your code through `Meteor.users`. The schema of a user document in this collection will depend on which login service was used to create the account. Here's an example of a user that created their account with `accounts-password`:
|
||||
|
||||
```js
|
||||
{
|
||||
"_id": "DQnDpEag2kPevSdJY",
|
||||
"createdAt": "2015-12-10T22:34:17.610Z",
|
||||
"services": {
|
||||
"password": {
|
||||
"bcrypt": "XXX"
|
||||
},
|
||||
"resume": {
|
||||
"loginTokens": [
|
||||
{
|
||||
"when": "2015-12-10T22:34:17.615Z",
|
||||
"hashedToken": "XXX"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"emails": [
|
||||
{
|
||||
"address": "ada@lovelace.com",
|
||||
"verified": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Here's what the same user would look like if they instead logged in with Facebook:
|
||||
|
||||
```js
|
||||
{
|
||||
"_id": "Ap85ac4r6Xe3paeAh",
|
||||
"createdAt": "2015-12-10T22:29:46.854Z",
|
||||
"services": {
|
||||
"facebook": {
|
||||
"accessToken": "XXX",
|
||||
"expiresAt": 1454970581716,
|
||||
"id": "XXX",
|
||||
"email": "ada@lovelace.com",
|
||||
"name": "Ada Lovelace",
|
||||
"first_name": "Ada",
|
||||
"last_name": "Lovelace",
|
||||
"link": "https://www.facebook.com/app_scoped_user_id/XXX/",
|
||||
"gender": "female",
|
||||
"locale": "en_US",
|
||||
"age_range": {
|
||||
"min": 21
|
||||
}
|
||||
},
|
||||
"resume": {
|
||||
"loginTokens": [
|
||||
{
|
||||
"when": "2015-12-10T22:29:46.858Z",
|
||||
"hashedToken": "XXX"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"name": "Sashko Stubailo"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the schema is different when users register with different login services. There are a few things to be aware of when dealing with this collection:
|
||||
|
||||
1. User documents in the database have secret data like access keys and hashed passwords. When [publishing user data to the client](#publish-custom-data), be extra careful not to include anything that client shouldn't be able to see.
|
||||
2. DDP, Meteor's data publication protocol, only knows how to resolve conflicts in top-level fields. This means that you can't have one publication send `services.facebook.first_name` and another send `services.facebook.locale` - one of them will win, and only one of the fields will actually be available on the client. The best way to fix this is to denormalize the data you want onto custom top-level fields, as described in the section about [custom user data](#custom-user-data).
|
||||
3. The OAuth login service packages populate `profile.name`. We don't recommend using this but, if you plan to, make sure to deny client-side writes to `profile`. See the section about the [`profile` field on users](#dont-use-profile).
|
||||
4. When finding users by email or username, make sure to use the case-insensitive functions provided by `accounts-password`. See the [section about case-sensitivity](#case-sensitivity) for more details.
|
||||
|
||||
<h2 id="custom-user-data">Custom data about users</h2>
|
||||
|
||||
As your app gets more complex, you will invariably need to store some data about individual users, and the most natural place to put that data is in additional fields on the `Meteor.users` collection described above. In a more normalized data situation it would be a good idea to keep Meteor's user data and yours in two separate tables, but since MongoDB doesn't deal well with data associations it makes sense to use one collection.
|
||||
|
||||
<h3 id="top-level-fields">Add top-level fields onto the user document</h3>
|
||||
|
||||
The best way to store your custom data onto the `Meteor.users` collection is to add a new uniquely-named top-level field on the user document. For example, if you wanted to add a mailing address to a user, you could do it like this:
|
||||
|
||||
```js
|
||||
// Using address schema from schema.org
|
||||
// https://schema.org/PostalAddress
|
||||
const newMailingAddress = {
|
||||
addressCountry: 'US',
|
||||
addressLocality: 'Seattle',
|
||||
addressRegion: 'WA',
|
||||
postalCode: '98052',
|
||||
streetAddress: "20341 Whitworth Institute 405 N. Whitworth"
|
||||
};
|
||||
|
||||
Meteor.users.update(userId, {
|
||||
$set: {
|
||||
mailingAddress: newMailingAddress
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can use any field name other than those [used by the Accounts system](http://docs.meteor.com/api/accounts.html#Meteor-users).
|
||||
|
||||
<h3 id="adding-fields-on-registration">Adding fields on user registration</h3>
|
||||
|
||||
The code above is code that you could run on the server inside a Meteor Method to set someone's mailing address. Sometimes, you want to set a field when the user first creates their account, for example to initialize a default value or compute something from their social data. You can do this using [`Accounts.onCreateUser`](http://docs.meteor.com/#/full/accounts_oncreateuser):
|
||||
|
||||
```js
|
||||
// Generate user initials after Facebook login
|
||||
Accounts.onCreateUser((options, user) => {
|
||||
if (! user.services.facebook) {
|
||||
throw new Error('Expected login with Facebook only.');
|
||||
}
|
||||
|
||||
const { first_name, last_name } = user.services.facebook;
|
||||
user.initials = first_name[0].toUpperCase() + last_name[0].toUpperCase();
|
||||
|
||||
// We still want the default hook's 'profile' behavior.
|
||||
if (options.profile) {
|
||||
user.profile = options.profile;
|
||||
}
|
||||
|
||||
// Don't forget to return the new user object at the end!
|
||||
return user;
|
||||
});
|
||||
```
|
||||
|
||||
Note that the `user` object provided doesn't have an `_id` field yet. If you need to do something with the new user's ID inside this function, a useful trick can be to generate the ID yourself:
|
||||
|
||||
```js
|
||||
// Generate a todo list for each new user
|
||||
Accounts.onCreateUser((options, user) => {
|
||||
// Generate a user ID ourselves
|
||||
user._id = Random.id(); // Need to add the `random` package
|
||||
|
||||
// Use the user ID we generated
|
||||
Lists.createListForUser(user._id);
|
||||
|
||||
// Don't forget to return the new user object at the end!
|
||||
return user;
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="dont-use-profile">Don't use profile</h3>
|
||||
|
||||
There's a tempting existing field called `profile` that is added by default when a new user registers. This field was historically intended to be used as a scratch pad for user-specific data - maybe their image avatar, name, intro text, etc. Because of this, **the `profile` field on every user is automatically writeable by that user from the client**. It's also automatically published to the client for that particular user.
|
||||
|
||||
It turns out that having a field writeable by default without making that super obvious might not be the best idea. There are many stories of new Meteor developers storing fields such as `isAdmin` on `profile`... and then a malicious user can set that to true whenever they want, making themselves an admin. Even if you aren't concerned about this, it isn't a good idea to let malicious users store arbitrary amounts of data in your database.
|
||||
|
||||
Rather than dealing with the specifics of this field, it can be helpful to ignore its existence entirely. You can safely do that as long as you deny all writes from the client:
|
||||
|
||||
```js
|
||||
// Deny all client-side updates to user documents
|
||||
Meteor.users.deny({
|
||||
update() { return true; }
|
||||
});
|
||||
```
|
||||
|
||||
Even ignoring the security implications of `profile`, it isn't a good idea to put all of your app's custom data onto one field. As discussed in the [Collections article](collections.html#schema-design), Meteor's data transfer protocol doesn't do deeply nested diffing of fields, so it's a good idea to flatten out your objects into many top-level fields on the document.
|
||||
|
||||
<h3 id="publish-custom-data">Publishing custom data</h3>
|
||||
|
||||
If you want to access the custom data you've added to the `Meteor.users` collection in your UI, you'll need to publish it to the client. Mostly, you can follow the advice in the [Data Loading](data-loading.html#publications) and [Security](security.html#publications) articles.
|
||||
|
||||
The most important thing to keep in mind is that user documents are certain to contain private data about your users. In particular, the user document includes hashed password data and access keys for external APIs. This means it's critically important to [filter the fields](http://guide.meteor.com/security.html#fields) of the user document that you send to any client.
|
||||
|
||||
Note that in Meteor's publication and subscription system, it's totally fine to publish the same document multiple times with different fields - they will get merged internally and the client will see a consistent document with all of the fields together. So if you added one custom field, you should write a publication with that one field. Let's look at an example of how we might publish the `initials` field from above:
|
||||
|
||||
```js
|
||||
Meteor.publish('Meteor.users.initials', function ({ userIds }) {
|
||||
// Validate the arguments to be what we expect
|
||||
new SimpleSchema({
|
||||
userIds: { type: [String] }
|
||||
}).validate({ userIds });
|
||||
|
||||
// Select only the users that match the array of IDs passed in
|
||||
const selector = {
|
||||
_id: { $in: userIds }
|
||||
};
|
||||
|
||||
// Only return one field, `initials`
|
||||
const options = {
|
||||
fields: { initials: 1 }
|
||||
};
|
||||
|
||||
return Meteor.users.find(selector, options);
|
||||
});
|
||||
```
|
||||
|
||||
This publication will let the client pass an array of user IDs it's interested in, and get the initials for all of those users.
|
||||
|
||||
<h3 id="prevent-unnecessary-data-retrival">Preventing unnecessary data retrieval</h3>
|
||||
|
||||
Take care storing lots of custom data on the user document, particularly data which grows indefinitely, because by default the entire user document is fetched from the database whenever a user tries to log in or out. Plus any calls to (e.g.) `Meteor.user().profile.name` on the server will fetch the entire user document from the database even though may you only need their name. If you have stored lots of custom data on the user documents this could significantly waste server resources (RAM and CPU).
|
||||
|
||||
On the client, creating a reactive property based on (e.g.) `Meteor.user().profile.name` will cause any dependent DOM to update whenever **any** user data changes, not just their name, because the entire user document is being fetched from minimongo and becomes a reactive dependency for that property.
|
||||
|
||||
Meteor 1.10 introduced a solution to these problems. A new `options` parameter was added to some methods which retrieves a user document. This parameter can include a [mongo field specifier](https://docs.meteor.com/api/collections.html#fieldspecifiers) to include or omit specific fields from the query. The methods which have this new parameter, and some examples of their usage are:
|
||||
|
||||
```js
|
||||
// fetch only the user's name from the database:
|
||||
const name = Meteor.user({fields: {"profile.name": 1}}).profile.name;
|
||||
|
||||
// check if an email exists without fetching their entire document from the database:
|
||||
const userExists = !!Accounts.findUserByEmail(email, {fields: {_id: 1}});
|
||||
|
||||
// get the user id from a userName:
|
||||
const userId = Accounts.findUserByUsername(userName, {fields: {_id: 1}})?._id;
|
||||
```
|
||||
|
||||
However, you may not have control over 3rd party package code or Meteor-core code which makes use of these functions. Nor does Meteor know which user fields are needed by callbacks registered with `Accounts.onLogin()`, `Accounts.onLogout()`, `Accounts.onLoginFailure()` and `Accounts.validateLoginAttempt()`. To solve this problem Meteor 1.10 also introduced a new [`Accounts.config({defaultFieldSelector: {...})`](https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config) option to include or omit specific user fields by default.
|
||||
|
||||
You could use this to include (white-list) the standard fields as used by [the Accounts system](http://docs.meteor.com/api/accounts.html#Meteor-users):
|
||||
|
||||
```js
|
||||
Accounts.config({
|
||||
defaultFieldSelector: {
|
||||
username: 1,
|
||||
emails: 1
|
||||
createdAt: 1,
|
||||
profile: 1,
|
||||
services: 1
|
||||
}
|
||||
});
|
||||
```
|
||||
However, this may introduce bugs into any 3rd party or your own callbacks which expect non-standard fields to be present. Alternatively you could omit (black-list) any of your own fields which include large amounts of data, e.g.:
|
||||
```js
|
||||
Accounts.config({ defaultFieldSelector: { myBigArray: 0 }})
|
||||
```
|
||||
|
||||
To ensure backwards compatibility, if you don't define `defaultFieldSelector` then the entire user document will be fetched as with earlier versions of Meteor.
|
||||
|
||||
If you define a `defaultFieldSelector`, then you can override it by passing an `options` parameter, e.g. `Meteor.user({fields: {myBigArray: 1}})`. If you want to fetch the entire user document you can use an empty field specifier: `Meteor.user({fields: {}})`.
|
||||
|
||||
The `defaultFieldSelector` is not used within direct `Meteor.users` collection operations - e.g. `Meteor.users.findOne(Meteor.userId())` will still fetch the entire user document.
|
||||
|
||||
<h2 id="roles-and-permissions">Roles and permissions</h2>
|
||||
|
||||
One of the main reasons you might want to add a login system to your app is to have permissions for your data. For example, if you were running a forum, you would want administrators or moderators to be able to delete any post, but normal users can only delete their own. This uncovers two different types of permissions:
|
||||
|
||||
1. Role-based permissions
|
||||
2. Per-document permissions
|
||||
|
||||
<h3 id="alanning-roles">alanning:roles</h3>
|
||||
|
||||
The most popular package for role-based permissions in Meteor is [`alanning:roles`](https://atmospherejs.com/alanning/roles). For example, here is how you would make a user into an administrator, or a moderator:
|
||||
|
||||
```js
|
||||
// Give Alice the 'admin' role
|
||||
Roles.addUsersToRoles(aliceUserId, 'admin', Roles.GLOBAL_GROUP);
|
||||
|
||||
// Give Bob the 'moderator' role for a particular category
|
||||
Roles.addUsersToRoles(bobsUserId, 'moderator', categoryId);
|
||||
```
|
||||
|
||||
Now, let's say you wanted to check if someone was allowed to delete a particular forum post:
|
||||
|
||||
```js
|
||||
const forumPost = Posts.findOne(postId);
|
||||
|
||||
const canDelete = Roles.userIsInRole(userId,
|
||||
['admin', 'moderator'], forumPost.categoryId);
|
||||
|
||||
if (! canDelete) {
|
||||
throw new Meteor.Error('unauthorized',
|
||||
'Only admins and moderators can delete posts.');
|
||||
}
|
||||
|
||||
Posts.remove(postId);
|
||||
```
|
||||
|
||||
Note that we can check for multiple roles at once, and if someone has a role in the `GLOBAL_GROUP`, they are considered as having that role in every group. In this case, the groups were by category ID, but you could use any unique identifier to make a group.
|
||||
|
||||
Read more in the [`alanning:roles` package documentation](https://atmospherejs.com/alanning/roles).
|
||||
|
||||
<h3 id="per-document-permissions">Per-document permissions</h3>
|
||||
|
||||
Sometimes, it doesn't make sense to abstract permissions into "groups" - you want documents to have owners and that's it. In this case, you can use a simpler strategy using collection helpers.
|
||||
|
||||
```js
|
||||
Lists.helpers({
|
||||
// ...
|
||||
editableBy(userId) {
|
||||
if (!this.userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.userId === userId;
|
||||
},
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Now, we can call this simple function to determine if a particular user is allowed to edit this list:
|
||||
|
||||
```js
|
||||
const list = Lists.findOne(listId);
|
||||
|
||||
if (! list.editableBy(userId)) {
|
||||
throw new Meteor.Error('unauthorized',
|
||||
'Only list owners can edit private lists.');
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about how to use collection helpers in the [Collections article](collections.html#collection-helpers).
|
||||
10
content/angular.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Angular
|
||||
description: The correct place to find details about using Angular with Meteor
|
||||
---
|
||||
|
||||
Angular is a frontend rendering library that is officially supported by Meteor.
|
||||
|
||||
The best place to read about how to use Angular in Meteor is the [Angular-Meteor](http://www.angular-meteor.com) site.
|
||||
|
||||
You can also check the [repository](https://github.com/urigo/angular-meteor/).
|
||||
111
content/apollo.md
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: Apollo
|
||||
order: 15
|
||||
description: The Apollo data stack for Reactive GraphQL
|
||||
discourseTopicId: TODO
|
||||
---
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
Apollo is a GraphQL client/server for transporting data. While it doesn't yet have all the features that Meteor's pub/sub system has, it provides a way to get data from any database – not just MongoDB.
|
||||
|
||||
- [Apollo docs](https://www.apollographql.com/docs/)
|
||||
|
||||
You can get started with Apollo and Meteor by creating a new Meteor application with the Apollo skeleton:
|
||||
```shell
|
||||
meteor create apollo-app --apollo
|
||||
```
|
||||
|
||||
<h3 id="client">Apollo Client</h3>
|
||||
|
||||
[Apollo client docs](https://www.apollographql.com/docs/react/)
|
||||
|
||||
<h4 id="getting-data">Getting data</h4>
|
||||
|
||||
Instead of calling `Meteor.subscribe`, you will use [queries](https://www.apollographql.com/docs/react/data/queries/) to get data.
|
||||
|
||||
The main difference with subscriptions is that queries get called only once (by default) and don't get updated data like a subscription would. This is great for data that doesn't change often and where you don't need reactivity.
|
||||
|
||||
<h4 id="changing-data">Changing data</h4>
|
||||
|
||||
Instead of calling a Meteor method with `Meteor.call`, you use a function called [`mutate`](https://www.apollographql.com/docs/react/data/mutations/) to run a *mutator*, which is GraphQL's equivalent to a method.
|
||||
|
||||
Mutators are only run on the server, but they can return an object which then can update the local cache without the need to call a query again.
|
||||
|
||||
<h3 id="server">Apollo Server</h3>
|
||||
|
||||
[Apollo server docs](https://www.apollographql.com/docs/apollo-server/)
|
||||
|
||||
<h4 id="getting-data-server">Getting data</h4>
|
||||
|
||||
Instead of using `Meteor.publish` to define publications, you write [resolve functions](https://www.apollographql.com/docs/apollo-server/data/resolvers/) – called *resolvers* – that fetch different types of data in the query.
|
||||
|
||||
<h4 id="changing-data-server">Changing data</h4>
|
||||
|
||||
Instead of using `Meteor.methods` to define methods, you write [mutators](https://www.apollographql.com/docs/tutorial/mutation-resolvers/) – functions that *mutate* (change) data.
|
||||
|
||||
These are part of the resolver functions under `Mutation` key.
|
||||
|
||||
<h3 id="graphql">GraphQL</h3>
|
||||
|
||||
GraphQL is a query language for apps to get the data they want. Instead of the server deciding what's in a publication, the client uses GraphQL to say exactly which fields of which objects it wants.
|
||||
|
||||
- [About GraphQL](https://graphql.org/)
|
||||
- [Intro to GraphQL](https://medium.com/apollo-stack/the-basics-of-graphql-in-5-links-9e1dc4cac055)
|
||||
- [GraphQL coming from REST](https://medium.com/apollo-stack/how-do-i-graphql-2fcabfc94a01#.pfdj5bxxj)
|
||||
|
||||
<h3 id="advanced">Advanced<h3>
|
||||
|
||||
[Principled GraphQL](https://principledgraphql.com/)
|
||||
|
||||
<h4 id="latency">Latency</h4>
|
||||
|
||||
Meteor publications are blocking by default, whereas multiple GraphQL queries are executed in parallel. Publications stream data to the client as it arrives, whereas all the resolvers in a GraphQL query have to return before the data is sent to the client. (Although GraphQL is discussing adding the ability to stream results to the client as they come in.)
|
||||
|
||||
<h3>Meteor specific</h3>
|
||||
|
||||
Meteor has a specific Apollo package which includes user object into the context of a query.
|
||||
|
||||
```shell
|
||||
meteor add apollo
|
||||
```
|
||||
|
||||
On server you import `getUser` function and include it into the context option when setting up Apollo server:
|
||||
|
||||
```javascript
|
||||
import { ApolloServer } from 'apollo-server-express';
|
||||
import { getUser } from 'meteor/apollo';
|
||||
import typeDefs from '/imports/apollo/schema.graphql';
|
||||
import { resolvers } from '/server/resolvers';
|
||||
|
||||
const server = new ApolloServer({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
context: async ({ req }) => ({
|
||||
user: await getUser(req.headers.authorization)
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
This will make user data available (if user is logged in) as the option in the query:
|
||||
```javascript
|
||||
{
|
||||
Query: {
|
||||
userUniverses: async (obj, { hideOrgs }, { user }) => {
|
||||
if (!user) return null
|
||||
const selector = { userId: user._id, }
|
||||
if (hideOrgs) selector.organizationId = { $exists: false }
|
||||
return UniversesCollection.find(selector).fetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are many other community packages that provide additional features or makes the initial setup easier, here is an incomplete list of some of them:
|
||||
|
||||
* [quave:graphql](https://atmospherejs.com/quave/graphql) - Utility package to create GraphQL setup in a standard way.
|
||||
* [cultofcoders:apollo](https://atmospherejs.com/cultofcoders/apollo) - Meteor & Apollo integration.
|
||||
* [cultofcoders:graphql-loader](https://atmospherejs.com/cultofcoders/graphql-loader) - Easily load your GraphQL schema in your Meteor app!
|
||||
* [cultofcoders:apollo-accounts](https://atmospherejs.com/cultofcoders/apollo-accounts) - Meteor accounts in GraphQL
|
||||
* [swydo:blaze-apollo](https://atmospherejs.com/swydo/blaze-apollo) - Blaze integration for the Apollo Client
|
||||
* [swydo:ddp-apollo](https://atmospherejs.com/swydo/ddp-apollo) - DDP link and server for Apollo.
|
||||
31
content/atmosphere-vs-npm.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Atmosphere vs. npm
|
||||
discourseTopicId: 20193
|
||||
---
|
||||
|
||||
Building an application completely from scratch is a tall order. This is one of the main reasons you might consider using Meteor in the first place - you can focus on writing the code that is specific to your app, instead of reinventing wheels like user login and data synchronization. To streamline your workflow even further, it makes sense to use community packages from [npm](https://www.npmjs.com) and [Atmosphere](https://atmospherejs.com). Many of these packages are recommended in the guide, and you can find more in the online directories.
|
||||
|
||||
**With the release of version 1.3, Meteor has full support for npm. In the future, there will be a time when all packages will be migrated to npm, but currently there are benefits to both systems.**
|
||||
|
||||
<h2 id="when-atmosphere">When to use Atmosphere packages</h2>
|
||||
|
||||
Atmosphere packages are packages written specifically for Meteor and have several advantages over npm when used with Meteor. In particular, Atmosphere packages can:
|
||||
|
||||
- Depend on core Meteor packages, such as `ddp` and `blaze`
|
||||
- Explicitly include non-javascript files including CSS, Less, Sass, Stylus and static assets
|
||||
- Take advantage of Meteor's [build system](build-tool.html) to be automatically transpiled from languages like CoffeeScript
|
||||
- Have a well defined way to ship different code for client and server, enabling different behavior in each context
|
||||
- Get direct access to Meteor's [package namespacing](using-atmosphere-packages.html#package-namespacing) and package global exports without having to explicitly use ES2015 `import`
|
||||
- Enforce exact version dependencies between packages using Meteor's [constraint resolver](writing-atmosphere-packages.html#version-constraints)
|
||||
- Include [build plugins](build-tool.html#compiles-with-build-plugins) for Meteor's build system
|
||||
- Include pre-built binary code for different server architectures, such as Linux or Windows
|
||||
|
||||
If your package depends on an Atmosphere package (which, in Meteor 1.3, includes the Meteor core packages), or needs to take advantage of Meteor's [build system](build-tool.html), writing an Atmosphere package might be the best option for now.
|
||||
|
||||
<h2 id="when-npm">When to use npm packages</h2>
|
||||
|
||||
npm is a repository of general JavaScript packages. These packages were originally intended solely for the Node.js server-side environment, but as the JavaScript ecosystem matured, solutions arose to enable the use of npm packages in other environments such as the browser. Today, npm is used for all types of JavaScript packages.
|
||||
|
||||
If you want to distribute and reuse code that you've written for a Meteor application, then you should consider publishing that code on npm if it's general enough to be consumed by a wider JavaScript audience. It's possible to [use npm packages in Meteor applications](using-npm-packages.html#using-npm), and possible to [use npm packages within Atmosphere packages](writing-atmosphere-packages.html#npm-dependencies), so even if your main audience is Meteor developers, npm might be the best choice.
|
||||
|
||||
> Meteor comes with npm bundled so that you can type `meteor npm` without worrying about installing it yourself. If you like, you can also use a globally installed npm to manage your packages.
|
||||
7
content/blaze.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Blaze
|
||||
description: How to use Blaze, Meteor's frontend rendering system, to build usable and maintainable user interfaces.
|
||||
discourseTopicId: 19666
|
||||
---
|
||||
|
||||
This content has moved to the [Blaze Community Site](http://blazejs.org/guide/introduction.html).
|
||||
322
content/build-tool.md
Normal file
@@ -0,0 +1,322 @@
|
||||
---
|
||||
title: Build System
|
||||
description: How to use Meteor's build system to compile your app.
|
||||
discourseTopicId: 19669
|
||||
---
|
||||
|
||||
The Meteor build system is the actual command line tool that you get when you install Meteor. You run it by typing the `meteor` command in your terminal, possibly followed by a set of arguments. Read the [docs about the command line tool](https://docs.meteor.com/commandline.html) or type `meteor help` in your terminal to learn about all of the commands.
|
||||
|
||||
<h2 id="what-it-does">What does it do?</h2>
|
||||
|
||||
The Meteor build tool is what compiles, runs, deploys, and publishes all of your Meteor apps and packages. It's Meteor's built-in solution to the problems also solved by tools like Grunt, Gulp, Webpack, Browserify, Nodemon, and many others, and uses many popular Node.js tools like Babel and UglifyJS internally to enable a seamless experience.
|
||||
|
||||
<h3 id="reload-on-file-change">Reloads app on file change</h3>
|
||||
|
||||
After executing the `meteor` command to start the build tool you should leave it running while further developing your app. The build tool automatically detects any relevant file changes using a file watching system and recompiles the necessary changes, restarting your client or server environment as needed. [Hot module replacement](#hot-module-replacement) can optionally be used so you can view and test your changes even quicker.
|
||||
|
||||
<h3 id="compiles-with-build-plugins">Compiles files with build plugins</h3>
|
||||
|
||||
The main function of the Meteor build tool is to run "build plugins". These plugins define different parts of your app build process. Meteor puts heavy emphasis on reducing or removing build configuration files, so you won't see any large build process config files like you would in Gulp or Webpack. The Meteor build process is configured almost entirely through adding and removing packages to your app and putting files in specially named directories. For example, to get all of the newest stable ES2015 JavaScript features in your app, you add the [`ecmascript` package](http://docs.meteor.com/#/full/ecmascript). This package provides support for ES2015 modules, which gives you even more fine grained control over file load order using ES2015 `import` and `export`. As new Meteor releases add new features to this package you get them for free.
|
||||
|
||||
<h4 id="controlling-build-files">Controlling which files to build</h4>
|
||||
|
||||
By default Meteor will build certain files as controlled by your application [file structure](structure.html#javascript-structure) and Meteor's [default file load order](structure.html#load-order) rules. However, you may override the default behavior using `.meteorignore` files, which cause the build system to ignore certain files and directories using the same pattern syntax as `.gitignore` files. These files may appear in any directory of your app or package, specifying rules for the directory tree below them. These `.meteorignore` files are also fully integrated with Meteor's file watching system, so they can be added, removed, or modified during development.
|
||||
|
||||
<h3 id="concatenate-and-minify">Combines and minifies code</h3>
|
||||
|
||||
Another important feature of the Meteor build tool is that it automatically concatenates your application asset files, and in production minifies these bundles. This lets you add all of the comments and whitespace you want to your source code and split your code into as many files as necessary, all without worrying about app performance and load times. This is enabled by the [`standard-minifier-js`](https://atmospherejs.com/meteor/standard-minifiers-js) and [`standard-minifier-css`](https://atmospherejs.com/meteor/standard-minifiers-css) packages, which are included in all Meteor apps by default. If you need different minification behavior, you can replace these packages. See adding [PostCSS to your build process](#postcss) as an example.
|
||||
|
||||
<h3 id="dev-vs-prod">Development vs. production</h3>
|
||||
|
||||
Running an app in development is all about fast iteration time. All kinds of different parts of your app are handled differently and instrumented to enable better reloads and debugging. In production, the app is reduced to the necessary code and functions just like any standard Node.js app. Therefore, you shouldn't run your app in production by executing the `meteor run` command. Instead, follow the directions in [Deploying Meteor Applications](deployment.html#deploying). If you find an error in production that you suspect is related to minification, you can run the minified version of your app locally for testing with `meteor --production`.
|
||||
|
||||
<h2 id="javascript-transpilation">JavaScript transpilation</h2>
|
||||
|
||||
These days, the landscape of JavaScript tools and frameworks is constantly shifting, and the language itself is evolving just as rapidly. It's no longer reasonable to wait for web browsers to implement the language features you want to use. Most JavaScript development workflows rely on compiling code to work on the lowest common denominator of environments, while letting you use the newest features in development. Meteor has support for some of the most popular tools out of the box.
|
||||
|
||||
<h3 id="es2015">ES2015+ (recommended)</h3>
|
||||
|
||||
The `ecmascript` package (which is installed into all new apps and packages by default, but can be removed), allows support for many ES2015 features. We recommend using it. You can read more about it in the [Code Style](code-style.html#ecmascript) article.
|
||||
|
||||
<h3 id="es2015">Babel</h3>
|
||||
|
||||
Babeljs is a configurable transpiler, which allows you write code in the latest version of JavaScript even when your supported environments don't support certain features natively. Babel will compile those features down to a supported version.
|
||||
|
||||
Meteor provides a set appropriate core plugins for each environment (Node 8, modern browsers, and legacy browsers) and React to support most modern Javascript code practices. In addition, Meteor (as of 1.3.3) supports custom .babelrc files which allows developers to further customise their Babel configuration to suit there needs (e.g. Stage 0 proposals).
|
||||
|
||||
Developers are encouraged to avoid adding large presets (such as babel-preset-env & babel-preset-react) and adding specific plugins as needed (even though it seems to work). You will avoid unnecessary Babel compilation and you'll be less likely to experience plugin ordering issues.
|
||||
|
||||
<h3 id="coffeescript">CoffeeScript</h3>
|
||||
|
||||
While we recommend using ES2015 with the `ecmascript` package as the best development experience for Meteor, everything in the platform is 100% compatible with [CoffeeScript](http://coffeescript.org/) and many people in the Meteor community prefer it.
|
||||
|
||||
All you need to do to use CoffeeScript is add the right Meteor package:
|
||||
|
||||
```sh
|
||||
meteor add coffeescript
|
||||
```
|
||||
|
||||
All code written in CoffeeScript compiles to JavaScript under the hood, and is completely compatible with any code in other packages that is written in JS or ES2015.
|
||||
|
||||
<h3 id="typescript">TypeScript</h3>
|
||||
|
||||
[TypeScript](https://www.typescriptlang.org/) is modern JavaScript with optional types and more.
|
||||
|
||||
Adding types will make your code more readable and less prone to runtime errors.
|
||||
|
||||
TypeScript can be installed with:
|
||||
|
||||
```sh
|
||||
meteor add typescript
|
||||
```
|
||||
|
||||
It is necessary to configure the TypeScript compiler with a `tsconfig.json` file. Here's the one generated by `meteor create --typescript`:
|
||||
|
||||
```
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es2018",
|
||||
"module": "esNext",
|
||||
"lib": ["esnext", "dom"],
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
|
||||
/* Module Resolution Options */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
/* Support absolute /imports/* with a leading '/' */
|
||||
"/*": ["*"]
|
||||
},
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node", "mocha"],
|
||||
"esModuleInterop": true,
|
||||
"preserveSymlinks": true
|
||||
},
|
||||
"exclude": [
|
||||
"./.meteor/**",
|
||||
"./packages/**"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If you want to add TypeScript from the point of project creation, as of Meteor 1.8.2, you can run the create command with the --typescript flag:
|
||||
|
||||
```
|
||||
meteor create --typescript name-of-my-new-typescript-app
|
||||
```
|
||||
|
||||
<h4 id="typescript-conditional-imports">Conditional imports</h4>
|
||||
|
||||
TypeScript does not support nested `import` statements, therefore conditionally importing modules requires you to use the `require` statement (see [Using `require`](https://guide.meteor.com/structure.html#using-require)).
|
||||
|
||||
To maintain type safety, you can take advantage of TypeScript's import elision and reference the types using the `typeof` keyword. See the [TypeScript handbook article](https://www.typescriptlang.org/docs/handbook/modules.html#optional-module-loading-and-other-advanced-loading-scenarios) for details or [this blog post](http://ideasintosoftware.com/typescript-conditional-imports/) for a concrete Meteor example.
|
||||
|
||||
<h2 id="blaze-templates">Templates and HTML</h2>
|
||||
|
||||
Since Meteor uses client-side rendering for your app's UI, all of your HTML code, UI components, and templates need to be compiled to JavaScript. There are a few options at your disposal to write your UI code.
|
||||
|
||||
<h3 id="blaze-spacebars">Blaze HTML templates</h3>
|
||||
|
||||
The aptly named `blaze-html-templates` package that comes with every new Meteor app by default compiles your `.html` files written using [Spacebars](http://blazejs.org/api/spacebars.html) into Blaze-compatible JavaScript code. You can also add `blaze-html-templates` to any of your packages to compile template files located in the package.
|
||||
|
||||
[Read about how to use Blaze and Spacebars in the Blaze article.](http://blazejs.org/guide/spacebars.html)
|
||||
|
||||
<h3 id="blaze-jade">Blaze Jade templates</h3>
|
||||
|
||||
If you don't like the Spacebars syntax Meteor uses by default and want something more concise, you can give Jade a try by using [`pacreach:jade`](https://atmospherejs.com/pacreach/jade). This package will compile all files in your app with the `.jade` extension into Blaze-compatible code, and can be used side-by-side with `blaze-html-templates` if you want to have some of your code in Spacebars and some in Jade.
|
||||
|
||||
<h3 id="react-jsx">JSX for React</h3>
|
||||
|
||||
If you're building your app's UI with React, currently the most popular way to write your UI components involves JSX, an extension to JavaScript that allows you to type HTML tags that are converted to React DOM elements. JSX code is handled automatically by the `ecmascript` package.
|
||||
|
||||
<h4 id="react-other">Other options for React</h4>
|
||||
|
||||
If you want to use React but don't want to deal with JSX and prefer a more HTML-like syntax, there are a few community options available. One that stands out in particular is [Blaze-React](https://github.com/timbrandin/blaze-react), which simulates the entire Blaze API using React as a rendering engine.
|
||||
|
||||
<h2 id="css">CSS processing</h2>
|
||||
|
||||
All your CSS style files will be processed using Meteor's default file load order rules along with any import statements and concatenated into a single stylesheet, `merged-stylesheets.css`. In a production build this file is also minified. By default this single stylesheet is injected at the beginning of the HTML `<head />` section of your application.
|
||||
|
||||
However, this can potentially be an issue for some applications that use a third party UI framework, such as Bootstrap, which is loaded from a CDN. This could cause Bootstrap's CSS to come after your CSS and override your user-defined styles.
|
||||
|
||||
To get around this problem Meteor supports the use of a pseudo tag `<meteor-bundled-css />` that if placed anywhere in the `<head />` section your app will be replaced by a link to this concatenated CSS file. If this pseudo tag isn't used, the CSS file will be placed at the beginning of the <head /> section as before.
|
||||
|
||||
<h3 id="css-which-preprocessor">CSS pre-processors</h3>
|
||||
|
||||
It's no secret that writing plain CSS can often be a hassle as there's no way to share common CSS code between different selectors or have a consistent color scheme between different elements. CSS compilers, or pre-processors, solve these issues by adding extra features on top of the CSS language like variables, mixins, math, and more, and in some cases also significantly change the syntax of CSS to be easier to read and write.
|
||||
|
||||
Here are three example CSS pre-processors supported by Meteor:
|
||||
|
||||
1. [Sass](http://sass-lang.com/)
|
||||
2. [Less.js](http://lesscss.org/)
|
||||
3. [Stylus](https://learnboost.github.io/stylus/)
|
||||
|
||||
They all have their pros and cons, and different people have different preferences, just like with JavaScript transpiled languages. Sass with the SCSS syntax is quite popular as CSS frameworks like Bootstrap 4 have switched to Sass, and the C++ LibSass implementation appears to be faster than some of the other compilers available.
|
||||
|
||||
CSS framework compatibility should be a primary concern when picking a pre-processor, because a framework written with Less won't be compatible with one written in Sass.
|
||||
|
||||
<h3 id="css-source-vs-import">Source vs. import files</h3>
|
||||
|
||||
An important feature shared by all of the available CSS pre-processors is the ability to import files. This lets you split your CSS into smaller pieces, and provides a lot of the same benefits that you get from JavaScript modules:
|
||||
|
||||
1. You can control the load order of files by encoding dependencies through imports, since the load order of CSS matters.
|
||||
2. You can create reusable CSS "modules" that only have variables and mixins and don't actually generate any CSS.
|
||||
|
||||
In Meteor, each of your `.scss`, `.less`, or `.styl` source files will be one of two types: "source" or "import".
|
||||
|
||||
A "source" file is evaluated eagerly and adds its compiled form to the CSS of the app immediately.
|
||||
|
||||
An "import" file is evaluated only if imported from some other file and can be used to share common mixins and variables between different CSS files in your app.
|
||||
|
||||
Read the documentation for each package listed below to see how to indicate which files are source files vs. imports.
|
||||
|
||||
<h3 id="css-importing">Importing styles</h3>
|
||||
|
||||
In all three Meteor supported CSS pre-processors you can import other style files from both relative and absolute paths in your app and from both npm and Meteor Atmosphere packages.
|
||||
|
||||
```less
|
||||
@import '../stylesheets/colors.less'; // a relative path
|
||||
@import '{}/imports/ui/stylesheets/button.less'; // absolute path with `{}` syntax
|
||||
```
|
||||
|
||||
You can also import CSS from a JavaScript file if you have the `ecmascript` package installed:
|
||||
|
||||
```js
|
||||
import '../stylesheets/styles.css';
|
||||
```
|
||||
|
||||
> When importing CSS from a JavaScript file, that CSS is not bundled with the rest of the CSS processed with the Meteor build tool, but instead is put in your app's `<head>` tag inside `<style>...</style>` after the main concatenated CSS file.
|
||||
|
||||
Importing styles from an Atmosphere package using the `{}` package name syntax:
|
||||
|
||||
```less
|
||||
@import '{my-package:pretty-buttons}/buttons/styles.import.less';
|
||||
```
|
||||
|
||||
> CSS files in an Atmosphere package are declared with [`api.addFiles`](http://docs.meteor.com/#/full/pack_addFiles), and therefore will be eagerly evaluated, and automatically bundled with all the other CSS in your app.
|
||||
|
||||
Importing styles from an npm package using the `{}` syntax:
|
||||
|
||||
```less
|
||||
@import '{}/node_modules/npm-package-name/button.less';
|
||||
```
|
||||
```js
|
||||
import 'npm-package-name/stylesheets/styles.css';
|
||||
```
|
||||
|
||||
For more examples and details on importing styles and using `@imports` with packages see the [Using Packages](using-packages.html#npm-styles) article.
|
||||
|
||||
<h3 id="sass">Sass</h3>
|
||||
|
||||
The best Sass build plugin for Meteor is [`fourseven:scss`](https://atmospherejs.com/fourseven/scss).
|
||||
|
||||
<h3 id="less">Less</h3>
|
||||
|
||||
Less is maintained as a [Meteor core package called `less`](https://atmospherejs.com/meteor/less).
|
||||
|
||||
<h3 id="stylus">Stylus</h3>
|
||||
|
||||
The best Stylus build plugin for Meteor is [coagmano:stylus](https://atmospherejs.com/coagmano/stylus)
|
||||
|
||||
<h2 id="postcss">PostCSS and Autoprefixer</h2>
|
||||
|
||||
In addition to CSS pre-processors like Sass, Less, and Stylus, there is now an ecosystem of CSS post-processors. Regardless of which CSS pre-processor you use, a post-processor can give you additional benefits like cross-browser compatibility.
|
||||
|
||||
The most popular CSS post-processor right now is [PostCSS](https://github.com/postcss/postcss), which supports a variety of plugins. [Autoprefixer](https://github.com/postcss/autoprefixer) is perhaps the most useful plugin, since it enables you to stop worrying about browser prefixes and compatibility and write standards-compliant CSS. No more copying 5 different statements every time you want a CSS gradient - you can write a standard gradient without any prefixes and Autoprefixer handles it for you.
|
||||
|
||||
Currently, Meteor doesn't have a separate build step for post-processing CSS, so the only way to integrate it is to build it into the minifier. Thankfully, there is a community package that has integrated PostCSS with plugin support into a replacement for Meteor's standard minification package.
|
||||
|
||||
<h3 id="juliancwirko-postcss">juliancwirko:postcss</h3>
|
||||
|
||||
>Note: This package is no longer actively maintained, therefore compatibility with newer versions of Meteor is not guaranteed. If you encouter problems with this, please let us know by [opening an issue on the Guide](https://github.com/meteor/guide/issues).
|
||||
|
||||
Use the package [juliancwirko:postcss](https://atmospherejs.com/juliancwirko/postcss) to your app to enable PostCSS for your Meteor app. To do so, we remove the standard CSS minifier and replace it with the postcss package:
|
||||
|
||||
```
|
||||
meteor remove standard-minifier-css
|
||||
meteor add juliancwirko:postcss
|
||||
```
|
||||
|
||||
As well as installing the postcss NPM package:
|
||||
|
||||
```
|
||||
meteor npm install postcss@^6.0.22 --save-dev
|
||||
meteor npm install postcss-load-config@^1.2.0 --save-dev
|
||||
```
|
||||
|
||||
Then we can install any npm CSS processing packages that we'd like to use and reference them from a `postcss` section of our `package.json`. In the Todos example app, we use `autoprefixer` package to increase browser support:
|
||||
|
||||
```
|
||||
{
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.3.1"
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {"browsers": ["last 2 versions"]}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After doing the above, you'll need to ensure you `npm install` and restart the `meteor` process running your app to make sure the PostCSS system has had a chance to set itself up.
|
||||
|
||||
<h2 id="hot-module-replacement">Hot Module Replacement</h2>
|
||||
|
||||
In Meteor apps, javascript, typescript, css files that are dynamically imported, and many other types of files are converted into javascript modules during the build process. Instead of reloading the client after a rebuild, Meteor is able to update the javascript modules within the running application that were modified. This reduces the feedback cycle while developing by allowing you to view and test your changes quicker.
|
||||
|
||||
Hot module replacement (HMR) can be enabled by adding the [hot-module-replacement](https://docs.meteor.com/packages/hot-module-replacement.html) package to your app:
|
||||
|
||||
```
|
||||
meteor add hot-module-replacement
|
||||
```
|
||||
|
||||
Many types of javascript modules can not be updated with HMR, so HMR has to be configured to know which modules can be replaced and how to replace them. Most apps never need to do this manually. Instead, you can use integrations that configure HMR for you:
|
||||
|
||||
- React components are automatically updated using [React Fast Refresh](https://atmospherejs.com/meteor/react-fast-refresh). This integration is enabled for all Meteor apps that use HMR and a supported react version.
|
||||
- An integration for Blaze templates is in [beta](https://github.com/meteor/blaze/pull/313).
|
||||
- Svelte files can be automatically updated with HMR by using the [zodern:melte](https://atmospherejs.com/zodern/melte) compiler package.
|
||||
- [akryum:vue-component](https://atmospherejs.com/akryum/vue-component) uses its own implementation of HMR to update vue components.
|
||||
- Some packages are able to help automatically dispose old versions of modules. For example, [zodern:pure-admin](https://atmospherejs.com/zodern/pure-admin) removes menu items and pages added in the old version of the module so you don't end up with duplicate or outdated items when the new version of the module is ran.
|
||||
|
||||
To further control how HMR applies updates in your app, you can use the [hot API](https://docs.meteor.com/packages/hot-module-replacement.html). This can be used to accept updates for additional types of files, help dispose a module so the old version no longer affects the app (such as stopping Tracker.autorun computations), or creating your own integrations with other view layers or libraries.
|
||||
|
||||
If a change was made to the app that can not be applied with HMR, it reloads the page with hot code push, as is done when HMR is not enabled. It currently only supports app code in the modern client architecture. Future versions of Meteor will add support for packages and other architectures.
|
||||
|
||||
<h2 id="build-plugins">Build plugins</h2>
|
||||
|
||||
The most powerful feature of Meteor's build system is the ability to define custom build plugins. If you find yourself writing scripts that mangle one type of file into another, merge multiple files, or something else, it's likely that these scripts would be better implemented as a build plugin. The `ecmascript`, `templating`, and `coffeescript` packages are all implemented as build plugins, so you can replace them with your own versions if you want to!
|
||||
|
||||
[Read the documentation about build plugins.](https://docs.meteor.com/api/packagejs.html#build-plugin-api)
|
||||
|
||||
<h3 id="types-of-build-plugins">Types of build plugins</h3>
|
||||
|
||||
There are three types of build plugins supported by Meteor today:
|
||||
|
||||
1. Compiler plugin - compiles source files (LESS, CoffeeScript) into built output (JS, CSS, asset files, and HTML). Only one compiler plugin can handle a single file extension.
|
||||
2. Minifier plugin - compiles lots of built CSS or JS files into one or more minified files, for example `standard-minifiers`. Only one minifier can handle each of `js` and `css`.
|
||||
3. Linter plugin - processes any number of files, and can print lint errors. Multiple linters can process the same files.
|
||||
|
||||
<h3 id="writing-build-plugins">Writing your own build plugin</h3>
|
||||
|
||||
Writing a build plugin is a very advanced task that only the most advanced Meteor users should get into. The best place to start is to copy a different plugin that is the most similar to what you are trying to do. For example, if you wanted to make a new CSS compiler plugin, you could fork the `less` package; if you wanted to make your own JS transpiler, you could fork `ecmascript`. A good example of a linter is the `jshint` package, and for a minifier you can look at `standard-minifiers-js` and `standard-minifiers-css`.
|
||||
|
||||
<h3 id="caching-build-plugins">Caching</h3>
|
||||
|
||||
The best way to make your build plugin fast is to use caching anywhere you can - the best way to save time is to do less work! Check out the [documentation about CachingCompiler](https://docs.meteor.com/api/packagejs.html#build-plugin-caching) to learn more. It's used in all of the above examples, so you can see how to use it by looking at them.
|
||||
271
content/code-style.md
Normal file
@@ -0,0 +1,271 @@
|
||||
---
|
||||
title: Code Style
|
||||
description: Suggested style guidelines for your code.
|
||||
discourseTopicId: 20189
|
||||
---
|
||||
|
||||
After reading this article, you'll know:
|
||||
|
||||
1. Why it's a good idea to have consistent code style
|
||||
2. Which style guide we recommend for JavaScript code
|
||||
3. How to set up ESLint to check code style automatically
|
||||
4. Style suggestions for Meteor-specific patterns, such as Methods, publications, and more
|
||||
|
||||
|
||||
<h2 id="benefits-style">Benefits of consistent style</h2>
|
||||
|
||||
Countless hours have been spent by developers throughout the years arguing over single vs. double quotes, where to put brackets, how many spaces to type, and all kinds of other cosmetic code style questions. These are all questions that have at best a tangential relationship to code quality, but are very easy to have opinions about because they are so visual.
|
||||
|
||||
While it's not necessarily important whether your code base uses single or double quotes for string literals, there are huge benefits to making that decision once and having it be consistent across your organization. These benefits also apply to the Meteor and JavaScript development communities as a whole.
|
||||
|
||||
<h3 id="easy-to-read">Easy to read code</h3>
|
||||
|
||||
The same way that you don't read English sentences one word at a time, you don't read code one token at a time. Mostly you just look at the shape of a certain expression, or the way it highlights in your editor, and assume what it does. If the style of every bit of code is consistent, that ensures that bits of code that look the same actually _are_ the same - there isn't any hidden punctuation or gotchas that you don't expect, so you can focus on understanding the logic instead of the symbols. One example of this is indentation - while in JavaScript, indentation is not meaningful, it's helpful to have all of your code consistently indented so that you don't need to read all of the brackets in detail to see what is going on.
|
||||
|
||||
```js
|
||||
// This code is misleading because it looks like both statements
|
||||
// are inside the conditional.
|
||||
if (condition)
|
||||
firstStatement();
|
||||
secondStatement();
|
||||
```
|
||||
|
||||
```js
|
||||
// Much clearer!
|
||||
if (condition) {
|
||||
firstStatement();
|
||||
}
|
||||
|
||||
secondStatement();
|
||||
```
|
||||
|
||||
<h3 id="automatic-error-checking">Automatic error checking</h3>
|
||||
|
||||
Having a consistent style means that it's easier to adopt standard tools for error checking. For example, if you adopt a convention that you must always use `let` or `const` instead of `var`, you can now use a tool to ensure all of your variables are scoped the way you expect. That means you can avoid bugs where variables act in unexpected ways. Also, by enforcing that all variables are declared before use, you can catch typos before even running any code!
|
||||
|
||||
<h3 id="deeper-understanding">Deeper understanding</h3>
|
||||
|
||||
It's hard to learn everything about a programming language at once. For example, programmers new to JavaScript often struggle with the `var` keyword and function scope. Using a community-recommended coding style with automatic linting can warn you about these pitfalls proactively. This means you can jump right into coding without learning about all of the edge cases of JavaScript ahead of time.
|
||||
|
||||
As you write more code and come up against the recommended style rules, you can take that as an opportunity to learn more about your programming language and how different people prefer to use it.
|
||||
|
||||
<h2 id="javascript">JavaScript style guide</h2>
|
||||
|
||||
Here at Meteor, we strongly believe that JavaScript is the best language to build web applications, for a variety of reasons. JavaScript is constantly improving, and the standards around ES2015 have really brought together the JavaScript community. Here are our recommendations about how to use ES2015 JavaScript in your app today.
|
||||
|
||||

|
||||
|
||||
> An example of refactoring from JavaScript to ES2015
|
||||
|
||||
<h3 id="ecmascript">Use the `ecmascript` package</h3>
|
||||
|
||||
ECMAScript, the language standard on which every browser's JavaScript implementation is based, has moved to yearly standards releases. The newest complete standard is ES2015, which includes some long-awaited and very significant improvements to the JavaScript language. Meteor's `ecmascript` package compiles this standard down to regular JavaScript that all browsers can understand using the [popular Babel compiler](https://babeljs.io/). It's fully backwards compatible to "regular" JavaScript, so you don't have to use any new features if you don't want to. We've put a lot of effort into making advanced browser features like source maps work great with this package, so that you can debug your code using your favorite developer tools without having to see any of the compiled output.
|
||||
|
||||
The `ecmascript` package is included in all new apps and packages by default, and compiles all files with the `.js` file extension automatically. See the [list of all ES2015 features supported by the ecmascript package](https://docs.meteor.com/packages/ecmascript.html#Supported-ES2015-Features).
|
||||
|
||||
To get the full experience, you should also use the `es5-shim` package which is included in all new apps by default. This means you can rely on runtime features like `Array#forEach` without worrying about which browsers support them.
|
||||
|
||||
All of the code samples in this guide and future Meteor tutorials will use all of the new ES2015 features. You can also read more about ES2015 and how to get started with it on the Meteor Blog:
|
||||
|
||||
- [Getting started with ES2015 and Meteor](http://info.meteor.com/blog/es2015-get-started)
|
||||
- [Set up Sublime Text for ES2015](http://info.meteor.com/blog/set-up-sublime-text-for-meteor-es6-es2015-and-jsx-syntax-and-linting)
|
||||
- [How much does ES2015 cost?](http://info.meteor.com/blog/how-much-does-es2015-cost)
|
||||
|
||||
<h3 id="style-guide">Follow a JavaScript style guide</h3>
|
||||
|
||||
We recommend choosing and sticking to a JavaScript style guide and enforcing it with tools. A popular option that we recommend is the [Airbnb style guide](https://github.com/airbnb/javascript) with the ES6 extensions (and optionally React extensions).
|
||||
|
||||
<h2 id="eslint">Check your code with ESLint</h2>
|
||||
|
||||
"Code linting" is the process of automatically checking your code for common errors or style problems. For example, ESLint can determine if you have made a typo in a variable name, or some part of your code is unreachable because of a poorly written `if` condition.
|
||||
|
||||
We recommend using the [Airbnb eslint configuration](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) which verifies the Airbnb styleguide.
|
||||
|
||||
Below, you can find directions for setting up automatic linting at many different stages of development. In general, you want to run the linter as often as possible, because it's an automated way to identify typos and small errors.
|
||||
|
||||
<h3 id="eslint-installing">Installing and running ESLint</h3>
|
||||
|
||||
To setup ESLint in your application, you can install the following [npm](https://docs.npmjs.com/getting-started/what-is-npm) packages:
|
||||
|
||||
```
|
||||
meteor npm install --save-dev babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-meteor eslint-plugin-react eslint-plugin-jsx-a11y eslint-import-resolver-meteor eslint @meteorjs/eslint-config-meteor
|
||||
```
|
||||
|
||||
> Meteor comes with npm bundled so that you can type meteor npm without worrying about installing it yourself. If you like, you can also use a globally installed npm command.
|
||||
|
||||
You can also add a `eslintConfig` section to your `package.json` to specify that you'd like to use the Airbnb config, and to enable [ESLint-plugin-Meteor](https://github.com/dferber90/eslint-plugin-meteor). You can also setup any extra rules you want to change, as well as adding a lint npm command:
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"pretest": "npm run lint --silent"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@meteorjs/eslint-config-meteor"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To run the linter, you can now type:
|
||||
|
||||
```bash
|
||||
meteor npm run lint
|
||||
```
|
||||
|
||||
For more details, read the [Getting Started](http://eslint.org/docs/user-guide/getting-started) directions from the ESLint website.
|
||||
|
||||
<h3 id="eslint-editor">Integrating with your editor</h3>
|
||||
|
||||
Linting is the fastest way to find potential bugs in your code. Running a linter is usually faster than running your app or your unit tests, so it's a good idea to run it all the time. Setting up linting in your editor can seem annoying at first since it will complain often when you save poorly-formatted code, but over time you'll develop the muscle memory to write well-formatted code in the first place. Here are some directions for setting up ESLint in different editors:
|
||||
|
||||
|
||||
<h4 id="eslint-sublime">Sublime Text</h4>
|
||||
|
||||
You can install the Sublime Text packages that integrate them into the text editor. It's generally recommended to use Package Control to add these packages. If you already have that setup, you can just add the these packages by name; if not, click the instructions links:
|
||||
|
||||
* Babel (for syntax highlighting – [full instructions](https://github.com/babel/babel-sublime#installation))
|
||||
* SublimeLinter ([full instructions](http://sublimelinter.readthedocs.org/en/latest/installation.html))
|
||||
* SublimeLinter-contrib-eslint ([full instructions](https://github.com/roadhump/SublimeLinter-eslint#plugin-installation))
|
||||
|
||||
To get proper syntax highlighting, go to a .js file, then select the following through the *View* dropdown menu: *Syntax* -> *Open all with current extension as...* -> *Babel* -> *JavaScript (Babel)*. If you are using React .jsx files, do the same from a .jsx file. If it's working, you will see "JavaScript (Babel)" in the lower right hand corner of the window when you are on one of these files. Refer to the [package readme](https://github.com/babel/babel-sublime) for information on compatible color schemes.
|
||||
|
||||
A side note for Emmet users: You can use *\<ctrl-e\>* to expand HTML tags in .jsx files, and it will correctly expand classes to React's "className" property. You can bind to the tab key for this, but [you may not want to](https://github.com/sergeche/emmet-sublime/issues/548).
|
||||
|
||||
<h4 id="eslint-atom">Atom</h4>
|
||||
|
||||
Install these three packages to use ESLint with Atom:
|
||||
|
||||
```bash
|
||||
apm install language-babel
|
||||
apm install linter
|
||||
apm install linter-eslint
|
||||
```
|
||||
|
||||
Then **restart** (or **reload** by pressing Ctrl+Alt+R / Cmd+Opt+R) Atom to activate linting.
|
||||
|
||||
|
||||
<h4 id="eslint-webstorm">WebStorm</h4>
|
||||
|
||||
WebStorm provides [these instructions for using ESLint](https://www.jetbrains.com/webstorm/help/eslint.html). After you install the ESLint Node packages and set up your `package.json`, enable ESLint and click "Apply". You can configure how WebStorm should find your `.eslintrc` file, but on my machine it worked without any changes. It also automatically suggested switching to "JSX Harmony" syntax highlighting.
|
||||
|
||||

|
||||
|
||||
Linting can be activated on WebStorm on a project-by-project basis, or you can set ESLint as a default under Editor > Inspections, choosing the Default profile, checking "ESLint", and applying.
|
||||
|
||||
<h4 id="eslint-vscode">Visual Studio Code</h4>
|
||||
|
||||
Using ESLint in VS Code requires installation of the 3rd party [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension. In order to install the extension, follow these steps:
|
||||
|
||||
1. Launch VS Code and open the quick open menu by typing `Ctrl+P`
|
||||
2. Paste `ext install vscode-eslint` in the command window and press `Enter`
|
||||
3. Restart VS Code
|
||||
|
||||
|
||||
<h2 id="meteor-features">Meteor code style</h2>
|
||||
|
||||
The section above talked about JavaScript code in general - you can apply it in any JavaScript application, not just with Meteor apps. However, there are some style questions that are Meteor-specific, in particular how to name and structure all of the different components of your app.
|
||||
|
||||
<h3 id="collections">Collections</h3>
|
||||
|
||||
Collections should be named as a plural noun, in [PascalCase](https://en.wikipedia.org/wiki/PascalCase). The name of the collection in the database (the first argument to the collection constructor) should be the same as the name of the JavaScript symbol.
|
||||
|
||||
```js
|
||||
// Defining a collection
|
||||
Lists = new Mongo.Collection('lists');
|
||||
```
|
||||
|
||||
Fields in the database should be camelCased just like your JavaScript variable names.
|
||||
|
||||
```js
|
||||
// Inserting a document with camelCased field names
|
||||
Widgets.insert({
|
||||
myFieldName: 'Hello, world!',
|
||||
otherFieldName: 'Goodbye.'
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="methods-and-publications">Methods and publications</h3>
|
||||
|
||||
Method and publication names should be camelCased, and namespaced to the module they are in:
|
||||
|
||||
```js
|
||||
// in imports/api/todos/methods.js
|
||||
updateText = new ValidatedMethod({
|
||||
name: 'todos.updateText',
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Note that this code sample uses the [ValidatedMethod package recommended in the Methods article](methods.html#validated-method). If you aren't using that package, you can use the name as the property passed to `Meteor.methods`.
|
||||
|
||||
Here's how this naming convention looks when applied to a publication:
|
||||
|
||||
```js
|
||||
// Naming a publication
|
||||
Meteor.publish('lists.public', function listsPublic() {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="files-and-exports">Files, exports, and packages</h3>
|
||||
|
||||
You should use the ES2015 `import` and `export` features to manage your code. This will let you better understand the dependencies between different parts of your code, and it will help you navigate to the source code of a dependency.
|
||||
|
||||
Each file in your app should represent one logical module. Avoid having catch-all utility modules that export a variety of unrelated functions and symbols. Often, this can mean that it's good to have one class, UI component, or collection per file, but there are cases where it is OK to make an exception, for example if you have a UI component with a small sub-component that isn't used outside of that file.
|
||||
|
||||
When a file represents a single class or UI component, the file should be named the same as the thing it defines, with the same capitalization. So if you have a file that exports a class:
|
||||
|
||||
```js
|
||||
export default class ClickCounter { ... }
|
||||
```
|
||||
|
||||
This class should be defined inside a file called `ClickCounter.js`. When you import it, it'll look like this:
|
||||
|
||||
```js
|
||||
import ClickCounter from './ClickCounter.js';
|
||||
```
|
||||
|
||||
Note that imports use relative paths, and include the file extension at the end of the file name.
|
||||
|
||||
For [Atmosphere packages](using-packages.html), as the older pre-1.3 `api.export` syntax allowed more than one export per package, you'll tend to see non-default exports used for symbols. For instance:
|
||||
|
||||
```js
|
||||
// You'll need to destructure here, as Meteor could export more symbols
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
// This will not work
|
||||
import Meteor from 'meteor/meteor';
|
||||
```
|
||||
|
||||
<h3 id="templates-and-components">Templates and components</h3>
|
||||
|
||||
Since Spacebars templates are always global, can't be imported and exported as modules, and need to have names that are completely unique across the whole app, we recommend naming your Blaze templates with the full path to the namespace, separated by underscores. Underscores are a great choice in this case because then you can type the name of the template as one symbol in JavaScript.
|
||||
|
||||
```html
|
||||
<template name="Lists_show">
|
||||
...
|
||||
</template>
|
||||
```
|
||||
|
||||
If this template is a "smart" component that loads server data and accesses the router, append `_page` to the name:
|
||||
|
||||
```html
|
||||
<template name="Lists_show_page">
|
||||
...
|
||||
</template>
|
||||
```
|
||||
|
||||
Often when you are dealing with templates or UI components, you'll have several closely coupled files to manage. They could be two or more of HTML, CSS, and JavaScript files. In this case, we recommend putting these together in the same directory with the same name:
|
||||
|
||||
```
|
||||
# The Lists_show template from the Todos example app has 3 files:
|
||||
show.html
|
||||
show.js
|
||||
show.less
|
||||
```
|
||||
|
||||
The whole directory or path should indicate that these templates are related to the `Lists` module, so it's not necessary to reproduce that information in the file name. Read more about directory structure [below](structure.html#javascript-structure).
|
||||
|
||||
If you are writing your UI in React, you don't need to use the underscore-split names because you can import and export your components using the JavaScript module system.
|
||||
526
content/collections.md
Normal file
@@ -0,0 +1,526 @@
|
||||
---
|
||||
title: Collections and Schemas
|
||||
description: How to define, use, and maintain MongoDB collections in Meteor.
|
||||
discourseTopicId: 19660
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. The different types of MongoDB collections in Meteor, and how to use them.
|
||||
2. How to define a schema for a collection to control its content.
|
||||
3. What to consider when defining your collection's schema.
|
||||
4. How to enforce the schema when writing to a collection.
|
||||
5. How to carefully change the schema of your collection.
|
||||
6. How to deal with associations between records.
|
||||
|
||||
<h2 id="mongo-collections">MongoDB collections in Meteor</h2>
|
||||
|
||||
At its core, a web application offers its users a view into, and a way to modify, a persistent set of data. Whether managing a list of todos, or ordering a car to pick you up, you are interacting with a permanent but constantly changing data layer.
|
||||
|
||||
In Meteor, that data layer is typically stored in MongoDB. A set of related data in MongoDB is referred to as a "collection". In Meteor you access MongoDB through [collections](http://docs.meteor.com/api/collections.html#Mongo-Collection), making them the primary persistence mechanism for your app data.
|
||||
|
||||
However, collections are a lot more than a way to save and retrieve data. They also provide the core of the interactive, connected user experience that users expect from the best applications. Meteor makes this user experience easy to implement.
|
||||
|
||||
In this article, we'll look closely at how collections work in various places in the framework, and how to get the most out of them.
|
||||
|
||||
<h3 id="server-collections">Server-side collections</h3>
|
||||
|
||||
When you create a collection on the server:
|
||||
|
||||
```js
|
||||
Todos = new Mongo.Collection('todos');
|
||||
```
|
||||
|
||||
You are creating a collection within MongoDB, and an interface to that collection to be used on the server. It's a fairly straightforward layer on top of the underlying Node MongoDB driver, but with a synchronous API:
|
||||
|
||||
```js
|
||||
// This line won't complete until the insert is done
|
||||
Todos.insert({_id: 'my-todo'});
|
||||
// So this line will return something
|
||||
const todo = Todos.findOne({_id: 'my-todo'});
|
||||
// Look ma, no callbacks!
|
||||
console.log(todo);
|
||||
```
|
||||
|
||||
<h3 id="client-collections">Client-side collections</h3>
|
||||
|
||||
On the client, when you write the same line:
|
||||
|
||||
```js
|
||||
Todos = new Mongo.Collection('todos');
|
||||
```
|
||||
|
||||
It does something totally different!
|
||||
|
||||
On the client, there is no direct connection to the MongoDB database, and in fact a synchronous API to it is not possible (nor probably what you want). Instead, on the client, a collection is a client side *cache* of the database. This is achieved thanks to the [Minimongo](https://github.com/meteor/meteor/blob/master/packages/minimongo/README.md) library---an in-memory, all JS, implementation of the MongoDB API.
|
||||
|
||||
```js
|
||||
// This line is changing an in-memory Minimongo data structure
|
||||
Todos.insert({_id: 'my-todo'});
|
||||
// And this line is querying it
|
||||
const todo = Todos.findOne({_id: 'my-todo'});
|
||||
// So this happens right away!
|
||||
console.log(todo);
|
||||
```
|
||||
|
||||
The way that you move data from the server (and MongoDB-backed) collection into the client (in-memory) collection is the subject of the [data loading article](data-loading.html). Generally speaking, you *subscribe* to a *publication*, which pushes data from the server to the client. Usually, you can assume that the client contains an up-to-date copy of some subset of the full MongoDB collection.
|
||||
|
||||
To write data back to the server, you use a *Method*, the subject of the [methods article](methods.html).
|
||||
|
||||
<h3 id="local-collections">Local collections</h3>
|
||||
|
||||
There is a third way to use a collection in Meteor. On the client or server, if you create a collection in one of these two ways:
|
||||
|
||||
```js
|
||||
SelectedTodos = new Mongo.Collection(null);
|
||||
SelectedTodos = new Mongo.Collection('selectedtodos', {connection: null});
|
||||
```
|
||||
|
||||
This creates a *local collection*. This is a Minimongo collection that has no database connection (ordinarily a collection would either be directly connected to the database on the server, or via a subscription on the client).
|
||||
|
||||
A local collection is a convenient way to use the full power of the Minimongo library for in-memory storage. For instance, you might use it instead of a simple array if you need to execute complex queries over your data. Or you may want to take advantage of its *reactivity* on the client to drive some UI in a way that feels natural in Meteor.
|
||||
|
||||
<h2 id="schemas">Defining a schema</h2>
|
||||
|
||||
Although MongoDB is a schema-less database, which allows maximum flexibility in data structuring, it is generally good practice to use a schema to constrain the contents of your collection to conform to a known format. If you don't, then you tend to end up needing to write defensive code to check and confirm the structure of your data as it *comes out* of the database, instead of when it *goes into* the database. As in most things, you tend to *read data more often than you write it*, and so it's usually easier, and less buggy to use a schema when writing.
|
||||
|
||||
In Meteor, the pre-eminent schema package is the npm [simpl-schema](https://www.npmjs.com/package/simpl-schema) package. It's an expressive, MongoDB based schema that's used to insert and update documents. Another alternative is [jagi:astronomy](https://atmospherejs.com/jagi/astronomy) which is a full Object Model (OM) layer offering schema definition, server/client side validators, object methods and event handlers.
|
||||
|
||||
Let's assume that we have a `Lists` collection. To define a schema for this collection using `simpl-schema`, you can create a new instance of the `SimpleSchema` class and attach it to the `Lists` object:
|
||||
|
||||
```js
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
Lists.schema = new SimpleSchema({
|
||||
name: {type: String},
|
||||
incompleteCount: {type: Number, defaultValue: 0},
|
||||
userId: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true}
|
||||
});
|
||||
```
|
||||
|
||||
This example from the Todos app defines a schema with a few simple rules:
|
||||
|
||||
2. We specify that the `name` field of a list is required and must be a string.
|
||||
3. We specify the `incompleteCount` is a number, which on insertion is set to `0` if not otherwise specified.
|
||||
4. We specify that the `userId`, which is optional, must be a string that looks like the ID of a user document.
|
||||
|
||||
We attach the schema to the namespace of `Lists` directly, which allows us to check objects against this schema directly whenever we want, such as in a form or [Method](methods.html). In the [next section](#schemas-on-write) we'll see how to use this schema automatically when writing to the collection.
|
||||
|
||||
You can see that with relatively little code we've managed to restrict the format of a list significantly. You can read more about more complex things that can be done with schemas in the [Simple Schema docs](https://www.npmjs.com/package/simpl-schema).
|
||||
|
||||
<h3 id="validating-schemas">Validating against a schema</h3>
|
||||
|
||||
Now we have a schema, how do we use it?
|
||||
|
||||
It's pretty straightforward to validate a document with a schema. We can write:
|
||||
|
||||
```js
|
||||
const list = {
|
||||
name: 'My list',
|
||||
incompleteCount: 3
|
||||
};
|
||||
|
||||
Lists.schema.validate(list);
|
||||
```
|
||||
|
||||
In this case, as the list is valid according to the schema, the `validate()` line will run without problems. If however, we wrote:
|
||||
|
||||
```js
|
||||
const list = {
|
||||
name: 'My list',
|
||||
incompleteCount: 3,
|
||||
madeUpField: 'this should not be here'
|
||||
};
|
||||
|
||||
Lists.schema.validate(list);
|
||||
```
|
||||
|
||||
Then the `validate()` call will throw a `ValidationError` which contains details about what is wrong with the `list` document.
|
||||
|
||||
<h3 id="validation-error">The `ValidationError`</h3>
|
||||
|
||||
What is a [`ValidationError`](https://github.com/meteor/validation-error/)? It's a special error that is used in Meteor to indicate a user-input based error in modifying a collection. Typically, the details on a `ValidationError` are used to mark up a form with information about what inputs don't match the schema. In the [methods article](methods.html#validation-error), we'll see more about how this works.
|
||||
|
||||
<h2 id="schema-design">Designing your data schema</h2>
|
||||
|
||||
Now that you are familiar with the basic API of Simple Schema, it's worth considering a few of the constraints of the Meteor data system that can influence the design of your data schema. Although generally speaking you can build a Meteor data schema much like any MongoDB data schema, there are some important details to keep in mind.
|
||||
|
||||
The most important consideration is related to the way DDP, Meteor's data loading protocol, communicates documents over the wire. The key thing to realize is that DDP sends changes to documents at the level of top-level document *fields*. What this means is that if you have large and complex subfields on a document that change often, DDP can send unnecessary changes over the wire.
|
||||
|
||||
For instance, in "pure" MongoDB you might design the schema so that each list document had a field called `todos` which was an array of todo items:
|
||||
|
||||
```js
|
||||
Lists.schema = new SimpleSchema({
|
||||
name: {type: String},
|
||||
todos: {type: [Object]}
|
||||
});
|
||||
```
|
||||
|
||||
The issue with this schema is that due to the DDP behavior just mentioned, each change to *any* todo item in a list will require sending the *entire* set of todos for that list over the network. This is because DDP has no concept of "change the `text` field of the 3rd item in the field called `todos`". It can only "change the field called `todos` to a totally new array".
|
||||
|
||||
<h3 id="denormalization">Denormalization and multiple collections</h3>
|
||||
|
||||
The implication of the above is that we need to create more collections to contain sub-documents. In the case of the Todos application, we need both a `Lists` collection and a `Todos` collection to contain each list's todo items. Consequently we need to do some things that you'd typically associate with a SQL database, like using foreign keys (`todo.listId`) to associate one document with another.
|
||||
|
||||
In Meteor, it's often less of a problem doing this than it would be in a typical MongoDB application, as it's easy to publish overlapping sets of documents (we might need one set of users to render one screen of our app, and an intersecting set for another), which may stay on the client as we move around the application. So in that scenario there is an advantage to separating the subdocuments from the parent.
|
||||
|
||||
However, given that MongoDB prior to version 3.2 doesn't support queries over multiple collections ("joins"), we typically end up having to denormalize some data back onto the parent collection. Denormalization is the practice of storing the same piece of information in the database multiple times (as opposed to a non-redundant "normal" form). MongoDB is a database where denormalizing is encouraged, and thus optimized for this practice.
|
||||
|
||||
In the case of the Todos application, as we want to display the number of unfinished todos next to each list, we need to denormalize `list.incompleteTodoCount`. This is an inconvenience but typically reasonably easy to do as we'll see in the section on [abstracting denormalizers](#abstracting-denormalizers) below.
|
||||
|
||||
Another denormalization that this architecture sometimes requires can be from the parent document onto sub-documents. For instance, in Todos, as we enforce privacy of the todo lists via the `list.userId` attribute, but we publish the todos separately, it might make sense to denormalize `todo.userId` also. To do this, we'd need to be careful to take the `userId` from the list when creating the todo, and updating all relevant todos whenever a list's `userId` changed.
|
||||
|
||||
<h3 id="designing-for-future">Designing for the future</h3>
|
||||
|
||||
An application, especially a web application, is rarely finished, and it's useful to consider potential future changes when designing your data schema. As in most things, it's rarely a good idea to add fields before you actually need them (often what you anticipate doesn't actually end up happening, after all).
|
||||
|
||||
However, it's a good idea to think ahead to how the schema may change over time. For instance, you may have a list of strings on a document (perhaps a set of tags). Although it's tempting to leave them as a subfield on the document (assuming they don't change much), if there's a good chance that they'll end up becoming more complicated in the future (perhaps tags will have a creator, or subtags later on?), then it might be easier in the long run to make a separate collection from the beginning.
|
||||
|
||||
The amount of foresight you bake into your schema design will depend on your app's individual constraints, and will need to be a judgement call on your part.
|
||||
|
||||
<h3 id="schemas-on-write">Using schemas on write</h3>
|
||||
|
||||
Although there are a variety of ways that you can run data through a Simple Schema before sending it to your collection (for instance you could check a schema in every method call), the simplest and most reliable is to use the [`aldeed:collection2`](https://atmospherejs.com/aldeed/collection2) package to run every mutator (`insert/update/upsert` call) through the schema.
|
||||
|
||||
To do so, we use `attachSchema()`:
|
||||
|
||||
```js
|
||||
Lists.attachSchema(Lists.schema);
|
||||
```
|
||||
|
||||
What this means is that now every time we call `Lists.insert()`, `Lists.update()`, `Lists.upsert()`, first our document or modifier will be automatically checked against the schema (in subtly different ways depending on the exact mutator).
|
||||
|
||||
<h3 id="default-value">`defaultValue` and data cleaning</h3>
|
||||
|
||||
One thing that Collection2 does is ["clean" the data](https://www.npmjs.com/package/simpl-schema#cleaning-objects) before sending it to the database. This includes but is not limited to:
|
||||
|
||||
1. Coercing types - converting strings to numbers
|
||||
2. Removing attributes not in the schema
|
||||
3. Assigning default values based on the `defaultValue` in the schema definition
|
||||
|
||||
However, sometimes it's useful to do more complex initialization to documents before inserting them into collections. For instance, in the Todos app, we want to set the name of new lists to be `List X` where `X` is the next available unique letter.
|
||||
|
||||
To do so, we can subclass `Mongo.Collection` and write our own `insert()` method:
|
||||
|
||||
```js
|
||||
class ListsCollection extends Mongo.Collection {
|
||||
insert(list, callback) {
|
||||
if (!list.name) {
|
||||
let nextLetter = 'A';
|
||||
list.name = `List ${nextLetter}`;
|
||||
|
||||
while (!!this.findOne({name: list.name})) {
|
||||
// not going to be too smart here, can't go past Z
|
||||
nextLetter = String.fromCharCode(nextLetter.charCodeAt(0) + 1);
|
||||
list.name = `List ${nextLetter}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Call the original `insert` method, which will validate
|
||||
// against the schema
|
||||
return super.insert(list, callback);
|
||||
}
|
||||
}
|
||||
|
||||
Lists = new ListsCollection('lists');
|
||||
```
|
||||
|
||||
<h3 id="hooks">Hooks on insert/update/remove</h3>
|
||||
|
||||
The technique above can also be used to provide a location to "hook" extra functionality into the collection. For instance, when removing a list, we *always* want to remove all of its todos at the same time.
|
||||
|
||||
We can use a subclass for this case as well, overriding the `remove()` method:
|
||||
|
||||
```js
|
||||
class ListsCollection extends Mongo.Collection {
|
||||
// ...
|
||||
remove(selector, callback) {
|
||||
Package.todos.Todos.remove({listId: selector});
|
||||
return super.remove(selector, callback);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This technique has a few disadvantages:
|
||||
|
||||
1. Mutators can get very long when you want to hook in multiple times.
|
||||
2. Sometimes a single piece of functionality can be spread over multiple mutators.
|
||||
3. It can be a challenge to write a hook in a completely general way (that covers every possible selector and modifier), and it may not be necessary for your application (because perhaps you only ever call that mutator in one way).
|
||||
|
||||
A way to deal with points 1. and 2. is to separate out the set of hooks into their own module, and use the mutator as a point to call out to that module in a sensible way. We'll see an example of that [below](#abstracting-denormalizers).
|
||||
|
||||
Point 3. can usually be resolved by placing the hook in the *Method* that calls the mutator, rather than the hook itself. Although this is an imperfect compromise (as we need to be careful if we ever add another Method that calls that mutator in the future), it is better than writing a bunch of code that is never actually called (which is guaranteed to not work!), or giving the impression that your hook is more general that it actually is.
|
||||
|
||||
<h3 id="abstracting-denormalizers">Abstracting denormalizers</h3>
|
||||
|
||||
Denormalization may need to happen on various mutators of several collections. Therefore, it's sensible to define the denormalization logic in one place, and hook it into each mutator with one line of code. The advantage of this approach is that the denormalization logic is one place rather than spread over many files, but you can still examine the code for each collection and fully understand what happens on each update.
|
||||
|
||||
In the Todos example app, we build a `incompleteCountDenormalizer` to abstract the counting of incomplete todos on the lists. This code needs to run whenever a todo item is inserted, updated (checked or unchecked), or removed. The code looks like:
|
||||
|
||||
```js
|
||||
const incompleteCountDenormalizer = {
|
||||
_updateList(listId) {
|
||||
// Recalculate the correct incomplete count direct from MongoDB
|
||||
const incompleteCount = Todos.find({
|
||||
listId,
|
||||
checked: false
|
||||
}).count();
|
||||
|
||||
Lists.update(listId, {$set: {incompleteCount}});
|
||||
},
|
||||
afterInsertTodo(todo) {
|
||||
this._updateList(todo.listId);
|
||||
},
|
||||
afterUpdateTodo(selector, modifier) {
|
||||
// We only support very limited operations on todos
|
||||
check(modifier, {$set: Object});
|
||||
|
||||
// We can only deal with $set modifiers, but that's all we do in this app
|
||||
if (_.has(modifier.$set, 'checked')) {
|
||||
Todos.find(selector, {fields: {listId: 1}}).forEach(todo => {
|
||||
this._updateList(todo.listId);
|
||||
});
|
||||
}
|
||||
},
|
||||
// Here we need to take the list of todos being removed, selected *before* the update
|
||||
// because otherwise we can't figure out the relevant list id(s) (if the todo has been deleted)
|
||||
afterRemoveTodos(todos) {
|
||||
todos.forEach(todo => this._updateList(todo.listId));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
We are then able to wire in the denormalizer into the mutations of the `Todos` collection like so:
|
||||
|
||||
```js
|
||||
class TodosCollection extends Mongo.Collection {
|
||||
insert(doc, callback) {
|
||||
doc.createdAt = doc.createdAt || new Date();
|
||||
const result = super.insert(doc, callback);
|
||||
incompleteCountDenormalizer.afterInsertTodo(doc);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that we only handled the mutators we actually use in the application---we don't deal with all possible ways the todo count on a list could change. For example, if you changed the `listId` on a todo item, it would need to change the `incompleteCount` of *two* lists. However, since our application doesn't do this, we don't handle it in the denormalizer.
|
||||
|
||||
Dealing with every possible MongoDB operator is difficult to get right, as MongoDB has a rich modifier language. Instead we focus on dealing with the modifiers we know we'll see in our app. If this gets too tricky, then moving the hooks for the logic into the Methods that actually make the relevant modifications could be sensible (although you need to be diligent to ensure you do it in *all* the relevant places, both now and as the app changes in the future).
|
||||
|
||||
It could make sense for packages to exist to completely abstract some common denormalization techniques and actually attempt to deal with all possible modifications. If you write such a package, please let us know!
|
||||
|
||||
<h2 id="migrations">Migrating to a new schema</h2>
|
||||
|
||||
As we discussed above, trying to predict all future requirements of your data schema ahead of time is impossible. Inevitably, as a project matures, there will come a time when you need to change the schema of the database. You need to be careful about how you make the migration to the new schema to make sure your app works smoothly during and after the migration.
|
||||
|
||||
<h3 id="writing-migrations">Writing migrations</h3>
|
||||
|
||||
A useful package for writing migrations is [`percolate:migrations`](https://atmospherejs.com/percolate/migrations), which provides a nice framework for switching between different versions of your schema.
|
||||
|
||||
Suppose, as an example, that we wanted to add a `list.todoCount` field, and ensure that it was set for all existing lists. Then we might write the following in server-only code (e.g. `/server/migrations.js`):
|
||||
|
||||
```js
|
||||
Migrations.add({
|
||||
version: 1,
|
||||
up() {
|
||||
Lists.find({todoCount: {$exists: false}}).forEach(list => {
|
||||
const todoCount = Todos.find({listId: list._id}).count();
|
||||
Lists.update(list._id, {$set: {todoCount}});
|
||||
});
|
||||
},
|
||||
down() {
|
||||
Lists.update({}, {$unset: {todoCount: true}}, {multi: true});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This migration, which is sequenced to be the first migration to run over the database, will, when called, bring each list up to date with the current todo count.
|
||||
|
||||
To find out more about the API of the Migrations package, refer to [its documentation](https://atmospherejs.com/percolate/migrations).
|
||||
|
||||
<h3 id="bulk-data-changes">Bulk changes</h3>
|
||||
|
||||
If your migration needs to change a lot of data, and especially if you need to stop your app server while it's running, it may be a good idea to use a [MongoDB Bulk Operation](https://docs.mongodb.org/v3.0/core/bulk-write-operations/).
|
||||
|
||||
The advantage of a bulk operation is that it only requires a single round trip to MongoDB for the write, which usually means it is a *lot* faster. The downside is that if your migration is complex (which it usually is if you can't do an `.update(.., .., {multi: true})`), it can take a significant amount of time to prepare the bulk update.
|
||||
|
||||
What this means is if users are accessing the site whilst the update is being prepared, it will likely go out of service! Also, a bulk update will lock the entire collection while it is being applied, which can cause a significant blip in your user experience if it takes a while. For these reason, you often need to stop your server and let your users know you are performing maintenance while the update is happening.
|
||||
|
||||
We could write our above migration like so (note that you must be on MongoDB 2.6 or later for the bulk update operations to exist). We can access the native MongoDB API via [`Collection#rawCollection()`](http://docs.meteor.com/api/collections.html#Mongo-Collection-rawCollection):
|
||||
|
||||
```js
|
||||
Migrations.add({
|
||||
version: 1,
|
||||
up() {
|
||||
// This is how to get access to the raw MongoDB node collection that the Meteor server collection wraps
|
||||
const batch = Lists.rawCollection().initializeUnorderedBulkOp();
|
||||
|
||||
//Mongo throws an error if we execute a batch operation without actual operations, e.g. when Lists was empty.
|
||||
let hasUpdates = false;
|
||||
Lists.find({todoCount: {$exists: false}}).forEach(list => {
|
||||
const todoCount = Todos.find({listId: list._id}).count();
|
||||
// We have to use pure MongoDB syntax here, thus the `{_id: X}`
|
||||
batch.find({_id: list._id}).updateOne({$set: {todoCount}});
|
||||
hasUpdates = true;
|
||||
});
|
||||
|
||||
if(hasUpdates){
|
||||
// We need to wrap the async function to get a synchronous API that migrations expects
|
||||
const execute = Meteor.wrapAsync(batch.execute, batch);
|
||||
return execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
down() {
|
||||
Lists.update({}, {$unset: {todoCount: true}}, {multi: true});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that we could make this migration faster by using an [Aggregation](https://docs.mongodb.org/v3.4/aggregation/) to gather the initial set of todo counts.
|
||||
|
||||
<h3 id="running-migrations">Running migrations</h3>
|
||||
|
||||
To run a migration against your development database, it's easiest to use the Meteor shell:
|
||||
|
||||
```js
|
||||
// After running `meteor shell` on the command line:
|
||||
Migrations.migrateTo('latest');
|
||||
```
|
||||
|
||||
If the migration logs anything to the console, you'll see it in the terminal window that is running the Meteor server.
|
||||
|
||||
To run a migration against your production database, run your app locally in production mode (with production settings and environment variables, including database settings), and use the Meteor shell in the same way. What this does is run the `up()` function of all outstanding migrations, against your production database. In our case, it should ensure all lists have a `todoCount` field set.
|
||||
|
||||
A good way to do the above is to spin up a virtual machine close to your database that has Meteor installed and SSH access (a special EC2 instance that you start and stop for the purpose is a reasonable option), and running the command after shelling into it. That way any latencies between your machine and the database will be eliminated, but you still can be very careful about how the migration is run.
|
||||
|
||||
**Note that you should always make a database backup before running any migration!**
|
||||
|
||||
<h3 id="breaking-changes">Breaking schema changes</h3>
|
||||
|
||||
Sometimes when we change the schema of an application, we do so in a breaking way -- so that the old schema doesn't work properly with the new code base. For instance, if we had some UI code that heavily relied on all lists having a `todoCount` set, there would be a period, before the migration runs, in which the UI of our app would be broken after we deployed.
|
||||
|
||||
The simple way to work around the problem is to take the application down for the period in between deployment and completing the migration. This is far from ideal, especially considering some migrations can take hours to run (although using [Bulk Updates](#bulk-data-changes) probably helps a lot here).
|
||||
|
||||
A better approach is a multi-stage deployment. The basic idea is that:
|
||||
|
||||
1. Deploy a version of your application that can handle both the old and the new schema. In our case, it'd be code that doesn't expect the `todoCount` to be there, but which correctly updates it when new todos are created.
|
||||
2. Run the migration. At this point you should be confident that all lists have a `todoCount`.
|
||||
3. Deploy the new code that relies on the new schema and no longer knows how to deal with the old schema. Now we are safe to rely on `list.todoCount` in our UI.
|
||||
|
||||
Another thing to be aware of, especially with such multi-stage deploys, is that being prepared to rollback is important! For this reason, the migrations package allows you to specify a `down()` function and call `Migrations.migrateTo(x)` to migrate _back_ to version `x`.
|
||||
|
||||
So if we wanted to reverse our migration above, we'd run
|
||||
```js
|
||||
// The "0" migration is the unmigrated (before the first migration) state
|
||||
Migrations.migrateTo(0);
|
||||
```
|
||||
|
||||
If you find you need to roll your code version back, you'll need to be careful about the data, and step carefully through your deployment steps in reverse.
|
||||
|
||||
<h3 id="migration-caveats">Caveats</h3>
|
||||
|
||||
Some aspects of the migration strategy outlined above are possibly not the most ideal way to do things (although perhaps appropriate in many situations). Here are some other things to be aware of:
|
||||
|
||||
1. Usually it is better to not rely on your application code in migrations (because the application will change over time, and the migrations should not). For instance, having your migrations pass through your Collection2 collections (and thus check schemas, set autovalues etc) is likely to break them over time as your schemas change over time.
|
||||
|
||||
One way to avoid this problem is to not run old migrations on your database. This is a little bit limiting but can be made to work.
|
||||
|
||||
2. Running the migration on your local machine will probably make it take a lot longer as your machine isn't as close to the production database as it could be.
|
||||
|
||||
Deploying a special "migration application" to the same hardware as your real application is probably the best way to solve the above issues. It'd be amazing if such an application kept track of which migrations ran when, with logs and provided a UI to examine and run them. Perhaps a boilerplate application to do so could be built (if you do so, please let us know and we'll link to it here!).
|
||||
|
||||
<h2 id="associations">Associations between collections</h2>
|
||||
|
||||
As we discussed earlier, it's very common in Meteor applications to have associations between documents in different collections. Consequently, it's also very common to need to write queries fetching related documents once you have a document you are interested in (for instance all the todos that are in a single list).
|
||||
|
||||
To make this easier, we can attach functions to the prototype of the documents that belong to a given collection, to give us "methods" on the documents (in the object oriented sense). We can then use these methods to create new queries to find related documents.
|
||||
|
||||
To make things even easier, we can use the [`cultofcoders:grapher`](https://atmospherejs.com/cultofcoders/grapher) package to associate collections and fetch their relations. For example:
|
||||
|
||||
```js
|
||||
// Configure how collections relate to each other
|
||||
Todos.addLinks({
|
||||
list: {
|
||||
type: 'one',
|
||||
field: 'listId',
|
||||
collection: Lists
|
||||
}
|
||||
});
|
||||
|
||||
Lists.addLinks({
|
||||
todos: {
|
||||
collection: Todos,
|
||||
inversedBy: 'list' // This represents the name of the link we defined in Todos
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This allows us to properly fetch a list along with its todos:
|
||||
|
||||
```js
|
||||
// With Grapher you must always specify the fields you want
|
||||
const listsAndTodos = Lists.createQuery({
|
||||
name: 1,
|
||||
todos: {
|
||||
text: 1
|
||||
}
|
||||
}).fetch();
|
||||
```
|
||||
|
||||
`listsAndTodos` will look like this:
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
name: 'My List',
|
||||
todos: [
|
||||
{text: 'Do something'}
|
||||
],
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Grapher supports isomorphic queries (reactive and non-reactive), has built-in security features, works with many types of relationships, and more. Refer to the [Grapher documentation](https://github.com/cult-of-coders/grapher/blob/master/docs/index.md) for more details.
|
||||
|
||||
<h3 id="collection-helpers">Collection helpers</h3>
|
||||
|
||||
We can use the [`dburles:collection-helpers`](https://atmospherejs.com/dburles/collection-helpers) package to easily attach such methods (or "helpers") to documents. For instance:
|
||||
|
||||
```js
|
||||
Lists.helpers({
|
||||
// A list is considered to be private if it has a userId set
|
||||
isPrivate() {
|
||||
return !!this.userId;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Once we've attached this helper to the `Lists` collection, every time we fetch a list from the database (on the client or server), it will have a `.isPrivate()` function available:
|
||||
|
||||
```js
|
||||
const list = Lists.findOne();
|
||||
if (list.isPrivate()) {
|
||||
console.log('The first list is private!');
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="association-helpers">Association helpers</h3>
|
||||
|
||||
Now we can attach helpers to documents, we can define a helper that fetches related documents
|
||||
|
||||
```js
|
||||
Lists.helpers({
|
||||
todos() {
|
||||
return Todos.find({listId: this._id}, {sort: {createdAt: -1}});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Now we can find all the todos for a list:
|
||||
|
||||
```js
|
||||
const list = Lists.findOne();
|
||||
console.log(`The first list has ${list.todos().count()} todos`);
|
||||
```
|
||||
676
content/cordova.md
Normal file
@@ -0,0 +1,676 @@
|
||||
---
|
||||
title: Cordova
|
||||
description: How to build mobile apps using Meteor's Cordova integration.
|
||||
discourseTopicId: 20195
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. What Cordova is, and how Meteor integrates with it to build mobile apps from a single codebase
|
||||
1. How to set up your local machine for mobile development
|
||||
1. How to run and debug your app on a mobile device or simulator/emulator
|
||||
1. How hot code push allows you to update your mobile app's code without reinstalling the app on your device or submitting a new version to the store
|
||||
1. How to use Cordova plugins to take advantage of native device features
|
||||
1. How to access local files and remote resources from your app
|
||||
1. What you can do to create a good mobile user experience for your app
|
||||
1. How to configure your app to use your own app icon, launch screen, and set other preferences
|
||||
1. How to build your project and submit your mobile app to the store
|
||||
|
||||
<h2 id="introduction">Meteor Cordova integration</h2>
|
||||
|
||||
Meteor integrates with [Cordova](https://cordova.apache.org), a well-known Apache open source project, to build mobile apps from the same codebase you use to create regular web apps. With the Cordova integration in Meteor, you can take your existing app and run it on an iOS or Android device with a few commands.
|
||||
|
||||
A Cordova app is a web app written using HTML, CSS, and JavaScript as usual, but it runs in a [web view](#what-environment) embedded in a native app instead of in a stand-alone mobile browser. An important benefit of packaging up your web app as a Cordova app is that all your assets are bundled with the app. This ensures your app will load faster than a web app running on a remote server could, which can make a huge difference for users on slow mobile connections. Another feature of the Cordova integration in Meteor is support for [hot code push](#hot-code-push), which allows you to update your app on users' devices without going through the usual app store review process.
|
||||
|
||||
Cordova also opens up access to certain native device features through a [plugin architecture](#cordova-plugins). Plugins allow you to use features not usually available to web apps, such as accessing the device camera or the local file system, interact with barcode or NFC readers, etc.
|
||||
|
||||
Because a Cordova app is a web app, this means you use standard web elements to create your user interface instead of relying on platform-specific native UI components. Creating a good mobile user experience is an art in itself, but is fortunately helped by the availability of various frameworks and libraries.
|
||||
|
||||
> <h4 id="what-about-phonegap">What about PhoneGap?</h4>
|
||||
|
||||
> You may have heard of PhoneGap, and wonder how it relates to Cordova. PhoneGap is a product name used by Adobe since 2011, when they acquired a company called Nitobi, the original creators of what is now the Cordova project. When Adobe donated the code to Apache in 2012 to ensure a more open governance model, the open source project was rebranded as Cordova. PhoneGap is now one of the distributions of Cordova, on a par with other distributions like Ionic, Telerik, Monaca, or Intel XDK. These distributions mainly differ in tooling and integration with cloud services, but they share the underlying platform and plugins. Meteor could also be considered a Cordova distribution.
|
||||
|
||||
<h3 id="cordova-integration-in-meteor">How does it work?</h3>
|
||||
|
||||
With Meteor, there is no need to install Cordova yourself, or use the `cordova` command directly. Cordova project creation happens as part of the Meteor run and build commands, and the project itself is considered a build artifact (stored in `.meteor/local/cordova-build` in your app directory) that can be deleted and recreated at any time. Instead of having you modify Cordova's `config.xml` file, Meteor reads a [`mobile-config.js`](http://docs.meteor.com/api/mobile-config.html) file in the root of your app directory and uses the settings specified there to configure the generated project.
|
||||
|
||||
Cordova apps don’t load web content over the network, but rely on locally stored HTML, CSS, JavaScript code and other assets. While Cordova by default uses `file://` URLs to load the app, Meteor includes an integrated file serving mechanism on the device to support both bundling the initial assets and incrementally updating your app through [hot code push](#hot-code-push). This means your app will be served from `http://localhost:<port>`, which also has the benefit that web views consider it a [secure origin](https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features) and won't block any sensitive features (which they increasingly do for `file://` URLs).
|
||||
|
||||
The port mentioned above will be generated based on your app ID stored in the `.meteor/.id` file in your application. If you need to run multiple apps on the same device using the same source code, you should specify a different port for each running app, by using the `--cordova-server-port <port>` option when running the Cordova `run` and `build` commands. Otherwise you will not be able to run multiple apps simultaneously on iOS. One common symptom of this problem is the error `Failed binding IPv4 listening socket: Address already in use (48)` in the XCode console.
|
||||
|
||||
> <h4 id="what-port">What port will your app be served from?</h4>
|
||||
|
||||
> While Meteor uses a built-in request interception mechanism on Android, supporting `WKWebView` on iOS requires running a real embedded web server instead. That means the local web server needs a port to bind to, and we can’t simply use a fixed port because that might lead to conflicts when running multiple Meteor Cordova apps on the same device. The easiest solution may seem to use a randomized port, but this has a serious drawback: if the port changes each time you run the app, web features that depend on the origin (like caching, localStorage, IndexedDB) won’t persist between runs, and you also wouldn't be able to specify a stable OAuth redirect URL. So instead we now pick a port from a predetermined range (12000-13000), calculated based on the `appId`, a unique identifier that is part of every Meteor project. That ensures the same app will always use the same port, but it hopefully avoids collisions betweens apps as much as possible. (There is still a theoretical possibility of the selected port being in use. Currently, starting the local server will fail in that case.)
|
||||
|
||||
<h3 id="what-environment">The runtime environment</h3>
|
||||
|
||||
Cordova apps run in a web view. A web view is basically a browser without the browser UI. Browser engines differ in their underlying implementation and in what web standards they support. As a result, what web view your app runs on can have a huge impact on your app's performance and on the features you get to use. (If you want to know what features are supported on what browsers and versions, [caniuse.com](http://caniuse.com) is a great resource.)
|
||||
|
||||
<h4 id="what-environment-ios">iOS</h4>
|
||||
|
||||
The browser on iOS is Safari, which is based on the open source WebKit project, but tends to be somewhat slow in enabling new features. Because they use the same underlying framework, the features available to a web view match the features supported by Safari on the iOS release you're running on.
|
||||
|
||||
Meteor uses WKWebView by default, on both iOS 8 and iOS 9. WKWebView is part of the modern WebKit API introduced in iOS 8, and replaces UIWebView, which has been in iOS from the beginning. Its main benefit is that it runs in a separate process, allowing for much higher JavaScript performance (3–4x in some benchmarks!) because it can take advantage of Just-In-Time compilation (which UIWebView, running in the same process as your app, cannot do for security reasons).
|
||||
|
||||
> You may be aware that WKWebView on iOS 8 doesn't allow files to be loaded from the local filesystem. This is problematic for standard Cordova apps, because these use `file://` URLs to load the app. But because the Meteor integration serves assets from `localhost`, WKWebView works fine on both iOS 8 and iOS 9.
|
||||
|
||||
<h4 id="what-environment-android">Android</h4>
|
||||
|
||||
Android 5.0 and above come with a web view based on Chromium known as the [Android System Web View](https://play.google.com/store/apps/details?id=com.google.android.webview&hl=en), which can be automatically updated through the Play Store. This means updates to the web view can happen regularly and are independent of OS updates.
|
||||
|
||||
<h3 id="adding-platforms">Adding Cordova platforms</h3>
|
||||
|
||||
Every Meteor project targets a set of platforms. Platforms can be added to a Meteor project with `meteor add-platform`.
|
||||
|
||||
- `meteor add-platform ios` adds the iOS platform to a project.
|
||||
- `meteor add-platform android` adds the Android platform to a project.
|
||||
- `meteor remove-platform ios android` will remove the iOS and Android platforms from a project.
|
||||
- `meteor list-platforms` lists the platforms targeted by your project.
|
||||
|
||||
If your local machine does not (yet) fulfill the [prerequisites](#installing-prerequisites) for building apps for a mobile platform, an error message with a list of missing requirements is printed (but the platform is still added). You will have to make sure these requirements are fulfilled before you're able to build and run mobile apps from your machine.
|
||||
|
||||
<h2 id="installing-prerequisites">Installing prerequisites</h2>
|
||||
|
||||
In order to build and run mobile apps, you will need to install some prerequisites on your local machine.
|
||||
|
||||
<h3 id="installing-prerequisites-ios">iOS</h3>
|
||||
|
||||
In order to build and run iOS apps, you will need a Mac with [Apple Xcode](https://developer.apple.com/xcode/) developer tools installed. We recommend installing the latest version, but you should also check the [Meteor history](https://github.com/meteor/meteor/blob/devel/History.md) for any specific version dependencies. NOTE: To build with Xcode 10.2+, your webapp package must be v1.7.4 or higher.
|
||||
|
||||
<h4>Installing Xcode from the App Store</h4>
|
||||
|
||||
`meteor add-platform ios` will open a dialog asking you whether you want to install the 'command line developer tools'. Do not select 'Install' here, because a full Xcode installation is required to build and run iOS apps. Instead, selecting 'Get Xcode' will open the Mac App Store page for Xcode and you can click install there. (Alternatively, you can open the Mac App Store and search for 'Xcode' to get to that same page.)
|
||||
|
||||
<h4>Accepting the license agreement</h4>
|
||||
|
||||
After the download and installation completes, you will need to accept the license agreement. If you start Xcode for the first time, a dialog will pop up where you can read the license agreement and accept it. You can close Xcode directly afterwards.
|
||||
|
||||
A shortcut is to run `sudo xcodebuild -license accept` from the command line. (You will still be expected to have read and understood the [Xcode and Apple SDKs Agreement](https://www.apple.com/legal/sla/docs/xcode.pdf)).
|
||||
|
||||
> As of [Cordova iOS 4.3.0](https://cordova.apache.org/announcements/2016/10/24/ios-release.html) you may also need to `sudo gem install cocoapods` to resolve a dependency with [PhoneGap Push Plugin](https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md).
|
||||
|
||||
<h4>Enabling Xcode command line tools</h4>
|
||||
|
||||
After installing Xcode from the Mac App Store, it is still necesssary to enable those tools in the terminal environment. This can be accompilshed by running the following from the command prompt:
|
||||
```
|
||||
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
|
||||
```
|
||||
<h3 id="installing-prerequisites-android">Android</h3>
|
||||
|
||||
In order to build and run Android apps, you will need to:
|
||||
|
||||
- Install a Java Development Kit (JDK)
|
||||
- Install the Android SDK and download the required tools, platforms, and other components (which is done most easily by installing Android Studio)
|
||||
- Set `ANDROID_HOME` and add the tools directories to your `PATH`
|
||||
- Optionally: Create an Android Virtual Device to run apps on an emulator
|
||||
- If Gradle cannot be found: try using a package manager such as [Homebrew](https://brew.sh/), `apt-get`, or `yum` to install a system-wide, standalone version of `gradle`:
|
||||
```sh
|
||||
# On Mac OSX:
|
||||
brew install gradle
|
||||
|
||||
# On Debian/Ubuntu:
|
||||
sudo apt-get install gradle
|
||||
```
|
||||
More information about installing Gradle can be found [here](https://gradle.org/install/#install).
|
||||
|
||||
<h4>Installing the Java Development Kit (JDK)</h4>
|
||||
|
||||
> On Linux, you may want to use your distribution's package manager to install a JDK; on Ubuntu, you can even use [Ubuntu Make](#ubuntu-make) to install Android Studio and all dependencies at the same time.
|
||||
|
||||
1. Open the [Oracle Java website](http://www.oracle.com/technetwork/java/javase/downloads/index.html), and select the Java Platform (JDK)
|
||||
1. Check the box to accept the license agreement, and select the correct download for your platform
|
||||
1. After it has downloaded, launch the installer, and complete the installation steps
|
||||
|
||||
<h4>Installing Android Studio</h4>
|
||||
|
||||
The easiest way to get a working Android development environment is by installing [Android Studio](http://developer.android.com/sdk/index.html), which offers a setup wizard on first launch that installs the Android SDK for you, and downloads a default set of tools, platforms, and other components that you will need to start developing.
|
||||
|
||||
Please refer to [the Android Studio installation instructions](http://developer.android.com/sdk/installing/index.html?pkg=studio) for more details on the exact steps to follow.
|
||||
|
||||
> There is no need to use Android Studio if you prefer a stand-alone install. Just make sure you install the most recent versions of the [Android SDK Tools](http://developer.android.com/sdk/index.html#Other) and download the required [additional packages](http://developer.android.com/sdk/installing/adding-packages.html) yourself using the [Android SDK Manager](http://developer.android.com/tools/help/sdk-manager.html).
|
||||
|
||||
Make sure to select the correct version of the [Android Studio SDK Tools](https://developer.android.com/studio/releases/sdk-tools.html):
|
||||
|
||||
* Meteor 1.4.3.1 or later: Android SDK Tools v.25.**2**.x ([mac](https://dl.google.com/android/repository/tools_r25.2.3-macosx.zip), [linux](https://dl.google.com/android/repository/tools_r25.2.3-linux.zip), [windows](https://dl.google.com/android/repository/tools_r25.2.3-windows.zip)) or v.26.0.0 or later
|
||||
* v.25.**3.0** **will not work** due to [extensive changes](https://developer.android.com/studio/releases/sdk-tools.html). See [issue #8464](https://github.com/meteor/meteor/issues/8464) for more information.
|
||||
* Meteor 1.4.2.x or before: Android SDK Tools v.23 ([mac](https://dl.google.com/android/repository/tools_r23.0.1-macosx.zip), [linux](https://dl.google.com/android/repository/tools_r23.0.1-linux.zip), [windows](https://dl.google.com/android/repository/tools_r23.0.1-windows.zip))
|
||||
|
||||
To install an older version of SDK tools:
|
||||
|
||||
* Download the version that you need from the above links
|
||||
* Replace the `tools/` folder in `~/Library/Android/sdk/`
|
||||
|
||||
> Note: If you're using older version of Meteor, you may also need to install an older version of Android SDK, for example with the Android SDK Manager that comes with Android Studio.
|
||||
|
||||
<h4 id="ubuntu-make">Using Ubuntu Make</h4>
|
||||
|
||||
If you're running Ubuntu, one way to install both a Java Development Kit and Android Studio is by using [Ubuntu Make](https://wiki.ubuntu.com/ubuntu-make), a command line tool that sets up development environments and dependencies for you.
|
||||
|
||||
If you're on Ubuntu 14.04 LTS, you'll have to add the Ubuntu Make ppa first:
|
||||
* `sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make`
|
||||
* `sudo apt-get update`
|
||||
|
||||
Then, you can install Ubuntu Make itself:
|
||||
* `sudo apt-get install ubuntu-make`
|
||||
|
||||
And finally you use Ubuntu Make to install Android Studio and all dependencies:
|
||||
* `umake android`
|
||||
|
||||
<h4>Setting `ANDROID_HOME` and adding the tools directories to your `PATH`</h4>
|
||||
|
||||
Cordova will detect an Android SDK installed in various standard locations automatically, but in order to use tools like `android` or `adb` from the terminal, you will have to make some changes to your environment.
|
||||
|
||||
<h5>Mac</h5>
|
||||
|
||||
- Set the `ANDROID_HOME` environment variable to the location of the Android SDK. If you've used the Android Studio setup wizard, it should be installed in `~/Library/Android/sdk` by default.
|
||||
- Add `$ANDROID_HOME/tools`, and `$ANDROID_HOME/platform-tools` to your `PATH`
|
||||
|
||||
You can do this by adding these lines to your `~/.bash_profile` file (or the equivalent file for your shell environment, like `~/.zshrc`):
|
||||
```
|
||||
# Android
|
||||
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||||
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
|
||||
```
|
||||
|
||||
You will then have to reload `.bash_profile` (by executing `source ~/.bash_profile`) or open a new terminal session to apply the new environment.
|
||||
|
||||
<h4>Optionally: Creating an Android Virtual Device (AVD) to run apps on an emulator</h4>
|
||||
|
||||
The current Android emulator tends to be rather slow and can be unstable, so our recommendation is to run your app on a physical device instead.
|
||||
|
||||
If you do want to run on an emulator however, you will have to create an Android Virtual Device (AVD) using the [AVD Manager](http://developer.android.com/tools/devices/managing-avds.html). Make sure to configure an AVD with an API level that is supported by the version of [Cordova Android](https://github.com/apache/cordova-android/blob/master/RELEASENOTES.md) you are using.
|
||||
|
||||
<h2 id ="running-your-app">Developing on a device</h2>
|
||||
|
||||
During development, the Meteor [build tool](build-tool.html) integrates with Cordova to run your app on a physical device or the iOS Simulator/Android emulator. In addition to starting a development server and MongoDB instance as usual, `meteor run` accepts arguments to run the app on one or more mobile targets:
|
||||
|
||||
- `ios`: Runs the app on the iOS Simulator
|
||||
> This will run your app on a default simulated iOS device. You can open Xcode to install and select another simulated device.
|
||||
- `ios-device`: Opens Xcode, where you can run the app on a connected iOS device or simulator
|
||||
- `android`: Runs the app on the Android emulator
|
||||
> The current Android emulator tends to be rather slow and can be unstable. Our recommendation is to run on a physical device or to use an alternative emulator like [Genymotion](https://www.genymotion.com).
|
||||
- `android-device`: Runs the app on a connected Android device
|
||||
|
||||
You can specify multiple targets, so `meteor run ios android-device` will run the app on both the iOS Simulator and an Android device for example.
|
||||
|
||||
<h4 id="connecting-to-the-server">Connecting to the server</h4>
|
||||
|
||||
A Meteor app should be able to connect to a server in order to load data and to enable [hot code push](#hot-code-push), which automatically updates a running app when you make changes to its files. During development, this means the device and the computer you run `meteor` on will have to be part of the same WiFi network, and the network configuration shouldn't prevent the device from reaching the server. You may have to change your firewall or router settings to allow for this (no client isolation).
|
||||
|
||||
`meteor run` will try to detect the local IP address of the computer running the command automatically. If this fails, or if you would like your mobile app to connect to a different server, you can specify an address using the `--mobile-server` option.
|
||||
|
||||
<h3 id="running-on-ios">On iOS</h3>
|
||||
|
||||
> Note: If you haven't previously developed iOS apps, or haven't used the connected device for development, a series of dialogs and warnings may appear as Xcode resolves code signing issues. It may also prompt you for permission to access the key in your keychain. See [Apple's instructions](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/LaunchingYourApponDevices/LaunchingYourApponDevices.html#//apple_ref/doc/uid/TP40012582-CH27-SW4) for more information. You will also need to join the [Apple Developer Program](https://developer.apple.com/programs/) to deploy your app on the Apple iOS App Store.
|
||||
|
||||
1. Make sure the device is connected to your computer via a USB cable.
|
||||
1. Connect the device to a WiFi network that allows for communication with the server.
|
||||
1. Run `meteor run ios-device` to open your project in Xcode.
|
||||
1. In the project navigator, choose your device from the Scheme toolbar menu:
|
||||
<img src="images/mobile/xcode-select-device.png" style="width: 30%; height: 30%">
|
||||
1. Click the Run button:
|
||||
<img src="images/mobile/xcode-run-scheme.png" style="width: 50%; height: 50%">
|
||||
1. Xcode builds the app, installs it on the device, and launches it.
|
||||
|
||||
<h3 id="running-on-android">On Android</h3>
|
||||
|
||||
1. Make sure the device is connected to your computer via a USB cable.
|
||||
1. Connect the device to a WiFi network that allows for communication with the server.
|
||||
1. Make sure your device is set up for development [as explained here](http://developer.android.com/tools/device.html#setting-up).
|
||||
1. You may also need to click 'Allow' on the `Allow USB debugging?` prompt on the device.
|
||||
1. Run `meteor run android-device` to build the app, install it on the device, and launch it.
|
||||
|
||||
> To check if your device has been connected and set up correctly, you can run `adb devices` to get a list of devices.
|
||||
|
||||
<h2 id="logging-and-remote-debugging">Logging and debugging</h2>
|
||||
|
||||
A full-stack mobile app consists of many moving parts, and this can make it difficult to diagnose issues. Logging is indispensable in keeping track of what's going on in your app, and may show warnings and errors that you would otherwise miss. Even more powerful is remote debugging, which is the ability to interact with a mobile app running on a remote device from a debugging interface in Safari (for iOS) or Chrome (for Android).
|
||||
|
||||
<h3 id="understanding=logs">Different types of logs</h3>
|
||||
|
||||
You will encounter three types of logs in a Meteor Cordova app:
|
||||
|
||||
- **Server-side logs** - Messages printed by the Meteor build system, and the result of `console` logging calls from server-side code.
|
||||
- **Client-side web logs** - Warnings and errors from the web view, and the result of `console` logging calls from client-side code.
|
||||
- **Client-side native logs** - Messages from system components and Cordova plugins. This also includes more detailed logging from the Meteor plugin used for [hot code push](#hot-code-push).
|
||||
|
||||
When using `meteor run`, server-side logs will be printed in the terminal as usual. In addition, running on an Android device or emulator will print a subset of the logs to that same terminal (these logs also include `console` logging calls made from client-side code).
|
||||
|
||||
Running on iOS will not show client-side logs in the terminal, but Xcode will show native logs as usual in the [debug console](https://developer.apple.com/library/tvos/documentation/DeveloperTools/Conceptual/debugging_with_xcode/chapters/debugging_tools.html). You can add [cordova-plugin-console](https://github.com/apache/cordova-plugin-console) to your project to output `console` logging calls to the native logs (which Android does by default), but this isn't recommended because it has a substantial performance impact, and remote debugging gives you much nicer and more complete console output.
|
||||
|
||||
Although having client-side logs in the terminal can be useful, in most cases remote debugging is a much better option. This allows you to use the debugging tools built into Safari (for iOS apps) or Chrome (for Android apps) to investigate an app running on a remote device or a simulator/emulator. Here, you can not only view the logs, but also interact with running JavaScript code and the DOM, monitor network access, etc.
|
||||
|
||||
<h3 id="remote-debugging-ios">Debugging on iOS with Safari</h3>
|
||||
|
||||
1. To use remote debugging in Safari, you'll first need to enable the Developer menu. Go to *Safari > Preferences* and make sure 'Show Develop menu in menu bar' is checked:
|
||||
<img src="images/mobile/mac-safari-preferences-show-develop-menu.png">
|
||||
|
||||
1. You'll also need to enable the Web Inspector on your iOS device. Go to *Settings > Safari > Advanced* and enable 'Web Inspector':
|
||||
<img src="images/mobile/ios-safari-settings-web-inspector.png" style="width: 75%; height: 75%">
|
||||
|
||||
1. Launch the app on your device and open remote debugger by choosing *Develop > <Your device> > <Your app>/localhost*.
|
||||
|
||||
1. Because you can only connect to your app after it has started up, you sometimes miss startup warnings and errors. You can invoke `location.reload()` in the Web Inspector console to reload a running app, this time with the remote debugger connected.
|
||||
|
||||
You can find more information about remote debugging in the [Safari Developer Guide](https://developer.apple.com/library/safari/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/).
|
||||
|
||||
<h3 id="remote-debugging-android">Debugging on Android with Chrome</h3>
|
||||
|
||||
See [this article](https://developers.google.com/web/tools/chrome-devtools/debug/remote-debugging/remote-debugging#remote-debugging-on-android-with-chrome-devtools) for instructions on how to remote debug your Android app with the Chrome DevTools.
|
||||
|
||||
- Because you can only connect to your app after it has started up, you sometimes miss startup warnings and errors. You can invoke `location.reload()` in the DevTools console to reload a running app, this time with the remote debugger connected.
|
||||
|
||||
- An .apk built by `meteor build` cannot be remotely debugged unless you make a debug build via `meteor build --debug`.
|
||||
|
||||
<h2 id="hot-code-push">Hot code push on mobile</h2>
|
||||
|
||||
During development, the Meteor [build tool](build-tool.html) detects any relevant file changes, recompiles the necessary files, and notifies all connected clients a new version is available. Clients can then automatically reload the app, switching over to the new version of the code. This is referred to as *hot code push*.
|
||||
|
||||
Meteor supports hot code push on both browser and mobile clients, but the process on mobile is a bit different. In a browser, reloading the app will re-request assets from the server, and the server will respond with the most recent versions. Because Cordova apps rely on locally stored assets however, hot code push on mobile is a two step process:
|
||||
|
||||
1. Updated assets are downloaded from the server using native downloading mechanisms, and stored on the device
|
||||
1. The page is reloaded and the web view re-requests the assets from the local web server
|
||||
|
||||
An important benefit of this is that while downloading may be slow over mobile connections, this is done in the background, and we won't attempt to reload the app until all assets have been downloaded to the device.
|
||||
|
||||
Downloading updates is done incrementally, so we only download assets that have actually changed (based on a content hash). In addition, if we haven't been able to download all changed assets in one go, because of a network failure or because the app was closed before we finished, we will reuse the ones that have already completed downloading the next time the app starts up or the network connection is restored.
|
||||
|
||||
If Hot Code Push is not working reliably in your app, and this section doesn't help, see our [guide on diagnosing Hot Code Push issues](/hot-code-push).
|
||||
|
||||
<h3 id="updating-production-apps">In production</h3>
|
||||
|
||||
Hot code push greatly improves the development experience, but on mobile, it is also a really useful feature for production apps, because it allows you to quickly push updates to devices without having users update the app through the store and without going through a possibly lengthy review process to get your update accepted.
|
||||
|
||||
However, it is important to realize that hot code push can only be used to update the HTML, CSS, JavaScript code and other assets making up your web app. Changes to native code will still require you [to submit a new version of your app to the store](#building-and-submitting).
|
||||
|
||||
In order to avoid a situation where JavaScript code that relies on changed native code is pushed to a client, we calculate a compatibility version hash from the Cordova platform and plugin versions, and only download a new version to a device when there is an exact match. This means any change to the list of plugins, or updating to a Meteor release which contains a new platform version, will block hot code push to existing mobile clients until the app has been updated from the store.
|
||||
|
||||
Something else to keep in mind is that your server-side code should be prepared to handle requests from older client versions, which may not yet have been updated. As you make changes to your data schema or publication functions for example, you may want to reflect on how this will impact backwards compatibility.
|
||||
|
||||
<h3 id="controlling-compatibility-version">Controlling compatibility version</h3>
|
||||
|
||||
The compatibility version can be found in the `cordovaCompatibilityVersions` attribute of the JSON file served at `ROOT_URL/__cordova/manifest.json` during `meteor run [ios/android]`.
|
||||
|
||||

|
||||
|
||||
You may want to override the compatibility version if you want hot code push to reach older apps that don't have the latest version of your native code from the app store. Let's say you're developing an iOS app, you have the plugin `cordova-plugin-camera@2.4.0`, and your app has the compatibility version pictured above, `3ed5b9318b2916b595f7721759ead4d708dfbd46`. If you were to update to version `2.4.1` of `cordova-plugin-camera`, your server would generate a new compatibility version and your users' apps would stop receiving hot code pushes. However, you can tell your server to use the old compatilibity version:
|
||||
|
||||
```sh
|
||||
METEOR_CORDOVA_COMPAT_VERSION_IOS=3ed5b9318b2916b595f7721759ead4d708dfbd46 meteor run ios-device
|
||||
# or
|
||||
METEOR_CORDOVA_COMPAT_VERSION_IOS=3ed5b9318b2916b595f7721759ead4d708dfbd46 meteor build ../build --server=127.0.0.1:3000
|
||||
```
|
||||
|
||||
Now your users' apps will continue receiving hot code pushes. However, they won't get the new version of the Cordova plugin until they update from the app store. In this case, that's okay, because we only updated a patch version, so the `cordova-plugin-camera` API didn't change. But if you had added a new plugin, like `cordova-plugin-gyroscope`, and changed your Javascript to call `navigator.gyroscope.getCurrent()`, then when the old apps get the new JS code, they will throw the error: `Uncaught TypeError: Cannot read property 'getCurrent' of undefined`.
|
||||
|
||||
Another option is using the `METEOR_CORDOVA_COMPAT_VERSION_EXCLUDE` environment variable. If you were to do this:
|
||||
|
||||
```sh
|
||||
meteor add cordova:cordova-plugin-camera@4.1.0
|
||||
meteor add cordova:cordova-plugin-gyroscope@0.1.4
|
||||
METEOR_CORDOVA_COMPAT_VERSION_EXCLUDE='cordova-plugin-camera,cordova-plugin-gyroscope' meteor run ios-device
|
||||
```
|
||||
|
||||
your compatibility version would not change.
|
||||
|
||||
The `METEOR_CORDOVA_COMPAT_VERSION_*` env vars must be present __while building__ your app through `run`, `build` or `deploy`.
|
||||
|
||||
<h4 id="access-versions-inside-app">Access compatibility versions inside your app</h4>
|
||||
|
||||
The `version` attribute of `manifest.json`, which reflects the version of only your JS bundle, is accessible from JS at `__meteor_runtime_config__.autoupdateVersionCordova`.
|
||||
|
||||
The `cordovaCompatibilityVersions.*` attributes can be read from the manifest file with `cordova-plugin-file`.
|
||||
|
||||
<h3 id="configuring-server-for-hot-code-push">Configuring your server</h3>
|
||||
|
||||
As mentioned before, mobile apps need to be able to [connect to a server](#connecting-to-the-server) to support hot code push. In production, you will need to specify which server to connect to [when building the app](#building-for-production) using the `--server` option. The specified server address is used to set `ROOT_URL` in `__meteor_runtime_config__`, which is defined as part of the generated `index.html` in the app bundle.
|
||||
|
||||
In addition, you will need to configure the server with the right connection address. This happens automatically if you're using `meteor deploy` to deploy to Galaxy, but when deploying to your own server you'll have to make sure to define the `ROOT_URL` environment variable there. (For Meteor Up, you can configure this in `mup.json`.)
|
||||
|
||||
The reason this is needed is because updates delivered through hot code push replace the initially bundled `index.html` with a freshly generated one. If the `ROOT_URL` on your server hasn't been set, it defaults to `localhost:3000`, and this would leave the app unable to connect to the server, both for data loading and for receiving further hot code pushes. In Meteor 1.3, we protect against this by blocking updates that would change the `ROOT_URL` to `localhost`, but the consequence of this is that hot code push is disabled until you configure `ROOT_URL` correctly.
|
||||
|
||||
<h3 id="recovering-from-faulty-versions">Recovering from faulty versions</h3>
|
||||
|
||||
Hot code pushing new JavaScript code to a device could accidentally push code containing errors, which might leave users with a broken app (a "white screen of death" in the worst case), and could even disable hot code push (because the code that makes a connection to the server may no longer run).
|
||||
|
||||
To avoid this, we try to detect faulty versions and revert to the last known good version when this happens. The way detection works is that we expect all `Meteor.startup()` callbacks to complete within a set period of time. If this doesn't happen we consider the version faulty and will rollback the update. Unless the version on the server has been updated in the meantime, the server will try to hot code push the faulty version again. Therefore, we blacklist faulty versions on the device so we know not to retry.
|
||||
|
||||
By default, the startup timeout is set to 20 seconds. If your app needs more time to startup (or considerably less), you can use [`App.setPreference`](http://docs.meteor.com/api/mobile-config.html#App-setPreference) to set `WebAppStartupTimeout` to another value.
|
||||
|
||||
```js
|
||||
// The timeout is specified in milliseconds!
|
||||
App.setPreference('WebAppStartupTimeout', 30000);
|
||||
```
|
||||
|
||||
<h2 id="cordova-plugins">Native features with Cordova plugins</h2>
|
||||
|
||||
Cordova comes with a plugin architecture that opens up access to features not usually available to web apps. Plugins are installable add-ons that contain both JavaScript and native code, which allows them to translate calls from your web app to platform-specific APIs.
|
||||
|
||||
The Apache Cordova project maintains a set of [core plugins](https://cordova.apache.org/docs/en/dev/guide/support/index.html#core-plugin-apis) that provide access to various native device features such as the camera, contacts, or access to the file system. But anyone can write a Cordova plugin to do basically anything that can be done from native code, and many third-party plugins are available. You can [search for plugins on the Cordova website](https://cordova.apache.org/plugins/) or directly on [npm](https://www.npmjs.com/search?q=ecosystem%3Acordova).
|
||||
|
||||
Be warned however, that although the core plugins are generally well maintained and up to date with the rest of Cordova, the quality of third-party plugins can be a bit of a gamble. You also have to make sure the plugin you want to use is [compatible with the Cordova platform versions Meteor bundles](#plugin-compatibility).
|
||||
|
||||
<h3 id="installing-plugins">Installing plugins</h3>
|
||||
|
||||
Plugins are identified by a name, which is generally the same as their npm package name. The current convention is for plugin names to start with `cordova-plugin-`, but not all third-party plugins adhere to this.
|
||||
|
||||
You can add Cordova plugins to your project either directly, or as a dependency of a Meteor package.
|
||||
|
||||
If you want to add a plugin to your project directly, you use the same `meteor add` command you use for Meteor packages, but with a `cordova:` prefix:
|
||||
|
||||
```sh
|
||||
meteor add cordova:cordova-plugin-camera@1.2.0
|
||||
```
|
||||
|
||||
In contrast to Meteor packages, you'll have to specify the exact version of the plugin. This can be a bit of a pain because you first need to look up what the most recent [(compatible)](#plugin-compatibility) version of a plugin is before you can add it.
|
||||
|
||||
A Meteor package can register a dependency on a Cordova plugin with the `Cordova.depends()` syntax. For example, a Meteor package that depends on the Cordova camera plugin would add the following to its `package.js`:
|
||||
|
||||
```js
|
||||
Cordova.depends({
|
||||
'cordova-plugin-camera': '1.2.0'
|
||||
});
|
||||
```
|
||||
|
||||
This means adding the Meteor package to your project would also install the specified Cordova plugin.
|
||||
|
||||
> Note: If multiple Meteor packages add the same Cordova plugin but at different versions, there is no clear way of telling which version will end up being installed. Plugins added to your project directly however, will always override versions of the same plugin added as a dependency of packages.
|
||||
|
||||
Because installing plugins into a Cordova project already containing plugins can lead to indeterminate results, Meteor will remove and add back all plugins whenever a change to any of the plugins in your project is made.
|
||||
|
||||
Cordova downloads plugins from npm, and caches them (in `~/.cordova/lib/npm_cache`) so they don't have to be downloaded repeatedly if you rebuild or use them again in another project.
|
||||
|
||||
> <h4 id="plugin-compatibility">Making sure a plugin is compatible with the bundled Cordova platform versions</h4>
|
||||
|
||||
> Because there is a tight coupling between plugin versions and Cordova platform versions, you may encounter build time or runtime errors as a result of incompatible plugins. If this happens, you will have to install a different plugin version, or it may turn out a plugin is not (yet) compatible with the Cordova platform versions we bundle.
|
||||
|
||||
> In order to help with this, we pin core plugins to a minimum version known to work with the Cordova versions we bundle. This mechanism doesn't apply to third-party plugins however, so you'll have to assess compatibility for these yourself.
|
||||
|
||||
> There is ongoing work in the Cordova project that will improve this situation and make it easier for plugins to specify their platform dependencies, so Cordova can determine compatible versions.
|
||||
|
||||
<h4>Setting plugin parameters</h4>
|
||||
|
||||
Some Cordova plugins require certain parameters to be set as part of the build process. For example, `com-phonegap-plugins-facebookconnect` requires you to specify an `APP_ID` and `APP_NAME`. You can set these using `App.configurePlugin` in your [mobile-config.js](http://docs.meteor.com/api/mobile-config.html).
|
||||
|
||||
<h4>Installing a plugin from Git</h4>
|
||||
|
||||
Alternatively, if unreleased changes have been made to a plugin you'd like to use, you can also have Cordova download plugin code from a Git repository. Note that this will clone the plugin repository on every rebuild however, so this can be rather slow and should be avoided where possible. In contrast to default Cordova, Meteor requires you to specify the exact SHA hash for a commit, rather than allow you to refer to a branch or tag. This is done to guarantee repeatable builds and also avoids unnecessary reinstallation of all plugins because as long as the SHA is the same we know nothing has changed.
|
||||
|
||||
The syntax to add a plugin from Git is kind of awkward. The name (the part before the `@`) is the plugin ID and will have to match what is specified in the plugin's `plugin.xml`. Instead of a version, you specify a URL to a Git repository with the SHA hash as an anchor (the part after the `#`):
|
||||
|
||||
```sh
|
||||
meteor add cordova:com.phonegap.plugins.facebookconnect@https://github.com/Wizcorp/phonegap-facebook-plugin.git#5dbb1583168558b4447a13235283803151cb04ec
|
||||
```
|
||||
|
||||
Meteor packages can also depend on plugins downloaded from Git:
|
||||
|
||||
```js
|
||||
Cordova.depends({
|
||||
'com.phonegap.plugins.facebookconnect': 'https://github.com/Wizcorp/phonegap-facebook-plugin.git#5dbb1583168558b4447a13235283803151cb04ec'
|
||||
});
|
||||
```
|
||||
|
||||
<h4>Installing a plugin from the local file system</h4>
|
||||
|
||||
Finally, especially if you're developing your own plugin, installing it from the local filesystem can be a convenient way to keep up with changes you make to plugin code. The downside of this is that Meteor will reinstall all plugins on every build however, so this could really slow things down. We do add local plugins with the `--link` option however, so Cordova will try to install the plugin's files using symlinks instead of copying them, which means changes to files will be reflected in the generated native project (e.g. an Xcode project) and may not require a rebuild.
|
||||
|
||||
You install plugins from the local file system by specifying a `file://` URL, which gets interpreted relative to the project directory:
|
||||
|
||||
```sh
|
||||
meteor add cordova:cordova-plugin-underdevelopment@file://../plugins/cordova-plugin-underdevelopment
|
||||
```
|
||||
|
||||
Meteor packages can also depend on plugins installed from the local file system, although this probably only makes sense for local packages:
|
||||
|
||||
```js
|
||||
Cordova.depends({
|
||||
'cordova-plugin-underdevelopment': 'file://../plugins/cordova-plugin-underdevelopment'
|
||||
});
|
||||
```
|
||||
|
||||
<h4>Removing directly installed plugins</h4>
|
||||
|
||||
You can remove a previously added plugin using `meteor remove`:
|
||||
|
||||
```sh
|
||||
meteor remove cordova:cordova-plugin-camera
|
||||
meteor remove cordova:com.phonegap.plugins.facebookconnect
|
||||
meteor remove cordova:cordova-plugin-underdevelopment
|
||||
```
|
||||
|
||||
<h3 id="using-plugins">Using plugins</h3>
|
||||
|
||||
You should wrap any functionality which relies on a Cordova plugin in a `Meteor.startup()` block to make sure the plugin has been fully initialized (by listening to the `deviceready` event). For example, when using the Cordova geolocation plugin:
|
||||
|
||||
```js
|
||||
// The plugin may not have been initialized here
|
||||
navigator.geolocation.getCurrentPosition(success);
|
||||
|
||||
Meteor.startup(function() {
|
||||
// Here we can be sure the plugin has been initialized
|
||||
navigator.geolocation.getCurrentPosition(success);
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="cordova-specific-javascript">Detecting Cordova in your JavaScript code</h3>
|
||||
|
||||
Just as you can use `Meteor.isServer` and `Meteor.isClient` to separate your client-side and server-side code, you can use `Meteor.isCordova` to separate your Cordova-specific code from the rest of your code.
|
||||
|
||||
```js
|
||||
if (Meteor.isServer) {
|
||||
console.log("Printed on the server");
|
||||
}
|
||||
|
||||
if (Meteor.isClient) {
|
||||
console.log("Printed in browsers and mobile apps");
|
||||
}
|
||||
|
||||
if (Meteor.isCordova) {
|
||||
console.log("Printed only in mobile Cordova apps");
|
||||
}
|
||||
```
|
||||
|
||||
In addition, packages can include a different set of files for Cordova builds and browser builds with `addFiles`:
|
||||
|
||||
- `api.addFiles('foo.js', 'web.cordova')`: includes `foo.js` in only Cordova builds.
|
||||
- `api.addFiles('bar.js', 'web.browser')`: includes `bar.js` in only browser builds.
|
||||
- `api.addFiles('baz.js', 'web')`: includes `baz.js` in all client builds.
|
||||
|
||||
The same syntax can be used for `api.use`, `api.imply`, and `api.export`.
|
||||
|
||||
<h2 id="accessing-local-files-and-remote-resources">Accessing local files and remote resources</h2>
|
||||
|
||||
As a web app, Cordova apps are subject to various security mechanisms designed to protect the integrity of your code and to avoid certain types of attacks. Which security mechanisms are in use may depend on the type and version of the web view your app runs in. In addition, Cordova itself, and in some cases the OS, adds different levels of access control that may also affect what content can and cannot be loaded. All this can make it fairly confusing to understand why something is not working, and even harder to understand the security implications of the various ways of configuring these mechanisms.
|
||||
|
||||
<h3 id="accessing-local-files">Local files</h3>
|
||||
|
||||
Because the Cordova integration in Meteor does not serve your app from `file://` URLs, access to local files through `file://` URLs is not allowed either due to the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
|
||||
|
||||
The file serving mechanism used in Meteor allows for local file access through URLs of the form `http://localhost:<port>/local-filesystem/<path>`) however. You can construct these file system URLs manually, or use `WebAppLocalServer.localFileSystemUrl()` to convert `file://` URLs. You can use this to convert URLs received from plugins like `cordova-plugin-file` and `cordova-plugin-camera` for example.
|
||||
|
||||
<h3 id="domain-whitelisting">Domain whitelisting</h3>
|
||||
|
||||
Cordova controls access to external domains through a whitelisting mechanism, which is implemented as [`cordova-plugin-whitelist`](https://github.com/apache/cordova-plugin-whitelist) in the version of Cordova we bundle.
|
||||
|
||||
In Meteor, you use [`App.accessRule`](http://docs.meteor.com/api/mobile-config.html#App-accessRule) in [`mobile-config.js`](http://docs.meteor.com/api/mobile-config.html) to set additional rules. (These correspond to `<access>`, `<allow-navigation>` and `<allow-intent>` tags in the generated `config.xml`.)
|
||||
|
||||
> On iOS, these settings also control [Application Transport Security (ATS)](https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33), which is an OS level mechanism to enforce security best practices new to iOS 9. If the server you're connecting to does not (yet) fulfill these requirements, you can use additional options to override them for specific domains:
|
||||
> ```js
|
||||
App.accessRule('https://domain.com', {
|
||||
'minimum-tls-version': 'TLSv1.0',
|
||||
'requires-forward-secrecy': false,
|
||||
});
|
||||
```
|
||||
|
||||
By default, Cordova apps in Meteor are only allowed access to `localhost` (the device itself, to serve the app from) and the server your app connects to for data loading and hot code push (either an automatically detected IP address an explicitly configured mobile server domain). These restrictions also apply to loading files in iframes and to opening files in other apps (including the mobile browser).
|
||||
|
||||
> Note that these restrictions mean you will have to explicitly allow loading `data:` URLs. For example, to allow loading `data:` URLs in iframes you would add:
|
||||
> ```js
|
||||
App.accessRule('data:*', { type: 'navigation' });
|
||||
```
|
||||
|
||||
<h3 id="csp">Content Security Policy (CSP)</h3>
|
||||
|
||||
In addition to the domain whitelisting mechanism Cordova implements, the web view itself may also enforce access rules through [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/Security/CSP). For now, Meteor adds a permissive `<meta http-equiv="Content-Security-Policy" content="..."` header to the generated index page. If users want more fine grained control, Meteor recommends Helmet. See the [`guide`](https://guide.meteor.com/security.html#httpheaders) for more details.
|
||||
|
||||
<h3 id="cors">Cross-Origin Resource Sharing (CORS)</h3>
|
||||
|
||||
What is often confusing to people is that setting `App.accessRule` is not enough to allow access to remote resources. While domain whitelisting allows the client to control which domains it can connect to, additional restrictions based on the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) also apply. By default, web views will not allow cross-origin HTTP requests initiated from JavaScript for instance, so you will likely run into this when using [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest).
|
||||
|
||||
To get around these restrictions, you'll have to use what is known as [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). In contrast to the whitelisting mechanism configured on the client, CORS relies on headers set by the server. In other words, in order to allow access to a remote resource, you may have to make configuration changes on the server, such as setting a `Access-Control-Allow-Origin` header.
|
||||
|
||||
<h3 id="system-permissions">System Permissions</h3>
|
||||
Since the release of iOS 8.0 and Android 6.0 (Android Marshmallow), certain system features (e.g. camera, microphone, location, photos, etc.) typically require additional permissions in order to access them, and for iOS 10+ you must also provide a customized privacy usage notification prompt. These values for Android are specified in your app's `AndroidManifest.xml` file and are **also requested at runtime**. For iOS they are specified in your apps `Info.plist` file.
|
||||
|
||||
To request them at runtime, consider using the [`cordova.plugins.diagnostic`](https://github.com/dpa99c/cordova-diagnostic-plugin) plugin.
|
||||
|
||||
For example, here we prepare our Android and iOS hardware permissions for a WebRTC session.
|
||||
|
||||
```sh
|
||||
meteor add cordova:cordova.plugins.diagnostic@3.0.2
|
||||
```
|
||||
|
||||
```js
|
||||
if (Meteor.isCordova) {
|
||||
cordova.plugins.diagnostic.isCameraAuthorized(
|
||||
authorized => {
|
||||
if (!authorized) {
|
||||
cordova.plugins.diagnostic.requestCameraAuthorization(
|
||||
granted => {
|
||||
console.log( "Authorization request for camera use was " +
|
||||
(granted ? "granted" : "denied"));
|
||||
},
|
||||
error => { console.error(error); }
|
||||
);
|
||||
}
|
||||
},
|
||||
error => { console.error(error); }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively for iOS you can specify the required privacy usage notification prompts in your `mobile-config.js` by using [`App.appendToConfig`](https://docs.meteor.com/api/mobile-config.html#App-appendToConfig) along with the correct [Cocoa Keys](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html).
|
||||
|
||||
Here is an example to access iOS geolocation data:
|
||||
```
|
||||
App.appendToConfig(`
|
||||
<edit-config target="NSLocationWhenInUseUsageDescription" file="*-Info.plist" mode="merge">
|
||||
<string>My app needs access to your location for navigation purposes</string>
|
||||
</edit-config>
|
||||
`);
|
||||
```
|
||||
|
||||
<h2 id="configuring-your-app">Configuring your app</h2>
|
||||
|
||||
Meteor reads a [`mobile-config.js`](http://docs.meteor.com/api/mobile-config.html) file in the root of your app directory during build and uses the settings specified there to generate Cordova's [`config.xml`](https://cordova.apache.org/docs/en/dev/config_ref/index.html) file.
|
||||
|
||||
<h3 id="configuring-metadata">Metadata</h3>
|
||||
|
||||
```js
|
||||
App.info({
|
||||
id: 'com.meteor.examples.todos',
|
||||
name: 'Todos',
|
||||
version: "0.0.1"
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="configuring-preferences">Preferences</h3>
|
||||
|
||||
```
|
||||
App.setPreference('BackgroundColor', '0xff0000ff');
|
||||
App.setPreference('Orientation', 'default');
|
||||
App.setPreference('Orientation', 'all', 'ios');
|
||||
```
|
||||
|
||||
Refer to [Meteor's Mobile Configuration](http://docs.meteor.com/api/mobile-config.html) documentation and the [preferences section](https://cordova.apache.org/docs/en/dev/config_ref/index.html#preference) of the Cordova documentation for more information on Meteor's Cordova configuration API and the supported options.
|
||||
|
||||
<h3 id="configuring-app-icons-and-launch-screens">App icons and launch screens</h3>
|
||||
|
||||
Although Meteor includes a standard set of app icons and launch screens, you will want to configure your own images to match your app's branding in your `mobile-config.js` file.
|
||||
|
||||
You can configure the icon and splash screen image sizes using the specific supported settings in [`App.icons`](http://docs.meteor.com/api/mobile-config.html#App-icons) and [`App.launchScreens`](http://docs.meteor.com/api/mobile-config.html#App-launchScreens).
|
||||
|
||||
In addition, Cordova on iOS supports using [launch story board images](https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-splashscreen/#launch-storyboard-images), which is now Apple's recommended approach for providing launch screens. This has the benefit of not requiring you to provide an image for every possible device screen size. Remove all the iOS `App.launchScreens` directives from your `mobile-config.js` and use [`App.appendToConfig`](http://docs.meteor.com/api/mobile-config.html#App-appendToConfig) to add the paths to your universal images.
|
||||
|
||||
```
|
||||
App.appendToConfig(`
|
||||
<splash src="../../../app/path/to/Default@2x~universal~anyany.png" />
|
||||
<splash src="../../../app/path/to/Default@3x~universal~anyany.png" />
|
||||
`);
|
||||
```
|
||||
|
||||
See the [iOS Human Interface Guidelines for icon and image sizes](https://developer.apple.com/ios/human-interface-guidelines/icons-and-images/image-size-and-resolution/) for more information.
|
||||
|
||||
> On [iPhone X](https://developer.apple.com/ios/human-interface-guidelines/overview/iphone-x/) it is likely required that you use launch story board images if you want the launch image to cover the entire screen. There are also other app layout issues that need to be adresssed such as "safe areas" and "rounded corners", see [Apple's iOS app updates for iPhone X](https://developer.apple.com/ios/update-apps-for-iphone-x/).
|
||||
|
||||
<h3 id="advanced-build">Advanced build customization</h3>
|
||||
|
||||
There is a special top-level directory named `cordova-build-override/` that allows you to override, in an ad-hoc way, parts of your Cordova project that Meteor generates for you in the `.meteor/local/cordova-build` directory. The entire file tree of this directory will be `cp -R` (copied overwriting existing files) to the Cordova project right before the build and compilation step.
|
||||
|
||||
The problem with this mechanism is that it overrides complete files, so it is not a good solution for customizing `config.xml`. Replacing the generated version with your own file means you lose all configuration information set by the build process and by installed plugins, which will likely break your app.
|
||||
|
||||
If you need to customize configuration files, a workaround is to create a dummy Cordova plugin. In its `plugin.xml`, you can specify a [`config-file` element](https://cordova.apache.org/docs/en/dev/plugin_ref/spec.html#config-file) to selectively change parts of configuration files, including `config.xml`.
|
||||
|
||||
> We recommend using these approaches only if absolutely required and if your customizations can not be handled by standard configuration options.
|
||||
|
||||
<h2 id="building-and-submitting">Deploying to production</h2>
|
||||
|
||||
<h3 id="building-for-production">Building for production</h3>
|
||||
|
||||
Use `meteor build <build-output-directory> --server=<host>:<port>` to build your app for production.
|
||||
|
||||
The `<host>` and `<port>` should be the address of the server you want your app to connect to.
|
||||
|
||||
This will generate a directory at `<build-output-directory>`, which includes a server bundle tarball and the project source for each targeted mobile platform in the `/ios` and `/android` directories.
|
||||
|
||||
If you pass `--debug`, the bundles will be compiled in Cordova's debug mode instead of release mode. On Android, this produces a `<build-output-directory>/android/debug.apk` file that can be installed without signing.
|
||||
|
||||
You can pass `--server-only` to only build the server bundle. This allows you to build your app without installing the mobile SDKs on the build machine. This is useful if you use an automated deployment setup for instance. (If you remove the mobile platforms before building instead, hot code push will be disabled because the assets for Cordova included in the server bundle will not be generated.)
|
||||
|
||||
<h3 id="submitting-ios">iOS App Store</h3>
|
||||
|
||||
In order to build your app for iOS, you will need to [configure your app](#configuring-your-app) with at least a version number, and the required set of app icons and launch screens.
|
||||
|
||||
After running `meteor build` you can open the generated Xcode project in Xcode:
|
||||
```sh
|
||||
cd <build-output-directory>/ios/project
|
||||
open MyApp.xcodeproj
|
||||
```
|
||||
|
||||
From this point on, the process for building the app archive and submitting it to the App Store is the same as it would be for any other iOS app. Please refer to [Apple's documentation](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/SubmittingYourApp/SubmittingYourApp.html) for further details.
|
||||
|
||||
<h3 id="submitting-android">Android Play Store</h3>
|
||||
|
||||
In order to build your app for Android, you will need to [configure your app](#configuring-your-app) with at least a version number, and the required set of app icons and launch screens.
|
||||
|
||||
After running `meteor build` the generated APK will be copied from the `<build-output-directory>/android/project/build/outputs/apk/release` directory to `<build-output-directory>/android/release-unsigned.apk`.
|
||||
> If you have installed [Crosswalk](https://atmospherejs.com/meteor/crosswalk), you will need to manually copy the APK file `cp ~/build-output-directory/android/project/build/outputs/apk/android-armv7-release-unsigned.apk ~/build-output-directory/android/release-unsigned.apk`
|
||||
|
||||
Before submitting the APK(s) to the Play Store, you will need to sign the APK and run [`zipalign`](http://developer.android.com/tools/help/zipalign.html) on it to optimize the archive.
|
||||
|
||||
(See the [Android developer documentation](http://developer.android.com/tools/publishing/app-signing.html) for more details about the app signing procedure.)
|
||||
|
||||
To sign your app, you'll need a private key. This key lets you publish and update your app. If you haven't made a key for this app yet, run:
|
||||
```sh
|
||||
keytool -genkey -alias your-app-name -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
Optionally, you can specify `--keystore` to use a different keystore. Don't forget to specify the same keystore when signing the APK.
|
||||
> Note: Ensure that you have secure backups of your keystore (`~/.keystore` is the default). If you publish an app to the Play Store and then lose the key with which you signed your app, you will not be able to publish any updates to your app, since you must always sign all versions of your app with the same key.
|
||||
|
||||
Now, you can sign the APK:
|
||||
```sh
|
||||
cd ~/build-output-directory/android/
|
||||
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 release-unsigned.apk your-app-name
|
||||
```
|
||||
Next, you can run zipalign on it to optimize the APK:
|
||||
```sh
|
||||
$ANDROID_HOME/build-tools/<build-tools-version>/zipalign 4 release-unsigned.apk <your-app-name>.apk
|
||||
```
|
||||
|
||||
From this point on, the process for submitting the app to the Play Store is the same as it would be for any other Android app. `<your-app-name>.apk` is the APK to upload to the store. Learn more by visiting https://play.google.com/apps/publish.
|
||||
|
||||
<h4>Submitting an app using Crosswalk to to Play Store</h4>
|
||||
|
||||
Because Crosswalk bundles native code for Chromium, you will end up with APKs for both ARM and x86. You can find the generated APKs in the `<build-output-directory>/android/project/build/outputs/apk` directory.
|
||||
|
||||
You will have to sign and `zipalign` both APKs. You will also have to submit both to the Play Store, see [submitting multiple APKs](http://developer.android.com/google/play/publishing/multiple-apks.html) for more information.
|
||||
712
content/data-loading.md
Normal file
@@ -0,0 +1,712 @@
|
||||
---
|
||||
title: Publications and Data Loading
|
||||
description: How and where to load data in your Meteor app using publications and subscriptions.
|
||||
discourseTopicId: 19661
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. What publications and subscriptions are in the Meteor platform.
|
||||
2. How to define a publication on the server.
|
||||
3. Where to subscribe on the client and in which templates.
|
||||
4. Useful patterns for managing subscriptions.
|
||||
5. How to reactively publish related data.
|
||||
6. How to ensure your publication is secure in the face of reactive changes.
|
||||
7. How to use the low-level publish API to publish anything.
|
||||
8. What happens when you subscribe to a publication.
|
||||
8. How to turn a 3rd-party REST endpoint into a publication.
|
||||
10. How to turn a publication in your app into a REST endpoint.
|
||||
|
||||
<h2 id="publications-and-subscriptions">Publications and subscriptions</h2>
|
||||
|
||||
In a traditional, HTTP-based web application, the client and server communicate in a "request-response" fashion. Typically the client makes RESTful HTTP requests to the server and receives HTML or JSON data in response, and there's no way for the server to "push" data to the client when changes happen at the backend.
|
||||
|
||||
Meteor is built from the ground up on the Distributed Data Protocol (DDP) to allow data transfer in both directions. Building a Meteor app doesn't require you to set up REST endpoints to serialize and send data. Instead you create *publication* endpoints that can push data from server to client.
|
||||
|
||||
In Meteor a **publication** is a named API on the server that constructs a set of data to send to a client. A client initiates a **subscription** which connects to a publication, and receives that data. That data consists of a first batch sent when the subscription is initialized and then incremental updates as the published data changes.
|
||||
|
||||
So a subscription can be thought of as a set of data that changes over time. Typically, the result of this is that a subscription "bridges" a [server-side MongoDB collection](/collections.html#server-collections), and the [client side Minimongo cache](collections.html#client-collections) of that collection. You can think of a subscription as a pipe that connects a subset of the "real" collection with the client's version, and constantly keeps it up to date with the latest information on the server.
|
||||
|
||||
<h2 id="publications">Defining a publication</h2>
|
||||
|
||||
A publication should be defined in a server-only file. For instance, in the Todos example app, we want to publish the set of public lists to all users:
|
||||
|
||||
```js
|
||||
Meteor.publish('lists.public', function() {
|
||||
return Lists.find({
|
||||
userId: {$exists: false}
|
||||
}, {
|
||||
fields: Lists.publicFields
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
There are a few things to understand about this code block. First, we've named the publication with the unique string `lists.public`, and that will be how we access it from the client. Second, we are returning a Mongo *cursor* from the publication function. Note that the cursor is filtered to only return certain fields from the collection, as detailed in the [Security article](security.html#fields).
|
||||
|
||||
What that means is that the publication will ensure the set of data matching that query is available to any client that subscribes to it. In this case, all lists that do not have a `userId` setting. So the collection named `Lists` on the client will have all of the public lists that are available in the server collection named `Lists` while that subscription is open. In this particular example in the Todos application, the subscription is initialized when the app starts and never stopped, but a later section will talk about [subscription life cycle](data-loading.html#patterns).
|
||||
|
||||
Every publication takes two types of parameters:
|
||||
|
||||
1. The `this` context, which has information about the current DDP connection. For example, you can access the current user's `_id` with `this.userId`.
|
||||
2. The arguments to the publication, which can be passed in when calling `Meteor.subscribe`.
|
||||
|
||||
> Note: Since we need to access context on `this` we need to use the `function() {}` form for publications rather than the ES2015 `() => {}`. You can disable the arrow function linting rule for publication files with `eslint-disable prefer-arrow-callback`. A future version of the publication API will work more nicely with ES2015.
|
||||
|
||||
In this publication, which loads private lists, we need to use `this.userId` to get only the todo lists that belong to a specific user.
|
||||
|
||||
```js
|
||||
Meteor.publish('lists.private', function() {
|
||||
if (!this.userId) {
|
||||
return this.ready();
|
||||
}
|
||||
|
||||
return Lists.find({
|
||||
userId: this.userId
|
||||
}, {
|
||||
fields: Lists.publicFields
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Thanks to the guarantees provided by DDP and Meteor's accounts system, the above publication can be confident that it will only ever publish private lists to the user that they belong to. Note that the publication will re-run if the user logs out (or back in again), which means that the published set of private lists will change as the active user changes.
|
||||
|
||||
In the case of a logged-out user, we explicitly call `this.ready()`, which indicates to the subscription that we've sent all the data we are initially going to send (in this case none). It's important to know that if you don't return a cursor from the publication or call `this.ready()`, the user's subscription will never become ready, and they will likely see a loading state forever.
|
||||
|
||||
Here's an example of a publication which takes a named argument. Note that it's important to check the types of arguments that come in over the network.
|
||||
|
||||
```js
|
||||
Meteor.publish('todos.inList', function(listId) {
|
||||
// We need to check the `listId` is the type we expect
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
When we subscribe to this publication on the client, we can provide this argument via the `Meteor.subscribe()` call:
|
||||
|
||||
```js
|
||||
Meteor.subscribe('todos.inList', list._id);
|
||||
```
|
||||
|
||||
<h3 id="organization-publications">Organizing publications</h3>
|
||||
|
||||
It makes sense to place a publication alongside the feature that it's targeted for. For instance, sometimes publications provide very specific data that's only really useful for the view for which they were developed. In that case, placing the publication in the same module or directory as the view code makes perfect sense.
|
||||
|
||||
Often, however, a publication is more general. For example in the Todos example application, we create a `todos.inList` publication, which publishes all the todos in a list. Although in the application we only use this in one place (in the `Lists_show` template), in a larger app, there's a good chance we might need to access all the todos for a list in other places. So putting the publication in the `todos` package is a sensible approach.
|
||||
|
||||
<h2 id="subscriptions">Subscribing to data</h2>
|
||||
|
||||
To use publications, you need to create a subscription to it on the client. To do so, you call `Meteor.subscribe()` with the name of the publication. When you do this, it opens up a subscription to that publication, and the server starts sending data down the wire to ensure that your client collections contain up to date copies of the data specified by the publication.
|
||||
|
||||
`Meteor.subscribe()` also returns a "subscription handle" with a property called `.ready()`. This is a reactive function that returns `true` when the publication is marked ready (either you call `this.ready()` explicitly, or the initial contents of a returned cursor are sent over).
|
||||
|
||||
```js
|
||||
const handle = Meteor.subscribe('lists.public');
|
||||
```
|
||||
|
||||
<h3 id="stopping-subscriptions">Stopping Subscriptions</h3>
|
||||
|
||||
The subscription handle also has another important property, the `.stop()` method. When you are subscribing, it is very important to ensure that you always call `.stop()` on the subscription when you are done with it. This ensures that the documents sent by the subscription are cleared from your local Minimongo cache and the server stops doing the work required to service your subscription. If you forget to call stop, you'll consume unnecessary resources both on the client and the server.
|
||||
|
||||
*However*, if you call `Meteor.subscribe()` conditionally inside a reactive context (such as an `autorun`, or `getMeteorData` in React) or via `this.subscribe()` in a Blaze component, then Meteor's reactive system will automatically call `this.stop()` for you at the appropriate time.
|
||||
|
||||
<h3 id="organizing-subscriptions">Subscribe in UI components</h3>
|
||||
|
||||
It is best to place the subscription as close as possible to the place where the data from the subscription is needed. This reduces "action at a distance" and makes it easier to understand the flow of data through your application. If the subscription and fetch are separated, then it's not always clear how and why changes to the subscriptions (such as changing arguments), will affect the contents of the cursor.
|
||||
|
||||
What this means in practice is that you should place your subscription calls in *components*. In Blaze, it's best to do this in the `onCreated()` callback:
|
||||
|
||||
```js
|
||||
Template.Lists_show_page.onCreated(function() {
|
||||
this.getListId = () => FlowRouter.getParam('_id');
|
||||
|
||||
this.autorun(() => {
|
||||
this.subscribe('todos.inList', this.getListId());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
In this code snippet we can see two important techniques for subscribing in Blaze templates:
|
||||
|
||||
1. Calling `this.subscribe()` (rather than `Meteor.subscribe`), which attaches a special `subscriptionsReady()` function to the template instance, which is true when all subscriptions made inside this template are ready.
|
||||
|
||||
2. Calling `this.autorun` sets up a reactive context which will re-initialize the subscription whenever the reactive function `this.getListId()` changes.
|
||||
|
||||
Read more about Blaze subscriptions in the [Blaze article](http://blazejs.org/api/templates.html#Blaze-TemplateInstance-subscribe), and about tracking loading state inside UI components in the [UI article](ui-ux.html#subscription-readiness).
|
||||
|
||||
<h3 id="fetching">Fetching data</h3>
|
||||
|
||||
Subscribing to data puts it in your client-side collection. To use the data in your user interface, you need to query your client-side collection. There are a couple of important rules to follow when doing this.
|
||||
|
||||
<h4 id="specific-queries">Always use specific queries to fetch data</h4>
|
||||
|
||||
If you're publishing a subset of your data, it might be tempting to query for all data available in a collection (i.e. `Lists.find()`) in order to get that subset on the client, without re-specifying the Mongo selector you used to publish that data in the first place.
|
||||
|
||||
But if you do this, then you open yourself up to problems if another subscription pushes data into the same collection, since the data returned by `Lists.find()` might not be what you expected anymore. In an actively developed application, it's often hard to anticipate what may change in the future and this can be a source of hard to understand bugs.
|
||||
|
||||
Also, when changing between subscriptions, there is a brief period where both subscriptions are loaded (see [Publication behavior when changing arguments](#publication-behavior-with-arguments) below), so when doing things like pagination, it's exceedingly likely that this will be the case.
|
||||
|
||||
<h4 id="fetch-near-subscribe">Fetch the data nearby where you subscribed to it</h4>
|
||||
|
||||
We do this for the same reason we subscribe in the component in the first place---to avoid action at a distance and to make it easier to understand where data comes from. A common pattern is to fetch the data in a parent template, and then pass it into a "pure" child component, as we'll see it in the [UI Article](ui-ux.html#components).
|
||||
|
||||
Note that there are some exceptions to this second rule. A common one is `Meteor.user()`---although this is strictly speaking subscribed to (automatically usually), it's typically over-complicated to pass it through the component hierarchy as an argument to each component. However keep in mind it's best not to use it in too many places as it makes components harder to test.
|
||||
|
||||
<h3 id="global-subscriptions">Global subscriptions</h3>
|
||||
|
||||
One place where you might be tempted to not subscribe inside a component is when it accesses data that you know you *always* need. For instance, a subscription to extra fields on the user object (see the [Accounts Article](accounts.html)) that you need on every screen of your app.
|
||||
|
||||
However, it's generally a good idea to use a layout component (which you wrap all your components in) to subscribe to this subscription anyway. It's better to be consistent about such things, and it makes for a more flexible system if you ever decide you have a screen that *doesn't* need that data.
|
||||
|
||||
<h2 id="patterns">Patterns for data loading</h2>
|
||||
|
||||
Across Meteor applications, there are some common patterns of data loading and management on the client side that are worth knowing. We'll go into more detail about some of these in the [UI/UX Article](ui-ux.html).
|
||||
|
||||
<h3 id="readiness">Subscription readiness</h3>
|
||||
|
||||
It is key to understand that a subscription will not instantly provide its data. There will be a latency between subscribing to the data on the client and it arriving from the publication on the server. You should also be aware that this delay may be a lot longer for your users in production than for you locally in development!
|
||||
|
||||
Although the Tracker system means you often don't *need* to think too much about this in building your apps, usually if you want to get the user experience right, you'll need to know when the data is ready.
|
||||
|
||||
To find that out, `Meteor.subscribe()` and (`this.subscribe()` in Blaze components) returns a "subscription handle", which contains a reactive data source called `.ready()`:
|
||||
|
||||
```js
|
||||
const handle = Meteor.subscribe('lists.public');
|
||||
Tracker.autorun(() => {
|
||||
const isReady = handle.ready();
|
||||
console.log(`Handle is ${isReady ? 'ready' : 'not ready'}`);
|
||||
});
|
||||
```
|
||||
|
||||
If you're subscribing to multiple publications, you can create an array of handles and use [`every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) to determine if all are ready:
|
||||
|
||||
```js
|
||||
const handles = [
|
||||
Meteor.subscribe('lists.public'),
|
||||
Meteor.subscribe('todos.inList'),
|
||||
];
|
||||
|
||||
Tracker.autorun(() => {
|
||||
const areReady = handles.every(handle => handle.ready());
|
||||
console.log(`Handles are ${areReady ? 'ready' : 'not ready'}`);
|
||||
});
|
||||
```
|
||||
|
||||
We can use this information to be more subtle about when we try and show data to users, and when we show a loading screen.
|
||||
|
||||
<h3 id="changing-arguments">Reactively changing subscription arguments</h3>
|
||||
|
||||
We've already seen an example of using an `autorun` to re-subscribe when the (reactive) arguments to a subscription change. It's worth digging in a little more detail to understand what happens in this scenario.
|
||||
|
||||
```js
|
||||
Template.Lists_show_page.onCreated(function() {
|
||||
this.getListId = () => FlowRouter.getParam('_id');
|
||||
|
||||
this.autorun(() => {
|
||||
this.subscribe('todos.inList', this.getListId());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
In our example, the `autorun` will re-run whenever `this.getListId()` changes, (ultimately because `FlowRouter.getParam('_id')` changes), although other common reactive data sources are:
|
||||
|
||||
1. Template data contexts (which you can access reactively with `Template.currentData()`).
|
||||
2. The current user status (`Meteor.user()` and `Meteor.loggingIn()`).
|
||||
3. The contents of other application specific client data stores.
|
||||
|
||||
Technically, what happens when one of these reactive sources changes is the following:
|
||||
|
||||
1. The reactive data source *invalidates* the autorun computation (marks it so that it re-runs in the next Tracker flush cycle).
|
||||
2. The subscription detects this, and given that anything is possible in next computation run, marks itself for destruction.
|
||||
3. The computation re-runs, with `.subscribe()` being re-called either with the same or different arguments.
|
||||
4. If the subscription is run with the *same arguments* then the "new" subscription discovers the old "marked for destruction" subscription that's sitting around, with the same data already ready, and reuses that.
|
||||
5. If the subscription is run with *different arguments*, then a new subscription is created, which connects to the publication on the server.
|
||||
6. At the end of the flush cycle (i.e. after the computation is done re-running), the old subscription checks to see if it was re-used, and if not, sends a message to the server to tell the server to shut it down.
|
||||
|
||||
Step 4 above is an important detail---that the system cleverly knows not to re-subscribe if the autorun re-runs and subscribes with the exact same arguments. This holds true even if the new subscription is set up somewhere else in the template hierarchy. For example, if a user navigates between two pages that both subscribe to the exact same subscription, the same mechanism will kick in and no unnecessary subscribing will happen.
|
||||
|
||||
<h3 id="publication-behavior-with-arguments">Publication behavior when arguments change</h3>
|
||||
|
||||
It's also worth knowing a little about what happens on the server when the new subscription is started and the old one is stopped.
|
||||
|
||||
The server *explicitly* waits until all the data is sent down (the new subscription is ready) for the new subscription before removing the data from the old subscription. The idea here is to avoid flicker---you can, if desired, continue to show the old subscription's data until the new data is ready, then instantly switch over to the new subscription's complete data set.
|
||||
|
||||
What this means is in general, when changing subscriptions, there'll be a period where you are *over-subscribed* and there is more data on the client than you strictly asked for. This is one very important reason why you should always fetch the same data that you have subscribed to (don't "over-fetch").
|
||||
|
||||
<h3 id="pagination">Paginating subscriptions</h3>
|
||||
|
||||
A very common pattern of data access is pagination. This refers to the practice of fetching an ordered list of data one "page" at a time---typically some number of items, say twenty.
|
||||
|
||||
There are two styles of pagination that are commonly used, a "page-by-page" style---where you show only one page of results at a time, starting at some offset (which the user can control), and "infinite-scroll" style, where you show an increasing number of pages of items, as the user moves through the list (this is the typical "feed" style user interface).
|
||||
|
||||
In this section, we'll consider a publication/subscription technique for the second, infinite-scroll style pagination. The page-by-page technique is a little tricker to handle in Meteor, due to it being difficult to calculate the offset on the client. If you need to do so, you can follow many of the same techniques that we use here and use the [`percolate:find-from-publication`](https://atmospherejs.com/percolate/find-from-publication) package to keep track of which records have come from your publication.
|
||||
|
||||
In an infinite scroll publication, we need to add a new argument to our publication controlling how many items to load. Suppose we wanted to paginate the todo items in our Todos example app:
|
||||
|
||||
```js
|
||||
const MAX_TODOS = 1000;
|
||||
|
||||
Meteor.publish('todos.inList', function(listId, limit) {
|
||||
new SimpleSchema({
|
||||
listId: { type: String },
|
||||
limit: { type: Number }
|
||||
}).validate({ listId, limit });
|
||||
|
||||
const options = {
|
||||
sort: {createdAt: -1},
|
||||
limit: Math.min(limit, MAX_TODOS)
|
||||
};
|
||||
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
It's important that we set a `sort` parameter on our query (to ensure a repeatable order of list items as more pages are requested), and that we set an absolute maximum on the number of items a user can request (at least in the case where lists can grow without bound).
|
||||
|
||||
Then on the client side, we'd set some kind of reactive state variable to control how many items to request:
|
||||
|
||||
```js
|
||||
Template.Lists_show_page.onCreated(function() {
|
||||
this.getListId = () => FlowRouter.getParam('_id');
|
||||
|
||||
this.autorun(() => {
|
||||
this.subscribe('todos.inList',
|
||||
this.getListId(), this.state.get('requestedTodos'));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
We'd increment that `requestedTodos` variable when the user clicks "load more" (or perhaps when they scroll to the bottom of the page).
|
||||
|
||||
One piece of information that's very useful to know when paginating data is the *total number of items* that you could see. The [`tmeasday:publish-counts`](https://atmospherejs.com/tmeasday/publish-counts) package can be useful to publish this. We could add a `Lists.todoCount` publication like so
|
||||
|
||||
```js
|
||||
Meteor.publish('Lists.todoCount', function({ listId }) {
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
Counts.publish(this, `Lists.todoCount.${listId}`, Todos.find({listId}));
|
||||
});
|
||||
```
|
||||
|
||||
Then on the client, after subscribing to that publication, we can access the count with
|
||||
|
||||
```js
|
||||
Counts.get(`Lists.todoCount.${listId}`)
|
||||
```
|
||||
|
||||
<h2 id="stores">Client-side data with reactive stores</h2>
|
||||
|
||||
In Meteor, persistent or shared data comes over the wire on publications. However, there are some types of data which doesn't need to be persistent or shared between users. For instance, the "logged-in-ness" of the current user, or the route they are currently viewing.
|
||||
|
||||
Although client-side state is often best contained as state of an individual template (and passed down the template hierarchy as arguments where necessary), sometimes you have a need for "global" state that is shared between unrelated sections of the template hierarchy.
|
||||
|
||||
Usually such state is stored in a *global singleton* object which we can call a store. A singleton is a data structure of which only a single copy logically exists. The current user and the router from above are typical examples of such global singletons.
|
||||
|
||||
<h3 id="store-types">Types of stores</h3>
|
||||
|
||||
In Meteor, it's best to make stores *reactive data* sources, as that way they tie most naturally into the rest of the ecosystem. There are a few different packages you can use for stores.
|
||||
|
||||
If the store is single-dimensional, you can probably use a `ReactiveVar` to store it (provided by the [`reactive-var`](https://atmospherejs.com/meteor/reactive-var) package). A `ReactiveVar` has two properties, `get()` and `set()`:
|
||||
|
||||
```js
|
||||
DocumentHidden = new ReactiveVar(document.hidden);
|
||||
$(window).on('visibilitychange', (event) => {
|
||||
DocumentHidden.set(document.hidden);
|
||||
});
|
||||
```
|
||||
|
||||
If the store is multi-dimensional, you may want to use a `ReactiveDict` (from the [`reactive-dict`](https://atmospherejs.com/meteor/reactive-dict) package):
|
||||
|
||||
```js
|
||||
const $window = $(window);
|
||||
function getDimensions() {
|
||||
return {
|
||||
width: $window.width(),
|
||||
height: $window.height()
|
||||
};
|
||||
};
|
||||
|
||||
WindowSize = new ReactiveDict();
|
||||
WindowSize.set(getDimensions());
|
||||
$window.on('resize', () => {
|
||||
WindowSize.set(getDimensions());
|
||||
});
|
||||
```
|
||||
|
||||
The advantage of a `ReactiveDict` is you can access each property individually (`WindowSize.get('width')`), and the dict will diff the field and track changes on it individually (so your template will re-render less often for instance).
|
||||
|
||||
If you need to query the store, or store many related items, it's probably a good idea to use a Local Collection (see the [Collections Article](collections.html#local-collections)).
|
||||
|
||||
<h3 id="accessing-stores">Accessing stores</h3>
|
||||
|
||||
You should access stores in the same way you'd access other reactive data in your templates---that means centralizing your store access, much like you centralize your subscribing and data fetch. For a Blaze template, that's either in a helper, or from within a `this.autorun()` inside an `onCreated()` callback.
|
||||
|
||||
This way you get the full reactive power of the store.
|
||||
|
||||
<h3 id="updating-stores">Updating stores</h3>
|
||||
|
||||
If you need to update a store as a result of user action, you'd update the store from an event handler, just like you call [Methods](methods.html).
|
||||
|
||||
If you need to perform complex logic in the update (e.g. not just call `.set()` etc), it's a good idea to define a mutator on the store. As the store is a singleton, you can just attach a function to the object directly:
|
||||
|
||||
```js
|
||||
WindowSize.simulateMobile = (device) => {
|
||||
if (device === 'iphone6s') {
|
||||
this.set({width: 750, height: 1334});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="advanced-publications">Advanced publications</h2>
|
||||
|
||||
Sometimes, the mechanism of returning a query from a publication function won't cover your needs. In those situations, there are some more powerful publication patterns that you can use.
|
||||
|
||||
<h3 id="publishing-relations">Publishing relational data</h3>
|
||||
|
||||
It's common to need related sets of data from multiple collections on a given page. For instance, in the Todos app, when we render a todo list, we want the list itself, as well as the set of todos that belong to that list.
|
||||
|
||||
One way you might do this is to return more than one cursor from your publication function:
|
||||
|
||||
```js
|
||||
Meteor.publish('todos.inList', function(listId) {
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
const list = Lists.findOne(listId);
|
||||
|
||||
if (list && (!list.userId || list.userId === this.userId)) {
|
||||
return [
|
||||
Lists.find(listId),
|
||||
Todos.find({listId})
|
||||
];
|
||||
} else {
|
||||
// The list doesn't exist, or the user isn't allowed to see it.
|
||||
// In either case, make it appear like there is no list.
|
||||
return this.ready();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
However, this example will not work as you might expect. The reason is that reactivity doesn't work in the same way on the server as it does on the client. On the client, if *anything* in a reactive function changes, the whole function will re-run, and the results are fairly intuitive.
|
||||
|
||||
On the server however, the reactivity is limited to the behavior of the cursors you return from your publish functions. You'll see any changes to the data that matches their queries, but *their queries will never change*.
|
||||
|
||||
So in the case above, if a user subscribes to a list that is later made private by another user, although the `list.userId` will change to a value that no longer passes the condition, the body of the publication will not re-run, and so the query to the `Todos` collection (`{listId}`) will not change. So the first user will continue to see items they shouldn't.
|
||||
|
||||
However, we can write publications that are properly reactive to changes across collections. To do this, we use the [`reywood:publish-composite`](https://atmospherejs.com/reywood/publish-composite) package.
|
||||
|
||||
The way this package works is to first establish a cursor on one collection, and then explicitly set up a second level of cursors on a second collection with the results of the first cursor. The package uses a query observer behind the scenes to trigger the subscription to change and queries to re-run whenever the source data changes.
|
||||
|
||||
```js
|
||||
Meteor.publishComposite('todos.inList', function(listId) {
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
const userId = this.userId;
|
||||
|
||||
return {
|
||||
find() {
|
||||
const query = {
|
||||
_id: listId,
|
||||
$or: [{userId: {$exists: false}}, {userId}]
|
||||
};
|
||||
|
||||
// We only need the _id field in this query, since it's only
|
||||
// used to drive the child queries to get the todos
|
||||
const options = {
|
||||
fields: { _id: 1 }
|
||||
};
|
||||
|
||||
return Lists.find(query, options);
|
||||
},
|
||||
|
||||
children: [{
|
||||
find(list) {
|
||||
return Todos.find({ listId: list._id }, { fields: Todos.publicFields });
|
||||
}
|
||||
}]
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
In this example, we write a complicated query to make sure that we only ever find a list if we are allowed to see it, then, once per list we find (which can be one or zero times depending on access), we publish the todos for that list. Publish Composite takes care of stopping and starting the dependent cursors if the list stops matching the original query or otherwise.
|
||||
|
||||
<h3 id="complex-auth">Complex authorization</h3>
|
||||
|
||||
We can also use `publish-composite` to perform complex authorization in publications. For instance, consider if we had a `Todos.admin.inList` publication that allowed an admin to bypass default publication's security for users with an `admin` flag set.
|
||||
|
||||
We might want to write:
|
||||
|
||||
```js
|
||||
Meteor.publish('Todos.admin.inList', function({ listId }) {
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
const user = Meteor.users.findOne(this.userId);
|
||||
|
||||
if (user && user.admin) {
|
||||
// We don't need to worry about the list.userId changing this time
|
||||
return [
|
||||
Lists.find(listId),
|
||||
Todos.find({listId})
|
||||
];
|
||||
} else {
|
||||
return this.ready();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
However, due to the same reasons discussed above, the publication *will not re-run* if the user's `admin` status changes. If this is something that is likely to happen and reactive changes are needed, then we'll need to make the publication reactive. We can do this via the same technique as above however:
|
||||
|
||||
```js
|
||||
Meteor.publishComposite('Todos.admin.inList', function(listId) {
|
||||
new SimpleSchema({
|
||||
listId: {type: String}
|
||||
}).validate({ listId });
|
||||
|
||||
const userId = this.userId;
|
||||
return {
|
||||
find() {
|
||||
return Meteor.users.find({_id: userId, admin: true}, {fields: {admin: 1}});
|
||||
},
|
||||
children: [{
|
||||
find(user) {
|
||||
// We don't need to worry about the list.userId changing this time
|
||||
return Lists.find(listId);
|
||||
}
|
||||
},
|
||||
{
|
||||
find(user) {
|
||||
return Todos.find({listId});
|
||||
}
|
||||
}]
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Note that we explicitly set the `Meteor.users` query fields, as `publish-composite` publishes all of the returned cursors to the client and re-runs the child computations whenever the cursor changes.
|
||||
|
||||
Limiting the results serves a double purpose: it both prevents sensitive fields from being disclosed to the client and limits recomputation to the relevant fields only (namely, the `admin` field).
|
||||
|
||||
<h3 id="custom-publication">Custom publications with the low level API</h3>
|
||||
|
||||
In all of our examples so far (outside of using`Meteor.publishComposite()`) we've returned a cursor from our `Meteor.publish()` handlers. Doing this ensures Meteor takes care of the job of keeping the contents of that cursor in sync between the server and the client. However, there's another API you can use for publish functions which is closer to the way the underlying Distributed Data Protocol (DDP) works.
|
||||
|
||||
DDP uses three main messages to communicate changes in the data for a publication: the `added`, `changed` and `removed` messages. So, we can similarly do the same for a publication:
|
||||
|
||||
```js
|
||||
Meteor.publish('custom-publication', function() {
|
||||
// We can add documents one at a time
|
||||
this.added('collection-name', 'id', {field: 'values'});
|
||||
|
||||
// We can call ready to indicate to the client that the initial document sent has been sent
|
||||
this.ready();
|
||||
|
||||
// We may respond to some 3rd party event and want to send notifications
|
||||
Meteor.setTimeout(() => {
|
||||
// If we want to modify a document that we've already added
|
||||
this.changed('collection-name', 'id', {field: 'new-value'});
|
||||
|
||||
// Or if we don't want the client to see it any more
|
||||
this.removed('collection-name', 'id');
|
||||
});
|
||||
|
||||
// It's very important to clean up things in the subscription's onStop handler
|
||||
this.onStop(() => {
|
||||
// Perhaps kill the connection with the 3rd party server
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
From the client's perspective, data published like this doesn't look any different---there's actually no way for the client to know the difference as the DDP messages are the same. So even if you are connecting to, and mirroring, some esoteric data source, on the client it'll appear like any other Mongo collection.
|
||||
|
||||
One point to be aware of is that if you allow the user to *modify* data in the "pseudo-collection" you are publishing in this fashion, you'll want to be sure to re-publish the modifications to them via the publication, to achieve an optimistic user experience.
|
||||
|
||||
<h3 id="lifecycle">Subscription lifecycle</h3>
|
||||
|
||||
Although you can use publications and subscriptions in Meteor via an intuitive understanding, sometimes it's useful to know exactly what happens under the hood when you subscribe to data.
|
||||
|
||||
Suppose you have a publication of the following form:
|
||||
|
||||
```js
|
||||
Meteor.publish('Posts.all', function() {
|
||||
return Posts.find({}, {limit: 10});
|
||||
});
|
||||
```
|
||||
|
||||
Then when a client calls `Meteor.subscribe('Posts.all')` the following things happen inside Meteor:
|
||||
|
||||
1. The client sends a `sub` message with the name of the subscription over DDP.
|
||||
|
||||
2. The server starts up the subscription by running the publication handler function.
|
||||
|
||||
3. The publication handler identifies that the return value is a cursor. This enables a convenient mode for publishing cursors.
|
||||
|
||||
4. The server sets up a query observer on that cursor, unless such an observer already exists on the server (for any user), in which case that observer is re-used.
|
||||
|
||||
5. The observer fetches the current set of documents matching the cursor, and passes them back to the subscription (via the `this.added()` callback).
|
||||
|
||||
6. The subscription passes the added documents to the subscribing client's connection *mergebox*, which is an on-server cache of the documents that have been published to this particular client. Each document is merged with any existing version of the document that the client knows about, and an `added` (if the document is new to the client) or `changed` (if it is known but this subscription is adding or changing fields) DDP message is sent.
|
||||
|
||||
Note that the mergebox operates at the level of top-level fields, so if two subscriptions publish nested fields (e.g. sub1 publishes `doc.a.b = 7` and sub2 publishes `doc.a.c = 8`), then the "merged" document might not look as you expect (in this case `doc.a = {c: 8}`, if sub2 happens second).
|
||||
|
||||
7. The publication calls the `.ready()` callback, which sends the DDP `ready` message to the client. The subscription handle on the client is marked as ready.
|
||||
|
||||
8. The observer observes the query. Typically, it [uses MongoDB's Oplog](https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver) to notice changes that affect the query. If it sees a relevant change, like a new matching document or a change in a field on a matching document, it calls into the subscription (via `.added()`, `.changed()` or `.removed()`), which again sends the changes to the mergebox, and then to the client via DDP.
|
||||
|
||||
This continues until the client [stops](#stopping-subscriptions) the subscription, triggering the following behavior:
|
||||
|
||||
1. The client sends the `unsub` DDP message.
|
||||
|
||||
2. The server stops its internal subscription object, triggering the following effects:
|
||||
|
||||
3. Any `this.onStop()` callbacks setup by the publish handler run. In this case, it is a single automatic callback setup when returning a cursor from the handler, which stops the query observer and cleans it up if necessary.
|
||||
|
||||
4. All documents tracked by this subscription are removed from the mergebox, which may or may not mean they are also removed from the client.
|
||||
|
||||
5. The `nosub` message is sent to the client to indicate that the subscription has stopped.
|
||||
|
||||
<h2 id="rest-interop">Working with REST APIs</h2>
|
||||
|
||||
Publications and subscriptions are the primary way of dealing with data in Meteor's DDP protocol, but lots of data sources use the popular REST protocol for their API. It's useful to be able to convert between the two.
|
||||
|
||||
<h3 id="loading-from-rest">Loading data from a REST endpoint with a publication</h3>
|
||||
|
||||
As a concrete example of using the [low-level API](#custom-publication), consider the situation where you have some 3rd party REST endpoint which provides a changing set of data that's valuable to your users. How do you make that data available?
|
||||
|
||||
One option would be to provide a Method that proxies through to the endpoint, for which it's the client's responsibility to poll and deal with the changing data as it comes in. So then it's the clients problem to deal with keeping a local data cache of the data, updating the UI when changes happen, etc. Although this is possible (you could use a Local Collection to store the polled data, for instance), it's simpler, and more natural to create a publication that does this polling for the client.
|
||||
|
||||
A pattern for turning a polled REST endpoint looks something like this:
|
||||
|
||||
```js
|
||||
const POLL_INTERVAL = 5000;
|
||||
|
||||
Meteor.publish('polled-publication', function() {
|
||||
const publishedKeys = {};
|
||||
|
||||
const poll = () => {
|
||||
// Let's assume the data comes back as an array of JSON documents, with an _id field
|
||||
const data = HTTP.get(REST_URL, REST_OPTIONS);
|
||||
|
||||
data.forEach((doc) => {
|
||||
if (publishedKeys[doc._id]) {
|
||||
this.changed(COLLECTION_NAME, doc._id, doc);
|
||||
} else {
|
||||
publishedKeys[doc._id] = true;
|
||||
this.added(COLLECTION_NAME, doc._id, doc);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
poll();
|
||||
this.ready();
|
||||
|
||||
const interval = Meteor.setInterval(poll, POLL_INTERVAL);
|
||||
|
||||
this.onStop(() => {
|
||||
Meteor.clearInterval(interval);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Things can get more complicated; for instance you may want to deal with documents being removed, or share the work of polling between multiple users (in a case where the data being polled isn't private to that user), rather than doing the exact same poll for each interested user.
|
||||
|
||||
<h3 id="publications-as-rest">Accessing a publication as a REST endpoint</h3>
|
||||
|
||||
The opposite scenario occurs when you want to publish data to be consumed by a 3rd party, typically over REST. If the data we want to publish is the same as what we already publish via a publication, then we can use the [simple:rest](https://atmospherejs.com/simple/rest) package to do this.
|
||||
|
||||
In the Todos example app, we have done this, and you can now access our publications over HTTP:
|
||||
|
||||
```bash
|
||||
$ curl localhost:3000/publications/lists.public
|
||||
{
|
||||
"Lists": [
|
||||
{
|
||||
"_id": "rBt5iZQnDpRxypu68",
|
||||
"name": "Meteor Principles",
|
||||
"incompleteCount": 7
|
||||
},
|
||||
{
|
||||
"_id": "Qzc2FjjcfzDy3GdsG",
|
||||
"name": "Languages",
|
||||
"incompleteCount": 9
|
||||
},
|
||||
{
|
||||
"_id": "TXfWkSkoMy6NByGNL",
|
||||
"name": "Favorite Scientists",
|
||||
"incompleteCount": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can also access authenticated publications (such as `lists.private`). Suppose we've signed up (via the web UI) as `user@example.com`, with the password `password`, and created a private list. Then we can access it as follows:
|
||||
|
||||
```bash
|
||||
# First, we need to "login" on the commandline to get an access token
|
||||
$ curl localhost:3000/users/login -H "Content-Type: application/json" --data '{"email": "user@example.com", "password": "password"}'
|
||||
{
|
||||
"id": "wq5oLMLi2KMHy5rR6",
|
||||
"token": "6PN4EIlwxuVua9PFoaImEP9qzysY64zM6AfpBJCE6bs",
|
||||
"tokenExpires": "2016-02-21T02:27:19.425Z"
|
||||
}
|
||||
|
||||
# Then, we can make an authenticated API call
|
||||
$ curl localhost:3000/publications/lists.private -H "Authorization: Bearer 6PN4EIlwxuVua9PFoaImEP9qzysY64zM6AfpBJCE6bs"
|
||||
{
|
||||
"Lists": [
|
||||
{
|
||||
"_id": "92XAn3rWhjmPEga4P",
|
||||
"name": "My Private List",
|
||||
"incompleteCount": 5,
|
||||
"userId": "wq5oLMLi2KMHy5rR6"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="scaling-updates">Scaling updates</h2>
|
||||
|
||||
As previously mentioned, Meteor uses MongoDB's Oplog to identify which changes to apply to which publication. Each change to the database is processed by every Meteor server, so frequent changes can result in high CPU usage across the board. At the same time, your database will come under higher load as all your servers keep fetching data from the oplog.
|
||||
|
||||
To solve this issue, you can use the [`cultofcoders:redis-oplog`](https://github.com/cult-of-coders/redis-oplog) package, which opts out completely from using MongoDB's Oplog and shifts the communication to Redis' Pub/Sub system.
|
||||
|
||||
The caveats:
|
||||
1. You may not need it, if your application is smaller or your data doesn't change a lot.
|
||||
2. You'll have to maintain another database [Redis](https://redis.io/).
|
||||
3. Changes that happen outside Meteor instance, must be [manually submitted to Redis](https://github.com/cult-of-coders/redis-oplog/blob/master/docs/outside_mutations.md).
|
||||
|
||||
The benefits:
|
||||
1. Lower load on server CPU and MongoDB.
|
||||
2. Backwards compatible (no changes required to your publication/subscription code).
|
||||
3. [Better control](https://github.com/cult-of-coders/redis-oplog/blob/master/docs/finetuning.md) over which updates should trigger reactivity.
|
||||
4. You can work with a MongoDB database that does not have oplog enabled.
|
||||
5. Full control over reactivity using [Vent](https://github.com/cult-of-coders/redis-oplog/blob/master/docs/vent.md).
|
||||
|
||||
```
|
||||
meteor add cultofcoders:redis-oplog
|
||||
meteor add disable-oplog
|
||||
```
|
||||
|
||||
In your `settings.json` file:
|
||||
|
||||
```
|
||||
{
|
||||
"redisOplog": {}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
# Start Redis, then run Meteor
|
||||
meteor run --settings settings.json
|
||||
```
|
||||
|
||||
Read more about `redis-oplog` here: https://github.com/cult-of-coders/redis-oplog
|
||||
365
content/deployment.md
Normal file
@@ -0,0 +1,365 @@
|
||||
---
|
||||
title: Deployment and Monitoring
|
||||
description: How to deploy, run, and monitor your Meteor app in production.
|
||||
discourseTopicId: 19668
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. What to consider before you deploy a Meteor application.
|
||||
2. How to deploy to some common Meteor hosting environments.
|
||||
3. How to design a deployment process to make sure your application's quality is maintained.
|
||||
4. How to monitor user behavior with analytics tools.
|
||||
5. How to monitor your application.
|
||||
6. How to make sure your site is discoverable by search engines.
|
||||
|
||||
<h2 id="deploying">Deploying Meteor Applications</h2>
|
||||
|
||||
Once you've built and tested your Meteor application, you need to put it online to show it to the world. Deploying a Meteor application is similar to deploying any other websocket-based Node.js app, but is different in some of the specifics.
|
||||
|
||||
Deploying a web application is fundamentally different to releasing most other kinds of software, in that you can deploy as often as you'd like to. You don't need to wait for users to do something to get the new version of your software because the server will push it right at them.
|
||||
|
||||
However, it's still important to test your changes throughly with a good process of Quality Assurance (QA). Although it's easy to push out fixes to bugs, those bugs can still cause major problems to users and even potentially data corruption!
|
||||
|
||||
> <h3 id="never-use-production-flag">Never use `--production` flag to deploy!</h3>
|
||||
>
|
||||
> `--production` flag is purely meant to simulate production minification, but does almost nothing else. This still watches source code files, exchanges data with package server and does a lot more than just running the app, leading to unnecessary computing resource wasting and security issues. Please don't use `--production` flag to deploy!
|
||||
|
||||
<h3 id="environments">Deployment environments</h3>
|
||||
|
||||
In web application deployment it's common to refer to three runtime environments:
|
||||
|
||||
1. **Development.** This refers to your machine where you develop new features and run local tests.
|
||||
2. **Staging.** An intermediate environment that is similar to production, but not visible to users of the application. Can be used for testing and QA.
|
||||
3. **Production.** The real deployment of your app that your customers are currently using.
|
||||
|
||||
The idea of the staging environment is to provide a non-user-visible test environment that is as close as possible to production in terms of infrastructure. It's common for issues to appear with new code on the production infrastructure that don't happen in a development environment. A very simple example is issues that involve latency between the client and server---connecting to a local development server with tiny latencies, you just may never see such an issue.
|
||||
|
||||
For this reason, developers tend to try and get staging as close as possible to production. This means that all the steps we outline below about production deployment, should, if possible, also be followed for your staging server.
|
||||
|
||||
<h3 id="environment">Environment variables and settings</h3>
|
||||
|
||||
There are two main ways to configure your application outside of the code of the app itself:
|
||||
|
||||
1. **Environment variables.** This is the set of `ENV_VARS` that are set on the running process.
|
||||
2. **Settings.** These are in a JSON object set via either the `--settings` Meteor command-line flag or stringified into the `METEOR_SETTINGS` environment variable.
|
||||
|
||||
Settings should be used to set environment (i.e. staging vs production) specific things, like the access token and secret used to connect to Google. These settings will not change between any given process running your application in the given environment.
|
||||
|
||||
Environment variables are used to set process-specific things, which could conceivably change for different instances of your application's processes. A list of environment variables can be found [here](https://docs.meteor.com/environment-variables.html).
|
||||
|
||||
A final note on storing these settings: It's not a good idea to store settings the same repository where you keep your app code. Read about good places to put your settings in the [Security article](security.html#api-keys).
|
||||
|
||||
<h2 id="other-considerations">Other considerations</h2>
|
||||
|
||||
There are some other considerations that you should make before you deploy your application to a production host. Remember that you should if possible do these steps for both your production *and* staging environments.
|
||||
|
||||
<h3 id="domain-name">Domain name</h3>
|
||||
|
||||
What URL will users use to access your site? You'll probably need to register a domain name with a domain registrar, and setup DNS entries to point to the site (this will depend on how you deploy, see below). If you deploy to Galaxy, you can use a `x.meteorapp.com` or `x.eu.meteorapp.com` domain while you are testing the app. [Learn more about Galaxy domains »](http://galaxy-guide.meteor.com/custom-domains.html#meteorapp-subdomain)
|
||||
|
||||
<h3 id="ssl">SSL Certificate</h3>
|
||||
|
||||
It's always a good idea to use SSL for Meteor applications (see the [Security Article](security.html#ssl) to find out why). Once you have a registered domain name, you'll need to generate an SSL certificate with a certificate authority for your domain. If you deploy to Galaxy, you can [generate a free SSL certificate with a single click](http://galaxy-guide.meteor.com/encryption.html#lets-encrypt) (courtesy of Let's Encrypt!).
|
||||
|
||||
<h3 id="cdn">CDN</h3>
|
||||
|
||||
It's not strictly required, but often a good idea to set up a Content Delivery Network (CDN) for your site. A CDN is a network of servers that hosts the static assets of your site (such as JavaScript, CSS, and images) in numerous locations around the world and uses the server closest to your user to provide those files in order to speed up their delivery. For example, if the actual web server for your application is on the east coast of the USA and your user is in Australia, a CDN could host a copy of the JavaScript of the site within Australia or even in the city the user is in. This has huge benefits for the initial loading time of your site.
|
||||
|
||||
The basic way to use a CDN is to upload your files to the CDN and change your URLs to point at the CDN (for instance if your Meteor app is at `http://myapp.com`, changing your image URL from `<img src="http://myapp.com/cats.gif">` to `<img src="http://mycdn.com/cats.gif">`). However, this would be hard to do with Meteor, since the largest file – your Javascript bundle – changes every time you edit your app.
|
||||
|
||||
For Meteor, we recommend using a CDN with "origin" support (like [CloudFront](http://joshowens.me/using-a-cdn-with-your-production-meteor-app/)), which means that instead of uploading your files in advance, the CDN automatically fetches them from your server. You put your files in `public/` (in this case `public/cats.gif`), and when your Australian user asks the CDN for `http://mycdn.com/cats.gif`, the CDN, behind the scenes, fetches `http://myapp.com/cats.gif` and then delivers it to the user. While this is slightly slower than getting `http://myapp.com/cats.gif` directly, it only happens one time, because the CDN saves the file, and all subsequent Australians who ask for the file get it quickly.
|
||||
|
||||
To get Meteor to use the CDN for your Javascript and CSS bundles, call `WebAppInternals.setBundledJsCssPrefix("http://mycdn.com")` on the server. This will also take care of relative image URLs inside your CSS files. If you need to use a dynamic prefix, you can return the prefix from a function passed to `WebAppInternals.setBundledJsCssUrlRewriteHook()`.
|
||||
|
||||
For all your files in `public/`, change their URLs to point at the CDN. You can use a helper like `assetUrl`.
|
||||
|
||||
Before:
|
||||
|
||||
```html
|
||||
<img src="http://myapp.com/cats.gif">
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```js
|
||||
Template.registerHelper("assetUrl", (asset) => {
|
||||
return "http://mycdn.com/" + asset
|
||||
});
|
||||
```
|
||||
|
||||
```html
|
||||
<img src="{{assetUrl 'cats.gif'}}">
|
||||
```
|
||||
|
||||
<h4 id="cdn-webfonts">CDNs and webfonts</h4>
|
||||
|
||||
If you are hosting a webfont as part of your application and serving it via a CDN, you may need to configure the served headers for the font to allow cross-origin resource sharing (as the webfont is now served from a different origin to your site itself). You can do this in Meteor by adding a handler (you'll need to ensure your CDN is passing the header through):
|
||||
|
||||
```js
|
||||
import { WebApp } from 'meteor/webapp';
|
||||
|
||||
WebApp.rawConnectHandlers.use(function(req, res, next) {
|
||||
if (req._parsedUrl.pathname.match(/\.(ttf|ttc|otf|eot|woff|woff2|font\.css|css)$/)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', /* your hostname, or just '*' */);
|
||||
}
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
And then for example with Cloudfront, you would:
|
||||
|
||||
- Select your distribution
|
||||
- Behavior tab
|
||||
- Select your app origin
|
||||
- Edit button
|
||||
- Under "Whitelist Headers", scroll down to select "Origin"
|
||||
- Add button
|
||||
- "Yes, Edit" button
|
||||
|
||||
<h2 id="deployment-options">Deployment options</h2>
|
||||
|
||||
Meteor is an open source platform, and you can run the apps that you make with Meteor anywhere just like regular Node.js applications. But operating Meteor apps *correctly*, so that your apps work for everyone, can be tricky if you are managing your infrastructure manually. This is why we recommend running production Meteor apps on Galaxy.
|
||||
|
||||
<h3 id="galaxy">Galaxy (recommended)</h3>
|
||||
|
||||
The easiest way to operate your app with confidence is to use Galaxy, the service built by Meteor Development Group specifically to run Meteor apps.
|
||||
|
||||
Galaxy is a distributed system that runs on Amazon AWS. If you understand what it takes to run Meteor apps correctly and how Galaxy works, you’ll come to appreciate Galaxy’s value, and that it will save you a lot of time and trouble. Most large Meteor apps run on Galaxy today, and many of them have switched from custom solutions they used prior to Galaxy’s launch.
|
||||
|
||||
In order to deploy to Galaxy, you'll need to [sign up for an account](https://www.meteor.com/galaxy/signup), and separately provision a MongoDB database (see below).
|
||||
|
||||
Once you've done that, it's easy to [deploy to Galaxy](http://galaxy-guide.meteor.com/deploy-guide.html). You need to [add some environment variables to your settings file](http://galaxy-guide.meteor.com/environment-variables.html) to point it at your MongoDB, and you can deploy with:
|
||||
|
||||
```bash
|
||||
DEPLOY_HOSTNAME=galaxy.meteor.com meteor deploy your-app.com --settings production-settings.json
|
||||
```
|
||||
|
||||
To deploy to the EU region, set DEPLOY_HOSTNAME to eu-west-1.galaxy.meteor.com.
|
||||
|
||||
In order for Galaxy to work with your custom domain (`your-app.com` in this case), you need to [set up your DNS to point at Galaxy](http://galaxy-guide.meteor.com/dns.html). Once you've done this, you should be able to reach your site from a browser.
|
||||
|
||||
You can also log into the Galaxy UI at https://galaxy.meteor.com. Once there you can manage your applications, monitor the number of connections and resource usage, view logs, and change settings.
|
||||
|
||||
<img src="images/galaxy-org-dashboard.png">
|
||||
|
||||
If you are following [our advice](security.html#ssl), you'll probably want to [set up SSL](http://galaxy-guide.meteor.com/encryption.html) on your Galaxy application with the certificate and key for your domain. You should also read the [Security](security.html#ssl) section of this guide for information on how to forcibly redirect HTTP to HTTPS.
|
||||
|
||||
Once you are setup with Galaxy, deployment is simple (just re-run the `meteor deploy` command above), as is scaling --- log into galaxy.meteor.com, and scale instantly from there.
|
||||
|
||||
<img src="images/galaxy-scaling.png">
|
||||
|
||||
<h3 id="mup">Meteor Up</h3>
|
||||
|
||||
[Meteor Up](https://meteor-up.com), often referred to as "mup", is a third-party, open-source tool that can be used to deploy Meteor applications to any online server over SSH. It's essentially a way to automate the manual steps of using `meteor build` and putting that bundle on your server. It also handles setting up the servers, including installing any dependencies, and setting up load balancing and SSL. Although it takes care of many of the details, it can be more work than using a hosting provider.
|
||||
|
||||
You can obtain a server running Ubuntu or Debian from many generic hosting providers and Meteor Up can SSH into your server with the keys you provide in the config. You can get started with the [tutorial](https://meteor-up.com/getting-started.html).
|
||||
|
||||
One of its plugins, [mup-aws-beanstalk](https://github.com/zodern/mup-aws-beanstalk/) deploys Meteor Apps to [AWS Elastic Beanstalk](https://aws.amazon.com/elasticbeanstalk/) instead of a server. It supports autoscaling, load balancing, and zero downtime deploys while taking care of many of the challenges with using Meteor and Elastic Beanstalk.
|
||||
|
||||
<h3 id="docker">Docker</h3>
|
||||
|
||||
To orchestrate your own container-based deployment there are existing base images to consider before rolling your own:
|
||||
|
||||
- [tozd/docker-meteor](https://github.com/tozd/docker-meteor) with Mongo and Nginx images
|
||||
- [jshimko/meteor-launchpad](https://github.com/jshimko/meteor-launchpad)
|
||||
- [disney/meteor-base](https://github.com/disney/meteor-base)
|
||||
|
||||
_The recommendation above is primarily based on current state of maintenance to address upstream security vulnerabilities. Review the Dockerfiles and build scripts to make your own assessment._
|
||||
|
||||
<h3 id="custom-deployment">Custom deployment</h3>
|
||||
|
||||
If you want to figure out your hosting solution completely from scratch, the Meteor tool has a command `meteor build` that creates a deployment bundle that contains a plain Node.js application. Any npm dependencies must be installed before issuing the `meteor build` command to be included in the bundle. You can host this application wherever you like and there are many options in terms of how you set it up and configure it.
|
||||
|
||||
**NOTE** it's important that you build your bundle for the correct architecture. If you are building on your development machine, there's a good chance you are deploying to a different server architecture. You'll want to specify the correct architecture with `--architecture`:
|
||||
|
||||
```bash
|
||||
# for example if deploying to a Ubuntu linux server:
|
||||
npm install --production
|
||||
meteor build /path/to/build --architecture os.linux.x86_64
|
||||
```
|
||||
|
||||
This will provide you with a bundled application `.tar.gz` which you can extract and run without the `meteor` tool. The environment you choose will need the correct version of Node.js and connectivity to a MongoDB server.
|
||||
|
||||
Depending on the version of Meteor you are using, you should install the proper version of `node` using the appropriate installation process for your platform. To find out which version of `node` you should use, run `meteor node -v` in the development environment, or check the `.node_version.txt` file within the bundle generated by `meteor build`. For example, if you are using Meteor 1.6, the version of `node` you should use is 8.8.1.
|
||||
|
||||
> If you use a mis-matched version of Node when deploying your application, you will encounter errors!
|
||||
|
||||
You can then run the application by invoking `node` with a `ROOT_URL`, and `MONGO_URL`. These instructions are also available in the `README` file found in the root of the bundle you built above.
|
||||
|
||||
```bash
|
||||
cd my_build_bundle_directory
|
||||
(cd programs/server && npm install)
|
||||
MONGO_URL=mongodb://localhost:27017/myapp ROOT_URL=http://my-app.com node main.js
|
||||
```
|
||||
|
||||
* `ROOT_URL` is the base URL for your Meteor project
|
||||
* `MONGO_URL` is a [Mongo connection string URI](https://docs.mongodb.com/manual/reference/connection-string/) supplied by the MongoDB provider.
|
||||
|
||||
|
||||
Unless you have a specific need to roll your own hosting environment, the other options here are definitely easier, and probably make for a better setup than doing everything from scratch. Operating a Meteor app in a way that it works correctly for everyone can be complex, and [Galaxy](#galaxy) handles a lot of the specifics like routing clients to the right containers and handling coordinated version updates for you.
|
||||
|
||||
<h2 id="galaxy-mongo">MongoDB options</h2>
|
||||
|
||||
When you deploy your Meteor server, you need a `MONGO_URL` that points to your MongoDB database. You can either use a hosted MongoDB service or set up and run your own MongoDB server. We recommend using a hosted service, as the time saved and peace of mind are usually worth the higher monthly cost. In either case, the database should be hosted in the same region as the Meteor server (for lower latency). For example if your app is hosted on Galaxy in `us-east-1` (on AWS), then you could create a database on [Compose](https://www.compose.io) in `AWS us-east-1` or on [Amazon Lightsail](https://amazonlightsail.com/) in `us-east-1`.
|
||||
|
||||
<h3 id="hosted-service">Hosted service (recommended)</h3>
|
||||
|
||||
There are a variety of services out there, and we recommend that you select one of the below services depending on your requirements:
|
||||
|
||||
* [Compose](https://www.compose.io)
|
||||
* [MongoDB Atlas](https://www.mongodb.com/cloud/atlas)
|
||||
|
||||
When selecting a hosted MongoDB service for production it is important to assess the features that the service provides. Below is a nonexhaustive list of features to consider when selecting a service:
|
||||
|
||||
* Supports the MongoDB version you wish to run
|
||||
* Storage Engine Support (MMAPv1 or WiredTiger) – Since Meteor 1.4 WiredTiger is the default storage engine
|
||||
* Support for Replica Sets & Oplog tailing
|
||||
* Monitoring & Automated alerting
|
||||
* Continuous backups & Automated snapshots
|
||||
* Access Control, IP whitelisting, and AWS VPC Peering
|
||||
* Encryption of data in-flight and at-rest
|
||||
* Cost and pricing granularity
|
||||
* Instance size & options
|
||||
* Instance configurability – Independently configure your CPU, memory, storage and disk I/O speed.
|
||||
|
||||
You can read this [detailed guide](https://www.okgrow.com/posts/mongodb-atlas-setup) by OK GROW! for step-by-step instructions to deploying a production ready MongoDB database on MongoDB Atlas.
|
||||
|
||||
<h3 id="own-server">Own server</h3>
|
||||
|
||||
You can install MongoDB on your own server—one you own, rent, or a VPS (recommended) like [DigitalOcean](https://www.digitalocean.com/) or [Lightsail](https://amazonlightsail.com/). As you can see from the above section, there are many aspects of database setup and maintenance that you have to take care of. For example, to get the best performance, you should choose a server with an [SSD](https://docs.mongodb.com/manual/administration/production-notes/#use-solid-state-disks-ssds) large enough to fit your data and with enough RAM to fit the working set (indexes + active documents) in memory.
|
||||
|
||||
<h2 id="process">Deployment process</h2>
|
||||
|
||||
Although it's much easier to deploy a web application than release most other types of software, that doesn't mean you should be cavalier with your deployment. It's important to properly QA and test your releases before you push them live, to ensure that users don't have a bad experience, or even worse, data get corrupted.
|
||||
|
||||
It's a good idea to have a release process that you follow in releasing your application. Typically that process looks something like:
|
||||
|
||||
1. Deploy the new version of the application to your staging server.
|
||||
2. QA the application on the staging server.
|
||||
3. Fix any bugs found in step 2. and repeat.
|
||||
4. Once you are satisfied with the staging release, release the *exact same* version to production.
|
||||
5. Run final QA on production.
|
||||
|
||||
Steps 2. and 5. can be quite time-consuming, especially if you are aiming to maintain a high level of quality in your application. That's why it's a great idea to develop a suite of acceptance tests (see our [Testing Article](https://guide.meteor.com/testing.html) for more on this). To take things even further, you could run a load/stress test against your staging server on every release.
|
||||
|
||||
<h3 id="continuous-deployment">Continuous deployment</h3>
|
||||
|
||||
Continuous deployment refers to the process of deploying an application via a continuous integration tool, usually when some condition is reached (such as a git push to the `master` branch). You can use CD to deploy to Galaxy, as Nate Strauser explains in a [blog post on the subject](https://medium.com/@natestrauser/migrating-meteor-apps-from-modulus-to-galaxy-with-continuous-deployment-from-codeship-aed2044cabd9#.lvio4sh4a).
|
||||
|
||||
<h3 id="rolling-updates-and-data">Rolling deployments and data versions</h3>
|
||||
|
||||
It's important to understand what happens during a deployment, especially if your deployment involves changes in data format (and potentially data migrations, see the [Collections Article](collections.html#migrations)).
|
||||
|
||||
When you are running your app on multiple servers or containers, it's not a good idea to shut down all of the servers at once and then start them all back up again. This will result in more downtime than necessary, and will cause a huge spike in CPU usage when all of your clients reconnect again at the same time. To alleviate this, Galaxy stops and re-starts containers one by one during deployment. There will be a time period during which some containers are running the old version and some the new version, as users are migrated incrementally to the new version of your app.
|
||||
|
||||
<img src="images/galaxy-deploying.png">
|
||||
|
||||
If the new version involves different data formats in the database, then you need to be a little more careful about how you step through versions to ensure that all the versions that are running simultaneously can work together. You can read more about how to do this in the [collections article](collections.html#migrations).
|
||||
|
||||
<h2 id="analytics">Monitoring users via analytics</h2>
|
||||
|
||||
It's common to want to know which pages of your app are most commonly visited, and where users are coming from. Here's a setup that will get you URL tracking using Google Analytics. We'll be using the [`okgrow:analytics`](https://atmospherejs.com/okgrow/analytics) package.
|
||||
|
||||
```
|
||||
meteor add okgrow:analytics
|
||||
```
|
||||
Now, we need to configure the package with our Google Analytics key (the package also supports a large variety of other providers, check out the [documentation on Atmosphere](https://atmospherejs.com/okgrow/analytics)). Pass it in as part of [_Meteor settings_](#environment):
|
||||
|
||||
```js
|
||||
{
|
||||
"public": {
|
||||
"analyticsSettings": {
|
||||
// Add your analytics tracking id's here
|
||||
"Google Analytics" : {"trackingId": "Your tracking ID"}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The analytics package hooks into Flow Router (see the [routing article](routing.html) for more) and records all of the page events for you.
|
||||
|
||||
You may want to track non-page change related events (for instance publication subscription, or method calls) also. To do so you can use the custom event tracking functionality:
|
||||
|
||||
```js
|
||||
export const updateText = new ValidatedMethod({
|
||||
...
|
||||
run({ todoId, newText }) {
|
||||
// We use `isClient` here because we only want to track
|
||||
// attempted method calls from the client, not server to
|
||||
// server method calls
|
||||
if (Meteor.isClient) {
|
||||
analytics.track('todos.updateText', { todoId, newText });
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
To achieve a similar abstraction for subscriptions/publications, you may want to write a wrapper for `Meteor.subscribe()`.
|
||||
|
||||
<h2 id="apm">Monitoring your application</h2>
|
||||
|
||||
When you are running an app in production, it's vitally important that you keep tabs on the performance of your application and ensure it is running smoothly.
|
||||
|
||||
<h3 id="meteor-performance">Understanding Meteor performance</h3>
|
||||
|
||||
Although a host of tools exist to monitor the performance of HTTP, request-response based applications, the insights they give aren't necessarily useful for a connected client system like a Meteor application. Although it's true that slow HTTP response times would be a problem for your app, and so using a tool like [Pingdom](https://www.pingdom.com) can serve a purpose, there are many kinds of issues with your app that won't be surfaced by such tools.
|
||||
|
||||
<h3 id="galaxy-apm">Monitoring with Galaxy</h3>
|
||||
|
||||
[Galaxy](#galaxy) offers turnkey Meteor hosting and provides tools that are useful to debug the current and past state of your application. CPU and Memory load graphs in combination with connected user counts can be vital to determining if your setup is handling the current load (or if you need more containers), or if there's some specific user action that's causing disproportionate load (if they don't seem to be correlated):
|
||||
|
||||
<img src="images/galaxy-metrics.png">
|
||||
|
||||
Galaxy's UI provides a detailed logging system, which can be invaluable to determine which action it is causing that extra load, or to generally debug other application issues:
|
||||
|
||||
<img src="images/galaxy-logs.png">
|
||||
|
||||
<h3 id="meteor-apm">APM</h3>
|
||||
|
||||
If you really want to understand the ins and outs of running your Meteor application, you should use an Application Performance Monitoring (APM) service. There are multiple services designed for Meteor apps:
|
||||
|
||||
- [Meteor APM](https://www.meteor.com/cloud)
|
||||
- [Monti APM](https://montiapm.com/)
|
||||
- [Meteor Elastic APM](https://github.com/Meteor-Community-Packages/meteor-elastic-apm)
|
||||
|
||||
These APM's operate by taking regular client and server side observations of your application's performance as it conducts various activities and reporting them back to a master server.
|
||||
|
||||
When you visit the APM, you can view current and past behavior of your application over various useful metrics. The APM's have documentation on how to fully use the data to improve your app, but we'll discuss a few key areas here. The screenshots are similar to what you would see in Meteor APM or Monti APM.
|
||||
|
||||
<h4 id="apm-method-pub">Method and Publication Latency</h4>
|
||||
|
||||
Rather than monitoring HTTP response times, in a Meteor app it makes far more sense to consider DDP response times. The two actions your client will wait for in terms of DDP are *method calls* and *publication subscriptions*. APM's include tools to help you discover which of your methods and publications are slow and resource intensive.
|
||||
|
||||
<img src="images/kadira-method-latency.png">
|
||||
|
||||
In the above screenshot you can see the response time breakdown of the various methods commonly called by the Atmosphere application. The median time of 56ms and 99th percentile time of 200ms seems pretty reasonable, and doesn't seem like too much of a concern
|
||||
|
||||
You can also use the "traces" section to discover particular cases of the method call that are particular slow:
|
||||
|
||||
<img src="images/kadira-method-trace.png">
|
||||
|
||||
In the above screenshot we're looking at a slower example of a method call (which takes 214ms), which, when we drill in further we see is mostly taken up waiting on other actions on the user's connection (principally waiting on the `searches/top` and `counts` publications). So we could consider looking to speed up the initial time of those subscriptions as they are slowing down searches a little in some cases.
|
||||
|
||||
|
||||
<h4 id="apm-livequery">Livequery Monitoring</h4>
|
||||
|
||||
A key performance characteristic of Meteor is driven by the behavior of livequery, the key technology that allows your publications to push changing data automatically in realtime. In order to achieve this, livequery needs to monitor your MongoDB instance for changes (by tailing the oplog) and decide if a given change is relevant for the given publication.
|
||||
|
||||
If the publication is used by a lot of users, or there are a lot of changes to be compared, then these livequery observers can do a lot of work. So it's immensely useful that Kadira can tell you some statistics about your livequery usage:
|
||||
|
||||
<img src="images/kadira-observer-usage.png">
|
||||
|
||||
In this screenshot we can see that observers are fairly steadily created and destroyed, with a pretty low amount of reuse over time, although in general they don't survive for all that long. This would be consistent with the fact that we are looking at the `package` publication of Atmosphere which is started everytime a user visits a particular package's page. The behavior is more or less what we would expect so we probably wouldn't be too concerned by this information.
|
||||
|
||||
<h2 id="seo">Enabling SEO</h2>
|
||||
|
||||
If your application contains a lot of publicly accessible content, then you probably want it to rank well in Google and other search engines' indexes. As most webcrawlers do not support client-side rendering (or if they do, have spotty support for websockets), it's better to render the site on the server and deliver it as HTML in this special case.
|
||||
|
||||
If you’re using [Galaxy to host your meteor apps](https://www.meteor.com/galaxy/signup), you can take advantage of built-in automatic [Prerender.io](https://prerender.io) integration. Add [`mdg:seo`](https://atmospherejs.com/mdg/seo) to your app and Galaxy will take care of the rest: loading the code and configuring it with Galaxy-provided credentials.
|
||||
|
||||
If you're not using Galaxy, you can still use `mdg:seo`. You will need to sign up for your own Prerender.io account and provide your token to the `mdg:seo` package in `Meteor.settings`. You can also do this if you use Galaxy but would prefer to use your own Prerender.io account with more frequent cache changes. You can also use the [`prerender-node` NPM package](https://www.npmjs.com/package/prerender-node) directly, mimicing the small amount of [client](https://github.com/meteor/galaxy-seo-package/blob/master/client/prerender.html) and [server](https://github.com/meteor/galaxy-seo-package/blob/master/server/prerender.js) code in the Atmosphere package; do this if you need to use a newer version of the NPM package than the one in `mdg:seo`.
|
||||
|
||||
Chances are you also want to set `<title>` tags and other `<head>` content to make your site appear nicer in search results. The best way to do so is to use the [`kadira:dochead`](https://atmospherejs.com/kadira/dochead) package. The sensible place to call out to `DocHead` is from the `onCreated` callbacks of your page-level components.
|
||||
237
content/hot-code-push.md
Normal file
@@ -0,0 +1,237 @@
|
||||
---
|
||||
title: Hot Code Push
|
||||
description: How to diagnose issues with Hot Code Push in a Meteor Cordova app
|
||||
---
|
||||
|
||||
Is your Meteor Cordova app not getting the updates you’re deploying?
|
||||
|
||||
After reading this article, you'll know:
|
||||
|
||||
1. The prerequisites to using Hot Code Push
|
||||
2. Some techniques to diagnose and solve common issues
|
||||
3. How to dig deeper if that doesn't solve your issue
|
||||
|
||||
This article builds on the [Cordova](/cordova.html) article. We recommend reading that first, though we've tried to link back to its relevant sections.
|
||||
|
||||
<h2 id="prerequisites">Prerequisites</h2>
|
||||
|
||||
Make sure that you have:
|
||||
|
||||
- an Android and/or iOS mobile app based on Meteor's [Cordova integration](/cordova.html#cordova-integration-in-meteor)
|
||||
- the package `hot-code-push` listed in your `.meteor/versions` file
|
||||
- locally: make sure your test device and development device are [on the same network](/cordova.html#connecting-to-the-server)
|
||||
- in production: make sure the `--server` flag of your `meteor build` command points to the same place as your `ROOT_URL` environment variable (or, on Galaxy, the *site* in `meteor deploy site`). [See details](/cordova.html#configuring-server-for-hot-code-push)
|
||||
|
||||
<h2 id="known-issues">Known issues</h2>
|
||||
|
||||
<h3 id="override-compatability-versions">Override compatability versions</h3>
|
||||
|
||||
Did the app suddenly stop getting new code after you updated meteor, or you changed plugins?
|
||||
|
||||
The client probably logs: `Skipping downloading new version because the Cordova platform version or plugin versions have changed and are potentially incompatible`
|
||||
|
||||
Meteor, Cordova and plugins cannot be updated through Hot Code Push. So Meteor by default disables Hot Code Push to app versions that have different versions than the server. This avoids crashing a user’s app, for example, when new JS calls a plugin that his app version doesn’t yet have.
|
||||
|
||||
You can [override this behavior](/cordova.html#controlling-compatibility-version). Just make sure you deal with potentially incompatible versions in your JS instead.
|
||||
|
||||
<h3 id="set-autoupdate-version">Update your AUTOUPDATE_VERSION</h3>
|
||||
|
||||
`AUTOUPDATE_VERSION` is an environment variable you can add to your `run` and `deploy` [commands](https://docs.meteor.com/commandline.html):
|
||||
|
||||
```sh
|
||||
$ AUTOUPDATE_VERSION=abc meteor deploy example.com
|
||||
```
|
||||
|
||||
If your app has an `AUTOUPDATE_VERSION` set, make sure you change its value when you want a deploy to update your clients.
|
||||
|
||||
<h3 id="no-soft-update-in-cordova">Cordova doesn’t hot reload CSS separately</h3>
|
||||
|
||||
Are you seeing your web app incorporate changes without reload, yet your cordova app reloads each time?
|
||||
|
||||
For CSS-only changes, this is the expected behaviour. Browsers update the layout without reload, but in cordova, [any change reloads the whole app](https://docs.meteor.com/packages/autoupdate.html#Cordova-Client).
|
||||
|
||||
In case you want to implement soft CSS update for Cordova, see below [how to edit the source](#how-to-edit-the-source).
|
||||
|
||||
<h3 id="custom-code-and-packages">Outdated custom reload code and packages</h3>
|
||||
|
||||
There are [several reload packages](https://atmospherejs.com/?q=reload), and maybe your app includes some custom reload code. Of course, these may have bugs or be outdated.
|
||||
|
||||
In particular, when you push an update, does the app reload but use the old code anyways? Probably, the code hasn't been updated to work with Meteor 1.8.1 or later. As mentioned in the [changelog](https://docs.meteor.com/changelog.html#v18120190403), we recommend you call `WebAppLocalServer.switchToPendingVersion` before forcing a browser reload.
|
||||
|
||||
Alternatively, use the built-in behavior to reload. Instead of, say, `window.location.reload()`, call the `retry` function passed to the `Reload._onMigrate()` callback. For example:
|
||||
|
||||
```js
|
||||
Reload._onMigrate((retry) => {
|
||||
if (/* not ready */) {
|
||||
window.setTimeout(retry, 5 * 1000); // Check again in 5 seconds
|
||||
return [false];
|
||||
}
|
||||
// ready
|
||||
return [true];
|
||||
});
|
||||
```
|
||||
|
||||
If you use a package that is no longer compatible, consider forking it or opening a PR with the above changes. Alternatively, you can switch to a compatible one such as [`quave:reloader`](https://github.com/quavedev/reloader)
|
||||
|
||||
<h3 id="avoid-hash-fragments">Avoid hash fragments</h3>
|
||||
|
||||
Cordova doesn’t show the URL bar, but the user is still on some URL or other, which may have a hash (`#`). HCP [works better if it doesn't](https://github.com/meteor/meteor/blob/devel/packages/reload/reload.js#L224).
|
||||
|
||||
If you can, remove the hash fragment before the reload.
|
||||
|
||||
<h3 id="avoid-big-files">Avoid making it download big files</h3>
|
||||
|
||||
In the [client side logs](/cordova.html#logging-and-remote-debugging), you may see HCP fail with errors like:
|
||||
|
||||
```
|
||||
Error: Error downloading asset: /
|
||||
at http://localhost:12472/plugins/cordova-plugin-meteor-webapp/www/webapp-local-server.js:51:21
|
||||
at Object.callbackFromNative (http://localhost:12472/cordova.js:287:58)
|
||||
at <anonymous>:1:9
|
||||
```
|
||||
|
||||
This error from [cordova-plugin-meteor-webapp](https://github.com/meteor/cordova-plugin-meteor-webapp) may be caused by big files, often in the `public` folder. Downloading these can fail depending on connection speed, and available space on the device.
|
||||
|
||||
You could run `$ du -a public | sort -n -r | head -n 20` to find the 20 biggest files and their sizes. Consider serving them from an external storage service or CDN instead. Then they are only downloaded when really needed, and can fail downloading without blocking HCP.
|
||||
|
||||
<h3 id="locally">If it is only broken locally</h3>
|
||||
|
||||
If you notice HCP works in production but not when you test locally, you may need to enable clear text or set a correct `--mobile-server`. Both are [explained in the docs](https://docs.meteor.com/packages/autoupdate.html#Cordova-Client).
|
||||
|
||||
<h2 id="dig-deeper">Still having issues?</h2>
|
||||
|
||||
If none of that solved your issues and you’d like to dive deeper, here’s some tips to get you started.
|
||||
|
||||
If you end up finding a bug in one of Meteor's packages or plugins, don't hesitate to open an [issue](https://github.com/meteor/meteor/issues) and/or a [pull request](https://github.com/meteor/meteor/pulls).
|
||||
|
||||
<h3 id="where-does-it-live">Where does hot code push live?</h3>
|
||||
|
||||
Hot code push is included in `meteor-base` through a web of [official meteor packages](https://github.com/meteor/meteor/tree/devel/packages), most importantly [`reload`](https://github.com/meteor/meteor/tree/devel/packages/reload) and [`autoupdate`](https://github.com/meteor/meteor/tree/devel/packages/autoupdate).
|
||||
|
||||
In the case of cordova, a lot of the heavy lifting is done by [`cordova-plugin-meteor-webapp`](https://github.com/meteor/cordova-plugin-meteor-webapp).
|
||||
|
||||
To oversimplify, `autoupdate` decides *when* to refresh the client, the plugin then downloads the new client code and assets, and `reload` then refreshes the page to start using them.
|
||||
|
||||
<h3 id="what-are-the-steps">What are the steps it takes?</h3>
|
||||
|
||||
We can break it down a bit more:
|
||||
|
||||
- whenever the server thinks the client side may have changed, it calculates a hash of your entire client bundle
|
||||
- it [publishes](https://docs.meteor.com/api/pubsub.html) this hash to all clients
|
||||
- the clients subscribe to this publish
|
||||
- when a new hash arrives, each client compares it to its own hash
|
||||
- if it’s different, it starts to download the new client bundle
|
||||
- when it’s done, the client saves any data and announces that it will reload
|
||||
- the app and packages get a chance to [save their data or to deny the reload](https://forums.meteor.com/t/is-there-an-official-documentation-of-reload--onmigrate/16974/2)
|
||||
- if/when allowed, it reloads
|
||||
|
||||
<h3 id="how-to-inspect">How to spy on it?</h3>
|
||||
|
||||
To figure out where the issue is, we can log the various steps HCP takes.
|
||||
|
||||
First, make sure you can [see client-side logs](/cordova.html#logging-and-remote-debugging) (or print them on some screen of your app).
|
||||
|
||||
A few more useful values to print, and events to listen to, might be:
|
||||
|
||||
- The version hashes: `__meteor_runtime_config__.autoupdate.versions['web.cordova']`
|
||||
|
||||
- The reactive [`Autoupdate.newClientAvailable()`](https://github.com/meteor/meteor/blob/devel/packages/autoupdate/QA.md#autoupdatenewclientavailable): if this turns into `true` and then doesn’t refresh, you know the client does receive the new version but something goes wrong trying to download or apply it.
|
||||
|
||||
```js
|
||||
Tracker.autorun(() => {
|
||||
console.log(‘new client available:’, Autoupdate.newClientAvailable());
|
||||
});
|
||||
```
|
||||
|
||||
- To check the client’s subscription to the new versions, check `Meteor.default_connection._subscriptions`. For example, to log whether the subscription is `ready` and `inactive` (using lodash):
|
||||
|
||||
```js
|
||||
const { ready, inactive } = _.chain(Meteor)
|
||||
.get('default_connection._subscriptions', {})
|
||||
.toPairs()
|
||||
.map(1)
|
||||
.find({ name: 'meteor_autoupdate_clientVersions' })
|
||||
.pick(['inactive', 'ready']) // comment this to see all options
|
||||
.value();
|
||||
console.log(‘ready:’, ready);
|
||||
console.log(‘inactive:’, inactive);
|
||||
```
|
||||
Or, to log the value of `ready` each time the subscription changes:
|
||||
|
||||
```js
|
||||
const hcpSub = _.chain(Meteor)
|
||||
.get('default_connection._subscriptions', {})
|
||||
.toPairs()
|
||||
.map(1)
|
||||
.find({ name: 'meteor_autoupdate_clientVersions' })
|
||||
.value(); // no .pick() this time; return whole subscription object
|
||||
|
||||
Tracker.autorun(() => {
|
||||
hcpSub.readyDeps.depend(); // Rerun when something changes in the subscription
|
||||
console.log('hcpSub.ready', hcpSub.ready);
|
||||
});
|
||||
```
|
||||
Should print `false` and then `true` less than a second later.
|
||||
|
||||
- To see if we finish downloading and preparing the new version, listen to `WebAppLocalServer.onNewVersionReady`;
|
||||
|
||||
```js
|
||||
WebAppLocalServer.onNewVersionReady(() => {
|
||||
console.log('new version is ready!');
|
||||
// Copied from original in autoupdate/autoupdate_cordova.js because we overwrite it
|
||||
if (Package.reload) {
|
||||
Package.reload.Reload._reload();
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- To see if permission to reload is being requested, listen to `Reload._onMigrate()`. Be sure to return `[true]` or the reload may not happen. (I believe that if this is run in your app code, it means all packages allowed the reload. But I didn’t find my source on this.)
|
||||
|
||||
```js
|
||||
Reload._onMigrate(() => {
|
||||
console.log('going to reload now');
|
||||
return [true];
|
||||
});
|
||||
```
|
||||
|
||||
- To know if a run of `Meteor.startup` was the result of a HCP reload or not, we can take advantage of the fact that `Session`s (like `ReactiveDict`s) are preserved.
|
||||
|
||||
```js
|
||||
Meteor.startup(() => {
|
||||
console.log('Was HCP:', Session.get('wasHCP'));
|
||||
Session.set('wasHCP', false);
|
||||
|
||||
Reload._onMigrate(() => {
|
||||
Session.set('wasHCP', true);
|
||||
return [true];
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<h2 id="how-to-edit-source">How to edit the source</h2>
|
||||
|
||||
Finally, if you want to change some of the package and plugin code locally, you can.
|
||||
|
||||
<h3 id="editing-packages">Editing the packages</h3>
|
||||
|
||||
Say we want to edit the `autoupdate` package.
|
||||
|
||||
In the root of your project, create a folder named `packages`, then add a folder `autoupdate`. Here we put the code from the original package (found in `~/.meteor/packages`), then we edit it.
|
||||
|
||||
Meteor will now use the local version instead of the official one.
|
||||
|
||||
<h3 id="editing-plugins">Editing the plugin</h3>
|
||||
|
||||
To install a modified version of a plugin,
|
||||
|
||||
- from another folder, download the original code e.g. `git clone https://github.com/meteor/cordova-plugin-meteor-webapp.git`
|
||||
- install it into your meteor project with [`meteor add cordova:cordova-plugin-meteor-webapp@file://path/to/cordova-plugin-meteor-webapp`](https://stackoverflow.com/a/35941588/5786714)
|
||||
- modify it as you like
|
||||
|
||||
Meteor will start using the local version instead of the official one. But note you will have to rerun `meteor build` or `meteor run` every time you change the plugin.
|
||||
|
||||
<h2 id="file-issue">Found a bug?</h2>
|
||||
|
||||
If you found a bug in one of the packages or plugins, don't hesitate to open an [issue](https://github.com/meteor/meteor/issues) and/or [pull request](https://github.com/meteor/meteor/pulls).
|
||||
|
||||
|
||||
BIN
content/images/.DS_Store
vendored
Normal file
BIN
content/images/accounts-ui.png
Normal file
|
After Width: | Height: | Size: 523 KiB |
BIN
content/images/atom-configuration.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
content/images/ben-es2015-demo.gif
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
content/images/chromatic-how-it-works.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
content/images/galaxy-deploying.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
content/images/galaxy-flash-notification.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
content/images/galaxy-logs.png
Normal file
|
After Width: | Height: | Size: 404 KiB |
BIN
content/images/galaxy-metrics.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
content/images/galaxy-org-dashboard.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
content/images/galaxy-placeholders.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
content/images/galaxy-scaling.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
content/images/galaxy-styleguide-list.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
content/images/galaxy-styleguide.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
content/images/kadira-method-latency.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
content/images/kadira-method-trace.png
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
content/images/kadira-observer-usage.png
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
content/images/mobile/ios-safari-settings-web-inspector.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 313 KiB |
BIN
content/images/mobile/xcode-run-scheme.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
content/images/mobile/xcode-select-device.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
content/images/mocha-test-results.png
Normal file
|
After Width: | Height: | Size: 433 KiB |
BIN
content/images/throttle-vs-debounce.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
content/images/todos-loading.png
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
content/images/webstorm-configuration.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
82
content/index.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: This is the guide for using Meteor, a full-stack JavaScript platform for developing modern web and mobile applications.
|
||||
---
|
||||
|
||||
<!-- XXX: note that this content is somewhat duplicated on the docs, and should be updated in parallel -->
|
||||
<h2 id="what-is-meteor">What is Meteor?</h2>
|
||||
|
||||
Meteor is a full-stack JavaScript platform for developing modern web and mobile applications. Meteor includes a key set of technologies for building connected-client reactive applications, a build tool, and a curated set of packages from the Node.js and general JavaScript community.
|
||||
|
||||
- Meteor allows you to develop in **one language**, JavaScript, in all environments: application server, web browser, and mobile device.
|
||||
|
||||
- Meteor uses **data on the wire**, meaning the server sends data, not HTML, and the client renders it.
|
||||
|
||||
- Meteor **embraces the ecosystem**, bringing the best parts of the extremely active JavaScript community to you in a careful and considered way.
|
||||
|
||||
- Meteor provides **full stack reactivity**, allowing your UI to seamlessly reflect the true state of the world with minimal development effort.
|
||||
|
||||
<h2 id="quickstart">Quick start</h2>
|
||||
|
||||
Install the latest official Meteor release [following the steps in our docs](https://docs.meteor.com/install.html).
|
||||
|
||||
Once you've installed Meteor, open a new terminal window and create a project:
|
||||
|
||||
```bash
|
||||
meteor create myapp
|
||||
```
|
||||
|
||||
Run it locally:
|
||||
|
||||
```bash
|
||||
cd myapp
|
||||
meteor npm install
|
||||
meteor
|
||||
# Meteor server running on: http://localhost:3000/
|
||||
```
|
||||
|
||||
> Meteor comes with npm bundled so that you can type `meteor npm` without worrying about installing it yourself. If you like, you can also use a globally installed npm to manage your packages.
|
||||
|
||||
<h2 id="learning-more">Meteor resources</h2>
|
||||
|
||||
1. The place to get started with Meteor is the [tutorials page](https://www.meteor.com/developers/tutorials).
|
||||
|
||||
1. [Meteor Examples](https://github.com/meteor/examples) is a list of examples using Meteor. You can also include your example with Meteor.
|
||||
|
||||
1. Once you are familiar with the basics, the [Meteor Guide](http://guide.meteor.com) covers intermediate material on how to use Meteor in a larger scale app.
|
||||
|
||||
1. Visit the [Meteor discussion forums](https://forums.meteor.com) to announce projects, get help, talk about the community, or discuss changes to core.
|
||||
|
||||
1. [Meteor Slack Community](https://join.slack.com/t/meteor-community/shared_invite/enQtODA0NTU2Nzk5MTA3LWY5NGMxMWRjZDgzYWMyMTEyYTQ3MTcwZmU2YjM5MTY3MjJkZjQ0NWRjOGZlYmIxZjFlYTA5Mjg4OTk3ODRiOTc) is the best place to ask (and answer!) technical questions and also meet Meteor developers.
|
||||
|
||||
1. [Atmosphere](https://atmospherejs.com) is the repository of community packages designed especially for Meteor.
|
||||
|
||||
<h2 id="what-is-it">What is the Meteor Guide?</h2>
|
||||
|
||||
This is a set of articles outlining opinions on best-practice application development using the [Meteor](https://meteor.com) platform. Our aim is to cover patterns that are common to the development of all modern web and mobile applications, so many concepts documented here are not necessarily Meteor specific and could be applied to any application built with a focus on modern, interactive user interfaces.
|
||||
|
||||
Nothing in the Meteor guide is *required* to build a Meteor application---you can certainly use the platform in ways that contradict the principles and patterns of the guide. However, the guide is an attempt to document best practices and community conventions, so we hope that the majority of the Meteor community will benefit from adopting the practices documented here.
|
||||
|
||||
The APIs of the Meteor platform are available at the [docs site](https://docs.meteor.com), and you can browse community packages on [atmosphere](https://atmospherejs.com).
|
||||
|
||||
<h3 id="audience">Target audience</h3>
|
||||
|
||||
The guide is targeted towards intermediate developers that have some familiarity with JavaScript, the Meteor platform, and web development in general. If you are just getting started with Meteor, we recommend starting with the [tutorials](https://www.meteor.com/developers/tutorials).
|
||||
|
||||
<h3 id="example-app">Example apps</h3>
|
||||
|
||||
If you want to see some examples, we have a repository dedicated with several examples provided by the community showing many concepts that can be used when implementing your application with Meteor. To know more you can [here](https://github.com/meteor/examples).
|
||||
|
||||
<h2 id="guide-concepts">Guide development</h2>
|
||||
|
||||
<h3 id="contributing">Contributing</h3>
|
||||
|
||||
Ongoing Meteor Guide development takes place **in the open** [on GitHub](https://github.com/meteor/guide). We encourage pull requests and issues to discuss problems with any changes that could be made to the content. We hope that keeping our process open and honest will make it clear what we plan to include in the guide and what changes will be coming in future Meteor versions.
|
||||
|
||||
<h3 id="goals">Goals of the project</h3>
|
||||
|
||||
The decisions made and practices outlined in the guide must necessarily be **opinionated**. Certain best practices will be highlighted and other valid approaches ignored. We aim to reach community consensus around major decisions but there will always be other ways to solve problems when developing your application. We believe it's important to know what the "standard" way to solve a problem is before branching out to other options. If an alternate approach proves itself superior, then it should make its way into a future version of the guide.
|
||||
|
||||
An important function of the guide is to **shape future development** in the Meteor platform. By documenting best practices, the guide shines a spotlight on areas of the platform that could be better, easier, or more performant, and thus will be used to focus a lot of future platform choices.
|
||||
|
||||
Similarly, gaps in the platform highlighted by the guide can often be plugged by **community packages**; we hope that if you see an opportunity to improve the Meteor workflow by writing a package, that you take it! If you're not sure how best to design or architect your package, reach out on the forums and start a discussion.
|
||||
500
content/methods.md
Normal file
@@ -0,0 +1,500 @@
|
||||
---
|
||||
title: "Methods"
|
||||
description: How to use Methods, Meteor's remote procedure call system, to write to the database.
|
||||
discourseTopicId: 19662
|
||||
---
|
||||
|
||||
After reading this article, you'll know:
|
||||
|
||||
1. What Methods are in Meteor and how they work in detail.
|
||||
2. Best practices for defining and calling Methods.
|
||||
3. How to throw and handle errors with Methods.
|
||||
4. How to call a Method from a form.
|
||||
|
||||
<h2 id="what-is-a-method">What is a Method?</h2>
|
||||
|
||||
Methods are Meteor's remote procedure call (RPC) system, used to save user input events and data that come from the client. If you're familiar with REST APIs or HTTP, you can think of them like POST requests to your server, but with many nice features optimized for building a modern web application. Later on in this article, we'll go into detail about some of the benefits you get from Methods that you wouldn't get from an HTTP endpoint.
|
||||
|
||||
At its core, a Method is an API endpoint for your server; you can define a Method on the server and its counterpart on the client, then call it with some data, write to the database, and get the return value in a callback. Meteor Methods are also tightly integrated with the pub/sub and data loading systems of Meteor to allow for [Optimistic UI](http://info.meteor.com/blog/optimistic-ui-with-meteor-latency-compensation)—the ability to simulate server-side actions on the client to make your app feel faster than it actually is.
|
||||
|
||||
We'll be referring to Meteor Methods with a capital M to differentiate them from class methods in JavaScript.
|
||||
|
||||
<h2 id="defining-and-calling">Defining and calling Methods</h2>
|
||||
|
||||
<h3 id="basic">Basic Method</h3>
|
||||
|
||||
In a basic app, defining a Meteor Method is as simple as defining a function. In a complex app, you want a few extra features to make Methods more powerful and testable. First, we're going to go over how to define a Method using the Meteor core API, and in a later section we'll go over how to use a helpful wrapper package we've created to enable a more powerful Method workflow.
|
||||
|
||||
<h4 id="basic-defining">Defining</h4>
|
||||
|
||||
Here's how you can use the built-in [`Meteor.methods` API](http://docs.meteor.com/#/full/meteor_methods) to define a Method. Note that Methods should always be defined in common code loaded on the client and the server to enable Optimistic UI. If you have some secret code in your Method, consult the [Security article](security.html#secret-code) for how to hide it from the client.
|
||||
|
||||
This example uses the [simpl-schema](https://www.npmjs.com/package/simpl-schema) npm package, which is recommended in several other articles, to validate the Method arguments.
|
||||
|
||||
```js
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
Meteor.methods({
|
||||
'todos.updateText'({ todoId, newText }) {
|
||||
new SimpleSchema({
|
||||
todoId: { type: String },
|
||||
newText: { type: String }
|
||||
}).validate({ todoId, newText });
|
||||
|
||||
const todo = Todos.findOne(todoId);
|
||||
|
||||
if (!todo.editableBy(this.userId)) {
|
||||
throw new Meteor.Error('todos.updateText.unauthorized',
|
||||
'Cannot edit todos in a private list that is not yours');
|
||||
}
|
||||
|
||||
Todos.update(todoId, {
|
||||
$set: { text: newText }
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h4 id="basic-calling">Calling</h4>
|
||||
|
||||
This Method is callable from the client and server using [`Meteor.call`](http://docs.meteor.com/#/full/meteor_call). Note that you should only use a Method in the case where some code needs to be callable from the client; if you just want to modularize code that is only going to be called from the server, use a regular JavaScript function, not a Method.
|
||||
|
||||
Here's how you can call this Method from the client:
|
||||
|
||||
```js
|
||||
Meteor.call('todos.updateText', {
|
||||
todoId: '12345',
|
||||
newText: 'This is a todo item.'
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
alert(err);
|
||||
} else {
|
||||
// success!
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If the Method throws an error, you get that in the first argument of the callback. If the Method succeeds, you get the result in the second argument and the first argument `err` will be `undefined`. For more information about errors, see the section below about error handling.
|
||||
|
||||
<h3 id="advanced-boilerplate">Advanced Method boilerplate</h3>
|
||||
|
||||
Meteor Methods have several features which aren't immediately obvious, but every complex app will need them at some point. These features were added incrementally over several years in a backwards-compatible fashion, so unlocking the full capabilities of Methods requires a good amount of boilerplate. In this article we will first show you all of the code you need to write for each feature, then the next section will talk about a Method wrapper package we have developed to make it easier.
|
||||
|
||||
Here's some of the functionality an ideal Method would have:
|
||||
|
||||
1. Run validation code by itself without running the Method body.
|
||||
2. Override the Method for testing.
|
||||
3. Call the Method with a custom user ID, especially in tests (as recommended by the [Discover Meteor two-tiered methods pattern](https://www.discovermeteor.com/blog/meteor-pattern-two-tiered-methods/)).
|
||||
4. Refer to the Method via JS module rather than a magic string.
|
||||
5. Get the Method simulation return value to get IDs of inserted documents.
|
||||
6. Avoid calling the server-side Method if the client-side validation failed, so we don't waste server resources.
|
||||
|
||||
<h4 id="advanced-boilerplate-defining">Defining</h4>
|
||||
|
||||
```js
|
||||
export const updateText = {
|
||||
name: 'todos.updateText',
|
||||
|
||||
// Factor out validation so that it can be run independently (1)
|
||||
validate(args) {
|
||||
new SimpleSchema({
|
||||
todoId: { type: String },
|
||||
newText: { type: String }
|
||||
}).validate(args)
|
||||
},
|
||||
|
||||
// Factor out Method body so that it can be called independently (3)
|
||||
run({ todoId, newText }) {
|
||||
const todo = Todos.findOne(todoId);
|
||||
|
||||
if (!todo.editableBy(this.userId)) {
|
||||
throw new Meteor.Error('todos.updateText.unauthorized',
|
||||
'Cannot edit todos in a private list that is not yours');
|
||||
}
|
||||
|
||||
Todos.update(todoId, {
|
||||
$set: { text: newText }
|
||||
});
|
||||
},
|
||||
|
||||
// Call Method by referencing the JS object (4)
|
||||
// Also, this lets us specify Meteor.apply options once in
|
||||
// the Method implementation, rather than requiring the caller
|
||||
// to specify it at the call site.
|
||||
call(args, callback) {
|
||||
const options = {
|
||||
returnStubValue: true, // (5)
|
||||
throwStubExceptions: true // (6)
|
||||
}
|
||||
|
||||
Meteor.apply(this.name, [args], options, callback);
|
||||
}
|
||||
};
|
||||
|
||||
// Actually register the method with Meteor's DDP system
|
||||
Meteor.methods({
|
||||
[updateText.name]: function (args) {
|
||||
updateText.validate.call(this, args);
|
||||
updateText.run.call(this, args);
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<h4 id="advanced-boilerplate-calling">Calling</h4>
|
||||
|
||||
Now calling the Method is as simple as calling a JavaScript function:
|
||||
|
||||
```js
|
||||
import { updateText } from './path/to/methods.js';
|
||||
|
||||
// Call the Method
|
||||
updateText.call({
|
||||
todoId: '12345',
|
||||
newText: 'This is a todo item.'
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
alert(err);
|
||||
} else {
|
||||
// success!
|
||||
}
|
||||
});
|
||||
|
||||
// Call the validation only
|
||||
updateText.validate({ wrong: 'args'});
|
||||
|
||||
// Call the Method with custom userId in a test
|
||||
updateText.run.call({ userId: 'abcd' }, {
|
||||
todoId: '12345',
|
||||
newText: 'This is a todo item.'
|
||||
});
|
||||
```
|
||||
|
||||
As you can see, this approach to calling Methods results in a better development workflow - you can more easily deal with the different parts of the Method separately and test your code without having to deal with Meteor internals. But this approach requires you to write a lot of boilerplate on the Method definition side.
|
||||
|
||||
<h3 id="validated-method">Advanced Methods with mdg:validated-method</h3>
|
||||
|
||||
To alleviate some of the boilerplate that's involved in correct Method definitions, we've published a wrapper package called `mdg:validated-method` that does most of this for you. Here's the same Method as above, but defined with the package:
|
||||
|
||||
```js
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
|
||||
export const updateText = new ValidatedMethod({
|
||||
name: 'todos.updateText',
|
||||
validate: new SimpleSchema({
|
||||
todoId: { type: String },
|
||||
newText: { type: String }
|
||||
}).validator(),
|
||||
run({ todoId, newText }) {
|
||||
const todo = Todos.findOne(todoId);
|
||||
|
||||
if (!todo.editableBy(this.userId)) {
|
||||
throw new Meteor.Error('todos.updateText.unauthorized',
|
||||
'Cannot edit todos in a private list that is not yours');
|
||||
}
|
||||
|
||||
Todos.update(todoId, {
|
||||
$set: { text: newText }
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You call it the same way you call the advanced Method above, but the Method definition is significantly simpler. We believe this style of Method lets you clearly see the important parts - the name of the Method sent over the wire, the format of the expected arguments, and the JavaScript namespace by which the Method can be referenced. Validated methods only accept a single argument and a callback function.
|
||||
|
||||
<h2 id="errors">Error handling</h2>
|
||||
|
||||
In regular JavaScript functions, you indicate errors by throwing an `Error` object. Throwing errors from Meteor Methods works almost the same way, but a bit of complexity is introduced by the fact that in some cases the error object will be sent over a websocket back to the client.
|
||||
|
||||
<h3 id="throwing-errors">Throwing errors from a Method</h3>
|
||||
|
||||
Meteor introduces two new types of JavaScript errors: [`Meteor.Error`](http://docs.meteor.com/#/full/meteor_error) and [`ValidationError`](https://atmospherejs.com/mdg/validation-error). These and the regular JavaScript `Error` type should be used in different situations:
|
||||
|
||||
<h4 id="internal-server-errors">Regular `Error` for internal server errors</h4>
|
||||
|
||||
When you have an error that doesn't need to be reported to the client, but is internal to the server, throw a regular JavaScript error object. This will be reported to the client as a totally opaque internal server error with no details.
|
||||
|
||||
<h4 id="meteor-error">Meteor.Error for general runtime errors</h4>
|
||||
|
||||
When the server was not able to complete the user's desired action because of a known condition, you should throw a descriptive `Meteor.Error` object to the client. In the Todos example app, we use these to report situations where the current user is not authorized to complete a certain action, or where the action is not allowed within the app - for example, deleting the last public list.
|
||||
|
||||
`Meteor.Error` takes three arguments: `error`, `reason`, and `details`.
|
||||
|
||||
1. `error` should be a short, unique, machine-readable error code string that the client can interpret to understand what happened. It's good to prefix this with the name of the Method for easy internationalization, for example: `'todos.updateText.unauthorized'`.
|
||||
2. `reason` should be a short description of the error for the developer. It should give your coworker enough information to be able to debug the error. The `reason` parameter should not be printed to the end user directly, since this means you now have to do internationalization on the server before sending the error message, and the UI developer has to worry about the Method implementation when thinking about what will be displayed in the UI.
|
||||
3. `details` is optional, and can be used where extra data will help the client understand what is wrong. In particular, it can be combined with the `error` field to print a more helpful error message to the end user.
|
||||
|
||||
<h4 id="validation-error">ValidationError for argument validation errors</h4>
|
||||
|
||||
When a Method call fails because the arguments are of the wrong type, it's good to throw a `ValidationError`. This works like `Meteor.Error`, but is a custom constructor that enforces a standard error format that can be read by different form and validation libraries. In particular, if you are calling this Method from a form, throwing a `ValidationError` will make it possible to display nice error messages next to particular fields in the form.
|
||||
|
||||
When you use `mdg:validated-method` with `simpl-schema` as demonstrated above, this type of error is thrown for you.
|
||||
|
||||
Read more about the error format in the [`mdg:validation-error` docs](https://atmospherejs.com/mdg/validation-error).
|
||||
|
||||
<h3 id="handling-errors">Handling errors</h3>
|
||||
|
||||
When you call a Method, any errors thrown by it will be returned in the callback. At this point, you should identify which error type it is and display the appropriate message to the user. In this case, it is unlikely that the Method will throw a `ValidationError` or an internal server error, so we will only handle the unauthorized error:
|
||||
|
||||
```js
|
||||
// Call the Method
|
||||
updateText.call({
|
||||
todoId: '12345',
|
||||
newText: 'This is a todo item.'
|
||||
}, (err, res) => {
|
||||
if (err) {
|
||||
if (err.error === 'todos.updateText.unauthorized') {
|
||||
// Displaying an alert is probably not what you would do in
|
||||
// a real app; you should have some nice UI to display this
|
||||
// error, and probably use an i18n library to generate the
|
||||
// message from the error code.
|
||||
alert('You aren\'t allowed to edit this todo item');
|
||||
} else {
|
||||
// Unexpected error, handle it in the UI somehow
|
||||
}
|
||||
} else {
|
||||
// success!
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
We'll talk about how to handle the `ValidationError` in the section on forms below.
|
||||
|
||||
<h3 id="throw-stub-exceptions">Errors in Method simulation</h3>
|
||||
|
||||
When a Method is called, it usually runs twice---once on the client to simulate the result for Optimistic UI, and again on the server to make the actual change to the database. This means that if your Method throws an error, it will likely fail on the client _and_ the server. For this reason, `ValidatedMethod` turns on undocumented option in Meteor to avoid calling the server-side implementation if the simulation throws an error.
|
||||
|
||||
While this behavior is good for saving server resources in cases where a Method will certainly fail, it's important to make sure that the simulation doesn't throw an error in cases where the server Method would have succeeded (for example, if you didn't load some data on the client that the Method needs to do the simulation properly). In this case, you can wrap server-side-only logic in a block that checks for a method simulation:
|
||||
|
||||
```js
|
||||
if (!this.isSimulation) {
|
||||
// Logic that depends on server environment here
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="method-form">Calling a Method from a form</h2>
|
||||
|
||||
The main thing enabled by the `ValidationError` convention is integration between Methods and the forms that call them. In general, your app is likely to have a one-to-one mapping of forms in the UI to Methods. First, let's define a Method for our business logic:
|
||||
|
||||
```js
|
||||
// This Method encodes the form validation requirements.
|
||||
// By defining them in the Method, we do client and server-side
|
||||
// validation in one place.
|
||||
export const insert = new ValidatedMethod({
|
||||
name: 'Invoices.methods.insert',
|
||||
validate: new SimpleSchema({
|
||||
email: { type: String, regEx: SimpleSchema.RegEx.Email },
|
||||
description: { type: String, min: 5 },
|
||||
amount: { type: String, regEx: /^\d*\.(\d\d)?$/ }
|
||||
}).validator(),
|
||||
run(newInvoice) {
|
||||
// In here, we can be sure that the newInvoice argument is
|
||||
// validated.
|
||||
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Invoices.methods.insert.not-logged-in',
|
||||
'Must be logged in to create an invoice.');
|
||||
}
|
||||
|
||||
Invoices.insert(newInvoice)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Let's define an HTML form:
|
||||
|
||||
```html
|
||||
<template name="Invoices_newInvoice">
|
||||
<form class="Invoices_newInvoice">
|
||||
<label for="email">Recipient email</label>
|
||||
<input type="email" name="email" />
|
||||
{{#each error in errors "email"}}
|
||||
<div class="form-error">{{error}}</div>
|
||||
{{/each}}
|
||||
|
||||
<label for="description">Item description</label>
|
||||
<input type="text" name="description" />
|
||||
{{#each error in errors "description"}}
|
||||
<div class="form-error">{{error}}</div>
|
||||
{{/each}}
|
||||
|
||||
<label for="amount">Amount owed</label>
|
||||
<input type="text" name="amount" />
|
||||
{{#each error in errors "amount"}}
|
||||
<div class="form-error">{{error}}</div>
|
||||
{{/each}}
|
||||
</form>
|
||||
</template>
|
||||
```
|
||||
|
||||
Now, let's write some JavaScript to handle this form nicely:
|
||||
|
||||
```js
|
||||
import { insert } from '../api/invoices/methods.js';
|
||||
|
||||
Template.Invoices_newInvoice.onCreated(function() {
|
||||
this.errors = new ReactiveDict();
|
||||
});
|
||||
|
||||
Template.Invoices_newInvoice.helpers({
|
||||
errors(fieldName) {
|
||||
return Template.instance().errors.get(fieldName);
|
||||
}
|
||||
});
|
||||
|
||||
Template.Invoices_newInvoice.events({
|
||||
'submit .Invoices_newInvoice'(event, instance) {
|
||||
const data = {
|
||||
email: event.target.email.value,
|
||||
description: event.target.description.value,
|
||||
amount: event.target.amount.value
|
||||
};
|
||||
|
||||
insert.call(data, (err, res) => {
|
||||
if (err) {
|
||||
if (err.error === 'validation-error') {
|
||||
// Initialize error object
|
||||
const errors = {
|
||||
email: [],
|
||||
description: [],
|
||||
amount: []
|
||||
};
|
||||
|
||||
// Go through validation errors returned from Method
|
||||
err.details.forEach((fieldError) => {
|
||||
// XXX i18n
|
||||
errors[fieldError.name].push(fieldError.type);
|
||||
});
|
||||
|
||||
// Update ReactiveDict, errors will show up in the UI
|
||||
instance.errors.set(errors);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
As you can see, there is a fair amount of boilerplate to handle errors nicely in a form, but most of it can be abstracted by an off-the-shelf form framework or an application-specific wrapper of your own design.
|
||||
|
||||
<h2 id="loading-data">Loading data with Methods</h2>
|
||||
|
||||
Since Methods can work as general purpose RPCs, they can also be used to fetch data instead of publications. There are some advantages and some disadvantages to this approach compared with loading data through publications, and at the end of the day we recommend always using publications to load data.
|
||||
|
||||
Methods can be useful to fetch the result of a complex computation from the server that doesn't need to update when the server data changes. The biggest disadvantage of fetching data through Methods is that the data won't be automatically loaded into Minimongo, Meteor's client-side data cache, so you'll need to manage the lifecycle of that data manually. Another disadvantage is that database queries are not shared between clients like publication cursors often are—the Method (and any queries it contains) will run once for each client that calls it.
|
||||
|
||||
<h4 id="local-collection">Using a local collection to store and display data fetched from a Method</h4>
|
||||
|
||||
Collections are a very convenient way of storing data on the client side. If you're fetching data using something other than subscriptions, you can put it in a collection manually. Let's look at an example where we have a complex algorithm for calculating average scores from a series of games for a number of players. We don't want to use a publication to load this data because we want to control exactly when it runs, and don't want the data to be cached automatically.
|
||||
|
||||
First, you need to create a _local collection_ - this is a collection that exists only on the client side and is not tied to a database collection on the server. Read more in the [Collections article](http://guide.meteor.com/collections.html#local-collections).
|
||||
|
||||
```js
|
||||
// In client-side code, declare a local collection
|
||||
// by passing `null` as the argument
|
||||
ScoreAverages = new Mongo.Collection(null);
|
||||
```
|
||||
|
||||
Now, if you fetch data using a Method, you can put into this collection:
|
||||
|
||||
```js
|
||||
import { calculateAverages } from '../api/games/methods.js';
|
||||
|
||||
function updateAverages() {
|
||||
// Clean out result cache
|
||||
ScoreAverages.remove({});
|
||||
|
||||
// Call a Method that does an expensive computation
|
||||
calculateAverages.call((err, res) => {
|
||||
res.forEach((item) => {
|
||||
ScoreAverages.insert(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
We can now use the data from the local collection `ScoreAverages` inside a UI component exactly the same way we would use a regular MongoDB collection. Instead of it updating automatically, we'll need to call `updateAverages` every time we need new results.
|
||||
|
||||
<h2 id="advanced">Advanced concepts</h2>
|
||||
|
||||
While you can use Methods in an app by following the Meteor introductory tutorial, it's important to understand exactly how they work to use them effectively in a production app. One of the downsides of using a framework like Meteor that does a lot for you under the hood is that you don't always understand what is going on, so it's good to learn some of the core concepts.
|
||||
|
||||
<h3 id="call-lifecycle">Method call lifecycle</h3>
|
||||
|
||||
Here's exactly what happens, in order, when a Method is called:
|
||||
|
||||
<h4 id="lifecycle-simulation">1. Method simulation runs on the client</h4>
|
||||
|
||||
If we defined this Method in client and server code, as all Methods should be, a Method simulation is executed in the client that called it.
|
||||
|
||||
The client enters a special mode where it tracks all changes made to client-side collections, so that they can be rolled back later. When this step is complete, the user of your app sees their UI update instantly with the new content of the client-side database, but the server hasn't received any data yet.
|
||||
|
||||
If an exception is thrown from the Method simulation, then by default Meteor ignores it and continues to step (2). If you are using `ValidatedMethod` or pass a special `throwStubExceptions` option to `Meteor.apply`, then an exception thrown from the simulation will stop the server-side Method from running at all.
|
||||
|
||||
The return value of the Method simulation is discarded, unless the `returnStubValue` option is passed when calling the Method, in which case it is returned to the Method caller. ValidatedMethod passes this option by default.
|
||||
|
||||
<h4 id="lifecycle-ddp-message">2. A `method` DDP message is sent to the server</h4>
|
||||
|
||||
The Meteor client constructs a DDP message to send to the server. This includes the Method name, arguments, and an automatically generated Method ID that represents this particular Method invocation.
|
||||
|
||||
<h4 id="lifecycle-server">3. Method runs on the server</h4>
|
||||
|
||||
When the server receives the message, it executes the Method code again on the server. The client side version was a simulation that will be rolled back later, but this time it's the real version that is writing to the actual database. Running the actual Method logic on the server is crucial because the server is a trusted environment where we know that security-critical code will run the way we expect.
|
||||
|
||||
<h4 id="lifecycle-result">4. Return value is sent to the client</h4>
|
||||
|
||||
Once the Method has finished running on the server, it sends a `result` message to the client with the Method ID generated in step 2, and the return value itself. The client stores this for later use, but _doesn't call the Method callback yet_. If you pass the [`onResultReceived` option to `Meteor.apply`](http://docs.meteor.com/#/full/meteor_apply), that callback is fired.
|
||||
|
||||
<h4 id="lifecycle-publications">5. Any DDP publications affected by the Method are updated</h4>
|
||||
|
||||
If we have any publications on the page that have been affected by the database writes from this Method, the server sends the appropriate updates to the client. Note that the client data system doesn't reveal these updates to the app UI until the next step.
|
||||
|
||||
<h4 id="lifecycle-updated">6. `updated` message sent to the client, data replaced with server result, Method callback fires</h4>
|
||||
|
||||
After the relevant data updates have been sent to the correct client, the server sends back the last message in the Method life cycle - the DDP `updated` message with the relevant Method ID. The client rolls back any changes to client side data made in the Method simulation in step 1, and replaces them with the actual changes sent from the server in step 5.
|
||||
|
||||
Lastly, the callback passed to `Meteor.call` actually fires with the return value from step 4. It's important that the callback waits until the client is up to date, so that your Method callback can assume that the client state reflects any changes done inside the Method.
|
||||
|
||||
<h4 id="lifecycle-error">Error case</h4>
|
||||
|
||||
In the list above, we didn't cover the case when the Method execution on the server throws an error. In that case, there is no return value, and the client gets an error instead. The Method callback is fired instantly with the returned error as the first argument. Read more about error handling in the section about errors below.
|
||||
|
||||
<h3 id="methods-vs-rest">Benefits of Methods over REST</h3>
|
||||
|
||||
We believe Methods provide a much better primitive for building modern applications than REST endpoints built on HTTP. Let's go over some of the things you get for free with Methods that you would have to worry about if using HTTP. The purpose of this section is not to convince you that REST is bad - it's just to remind you that you don't need to handle these things yourself in a Meteor app.
|
||||
|
||||
<h4 id="non-blocking">Methods use synchronous-style APIs, but are non-blocking</h4>
|
||||
|
||||
You may notice in the example Method above, we didn't need to write any callbacks when interacting with MongoDB, but the Method still has the non-blocking properties that people associate with Node.js and callback-style code. Meteor uses a coroutine library called [Fibers](https://github.com/laverdet/node-fibers) to enable you to write code that uses return values and throws errors, and avoid dealing with lots of nested callbacks.
|
||||
|
||||
<h4 id="ordered">Methods always run and return in order</h4>
|
||||
|
||||
When accessing a REST API, you will sometimes run into a situation where you make two requests one after the other, but the results arrive out of order. Meteor's underlying machinery makes sure this never happens with Methods. When multiple Method calls are received _from the same client_, Meteor runs each Method to completion before starting the next one. If you need to disable this functionality for one particularly long-running Method, you can use [`this.unblock()`](http://docs.meteor.com/#/full/method_unblock) to allow the next Method to run while the current one is still in progress. Also, since Meteor is based on Websockets instead of HTTP, all Method calls and results are guaranteed to arrive in the order they are sent. You can also pass a special option `wait: true` to `Meteor.apply` to wait to send a particular Method until all others have returned, and not send any other Methods until this one returns.
|
||||
|
||||
<h4 id="change-tracking">Change tracking for Optimistic UI</h4>
|
||||
|
||||
When Method simulations and server-side executions run, Meteor tracks any resulting changes to the database. This is what lets the Meteor data system roll back the changes from the Method simulation and replace them with the actual writes from the server. Without this automatic database tracking, it would be very difficult to implement a correct Optimistic UI system.
|
||||
|
||||
<h3 id="calling-method-from-method">Calling a Method from another Method</h3>
|
||||
|
||||
Sometimes, you'll want to call a Method from another Method. Perhaps you already have some functionality implemented and you want to add a wrapper that fills in some of the arguments automatically. This is a totally fine pattern, and Meteor does some nice things for you:
|
||||
|
||||
1. Inside a client-side Method simulation, calling another Method doesn't fire off an extra request to the server - the assumption is that the server-side implementation of the Method will do it. However, it does run the _simulation_ of the called Method, so that the simulation on the client closely matches what will happen on the server.
|
||||
2. Inside a Method execution on the server, calling another Method runs that Method as if it were called by the same client. That means the Method runs as usual, and the context - `userId`, `connection`, etc - are taken from the original Method call.
|
||||
|
||||
<h3 id="consistent-id-generation">Consistent ID generation and optimistic UI</h3>
|
||||
|
||||
When you insert documents into Minimongo from the client-side simulation of a Method, the `_id` field of each document is a random string. When the Method call is executed on the server, the IDs are generated again before being inserted into the database. If it were implemented naively, it could mean that the IDs generated on the server are different, which would cause undesirable flickering and re-renders in the UI when the Method simulation was rolled back and replaced with the server data. But this is not the case in Meteor!
|
||||
|
||||
Each Meteor Method invocation shares a random generator seed with the client that called the Method, so any IDs generated by the client and server Methods are guaranteed to be the same. This means you can safely use the IDs generated on the client to do things while the Method is being sent to the server, and be confident that the IDs will be the same when the Method finishes. One case where this is particularly useful is if you want to create a new document in the database, then immediately redirect to a URL that contains that new document's ID.
|
||||
|
||||
<h3 id="retries">Method retries</h3>
|
||||
|
||||
If you call a Method from the client, and the user's Internet connection disconnects before the result is received, Meteor assumes that the Method didn't actually run. When the connection is re-established, the Method call will be sent again. This means that, in certain situations, Methods can be sent more than once. This should only happen very rarely, but in the case where an extra Method call could have negative consequences it is worth putting in extra effort to ensure that Methods are idempotent - that is, calling them multiple times doesn't result in additional changes to the database.
|
||||
|
||||
Many Method operations are idempotent by default. Inserts will throw an error if they happen twice because the generated ID will conflict. Removes on collections won't do anything the second time, and most update operators like `$set` will have the same result if run again. The only places you need to worry about code running twice are MongoDB update operators that stack, like `$inc` and `$push`, and calls to external APIs.
|
||||
|
||||
<h3 id="comparison-with-allow-deny">Historical comparison with allow/deny</h3>
|
||||
|
||||
The Meteor core API includes an alternative to Methods for manipulating data from the client. Instead of explicitly defining Methods with specific arguments, you can instead call `insert`, `update`, and `remove` directly from the client and specify security rules with [`allow`](http://docs.meteor.com/#/full/allow) and [`deny`](http://docs.meteor.com/#/full/deny). In the Meteor Guide, we are taking a strong position that this feature should be avoided and Methods used instead. Read more about the problems with allow/deny in the [Security article](security.html#allow-deny).
|
||||
|
||||
Historically, there have been some misconceptions about the features of Meteor Methods as compared with the allow/deny feature, including that it was more difficult to achieve Optimistic UI when using Methods. However, the client-side `insert`, `update`, and `remove` feature is actually implemented _on top of_ Methods, so Methods are strictly more powerful. You get great default Optimistic UI by defining your Method code on the client and the server, as described in the Method lifecycle section above.
|
||||
7
content/mobile.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Mobile
|
||||
description: How to build mobile apps using Meteor's Cordova integration.
|
||||
discourseTopicId: 20195
|
||||
---
|
||||
|
||||
Moved to [Cordova](/cordova)
|
||||
139
content/react-native.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: React Native
|
||||
description: How to integrate your React Native apps with Meteor
|
||||
---
|
||||
|
||||
React Native has grown to be one of the most popular platforms for building native apps, being used by [companies like Tesla, Instagram, and Facebook](https://reactnative.dev/showcase) in production. React Native allows you to write apps in JavaScript that are rendered with native code. It has many of the features that you value when working with Meteor, like instant refresh on save.
|
||||
|
||||
You can easily integrate your React Native app with Meteor, using the same methods you would on a Meteor + React Web app. The integration supports most Meteor features, including Methods, Pub/Sub, and Password Accounts, and has the same usage as `react-meteor-data`.
|
||||
|
||||
<h2 id="getting-started">Getting started with React Native</h2>
|
||||
|
||||
React Native projects are coded using the same React principles, but have a completely separate codebase from your Meteor project.
|
||||
|
||||
A collection of NPM packages are being developed to make it easy for you to integrate React Native with Meteor. In order to use React Native with Meteor, you create a React Native app and use the `@meteorrn/core` package to connect your app to your Meteor server. The `@meteorrn/core` package contains Meteor, MongoDB, `withTracker`, Accounts, and more.
|
||||
|
||||
For most projects, since your native app will display the same data and call the same methods as your Meteor web app, creating a React Native app that connects to your Meteor server does not require any changes to your Meteor codebase.
|
||||
|
||||
The only time you will need to make changes to your Meteor codebase is to enable certain features that are unique to your native app. For example, if you want to add push notifications to your native app, you will need to create a method on your Meteor app to store the native push tokens for a user.
|
||||
|
||||
There are two routes for getting started with React Native. You can use "Vanilla" React Native, or you can use [Expo](https://expo.io/). Expo is a set of tools built around React Native. You can even try out React Native from your web browser using [Expo Snack](https://snack.expo.io/). You don't even need to install XCode or Android Studio to start using Expo.
|
||||
|
||||
Here are the downsides to using Expo:
|
||||
- You cannot add Native Modules that use Native Code (Java, Swift, etc)
|
||||
- You cannot use packages that require linking (these are npm modules that include native code, and allow you to acess native features like the camera, push notifications, fingerprint authentication, etc). \
|
||||
- Apps that use Expo are much larger then pure React Native apps
|
||||
|
||||
Expo does provide some native features ([click here for the full list](https://docs.expo.io/versions/latest/)), but if there is a feature missing that you need, you'll likely need to use an npm package or your own custom native code.
|
||||
|
||||
You can "eject" your app from Expo to take advantage of Vanilla React Native features, but ejection cannot be undone easily.
|
||||
|
||||
The React Native documentation lets you choose between the Expo ("Expo CLI") and Vanilla React Native ("React Native CLI") setup instructions. You can read through the installation instructions and decide which option makes more sense for you.
|
||||
|
||||
Here is the link to the React Native getting started documentation: https://reactnative.dev/docs/environment-setup
|
||||
|
||||
Once you have your environment setup and have your app running on your device or in the emulator, you can proceed to the next step of the guide: "Meteor React Native Installation"
|
||||
|
||||
<h2 id="installation">Meteor React Native Installation</h2>
|
||||
|
||||
To install the `@meteorrn/core` package, run the following command in your React Native project:
|
||||
|
||||
````
|
||||
npm install --save @meteorrn/core
|
||||
````
|
||||
|
||||
You also need to confirm you have the package's peer dependencies installed:
|
||||
- Confirm you have `@react-native-community/netinfo` installed
|
||||
- Confirm you have `@react-native-async-storage/async-storage@>=1.8.1` installed. If you are using Expo, or otherwise cannot use `@react-native-async-storage/async-storage`, please see [these instructions](https://github.com/TheRealNate/meteor-react-native#custom-storage-adapter).
|
||||
|
||||
The `@meteorrn/core` package enables your React Native app to establish a DDP connection with your Meteor server so it can receive data from publications and call server methods. It also provides access to core Meteor client methods like `Accounts.createUser` and `Meteor.loginWithPasword`, and allows you to display data in your app with the `withTracker` method.
|
||||
|
||||
**Note: If your React Native app uses version 0.59 or lower, the @meteorrn/core package contains breaking changes. Use [react-native-meteor](https://www.npmjs.com/package/react-native-meteor) instead.**
|
||||
|
||||
<h2 id="setup">Setup</h2>
|
||||
|
||||
|
||||
First, import `Meteor`, `withTracker`, and `Mongo`:
|
||||
|
||||
````
|
||||
import Meteor, { Mongo, withTracker } from '@meteorrn/core';
|
||||
````
|
||||
|
||||
Next, you need to connect to your Meteor server. This should typically be at the start of your App.jsx.
|
||||
|
||||
````
|
||||
Meteor.connect("wss://myapp.meteor.com/websocket");
|
||||
````
|
||||
|
||||
Define your collections:
|
||||
|
||||
````
|
||||
const Todos = new Mongo.Collection("todos");
|
||||
````
|
||||
|
||||
And now you're ready to start coding.
|
||||
|
||||
<h2 id="usage">Coding with Meteor React Native</h2>
|
||||
|
||||
If you've used React before, coding with React Native is pretty straightforward. However, instead of components like `div` and `span`, we have `View` and `Text`. You can learn the fundamentals of React Native [here](https://reactnative.dev/docs/intro-react).
|
||||
|
||||
Meteor React Native's usage is designed to be as close to `meteor/react-meteor-data` and the Meteor core as possible. It provides a `withTracker` method. The package also has full support for accounts, including `Meteor.loginWithPassword`, `Meteor.user`, `Accounts.createUser`, `Meteor.loggingIn`, `Accounts.forgotPassword`, etc.
|
||||
|
||||
````
|
||||
const MyAppContainer = withTracker(() => {
|
||||
|
||||
const myTodoTasks = Todos.find({completed:false}).fetch();
|
||||
const handle = Meteor.subscribe("myTodos");
|
||||
|
||||
return {
|
||||
myTodoTasks,
|
||||
loading:!handle.ready()
|
||||
};
|
||||
|
||||
})(MyApp);
|
||||
````
|
||||
|
||||
When rendering small amounts of data, you can use the array map method:
|
||||
|
||||
````
|
||||
import { View, ScrollView, Text } from 'react-native';
|
||||
|
||||
class MyApp extends React.Component {
|
||||
render() {
|
||||
const { loading, myTodoTasks } = this.props;
|
||||
|
||||
if(loading) {
|
||||
return <View><Text>Loading your tasks...</Text></View>
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
{!myTodoTasks.length ?
|
||||
<Text>You don't have any tasks</Text>
|
||||
:
|
||||
myTodoTasks.map(task => (
|
||||
<Text>{task.text}</Text>
|
||||
))
|
||||
}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
If you are rendering a large amounts of data, you should use the [FlatList](https://reactnative.dev/docs/flatlist) component.
|
||||
|
||||
<h2 id="conclusion">Conclusion</h2>
|
||||
|
||||
**Here are some useful links for futher reading:**
|
||||
|
||||
You can see a list of example components built with `MeteorRN` [here](https://github.com/TheRealNate/meteor-react-native/tree/master/examples).
|
||||
|
||||
You can view the full API docs for `MeteorRN` on the [meteor-react-native repo](https://github.com/TheRealNate/meteor-react-native/blob/master/docs/api.md)
|
||||
|
||||
You can see the official React Native API docs [here](https://reactnative.dev/docs/components-and-apis)
|
||||
|
||||
["How to setup your first app" from HackerNoon](https://hackernoon.com/react-native-how-to-setup-your-first-app-a36c450a8a2f)
|
||||
|
||||
["The Full React Native Layout Cheat Sheet" from WixEngineering](https://medium.com/wix-engineering/the-full-react-native-layout-cheat-sheet-a4147802405c)
|
||||
184
content/react.md
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
title: React
|
||||
description: How to use React with Meteor.
|
||||
discourseTopicId: 20192
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. What React is, and why you would consider using it with Meteor.
|
||||
2. How to install React in your Meteor application, and how to use it correctly.
|
||||
3. How to integrate React with Meteor's realtime data layer.
|
||||
4. How to route in a React/Meteor application.
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
[React](https://reactjs.org/) is a JavaScript library for building reactive user interfaces developed and distributed by the Facebook team.
|
||||
|
||||
React has a vibrant and growing ecosystem and is used widely in production in a variety of combinations with different frameworks.
|
||||
|
||||
To learn more about using React in general and coming up to speed with the library, you should check out the [React documentation](https://reactjs.org/docs/getting-started.html).
|
||||
|
||||
To get started with React in Meteor, you can follow along the [React tutorial](https://react-tutorial.meteor.com).
|
||||
|
||||
<h3 id="using-with-meteor">Installing and using React</h3>
|
||||
|
||||
To install React in Meteor should add it as a npm dependency:
|
||||
|
||||
```sh
|
||||
meteor npm install --save react react-dom
|
||||
```
|
||||
|
||||
This will install `react` into your project and allow you to access it within your files with `import React from 'react'`.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
|
||||
export const HelloWorld = () => <h1>Hello World</h1>;
|
||||
```
|
||||
|
||||
You can render a component hierarchy to the DOM using the `react-dom` package:
|
||||
|
||||
```jsx
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { HelloWorld } from './HelloWorld.js';
|
||||
|
||||
Meteor.startup(() => {
|
||||
render(<HelloWorld />, document.getElementById('app'));
|
||||
});
|
||||
```
|
||||
|
||||
You need to include a `<div id="app"></div>` in your body's HTML somewhere of course.
|
||||
|
||||
By default Meteor already uses React when you create a new app using
|
||||
`meteor create my-app` then this basic set up will be already ready for you.
|
||||
|
||||
<h3 id="using-third-party-npm-packages">Using 3rd party packages</h3>
|
||||
|
||||
Meteor does not require any different configuration as Meteor is 100% compatible with NPM, so you can use any React component library.
|
||||
|
||||
<h2 id="data">Using Meteor's data system</h2>
|
||||
|
||||
React is a front-end rendering library and as such doesn't concern itself with how data gets into and out of components.
|
||||
|
||||
On the other hand, Meteor offers in the core packages [publications](data-loading.html) and [methods](methods.html), used to subscribe to and modify the data in your application.
|
||||
|
||||
To integrate the two systems, we've developed a [`react-meteor-data`](https://atmospherejs.com/meteor/react-meteor-data) package which allows React components to respond to data changes via Meteor's [Tracker](https://www.meteor.com/tracker) reactivity system.
|
||||
|
||||
<h3 id="using-withTracker">Using `useTracker`</h3>
|
||||
|
||||
> The `useTracker` function follows latest best practices of React. Choosing hooks instead of HOCs.
|
||||
|
||||
To use data from a Meteor collection inside a React component, install [`react-meteor-data`](https://atmospherejs.com/meteor/react-meteor-data):
|
||||
|
||||
```sh
|
||||
meteor add react-meteor-data
|
||||
```
|
||||
|
||||
Once installed, you'll be able to import the `useTracker` function and others.
|
||||
|
||||
You can learn more about them [here](https://github.com/meteor/react-packages/tree/master/packages/react-meteor-data#usetrackerreactivefn-deps-hook)
|
||||
|
||||
<h2 id="routing">Routing</h2>
|
||||
|
||||
Although there are many solutions for routing with Meteor and React, [react-router](https://reactrouter.com/) is the most popular package right now.
|
||||
|
||||
As always Meteor does not require anything different when using React Router so you can follow their [quick-start guide](https://reactrouter.com/web/guides/quick-start) to set up React Router in your Meteor project.
|
||||
|
||||
<h2 id="meteor-and-react">Meteor Packages and Blaze</h2>
|
||||
|
||||
<h3 id="atmosphere-packages">Using React in Atmosphere Packages</h3>
|
||||
|
||||
If you are writing an Atmosphere package and want to depend on React or an npm package that itself depends on React, you can't use `Npm.depends()` and `Npm.require()`, as this will result in *2* copies of React being installed into the application (and besides `Npm.require()` only works on the server).
|
||||
|
||||
Instead, you need to ask your users to install the correct npm packages in the application itself. This will ensure that only one copy of React is shipped to the client and there are no version conflicts.
|
||||
|
||||
In order to check that a user has installed the correct versions of npm packages, you can use the [`tmeasday:check-npm-versions`](https://atmospherejs.com/tmeasday/check-npm-versions) package to check dependency versions at runtime.
|
||||
|
||||
<span id="using-with-blaze"><!-- don't break old links --></span>
|
||||
<h3 id="react-in-blaze">React Components in Blaze</h3>
|
||||
|
||||
If you are not using Blaze with React you can skip this.
|
||||
|
||||
If you'd like to use React within a larger app built with [Blaze](#blaze.html) (which is a good strategy if you'd like to incrementally migrate an app from Blaze to React), you can use the [`react-template-helper`](https://atmospherejs.com/meteor/react-template-helper) component which renders a react component inside a Blaze template. First run `meteor add react-template-helper`, then use the `React` helper in your template:
|
||||
|
||||
```html
|
||||
<template name="userDisplay">
|
||||
<div>Hello, {{username}}</div>
|
||||
<div>{{> React component=UserAvatar userId=_id}}</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
You will need to pass in the component class with a helper:
|
||||
|
||||
```js
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
import './userDisplay.html';
|
||||
import UserAvatar from './UserAvatar.js';
|
||||
|
||||
Template.userDisplay.helpers({
|
||||
UserAvatar() {
|
||||
return UserAvatar;
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `component` argument is the React component to include, which should be passed in with a helper.
|
||||
|
||||
Every other argument is passed as a prop to the component when it is rendered.
|
||||
|
||||
Note that there a few caveats:
|
||||
|
||||
- React components must be the only thing in the wrapper element. Due to a limitation of React (see facebook/react [#1970](https://github.com/facebook/react/issues/1970), [#2484](https://github.com/facebook/react/issues/2484)), a React component must be rendered as the only child of its parent node, meaning it cannot have any siblings.
|
||||
|
||||
- This means a React component also can't be the only thing in a Blaze template, because it's impossible to tell where the template will be used.
|
||||
|
||||
<h4 id="passing-callbacks-from-blaze">Passing callbacks to a React component</h4>
|
||||
|
||||
To pass a callback to a React component that you are including with this helper, make a [template helper that returns a function](http://blazejs.org/guide/reusable-components.html#Pass-callbacks), and pass it in as a prop, like so:
|
||||
|
||||
```js
|
||||
Template.userDisplay.helpers({
|
||||
onClick() {
|
||||
const instance = Template.instance();
|
||||
|
||||
// Return a function from this helper, where the template instance is in
|
||||
// a closure
|
||||
return () => {
|
||||
instance.hasBeenClicked.set(true)
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
To use it in Blaze:
|
||||
|
||||
```html
|
||||
<template name="userDisplay">
|
||||
<div>
|
||||
{{> React component=UserAvatar userId=_id onClick=onClick}}
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
<h3 id="blaze-in-react">Blaze Templates in React</h3>
|
||||
|
||||
We can also use Blaze templates in React components. This is similarly useful for a gradual transition strategy; but more importantly, it allows us to continue to use the multitude of Atmosphere packages built for Blaze in our React projects, as well as core packages like `accounts-ui`.
|
||||
|
||||
One way to do this is with the [`gadicc:blaze-react-component`](https://atmospherejs.com/gadicc/blaze-react-component) package. First run `meteor add gadicc:blaze-react-component`, then import and use it in your components as follows:
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<Blaze template="itemsList" items={items} />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
The `<Blaze template="itemsList" items={items} />` line is the same as if you had written `{% raw %}{{> itemsList items=items}}{% endraw %}` inside of a Blaze template. For other options and further information, see the package's [project page](https://github.com/gadicc/meteor-blaze-react-component).
|
||||
509
content/routing.md
Normal file
@@ -0,0 +1,509 @@
|
||||
---
|
||||
title: "URLs and Routing"
|
||||
description: How to drive your Meteor app's UI using URLs with FlowRouter.
|
||||
discourseTopicId: 19663
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. The role URLs play in a client-rendered app, and how it's different from a traditional server-rendered app.
|
||||
2. How to define client and server routes for your app using Flow Router.
|
||||
3. How to have your app display different content depending on the URL.
|
||||
4. How to dynamically load application modules depending on the URL.
|
||||
5. How to construct links to routes and go to routes programmatically.
|
||||
|
||||
<h2 id="client-side">Client-side Routing</h2>
|
||||
|
||||
In a web application, _routing_ is the process of using URLs to drive the user interface (UI). URLs are a prominent feature in every single web browser, and have several main functions from the user's point of view:
|
||||
|
||||
1. **Bookmarking** - Users can bookmark URLs in their web browser to save content they want to come back to later.
|
||||
2. **Sharing** - Users can share content with others by sending a link to a certain page.
|
||||
3. **Navigation** - URLs are used to drive the web browser's back/forward functions.
|
||||
|
||||
In a traditional web application stack, where the server renders HTML one page at a time, the URL is the fundamental entry point for the user to access the application. Users navigate an application by clicking through URLs, which are sent to the server via HTTP, and the server responds appropriately via a server-side router.
|
||||
|
||||
In contrast, Meteor operates on the principle of _data on the wire_, where the server doesn’t think in terms of URLs or HTML pages. The client application communicates with the server over DDP. Typically as an application loads, it initializes a series of _subscriptions_ which fetch the data required to render the application. As the user interacts with the application, different subscriptions may load, but there’s no technical need for URLs to be involved in this process - you could have a Meteor app where the URL never changes.
|
||||
|
||||
However, most of the user-facing features of URLs listed above are still relevant for typical Meteor applications. Since the server is not URL-driven, the URL becomes a useful representation of the client-side state the user is currently looking at. However, unlike in a server-rendered application, it does not need to describe the entirety of the user’s current state; it needs to contain the parts that you want to be linkable. For example, the URL should contain any search filters applied on a page, but not necessarily the state of a dropdown menu or popup.
|
||||
|
||||
<h2 id="flow-router">Using Flow Router</h2>
|
||||
|
||||
To add routing to your app, install the [`ostrio:flow-router-extra`](https://atmospherejs.com/ostrio/flow-router-extra) package:
|
||||
|
||||
```
|
||||
meteor add ostrio:flow-router-extra
|
||||
```
|
||||
|
||||
Flow Router is a community routing package for Meteor.
|
||||
|
||||
<h2 id="flow-router-extra">Using Flow Router Extra</h2>
|
||||
|
||||
Flow Router Extra is carefully extended `flow-router` package by `kadira` with waitOn and template context. Flow Router Extra shares original "Flow Router" API including flavoring for extra features like `waitOn`, template context and build in `.render()`. Note: `arillo:flow-router-helpers` and `zimme:active-route` already build into Flow Router Extra and updated to support latest Meteor release.
|
||||
|
||||
To add routing to your app, install the [`ostrio:flow-router-extra`](https://github.com/VeliovGroup/flow-router) package:
|
||||
|
||||
```
|
||||
meteor add ostrio:flow-router-extra
|
||||
```
|
||||
|
||||
<h2 id="defining-routes">Defining a simple route</h2>
|
||||
|
||||
The basic purpose of a router is to match certain URLs and perform actions as a result. This all happens on the client side, in the app user's browser or mobile app container. Let's take an example from the Todos example app:
|
||||
|
||||
```js
|
||||
FlowRouter.route('/lists/:_id', {
|
||||
name: 'Lists.show',
|
||||
action(params, queryParams) {
|
||||
console.log("Looking at a list?");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This route handler will run in two situations: if the page loads initially at a URL that matches the URL pattern, or if the URL changes to one that matches the pattern while the page is open. Note that, unlike in a server-side-rendered app, the URL can change without any additional requests to the server.
|
||||
|
||||
When the route is matched, the `action` method executes, and you can perform any actions you need to. The `name` property of the route is optional, but will let us refer to this route more conveniently later on.
|
||||
|
||||
<h3 id="url-pattern-matching">URL pattern matching</h3>
|
||||
|
||||
Consider the following URL pattern, used in the code snippet above:
|
||||
|
||||
```js
|
||||
'/lists/:_id'
|
||||
```
|
||||
|
||||
The above pattern will match certain URLs. You may notice that one segment of the URL is prefixed by `:` - this means that it is a *url parameter*, and will match any string that is present in that segment of the path. Flow Router will make that part of the URL available on the `params` property of the current route.
|
||||
|
||||
Additionally, the URL could contain an HTTP [*query string*](https://en.wikipedia.org/wiki/Query_string) (the part after an optional `?`). If so, Flow Router will also split it up into named parameters, which it calls `queryParams`.
|
||||
|
||||
|
||||
Here are some example URLs and the resulting `params` and `queryParams`:
|
||||
|
||||
| URL | matches pattern? | params | queryParams
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| / | no | | |
|
||||
| /about | no | | |
|
||||
| /lists/ | no | | |
|
||||
| /lists/eMtGij5AFESbTKfkT | yes | { _id: "eMtGij5AFESbTKfkT"} | { }
|
||||
| /lists/1 | yes | { _id: "1"} | { }
|
||||
| /lists/1?todoSort=top | yes | { _id: "1"} | { todoSort: "top" }
|
||||
|
||||
|
||||
Note that all of the values in `params` and `queryParams` are always strings since URLs don't have any way of encoding data types. For example, if you wanted a parameter to represent a number, you might need to use `parseInt(value, 10)` to convert it when you access it.
|
||||
|
||||
<h2 id="accessing-route-info">Accessing Route information</h2>
|
||||
|
||||
In addition to passing in the parameters as arguments to the `action` function on the route, Flow Router makes a variety of information available via (reactive and otherwise) functions on the global singleton `FlowRouter`. As the user navigates around your app, the values of these functions will change (reactively in some cases) correspondingly.
|
||||
|
||||
Like any other global singleton in your application (see the [data loading](data-loading.html#stores) for info about stores), it's best to limit your access to `FlowRouter`. That way the parts of your app will remain modular and more independent. In the case of `FlowRouter`, it's best to access it solely from the top of your component hierarchy, either in the "page" component, or the layouts that wrap it. Read more about accessing data in the [UI article](ui-ux.html#components).
|
||||
|
||||
<h3 id="current-route">The current route</h3>
|
||||
|
||||
It's useful to access information about the current route in your code. Here are some reactive functions you can call:
|
||||
|
||||
* `FlowRouter.getRouteName()` gets the name of the route
|
||||
* `FlowRouter.getParam(paramName)` returns the value of a single URL parameter
|
||||
* `FlowRouter.getQueryParam(paramName)` returns the value of a single URL query parameter
|
||||
|
||||
In our example of the list page from the Todos app, we access the current list's id with `FlowRouter.getParam('_id')` (we'll see more on this below).
|
||||
|
||||
<h3 id="active-route">Highlighting the active route</h3>
|
||||
|
||||
One situation where it is sensible to access the global `FlowRouter` singleton to access the current route's information deeper in the component hierarchy is when rendering links via a navigation component. It's often required to highlight the "active" route in some way (this is the route or section of the site that the user is currently looking at).
|
||||
|
||||
In the Todos example app, we link to each list the user knows about in the `App_body` template:
|
||||
|
||||
```html
|
||||
{{#each list in lists}}
|
||||
<a class="list-todo {{activeListClass list}}">
|
||||
...
|
||||
|
||||
{{list.name}}
|
||||
</a>
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
We can determine if the user is currently viewing the list with the `activeListClass` helper:
|
||||
|
||||
```js
|
||||
Template.App_body.helpers({
|
||||
activeListClass(list) {
|
||||
const active = ActiveRoute.name('Lists.show')
|
||||
&& FlowRouter.getParam('_id') === list._id;
|
||||
|
||||
return active && 'active';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h2 id="rendering-routes">Rendering based on the route</h2>
|
||||
|
||||
Now we understand how to define routes and access information about the current route, we are in a position to do what you usually want to do when a user accesses a route---render a user interface to the screen that represents it.
|
||||
|
||||
*In this section, we'll discuss how to render routes using Blaze as the UI engine. If you are building your app with React or Angular, you will end up with similar concepts but the code will be a bit different.*
|
||||
|
||||
When using Flow Router, the simplest way to display different views on the page for different URLs is to use the complementary Blaze Layout package. First, make sure you have the Blaze Layout package installed:
|
||||
|
||||
```bash
|
||||
meteor add kadira:blaze-layout
|
||||
```
|
||||
|
||||
To use this package, we need to define a "layout" component. In the Todos example app, that component is called `App_body`:
|
||||
|
||||
```html
|
||||
<template name="App_body">
|
||||
...
|
||||
{{> Template.dynamic template=main}}
|
||||
...
|
||||
</template>
|
||||
```
|
||||
|
||||
(This is not the entire `App_body` component, but we highlight the most important part here).
|
||||
Here, we are using a Blaze feature called `Template.dynamic` to render a template which is attached to the `main` property of the data context. Using Blaze Layout, we can change that `main` property when a route is accessed.
|
||||
|
||||
We do that in the `action` function of our `Lists.show` route definition:
|
||||
|
||||
```js
|
||||
FlowRouter.route('/lists/:_id', {
|
||||
name: 'Lists.show',
|
||||
action() {
|
||||
BlazeLayout.render('App_body', {main: 'Lists_show_page'});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
What this means is that whenever a user visits a URL of the form `/lists/X`, the `Lists.show` route will kick in, triggering the `BlazeLayout` call to set the `main` property of the `App_body` component.
|
||||
|
||||
<h2 id="page-templates">Components as pages</h2>
|
||||
|
||||
Notice that we called the component to be rendered `Lists_show_page` (rather than `Lists_show`). This indicates that this template is rendered directly by a Flow Router action and forms the 'top' of the rendering hierarchy for this URL.
|
||||
|
||||
The `Lists_show_page` template renders *without* arguments---it is this template's responsibility to collect information from the current route, and then pass this information down into its child templates. Correspondingly the `Lists_show_page` template is very tied to the route that rendered it, and so it needs to be a smart component. See the article on [UI/UX](ui-ux.html) for more about smart and reusable components.
|
||||
|
||||
It makes sense for a "page" smart component like `Lists_show_page` to:
|
||||
|
||||
1. Collect route information,
|
||||
2. Subscribe to relevant subscriptions,
|
||||
3. Fetch the data from those subscriptions, and
|
||||
4. Pass that data into a sub-component.
|
||||
|
||||
In this case, the HTML template for `Lists_show_page` will look very simple, with most of the logic in the JavaScript code:
|
||||
|
||||
```html
|
||||
<template name="Lists_show_page">
|
||||
{{#each listId in listIdArray}}
|
||||
{{> Lists_show (listArgs listId)}}
|
||||
{{else}}
|
||||
{{> App_notFound}}
|
||||
{{/each}}
|
||||
</template>
|
||||
```
|
||||
|
||||
(The `{% raw %}{{#each listId in listIdArray}}{% endraw %}}` is an animation technique for [page to page transitions](ui-ux.html#animating-page-changes)).
|
||||
|
||||
```js
|
||||
Template.Lists_show_page.helpers({
|
||||
// We use #each on an array of one item so that the "list" template is
|
||||
// removed and a new copy is added when changing lists, which is
|
||||
// important for animation purposes.
|
||||
listIdArray() {
|
||||
const instance = Template.instance();
|
||||
const listId = instance.getListId();
|
||||
return Lists.findOne(listId) ? [listId] : [];
|
||||
},
|
||||
listArgs(listId) {
|
||||
const instance = Template.instance();
|
||||
return {
|
||||
todosReady: instance.subscriptionsReady(),
|
||||
// We pass `list` (which contains the full list, with all fields, as a function
|
||||
// because we want to control reactivity. When you check a todo item, the
|
||||
// `list.incompleteCount` changes. If we didn't do this the entire list would
|
||||
// re-render whenever you checked an item. By isolating the reactiviy on the list
|
||||
// to the area that cares about it, we stop it from happening.
|
||||
list() {
|
||||
return Lists.findOne(listId);
|
||||
},
|
||||
// By finding the list with only the `_id` field set, we don't create a dependency on the
|
||||
// `list.incompleteCount`, and avoid re-rendering the todos when it changes
|
||||
todos: Lists.findOne(listId, {fields: {_id: true}}).todos()
|
||||
};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
It's the `listShow` component (a reusuable component) that actually handles the job of rendering the content of the page. As the page component is passing the arguments into the reusuable component, it is able to be quite mechanical and the concerns of talking to the router and rendering the page have been separated.
|
||||
|
||||
<h3 id="route-rendering-logic">Changing page when logged out</h3>
|
||||
|
||||
There are types of rendering logic that appear related to the route but which also seem related to user interface rendering. A classic example is authorization; for instance, you may want to render a login form for some subset of your pages if the user is not yet logged in.
|
||||
|
||||
It's best to keep all logic around what to render in the component hierarchy (i.e. the tree of rendered components). So this authorization should happen inside a component. Suppose we wanted to add this to the `Lists_show_page` we were looking at above. We could do something like:
|
||||
|
||||
```html
|
||||
<template name="Lists_show_page">
|
||||
{{#if currentUser}}
|
||||
{{#each listId in listIdArray}}
|
||||
{{> Lists_show (listArgs listId)}}
|
||||
{{else}}
|
||||
{{> App_notFound}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
Please log in to edit posts.
|
||||
{{/if}}
|
||||
</template>
|
||||
```
|
||||
|
||||
Of course, we might find that we need to share this functionality between multiple pages of our app that require access control. We can share functionality between templates by wrapping them in a wrapper "layout" component which includes the behavior we want.
|
||||
|
||||
You can create wrapper components by using the "template as block helper" ability of Blaze (see the [Blaze Article](http://blazejs.org/guide/spacebars.html#Block-Helpers)). Here's how we could write an authorization template:
|
||||
|
||||
```html
|
||||
<template name="App_forceLoggedIn">
|
||||
{{#if currentUser}}
|
||||
{{> Template.contentBlock}}
|
||||
{{else}}
|
||||
Please log in see this page.
|
||||
{{/if}}
|
||||
</template>
|
||||
```
|
||||
|
||||
Once that template exists, we can wrap our `Lists_show_page`:
|
||||
|
||||
```html
|
||||
<template name="Lists_show_page">
|
||||
{{#App_forceLoggedIn}}
|
||||
{{#each listId in listIdArray}}
|
||||
{{> Lists_show (listArgs listId)}}
|
||||
{{else}}
|
||||
{{> App_notFound}}
|
||||
{{/each}}
|
||||
{{/App_forceLoggedIn}}
|
||||
</template>
|
||||
```
|
||||
|
||||
The main advantage of this approach is that it is immediately clear when viewing the `Lists_show_page` what behavior will occur when a user visits the page.
|
||||
|
||||
Multiple behaviors of this type can be composed by wrapping a template in multiple wrappers, or creating a meta-wrapper that combines multiple wrapper templates.
|
||||
|
||||
<h2 id="changing-routes">Changing Routes</h2>
|
||||
|
||||
Rendering an updated UI when a user reaches a new route is not that useful without giving the user some way to reach a new route! The simplest way is with the trusty `<a>` tag and a URL. You can generate the URLs yourself using helpers such as `FlowRouter.pathFor` to display a link to a certain route. For example, in the Todos example app, our nav links look like:
|
||||
|
||||
|
||||
```html
|
||||
<a href="{{pathFor 'Lists.show' _id=list._id}}" title="{{list.name}}"
|
||||
class="list-todo {{activeListClass list}}">
|
||||
```
|
||||
|
||||
<h3 id="routing-programmatically">Routing programmatically</h3>
|
||||
|
||||
In some cases you want to change routes based on user action outside of them clicking on a link. For instance, in the example app, when a user creates a new list, we want to route them to the list they just created. We do this by calling `FlowRouter.go()` once we know the id of the new list:
|
||||
|
||||
```js
|
||||
import { insert } from '../../api/lists/methods.js';
|
||||
|
||||
Template.App_body.events({
|
||||
'click .js-new-list'() {
|
||||
const listId = insert.call();
|
||||
FlowRouter.go('Lists.show', { _id: listId });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can also change only part of the URL if you want to, using the `FlowRouter.setParams()` and `FlowRouter.setQueryParams()`. For instance, if we were viewing one list and wanted to go to another, we could write:
|
||||
|
||||
```js
|
||||
FlowRouter.setParams({_id: newList._id});
|
||||
```
|
||||
|
||||
Of course, calling `FlowRouter.go()`, will always work, so unless you are trying to optimize for a specific situation it's better to use that.
|
||||
|
||||
<h3 id="storing-data-in-the-url">Storing data in the URL</h3>
|
||||
|
||||
As we discussed in the introduction, the URL is really a serialization of some part of the client-side state the user is looking at. Although parameters can only be strings, it's possible to convert any type of data to a string by serializing it.
|
||||
|
||||
In general if you want to store arbitrary serializable data in a URL param, you can use [`EJSON.stringify()`](http://docs.meteor.com/#/full/ejson_stringify) to turn it into a string. You'll need to URL-encode the string using [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) to remove any characters that have meaning in a URL:
|
||||
|
||||
```js
|
||||
FlowRouter.setQueryParams({data: encodeURIComponent(EJSON.stringify(data))});
|
||||
```
|
||||
|
||||
You can then get the data back out of Flow Router using [`EJSON.parse()`](http://docs.meteor.com/#/full/ejson_parse). Note that Flow Router does the URL decoding for you automatically:
|
||||
|
||||
```js
|
||||
const data = EJSON.parse(FlowRouter.getQueryParam('data'));
|
||||
```
|
||||
|
||||
<h2 id="redirecting">Redirecting</h2>
|
||||
|
||||
Sometimes, your users will end up on a page that isn't a good place for them to be. Maybe the data they were looking for has moved, maybe they were on an admin panel page and logged out, or maybe they just created a new object and you want them to end up on the page for the thing they just created.
|
||||
|
||||
Usually, we can redirect in response to a user's action by calling `FlowRouter.go()` and friends, like in our list creation example above, but if a user browses directly to a URL that doesn't exist, it's useful to know how to redirect immediately.
|
||||
|
||||
If a URL is out-of-date (sometimes you might change the URL scheme of an application), you can redirect inside the `action` function of the route:
|
||||
|
||||
```js
|
||||
FlowRouter.route('/old-list-route/:_id', {
|
||||
action(params) {
|
||||
FlowRouter.go('Lists.show', params);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="redirecting-dynamically">Redirecting dynamically</h3>
|
||||
|
||||
The above approach will only work for static redirects. However, sometimes you need to load some data to figure out where to redirect to. In this case you'll need to render part of the component hierarchy to subscribe to the data you need. For example, in the Todos example app, we want to make the root (`/`) route redirect to the first known list. To achieve this, we need to render a special `App_rootRedirector` route:
|
||||
|
||||
```js
|
||||
FlowRouter.route('/', {
|
||||
name: 'App.home',
|
||||
action() {
|
||||
BlazeLayout.render('App_body', {main: 'App_rootRedirector'});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The `App_rootRedirector` component is rendered inside the `App_body` layout, which takes care of subscribing to the set of lists the user knows about *before* rendering its sub-component, and we are guaranteed there is at least one such list. This means that if the `App_rootRedirector` ends up being created, there'll be a list loaded, so we can do:
|
||||
|
||||
```js
|
||||
Template.App_rootRedirector.onCreated(function rootRedirectorOnCreated() {
|
||||
// We need to set a timeout here so that we don't redirect from inside a redirection
|
||||
// which is a limitation of the current version of FR.
|
||||
Meteor.setTimeout(() => {
|
||||
FlowRouter.go('Lists.show', Lists.findOne());
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
If you need to wait on specific data that you aren't already subscribed to at creation time, you can use an `autorun` and `subscriptionsReady()` to wait on that subscription:
|
||||
|
||||
```js
|
||||
Template.App_rootRedirector.onCreated(function rootRedirectorOnCreated() {
|
||||
// If we needed to open this subscription here
|
||||
this.subscribe('lists.public');
|
||||
|
||||
// Now we need to wait for the above subscription. We'll need the template to
|
||||
// render some kind of loading state while we wait, too.
|
||||
this.autorun(() => {
|
||||
if (this.subscriptionsReady()) {
|
||||
FlowRouter.go('Lists.show', Lists.findOne());
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="redirecting-after-user-action">Redirecting after a user's action</h3>
|
||||
|
||||
Often, you just want to go to a new route programmatically when a user has completed a certain action. Above we saw a case (creating a new list) when we wanted to do it *optimistically*---i.e. before we hear back from the server that the Method succeeded. We can do this because we reasonably expect that the Method will succeed in almost all cases (see the [UI/UX article](ui-ux.html#optimistic-ui) for further discussion of this).
|
||||
|
||||
However, if we wanted to wait for the method to return from the server, we can put the redirection in the callback of the method:
|
||||
|
||||
```js
|
||||
Template.App_body.events({
|
||||
'click .js-new-list'() {
|
||||
lists.insert.call((err, listId) => {
|
||||
if (!err) {
|
||||
FlowRouter.go('Lists.show', { _id: listId });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You will also want to show some kind of indication that the method is working in between their click of the button and the redirect completing. Don't forget to provide feedback if the method is returning an error.
|
||||
|
||||
<h2 id="advanced">Advanced Routing</h2>
|
||||
|
||||
<h3 id="dynamic-module-loading">Dynamically load modules</h3>
|
||||
|
||||
[Dynamic imports](https://blog.meteor.com/dynamic-imports-in-meteor-1-5-c6130419c3cd) was fist introduced in [Meteor 1.5](https://github.com/meteor/meteor/blob/devel/History.md#v15-2017-05-30). This technique allows to dramatically reduce *Client's* bundle size, and load modules and dependencies dynamically upon request, in this case based on the current URI.
|
||||
|
||||
Assume we have `index.html` and `index.js` with code for `index` template and this is only place in the application where it depend on large `moment` package. This means `moment` package is not needed in the other parts of our app, and it will only waste bandwidth and slow load time.
|
||||
|
||||
```html
|
||||
<!-- /imports/client/index.html -->
|
||||
<template name="index">
|
||||
<h1>Current time is: {{time}}</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
```js
|
||||
// /imports/client/index.js
|
||||
import moment from 'moment';
|
||||
import { Template } from 'meteor/templating';
|
||||
import './index.html';
|
||||
|
||||
Template.index.helpers({
|
||||
time() {
|
||||
return moment().format('LTS');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// /imports/lib/routes.js
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
|
||||
FlowRouter.route('/', {
|
||||
name: 'index',
|
||||
waitOn() {
|
||||
// Wait for index.js load over the wire
|
||||
return import('/imports/client/index.js');
|
||||
},
|
||||
action() {
|
||||
BlazeLayout.render('App_body', {main: 'index'});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
For more info and examples see [this thread](https://forums.meteor.com/t/flow-router-meteor-1-5-per-route-dynamic-import/36870)
|
||||
|
||||
<h3 id="404s">Missing pages</h3>
|
||||
|
||||
If a user types an incorrect URL, chances are you want to show them some kind of amusing not-found page. There are actually two categories of not-found pages. The first is when the URL typed in doesn't match any of your route definitions. You can use `FlowRouter.notFound` to handle this:
|
||||
|
||||
```js
|
||||
// the App_notFound template is used for unknown routes and missing lists
|
||||
FlowRouter.notFound = {
|
||||
action() {
|
||||
BlazeLayout.render('App_body', {main: 'App_notFound'});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The second is when the URL is valid, but doesn't actually match any data. In this case, the URL matches a route, but once the route has successfully subscribed, it discovers there is no data. It usually makes sense in this case for the page component (which subscribes and fetches the data) to render a not-found template instead of the usual template for the page:
|
||||
|
||||
```html
|
||||
<template name="Lists_show_page">
|
||||
{{#each listId in listIdArray}}
|
||||
{{> Lists_show (listArgs listId)}}
|
||||
{{else}}
|
||||
{{> App_notFound}}
|
||||
{{/each}}
|
||||
<template>
|
||||
```
|
||||
|
||||
<h3 id="analytics">Analytics</h3>
|
||||
|
||||
It's common to want to know which pages of your app are most commonly visited, and where users are coming from. You can read about how to set up Flow Router based analytics in the [Deployment Guide](deployment.html#analytics).
|
||||
|
||||
<h3 id="server-side">Server Side Routing</h3>
|
||||
|
||||
As we've discussed, Meteor is a framework for client rendered applications, but this doesn't always remove the requirement for server rendered routes. There are three main use cases for server-side routing.
|
||||
|
||||
<h4 id="server-side-apis">Server Routing for API access</h4>
|
||||
|
||||
Although Meteor allows you to [write low-level connect handlers](http://docs.meteor.com/#/full/webapp) to create any kind of API you like on the server-side, if all you want to do is create a RESTful version of your Methods and Publications, you can often use the [`simple:rest`](http://atmospherejs.com/simple/rest) package to do this. See the [Data Loading](data-loading.html#publications-as-rest) and [Methods](methods.html) articles for more information.
|
||||
|
||||
If you need more control, you can use the comprehensive [`nimble:restivus`](https://atmospherejs.com/nimble/restivus) package to create more or less whatever you need in whatever ontology you require.
|
||||
|
||||
<h4 id="server-side-rendering">Server Rendering</h4>
|
||||
|
||||
The Blaze UI library does not have support for server-side rendering, so it's not possible to render your pages on the server if you use Blaze. However, the React UI library does. This means it is possible to render HTML on the server if you use React as your rendering framework.
|
||||
|
||||
Although Flow Router can be used to render React components more or less as we've described above for Blaze, at the time of this writing Flow Router's support for SSR is still experimental. However, it's probably the best approach right now if you want to use SSR for Meteor.
|
||||
|
||||
<h4 id="server-side-resources">Server Routing for additional resources</h4>
|
||||
|
||||
There might be additional resources that you want to make available on your server or receive web hooks. If you need anything more complicated with dynamic parts of the URL you might want to implement [Picker](https://atmospherejs.com/communitypackages/picker) which is a simple server-side router that handles dynamic routes.
|
||||
|
||||
If you need to authenticate the user when providing additional server-side resources such as PDF documents or XLSX spreadsheets, you can use [`mhagmajer:server-router`](https://atmospherejs.com/mhagmajer/server-router) package to do this easily. There is a [blog article](https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94) that describes this in more detail.
|
||||
706
content/security.md
Normal file
@@ -0,0 +1,706 @@
|
||||
---
|
||||
title: "Security"
|
||||
description: How to secure your Meteor app.
|
||||
discourseTopicId: 19667
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. The security surface area of a Meteor app.
|
||||
2. How to secure Meteor Methods, publications, and source code.
|
||||
3. Where to store secret keys in development and production.
|
||||
4. How to follow a security checklist when auditing your app.
|
||||
5. How App Protection works in Galaxy Hosting.
|
||||
|
||||
<h1 id="introduction">Introduction</h1>
|
||||
|
||||
Securing a web application is all about understanding security domains and understanding the attack surface between these domains. In a Meteor app, things are pretty simple:
|
||||
|
||||
1. Code that runs on the server can be trusted.
|
||||
2. Everything else: code that runs on the client, data sent through Method and publication arguments, etc, can't be trusted.
|
||||
|
||||
In practice, this means that you should do most of your security and validation on the boundary between these two domains. In simple terms:
|
||||
|
||||
1. Validate and check all inputs that come from the client.
|
||||
2. Don't leak any secret information to the client.
|
||||
|
||||
<h2 id="attack-surface">Concept: Attack surface</h2>
|
||||
|
||||
Since Meteor apps are often written in a style that puts client and server code together, it's extra important to be aware what is running on the client, what is running on the server, and what the boundaries are. Here's a complete list of places security checks need to be done in a Meteor app:
|
||||
|
||||
1. **Methods**: Any data that comes in through Method arguments needs to be validated, and Methods should not return data the user shouldn't have access to.
|
||||
2. **Publications**: Any data that comes in through publication arguments needs to be validated, and publications should not return data the user shouldn't have access to.
|
||||
3. **Served files**: You should make sure none of the source code or configuration files served to the client have secret data.
|
||||
|
||||
Each of these points will have their own section below.
|
||||
|
||||
<h3 id="allow-deny">Avoid allow/deny</h3>
|
||||
|
||||
In this guide, we're going to take a strong position that using [allow](http://docs.meteor.com/#/full/allow) or [deny](http://docs.meteor.com/#/full/deny) to run MongoDB queries directly from the client is not a good idea. The main reason is that it is hard to follow the principles outlined above. It's extremely difficult to validate the complete space of possible MongoDB operators, which could potentially grow over time with new versions of MongoDB.
|
||||
|
||||
There have been several articles about the potential pitfalls of accepting MongoDB update operators from the client, in particular the [Allow & Deny Security Challenge](https://www.discovermeteor.com/blog/allow-deny-security-challenge/) and its [results](https://www.discovermeteor.com/blog/allow-deny-challenge-results/), both on the Discover Meteor blog.
|
||||
|
||||
Given the points above, we recommend that all Meteor apps should use Methods to accept data input from the client, and restrict the arguments accepted by each Method as tightly as possible.
|
||||
|
||||
Here's a code snippet to add to your server code which disables client-side updates on a collection. This will make sure no other part of your app can use `allow`:
|
||||
|
||||
```js
|
||||
// Deny all client-side updates on the Lists collection
|
||||
Lists.deny({
|
||||
insert() { return true; },
|
||||
update() { return true; },
|
||||
remove() { return true; },
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
<h2 id="methods">Methods</h2>
|
||||
|
||||
Methods are the way your Meteor server accepts inputs and data from the outside world, so it's natural that they are the most important topic for security. If you don't properly secure your Methods, users can end up modifying your database in unexpected ways - editing other people's documents, deleting data, or messing up your database schema causing the app to crash.
|
||||
|
||||
<h3 id="validate-arguments">Validate all arguments</h3>
|
||||
|
||||
It's much easier to write clean code if you can assume your inputs are correct, so it's valuable to validate all Method arguments before running any actual business logic. You don't want someone to pass a data type you aren't expecting and cause unexpected behavior.
|
||||
|
||||
Consider that if you are writing unit tests for your Methods, you would need to test all possible kinds of input to the Method; validating the arguments restricts the space of inputs you need to unit test, reducing the amount of code you need to write overall. It also has the extra bonus of being self-documenting; someone else can come along and read the code to find out what kinds of parameters a Method is looking for.
|
||||
|
||||
Just as an example, here's a situation where not checking arguments can be disastrous:
|
||||
|
||||
```js
|
||||
Meteor.methods({
|
||||
removeWidget(id) {
|
||||
if (! this.userId) {
|
||||
throw new Meteor.Error('removeWidget.unauthorized');
|
||||
}
|
||||
|
||||
Widgets.remove(id);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
If someone comes along and passes a non-ID selector like `{}`, they will end up deleting the entire collection.
|
||||
|
||||
<h3 id="validated-method">mdg:validated-method</h3>
|
||||
|
||||
To help you write good Methods that exhaustively validate their arguments, we've written a wrapper package for Methods that enforces argument validation. Read more about how to use it in the [Methods article](methods.html#validated-method). The rest of the code samples in this article will assume that you are using this package. If you aren't, you can still apply the same principles but the code will look a little different.
|
||||
|
||||
<h3 id="user-id-client">Don't pass userId from the client</h3>
|
||||
|
||||
The `this` context inside every Meteor Method has some useful information about the current connection, and the most useful is [`this.userId`](http://docs.meteor.com/#/full/method_userId). This property is managed by the DDP login system, and is guaranteed by the framework itself to be secure following widely-used best practices.
|
||||
|
||||
Given that the user ID of the current user is available through this context, you should never pass the ID of the current user as an argument to a Method. This would allow any client of your app to pass any user ID they want. Let's look at an example:
|
||||
|
||||
```js
|
||||
// #1: Bad! The client could pass any user ID and set someone else's name
|
||||
setName({ userId, newName }) {
|
||||
Meteor.users.update(userId, {
|
||||
$set: { name: newName }
|
||||
});
|
||||
}
|
||||
|
||||
// #2: Good, the client can only set the name on the currently logged in user
|
||||
setName({ newName }) {
|
||||
Meteor.users.update(this.userId, {
|
||||
$set: { name: newName }
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The _only_ times you should be passing any user ID as an argument are the following:
|
||||
|
||||
1. This is a Method only accessible by admin users, who are allowed to edit other users. See the section about [user roles](accounts.html##roles-and-permissions) to learn how to check that a user is in a certain role.
|
||||
2. This Method doesn't modify the other user, but uses it as a target; for example, it could be a Method for sending a private message, or adding a user as a friend.
|
||||
|
||||
<h3 id="specific-action">One Method per action</h3>
|
||||
|
||||
The best way to make your app secure is to understand all of the possible inputs that could come from an untrusted source, and make sure that they are all handled correctly. The easiest way to understand what inputs can come from the client is to restrict them to as small of a space as possible. This means your Methods should all be specific actions, and shouldn't take a multitude of options that change the behavior in significant ways. The end goal is that you can look at each Method in your app and validate or test that it is secure. Here's a secure example Method from the Todos example app:
|
||||
|
||||
```js
|
||||
export const makePrivate = new ValidatedMethod({
|
||||
name: 'lists.makePrivate',
|
||||
validate: new SimpleSchema({
|
||||
listId: { type: String }
|
||||
}).validator(),
|
||||
run({ listId }) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('lists.makePrivate.notLoggedIn',
|
||||
'Must be logged in to make private lists.');
|
||||
}
|
||||
|
||||
const list = Lists.findOne(listId);
|
||||
|
||||
if (list.isLastPublicList()) {
|
||||
throw new Meteor.Error('lists.makePrivate.lastPublicList',
|
||||
'Cannot make the last public list private.');
|
||||
}
|
||||
|
||||
Lists.update(listId, {
|
||||
$set: { userId: this.userId }
|
||||
});
|
||||
|
||||
Lists.userIdDenormalizer.set(listId, this.userId);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can see that this Method does a _very specific thing_ - it makes a single list private. An alternative would have been to have a Method called `setPrivacy`, which could set the list to private or public, but it turns out that in this particular app the security considerations for the two related operations - `makePrivate` and `makePublic` - are very different. By splitting our operations into different Methods, we make each one much clearer. It's obvious from the above Method definition which arguments we accept, what security checks we perform, and what operations we do on the database.
|
||||
|
||||
However, this doesn't mean you can't have any flexibility in your Methods. Let's look at an example:
|
||||
|
||||
```js
|
||||
Meteor.users.methods.setUserData = new ValidatedMethod({
|
||||
name: 'Meteor.users.methods.setUserData',
|
||||
validate: new SimpleSchema({
|
||||
fullName: { type: String, optional: true },
|
||||
dateOfBirth: { type: Date, optional: true },
|
||||
}).validator(),
|
||||
run(fieldsToSet) {
|
||||
Meteor.users.update(this.userId, {
|
||||
$set: fieldsToSet
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The above Method is great because you can have the flexibility of having some optional fields and only passing the ones you want to change. In particular, what makes it possible for this Method is that the security considerations of setting one's full name and date of birth are the same - we don't have to do different security checks for different fields being set. Note that it's very important that the `$set` query on MongoDB is generated on the server - we should never take MongoDB operators as-is from the client, since they are hard to validate and could result in unexpected side effects.
|
||||
|
||||
<h4 id="reusing-security-rules">Refactoring to reuse security rules</h4>
|
||||
|
||||
You might run into a situation where many Methods in your app have the same security checks. This can be simplified by factoring out the security into a separate module, wrapping the Method body, or extending the `Mongo.Collection` class to do security inside the `insert`, `update`, and `remove` implementations on the server. However, implementing your client-server communication via specific Methods is still a good idea rather than sending arbitrary `update` operators from the client, since a malicious client can't send an `update` operator that you didn't test for.
|
||||
|
||||
<h3 id="rate-limiting">Rate limiting</h3>
|
||||
|
||||
Like REST endpoints, Meteor Methods can be called from anywhere - a malicious program, script in the browser console, etc. It is easy to fire many Method calls in a very short amount of time. This means it can be easy for an attacker to test lots of different inputs to find one that works. Meteor has built-in rate limiting for password login to stop password brute-forcing, but it's up to you to define rate limits for your other Methods.
|
||||
|
||||
In the Todos example app, we use the following code to set a basic rate limit on all Methods:
|
||||
|
||||
```js
|
||||
// Get list of all method names on Lists
|
||||
const LISTS_METHODS = _.pluck([
|
||||
insert,
|
||||
makePublic,
|
||||
makePrivate,
|
||||
updateName,
|
||||
remove,
|
||||
], 'name');
|
||||
|
||||
// Only allow 5 list operations per connection per second
|
||||
|
||||
if (Meteor.isServer) {
|
||||
DDPRateLimiter.addRule({
|
||||
name(name) {
|
||||
return _.contains(LISTS_METHODS, name);
|
||||
},
|
||||
|
||||
// Rate limit per connection ID
|
||||
connectionId() { return true; }
|
||||
}, 5, 1000);
|
||||
}
|
||||
```
|
||||
|
||||
This will make every Method only callable 5 times per second per connection. This is a rate limit that shouldn't be noticeable by the user at all, but will prevent a malicious script from totally flooding the server with requests. You will need to tune the limit parameters to match your app's needs.
|
||||
|
||||
If you're using validated methods, there's an available [ddp-rate-limiter-mixin](https://github.com/nlhuykhang/ddp-rate-limiter-mixin).
|
||||
|
||||
<h2 id="publications">Publications</h2>
|
||||
|
||||
Publications are the primary way a Meteor server can make data available to a client. While with Methods the primary concern was making sure users can't modify the database in unexpected ways, with publications the main issue is filtering the data being returned so that a malicious user can't get access to data they aren't supposed to see.
|
||||
|
||||
#### You can't do security at the rendering layer
|
||||
|
||||
In a server-side-rendered framework like Ruby on Rails, it's sufficient to not display sensitive data in the returned HTML response. In Meteor, since the rendering is done on the client, an `if` statement in your HTML template is not secure; you need to do security at the data level to make sure that data is never sent in the first place.
|
||||
|
||||
<h3 id="method-rules">Rules about Methods still apply</h3>
|
||||
|
||||
All of the points above about Methods apply to publications as well:
|
||||
|
||||
1. Validate all arguments using `check` or npm `simpl-schema`.
|
||||
1. Never pass the current user ID as an argument.
|
||||
1. Don't take generic arguments; make sure you know exactly what your publication is getting from the client.
|
||||
1. Use rate limiting to stop people from spamming you with subscriptions.
|
||||
|
||||
<h3 id="fields">Always restrict fields</h3>
|
||||
|
||||
[`Mongo.Collection#find` has an option called `fields`](http://docs.meteor.com/#/full/find) which lets you filter the fields on the fetched documents. You should always use this in publications to make sure you don't accidentally publish secret fields.
|
||||
|
||||
For example, you could write a publication, then later add a secret field to the published collection. Now, the publication would be sending that secret to the client. If you filter the fields on every publication when you first write it, then adding another field won't automatically publish it.
|
||||
|
||||
```js
|
||||
// #1: Bad! If we add a secret field to Lists later, the client
|
||||
// will see it
|
||||
Meteor.publish('lists.public', function () {
|
||||
return Lists.find({userId: {$exists: false}});
|
||||
});
|
||||
|
||||
// #2: Good, if we add a secret field to Lists later, the client
|
||||
// will only publish it if we add it to the list of fields
|
||||
Meteor.publish('lists.public', function () {
|
||||
return Lists.find({userId: {$exists: false}}, {
|
||||
fields: {
|
||||
name: 1,
|
||||
incompleteCount: 1,
|
||||
userId: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
If you find yourself repeating the fields often, it makes sense to factor out a dictionary of public fields that you can always filter by, like so:
|
||||
|
||||
```js
|
||||
// In the file where Lists is defined
|
||||
Lists.publicFields = {
|
||||
name: 1,
|
||||
incompleteCount: 1,
|
||||
userId: 1
|
||||
};
|
||||
```
|
||||
|
||||
Now your code becomes a bit simpler:
|
||||
|
||||
```js
|
||||
Meteor.publish('lists.public', function () {
|
||||
return Lists.find({userId: {$exists: false}}, {
|
||||
fields: Lists.publicFields
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="publications-user-id">Publications and userId</h3>
|
||||
|
||||
The data returned from publications will often be dependent on the currently logged in user, and perhaps some properties about that user - whether they are an admin, whether they own a certain document, etc.
|
||||
|
||||
Publications are not reactive, and they only re-run when the currently logged in `userId` changes, which can be accessed through `this.userId`. Because of this, it's easy to accidentally write a publication that is secure when it first runs, but doesn't respond to changes in the app environment. Let's look at an example:
|
||||
|
||||
```js
|
||||
// #1: Bad! If the owner of the list changes, the old owner will still see it
|
||||
Meteor.publish('list', function (listId) {
|
||||
check(listId, String);
|
||||
|
||||
const list = Lists.findOne(listId);
|
||||
|
||||
if (list.userId !== this.userId) {
|
||||
throw new Meteor.Error('list.unauthorized',
|
||||
'This list doesn\'t belong to you.');
|
||||
}
|
||||
|
||||
return Lists.find(listId, {
|
||||
fields: {
|
||||
name: 1,
|
||||
incompleteCount: 1,
|
||||
userId: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// #2: Good! When the owner of the list changes, the old owner won't see it anymore
|
||||
Meteor.publish('list', function (listId) {
|
||||
check(listId, String);
|
||||
|
||||
return Lists.find({
|
||||
_id: listId,
|
||||
userId: this.userId
|
||||
}, {
|
||||
fields: {
|
||||
name: 1,
|
||||
incompleteCount: 1,
|
||||
userId: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
In the first example, if the `userId` property on the selected list changes, the query in the publication will still return the data, since the security check in the beginning will not re-run. In the second example, we have fixed this by putting the security check in the returned query itself.
|
||||
|
||||
Unfortunately, not all publications are as simple to secure as the example above. For more tips on how to use `reywood:publish-composite` to handle reactive changes in publications, see the [data loading article](data-loading.html#complex-auth).
|
||||
|
||||
<h3 id="publication-options">Passing options</h3>
|
||||
|
||||
For certain applications, for example pagination, you'll want to pass options into the publication to control things like how many documents should be sent to the client. There are some extra considerations to keep in mind for this particular case.
|
||||
|
||||
1. **Passing a limit**: In the case where you are passing the `limit` option of the query from the client, make sure to set a maximum limit. Otherwise, a malicious client could request too many documents at once, which could raise performance issues.
|
||||
2. **Passing in a filter**: If you want to pass fields to filter on because you don't want all of the data, for example in the case of a search query, make sure to use MongoDB `$and` to intersect the filter coming from the client with the documents that client should be allowed to see. Also, you should whitelist the keys that the client can use to filter - if the client can filter on secret data, it can run a search to find out what that data is.
|
||||
3. **Passing in fields**: If you want the client to be able to decide which fields of the collection should be fetched, make sure to intersect that with the fields that client is allowed to see, so that you don't accidentally send secret data to the client.
|
||||
|
||||
In summary, you should make sure that any options passed from the client to a publication can only restrict the data being requested, rather than extending it.
|
||||
|
||||
<h2 id="served-files">Served files</h2>
|
||||
|
||||
Publications are not the only place the client gets data from the server. The set of source code files and static assets that are served by your application server could also potentially contain sensitive data:
|
||||
|
||||
1. Business logic an attacker could analyze to find weak points.
|
||||
1. Secret algorithms that a competitor could steal.
|
||||
1. Secret API keys.
|
||||
|
||||
<h3 id="secret-code">Secret server code</h3>
|
||||
|
||||
While the client-side code of your application is necessarily accessible by the browser, every application will have some secret code on the server that you don't want to share with the world.
|
||||
|
||||
Secret business logic in your app should be located in code that is only loaded on the server. This means it is in a `server/` directory of your app, in a package that is only included on the server, or in a file inside a package that was loaded only on the server.
|
||||
|
||||
If you have a Meteor Method in your app that has secret business logic, you might want to split the Method into two functions - the optimistic UI part that will run on the client, and the secret part that runs on the server. Most of the time, putting the entire Method on the server doesn't result in the best user experience. Let's look at an example, where you have a secret algorithm for calculating someone's MMR (ranking) in a game:
|
||||
|
||||
```js
|
||||
// In a server-only file, for example /imports/server/mmr.js
|
||||
export const MMR = {
|
||||
updateWithSecretAlgorithm(userId) {
|
||||
// your secret code here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// In a file loaded on client and server
|
||||
Meteor.users.methods.updateMMR = new ValidatedMethod({
|
||||
name: 'Meteor.users.methods.updateMMR',
|
||||
validate: null,
|
||||
run() {
|
||||
if (this.isSimulation) {
|
||||
// Simulation code for the client (optional)
|
||||
} else {
|
||||
const { MMR } = require('/imports/server/mmr.js');
|
||||
MMR.updateWithSecretAlgorithm(this.userId);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Note that while the Method is defined on the client, the actual secret logic is only accessible from the server. Keep in mind that code inside `if (Meteor.isServer)` blocks is still sent to the client, it is just not executed. So don't put any secret code in there.
|
||||
|
||||
Secret API keys should never be stored in your source code at all, the next section will talk about how to handle them.
|
||||
|
||||
<h2 id="api-keys">Securing API keys</h2>
|
||||
|
||||
Every app will have some secret API keys or passwords:
|
||||
|
||||
1. Your database password.
|
||||
1. API keys for external APIs.
|
||||
|
||||
These should never be stored as part of your app's source code in version control, because developers might copy code around to unexpected places and forget that it contains secret keys. You can keep your keys separately in [Dropbox](https://www.dropbox.com/), [LastPass](https://lastpass.com), or another service, and then reference them when you need to deploy the app.
|
||||
|
||||
You can pass settings to your app through a _settings file_ or an _environment variable_. Most of your app settings should be in JSON files that you pass in when starting your app. You can start your app with a settings file by passing the `--settings` flag:
|
||||
|
||||
```sh
|
||||
# Pass development settings when running your app locally
|
||||
meteor --settings development.json
|
||||
|
||||
# Pass production settings when deploying your app to Galaxy
|
||||
meteor deploy myapp.com --settings production.json
|
||||
```
|
||||
|
||||
Here's what a settings file with some API keys might look like:
|
||||
|
||||
```js
|
||||
{
|
||||
"facebook": {
|
||||
"appId": "12345",
|
||||
"secret": "1234567"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In your app's JavaScript code, these settings can be accessed from the variable `Meteor.settings`.
|
||||
|
||||
[Read more about managing keys and settings in the Deployment article.](deployment.html#environment)
|
||||
|
||||
<h3 id="client-settings">Settings on the client</h3>
|
||||
|
||||
In most normal situations, API keys from your settings file will only be used by the server, and by default the data passed in through `--settings` is only available on the server. However, if you put data under a special key called `public`, it will be available on the client. You might want to do this if, for example, you need to make an API call from the client and are OK with users knowing that key. Public settings will be available on the client under `Meteor.settings.public`.
|
||||
|
||||
<h3 id="api-keys-oauth">API keys for OAuth</h3>
|
||||
|
||||
For the `accounts-facebook` package to pick up these keys, you need to add them to the service configuration collection in the database. Here's how you do that:
|
||||
|
||||
First, add the `service-configuration` package:
|
||||
|
||||
```sh
|
||||
meteor add service-configuration
|
||||
```
|
||||
|
||||
Then, upsert into the `ServiceConfiguration` collection:
|
||||
|
||||
```js
|
||||
ServiceConfiguration.configurations.upsert({
|
||||
service: "facebook"
|
||||
}, {
|
||||
$set: {
|
||||
appId: Meteor.settings.facebook.appId,
|
||||
loginStyle: "popup",
|
||||
secret: Meteor.settings.facebook.secret
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Now, `accounts-facebook` will be able to find that API key and Facebook login will work properly.
|
||||
|
||||
<h2 id="ssl">SSL</h2>
|
||||
|
||||
This is a very short section, but it deserves its own place in the table of contents.
|
||||
|
||||
**Every production Meteor app that handles user data should run with SSL.**
|
||||
|
||||
Yes, Meteor does hash your password or login token on the client before sending it over the wire, but that only prevents an attacker from figuring out your password - it doesn't prevent them from logging in as you, since they could send the hashed password to the server to log in! No matter how you slice it, logging in requires the client to send sensitive data to the server, and the only way to secure that transfer is by using SSL. Note that the same issue is present when using cookies for authentication in a normal HTTP web application, so any app that needs to reliably identify users should be running on SSL.
|
||||
|
||||
#### Setting up SSL
|
||||
|
||||
* On [Galaxy](deployment.html#galaxy), configuration of SSL is automatic. [See the help article about SSL on Galaxy](http://galaxy-guide.meteor.com/encryption.html).
|
||||
* If you are running on your own [infrastructure](deployment.html#custom-deployment), there are a few options for setting up SSL, mostly through configuring a proxy web server. See the articles: [Josh Owens on SSL and Meteor](http://joshowens.me/ssl-and-meteor-js/), [SSL on Meteorpedia](http://www.meteorpedia.com/read/SSL), and [Digital Ocean tutorial with an Nginx config](https://www.digitalocean.com/community/tutorials/how-to-deploy-a-meteor-js-application-on-ubuntu-14-04-with-nginx).
|
||||
|
||||
#### Forcing SSL
|
||||
|
||||
Generally speaking, all production HTTP requests should go over HTTPS, and all WebSocket data should be sent over WSS.
|
||||
|
||||
It's best to handle the redirection from HTTP to HTTPS on the platform which handles the SSL certificates and termination.
|
||||
|
||||
* On [Galaxy](deployment.html#galaxy), enable the "Force HTTPS" setting on a specific domain in the "Domains & Encryption" section of the application's "Settings" tab.
|
||||
* Other deployments *may* have control panel options or may need to be manually configured on the the proxy server (e.g. HAProxy, nginx, etc.). The articles linked above provide some assistance on this.
|
||||
|
||||
In the event that a platform does not offer the ability to configure this, the `force-ssl` package can be added to the project and Meteor will attempt to intelligently redirect based on the presence of the `x-forwarded-for` header.
|
||||
|
||||
<h2 id="httpheaders">HTTP Headers</h2>
|
||||
|
||||
HTTP headers can be used to improve the security of apps, although these are not a silver bullet, they will assist users in mitigating more common attacks.
|
||||
|
||||
<h4 id="helmet">Recommended: Helmet</h4>
|
||||
|
||||
Although there are many great open source solutions for setting HTTP headers, Meteor recommends [Helmet](https://helmetjs.github.io/). Helmet is a collection of 12 smaller middleware functions that set HTTP headers.
|
||||
|
||||
First, install helmet.
|
||||
|
||||
```js
|
||||
meteor npm install helmet --save
|
||||
```
|
||||
|
||||
By default, Helmet can be used to set various HTTP headers (see link above). These are a good starting point for mitigating common attacks. To use the default headers, users should use the following code anywhere in their server side meteor startup code.
|
||||
|
||||
> Note: Meteor has not extensively tested each header for compatibility with Meteor. Only headers listed below have been tested.
|
||||
|
||||
```js
|
||||
// With other import statements
|
||||
import helmet from "helmet";
|
||||
|
||||
// Within server side Meter.startup()
|
||||
WebApp.connectHandlers.use(helmet())
|
||||
```
|
||||
|
||||
At a minimum, Meteor recommends users to set the following headers. Note that code examples shown below are specific to Helmet.
|
||||
|
||||
<h3 id="csp">Content Security Policy</h3>
|
||||
|
||||
> Note: Content Security Policy is not configured using Helmet's default header configuration.
|
||||
|
||||
From MDN, Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft to site defacement or distribution of malware.
|
||||
|
||||
It is recommended that users use CSP to protect their apps from access by third parties. CSP assists to control how resources are loaded into your application.
|
||||
|
||||
By default, Meteor recommends unsafe inline scripts and styles are allowed, since many apps typically use them for analytics, etc. Unsafe eval is disallowed, and the only allowable content source is same origin or data, except for connect which allows anything (since meteor apps make websocket connections to a lot of different origins). Browsers will also be told not to sniff content types away from declared content types.
|
||||
|
||||
```js
|
||||
// With other import statements
|
||||
import helmet from "helmet";
|
||||
|
||||
// Within server side Meter.startup()
|
||||
WebApp.connectHandlers.use(
|
||||
helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'unsafe-inline'"],
|
||||
connectSrc: ["*"],
|
||||
imgSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
},
|
||||
browserSniff: false
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
Helmet supports a large number of directives, users should further customise their CSP based on their needs. For more detail please read the following guide: [Content Security Policy](https://helmetjs.github.io/docs/csp/). CSP can be complex, so in addition there are some excellent tools out there to help, including [Google's CSP Evaluator](https://csp-evaluator.withgoogle.com/), [Report-URI's CSP Builder](https://report-uri.com/home/generate), [CSP documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) from Mozilla and [CSPValidator](https://cspvalidator.org/).
|
||||
|
||||
The following example presents a potential CSP and other Security Headers used in a Production Meteor Application.
|
||||
This configuration may require customization, depending on your setup and use-cases.
|
||||
|
||||
```javascript
|
||||
/* global __meteor_runtime_config__ */
|
||||
import { Meteor } from 'meteor/meteor'
|
||||
import { WebApp } from 'meteor/webapp'
|
||||
import { Autoupdate } from 'meteor/autoupdate'
|
||||
import { check } from 'meteor/check'
|
||||
import crypto from 'crypto'
|
||||
import helmet from 'helmet'
|
||||
|
||||
const self = '\'self\''
|
||||
const data = 'data:'
|
||||
const unsafeEval = '\'unsafe-eval\''
|
||||
const unsafeInline = '\'unsafe-inline\''
|
||||
const allowedOrigins = Meteor.settings.allowedOrigins
|
||||
|
||||
// create the default connect source for our current domain in
|
||||
// a multi-protocol compatible way (http/ws or https/wss)
|
||||
const url = Meteor.absoluteUrl()
|
||||
const domain = url.replace(/http(s)*:\/\//, '').replace(/\/$/, '')
|
||||
const s = url.match(/(?!=http)s(?=:\/\/)/) ? 's' : ''
|
||||
const usesHttps = s.length > 0
|
||||
const connectSrc = [
|
||||
self,
|
||||
`http${s}://${domain}`,
|
||||
`ws${s}://${domain}`
|
||||
]
|
||||
|
||||
// Prepare runtime config for generating the sha256 hash
|
||||
// It is important, that the hash meets exactly the hash of the
|
||||
// script in the client bundle.
|
||||
// Otherwise the app would not be able to start, since the runtimeConfigScript
|
||||
// is rejected __meteor_runtime_config__ is not available, causing
|
||||
// a cascade of follow-up errors.
|
||||
const runtimeConfig = Object.assign(__meteor_runtime_config__, Autoupdate, {
|
||||
// the following lines may depend on, whether you called Accounts.config
|
||||
// and whether your Meteor app is a "newer" version
|
||||
accountsConfigCalled: true,
|
||||
isModern: true
|
||||
})
|
||||
|
||||
// add client versions to __meteor_runtime_config__
|
||||
Object.keys(WebApp.clientPrograms).forEach(arch => {
|
||||
__meteor_runtime_config__.versions[arch] = {
|
||||
version: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].version(),
|
||||
versionRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionRefreshable(),
|
||||
versionNonRefreshable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionNonRefreshable(),
|
||||
// comment the following line if you use Meteor < 2.0
|
||||
versionReplaceable: Autoupdate.autoupdateVersion || WebApp.clientPrograms[arch].versionReplaceable()
|
||||
}
|
||||
})
|
||||
|
||||
const runtimeConfigScript = `__meteor_runtime_config__ = JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(runtimeConfig))}"))`
|
||||
const runtimeConfigHash = crypto.createHash('sha256').update(runtimeConfigScript).digest('base64')
|
||||
|
||||
const helpmentOptions = {
|
||||
contentSecurityPolicy: {
|
||||
blockAllMixedContent: true,
|
||||
directives: {
|
||||
defaultSrc: [self],
|
||||
scriptSrc: [
|
||||
self,
|
||||
// Remove / comment out unsafeEval if you do not use dynamic imports
|
||||
// to tighten security. However, if you use dynamic imports this line
|
||||
// must be kept in order to make them work.
|
||||
unsafeEval,
|
||||
`'sha256-${runtimeConfigHash}'`
|
||||
],
|
||||
childSrc: [self],
|
||||
// If you have external apps, that should be allowed as sources for
|
||||
// connections or images, your should add them here
|
||||
// Call helmetOptions() without args if you have no external sources
|
||||
// Note, that this is just an example and you may configure this to your needs
|
||||
connectSrc: connectSrc.concat(allowedOrigins),
|
||||
fontSrc: [self, data],
|
||||
formAction: [self],
|
||||
frameAncestors: [self],
|
||||
frameSrc: ['*'],
|
||||
// This is an example to show, that we can define to show images only
|
||||
// from our self, browser data/blob and a defined set of hosts.
|
||||
// Configure to your needs.
|
||||
imgSrc: [self, data, 'blob:'].concat(allowedOrigins),
|
||||
manifestSrc: [self],
|
||||
mediaSrc: [self],
|
||||
objectSrc: [self],
|
||||
// these are just examples, configure to your needs, see
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox
|
||||
sandbox: [
|
||||
// allow-downloads-without-user-activation // experimental
|
||||
'allow-forms',
|
||||
'allow-modals',
|
||||
// 'allow-orientation-lock',
|
||||
// 'allow-pointer-lock',
|
||||
// 'allow-popups',
|
||||
// 'allow-popups-to-escape-sandbox',
|
||||
// 'allow-presentation',
|
||||
'allow-same-origin',
|
||||
'allow-scripts',
|
||||
// 'allow-storage-access-by-user-activation ', // experimental
|
||||
// 'allow-top-navigation',
|
||||
// 'allow-top-navigation-by-user-activation'
|
||||
],
|
||||
styleSrc: [self, unsafeInline],
|
||||
workerSrc: [self, 'blob:']
|
||||
}
|
||||
},
|
||||
// see the helmet documentation to get a better understanding of
|
||||
// the following configurations and settings
|
||||
strictTransportSecurity: {
|
||||
maxAge: 15552000,
|
||||
includeSubDomains: true,
|
||||
preload: false
|
||||
},
|
||||
referrerPolicy: {
|
||||
policy: 'no-referrer'
|
||||
},
|
||||
expectCt: {
|
||||
enforce: true,
|
||||
maxAge: 604800
|
||||
},
|
||||
frameguard: {
|
||||
action: 'sameorigin'
|
||||
},
|
||||
dnsPrefetchControl: {
|
||||
allow: false
|
||||
},
|
||||
permittedCrossDomainPolicies: {
|
||||
permittedPolicies: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
// We assume, that we are working on a localhost when there is no https
|
||||
// connection available.
|
||||
// Run your project with --production flag to simulate script-src hashing
|
||||
if (!usesHttps && Meteor.isDevelopment) {
|
||||
delete opt.contentSecurityPolicy.directives.blockAllMixedContent
|
||||
opt.contentSecurityPolicy.directives.scriptSrc = [self, unsafeEval, unsafeInline]
|
||||
}
|
||||
|
||||
// finally pass the options to helmet to make them apply
|
||||
helmet(helpmentOptions)
|
||||
```
|
||||
|
||||
<h3 id="xframeoptions">X-Frame-Options</h3>
|
||||
|
||||
> Note: The X-Frame Options header is configured using Helmet's default header configuration.
|
||||
|
||||
From MDN, the X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a `<frame>`, `<iframe>` or `<object>`. Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.
|
||||
|
||||
Meteor recommend users configure the X-Frame-Options header for same origin only. This tells browsers to prevent your webpage from being put in an iframe. By using this config, you will set your policy where only web pages on the same origin as your app can frame your app.
|
||||
|
||||
With Helmet, Frameguard sets the X-Frame-Options header.
|
||||
|
||||
```js
|
||||
// With other import statements
|
||||
import helmet from "helmet";
|
||||
|
||||
// Within server side Meter.startup()
|
||||
WebApp.connectHandlers.use(helmet.frameguard()); // defaults to sameorigin
|
||||
```
|
||||
For more detail please read the following guide: [Frameguard](https://helmetjs.github.io/docs/frameguard/).
|
||||
|
||||
<h2 id="checklist">Security checklist</h2>
|
||||
|
||||
This is a collection of points to check about your app that might catch common errors. However, it's not an exhaustive list yet---if we missed something, please let us know or file a pull request!
|
||||
|
||||
1. Make sure your app doesn't have the `insecure` or `autopublish` packages.
|
||||
1. Validate all Method and publication arguments, and include the `audit-argument-checks` to check this automatically.
|
||||
1. Apply rate limiting to your application to prevent DDoS attacks.
|
||||
1. [Deny writes to the `profile` field on user documents.](accounts.html#dont-use-profile)
|
||||
1. [Use Methods instead of client-side insert/update/remove and allow/deny.](security.html#allow-deny)
|
||||
1. Use specific selectors and [filter fields](http://guide.meteor.com/security.html#fields) in publications.
|
||||
1. Don't use [raw HTML inclusion in Blaze](http://blazejs.org/guide/spacebars.html#Rendering-raw-HTML) unless you really know what you are doing.
|
||||
1. [Make sure secret API keys and passwords aren't in your source code.](security.html#api-keys)
|
||||
1. Secure the data, not the UI - redirecting away from a client-side route does nothing for security, it's a nice UX feature.
|
||||
1. [Don't ever trust user IDs passed from the client.](http://guide.meteor.com/security.html#user-id-client) Use `this.userId` inside Methods and publications.
|
||||
1. Set up secure [HTTP headers](https://guide.meteor.com/security.html#httpheaders) using [Helmet](https://www.npmjs.com/package/helmet), but know that not all browsers support it so it provides an extra layer of security to users with modern browsers.
|
||||
|
||||
<h2 id="appProtection">App Protection</h2>
|
||||
App Protection on Galaxy Hosting is a feature in our proxy server layer that sits in front of every request to your application. This means that all requests across servers are analyzed and measured against expected limits. This will help protect against DoS and DDoS attacks that aimed to overload servers and make your app unavailable for legitimate requests.
|
||||
|
||||
If a type of request is classified as abusive (we’re not going to go into the specifics as to how we determine this), we will stop sending these requests to your app, and we start to return HTTP 429 (Too Many Requests).*
|
||||
|
||||
Although not all attacks are preventable, our App Protection functionality, along with standard AWS protection in front of our servers, will provide a greater level of security for all applications deployed to Galaxy moving forward.
|
||||
|
||||
For additional security, it is best to configure your app to limit the messages received via WebSockets, as our proxy servers are only acting in the first connection and not in the WebSocket messages after the connection is established. Meteor has the DDP Rate Limiter configuration already available, find out more [here](https://docs.meteor.com/api/methods.html#ddpratelimiter).
|
||||
353
content/structure.md
Normal file
@@ -0,0 +1,353 @@
|
||||
---
|
||||
title: Application Structure
|
||||
description: How to structure your Meteor app with ES2015 modules, ship code to the client and server, and split your code into multiple apps.
|
||||
discourseTopicId: 20187
|
||||
---
|
||||
|
||||
After reading this article, you'll know:
|
||||
|
||||
1. How a Meteor application compares to other types of applications in terms of file structure.
|
||||
2. How to organize your application both for small and larger applications.
|
||||
3. How to format your code and name the parts of your application in consistent and maintainable ways.
|
||||
|
||||
<h2 id="meteor-structure">Universal JavaScript</h2>
|
||||
|
||||
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](http://nodejs.org/) container, and _common_ code that runs in both environments. The [Meteor build tool](build-tool.html) 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](#load-order) rules.
|
||||
|
||||
<h3 id="es2015-modules">ES2015 modules</h3>
|
||||
|
||||
As of version 1.3, Meteor ships with full support for [ES2015 modules](https://developer.mozilla.org/en/docs/web/javascript/reference/statements/import). The ES2015 module standard is the replacement for [CommonJS](http://requirejs.org/docs/commonjs.html) and [AMD](https://github.com/amdjs/amdjs-api), which are commonly used JavaScript module format and loading systems.
|
||||
|
||||
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".
|
||||
|
||||
Since this is a new feature introduced in Meteor 1.3, you will find a lot of code online that uses the older, more centralized conventions built around packages and apps declaring global symbols. This old system still works, so to opt-in to the new module system, code must be placed inside the `imports/` directory in your application. We expect a future release of Meteor will turn on modules by default for all code, because this is more aligned with how developers in the wider JavaScript community write their code.
|
||||
|
||||
You can read about the module system in detail in the [`modules` package README](https://docs.meteor.com/#/full/modules). This package is automatically included in every new Meteor app as part of the [`ecmascript` meta-package](https://docs.meteor.com/#/full/ecmascript), so most apps won't need to do anything to start using modules right away.
|
||||
|
||||
<h3 id="intro-to-import-export">Introduction to using `import` and `export`</h3>
|
||||
|
||||
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](build-tool.html#css-importing) article.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
<h3 id="importing-from-packages">Importing from packages</h3>
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
For more details using `imports` with packages see [Using Packages](using-packages.html) in the Meteor Guide.
|
||||
|
||||
<h3 id="using-require">Using `require`</h3>
|
||||
|
||||
In Meteor, `import` statements compile to CommonJS `require` syntax. However, as a convention we encourage you to use `import`.
|
||||
|
||||
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 `import`s 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');
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
If you need to `require` from an ES2015 module with a `default` export, you can grab the export with `require("package").default`.
|
||||
|
||||
<h3 id="exporting-from-coffeescript">Using CoffeeScript</h3>
|
||||
|
||||
See the Docs: [Modules » Syntax » CoffeeScript](https://docs.meteor.com/packages/modules.html#CoffeeScript)
|
||||
|
||||
```cs
|
||||
// lists.coffee
|
||||
|
||||
export Lists = new Collection 'lists'
|
||||
```
|
||||
|
||||
```cs
|
||||
import { Lists } from './lists.coffee'
|
||||
```
|
||||
|
||||
<h2 id="javascript-structure">File structure</h2>
|
||||
|
||||
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 will load all files outside of any directory named `imports/` in the application using the [default file load order](#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/`.
|
||||
|
||||
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.
|
||||
|
||||
<h3 id="example-app-structure">Example directory layout</h3>
|
||||
|
||||
To start, let's look at our [Todos example application](https://github.com/meteor/todos), which is a great example to follow when structuring your app. Below is an overview of its directory structure. You can generate a new app with this structure using the command `meteor create appName --full`.
|
||||
|
||||
```sh
|
||||
imports/
|
||||
startup/
|
||||
client/
|
||||
index.js # import client startup through a single index entry point
|
||||
routes.js # set up all routes in the app
|
||||
useraccounts-configuration.js # configure login templates
|
||||
server/
|
||||
fixtures.js # fill the DB with example data on startup
|
||||
index.js # import server startup through a single index entry point
|
||||
|
||||
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
|
||||
|
||||
client/
|
||||
main.js # client entry point, imports all client code
|
||||
|
||||
server/
|
||||
main.js # server entry point, imports all server code
|
||||
```
|
||||
|
||||
<h3 id="structuring-imports">Structuring imports</h3>
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
> 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.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
<h3 id="startup-files">Startup files</h3>
|
||||
|
||||
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 Todos example app, the `imports/startup/client/useraccounts-configuration.js` file configures the `useraccounts` login templates (see the [Accounts](accounts.html) article for more information about `useraccounts`). The `imports/startup/client/routes.js` configures all of the routes and then imports *all* other code that is required on the client:
|
||||
|
||||
```js
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
|
||||
import { AccountsTemplates } from 'meteor/useraccounts:core';
|
||||
|
||||
// Import to load these templates
|
||||
import '../../ui/layouts/app-body.js';
|
||||
import '../../ui/pages/root-redirector.js';
|
||||
import '../../ui/pages/lists-show-page.js';
|
||||
import '../../ui/pages/app-not-found.js';
|
||||
|
||||
// Import to override accounts templates
|
||||
import '../../ui/accounts/accounts-templates.js';
|
||||
|
||||
// Below here are the route definitions
|
||||
```
|
||||
|
||||
We then import both of these files in `imports/startup/client/index.js`:
|
||||
|
||||
```js
|
||||
import './useraccounts-configuration.js';
|
||||
import './routes.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';
|
||||
```
|
||||
|
||||
On the server, we use the same technique of importing all the startup code in `imports/startup/server/index.js`:
|
||||
|
||||
```js
|
||||
// This defines a starting set of data to be loaded if the app is loaded with an empty db.
|
||||
import '../imports/startup/server/fixtures.js';
|
||||
|
||||
// This file configures the Accounts package to define the UI of the reset password email.
|
||||
import '../imports/startup/server/reset-password-email.js';
|
||||
|
||||
// Set up some rate limiting and other important security settings.
|
||||
import '../imports/startup/server/security.js';
|
||||
|
||||
// This defines all the collections, publications and methods that the application provides
|
||||
// as an API to the client.
|
||||
import '../imports/api/api.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.
|
||||
|
||||
<h3 id="importing-meteor-globals">Importing Meteor "pseudo-globals"</h3>
|
||||
|
||||
For backwards compatibility Meteor 1.3 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`](http://docs.meteor.com/#/full/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';
|
||||
```
|
||||
|
||||
<h2 id="load-order">Default file load order</h2>
|
||||
|
||||
Even though it is recommended that you write your application to use ES2015 modules and the `imports/` directory, Meteor 1.3 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](https://stackoverflow.com/a/51158735/219238).
|
||||
|
||||
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:
|
||||
|
||||
1. HTML template files are **always** loaded before everything else
|
||||
2. Files beginning with `main.` are loaded **last**
|
||||
3. Files inside **any** `lib/` directory are loaded next
|
||||
4. Files with deeper paths are loaded next
|
||||
5. 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
|
||||
```
|
||||
|
||||
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` 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.
|
||||
|
||||
> You can also use [Meteor.startup](http://docs.meteor.com/#/full/meteor_startup) to control when run code is run on both the server and the client.
|
||||
|
||||
<h3 id="special-directories">Special directories</h3>
|
||||
|
||||
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**
|
||||
|
||||
Any directory named `imports/` is not loaded anywhere and files must be imported using `import`.
|
||||
|
||||
- **node_modules**
|
||||
|
||||
Any directory named `node_modules/` is not loaded anywhere. node.js packages installed into `node_modules` directories must be imported using `import` or by using `Npm.depends` in `package.js`.
|
||||
|
||||
- **client**
|
||||
|
||||
Any directory named `client/` is not loaded on the server. Similar to wrapping your code in `if (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.
|
||||
|
||||
> 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**
|
||||
|
||||
Any directory named `server/` is not loaded on the client. Similar to wrapping your code in `if (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 the `server/` directory.
|
||||
|
||||
Meteor gathers all your JavaScript files, excluding anything under the `client`, `public`, and `private` subdirectories, 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**
|
||||
|
||||
All files inside a top-level directory called `public/` are served as-is to the client. When referencing these assets, do not include `public/` in the URL, write the URL as if they were all in the top level. For example, reference `public/bg.png` as `<img src='/bg.png' />`. This is the best place for `favicon.ico`, `robots.txt`, and similar files.
|
||||
|
||||
- **private**
|
||||
|
||||
All files inside a top-level directory called `private/` are only accessible from server code and can be loaded via the [`Assets`](http://docs.meteor.com/#/full/assets_getText) API. 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**
|
||||
|
||||
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.
|
||||
|
||||
> It is recommended to use npm for 3rd party JavaScript libraries and use `import` to control when files are loaded.
|
||||
|
||||
- **tests**
|
||||
|
||||
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](testing.html).
|
||||
|
||||
The following directories are also not loaded as part of your app code:
|
||||
|
||||
- Files/directories whose names start with a dot, like `.meteor` and `.git`
|
||||
- `packages/`: Used for local packages
|
||||
- `cordova-build-override/`: Used for [advanced mobile build customizations](mobile.html#advanced-build)
|
||||
- `programs`: For legacy reasons
|
||||
|
||||
<h3 id="files-outside">Files outside special directories</h3>
|
||||
|
||||
All JavaScript files outside special directories are loaded on both the client and the server. Meteor provides the variables [`Meteor.isClient`](http://docs.meteor.com/#/full/meteor_isserver) and [`Meteor.isServer`](http://docs.meteor.com/#/full/meteor_isserver) so that your code can alter its behavior depending on whether it's running on the client or the server.
|
||||
|
||||
CSS and HTML files outside special directories are loaded on the client only and cannot be used from server code.
|
||||
|
||||
<h2 id="splitting-your-app">Splitting into multiple apps</h2>
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
|
||||
<h3 id="sharing-code">Sharing code</h3>
|
||||
|
||||
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](deployment.html#environment). This approach allows you to deploy different versions with different scaling behavior but doesn't enjoy most of the other advantages stated above.
|
||||
|
||||
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](writing-packages.html), either npm or Atmosphere, depending on whether the code is Meteor-specific or otherwise.
|
||||
|
||||
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](https://www.npmjs.com/private-modules)). There are several ways to do this:
|
||||
|
||||
- a straightforward approach is to include the common code as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) 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.
|
||||
|
||||
<h3 id="sharing-data">Sharing data</h3>
|
||||
|
||||
Another important consideration is how you'll share the data between your different applications.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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()`](http://docs.meteor.com/#/full/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.
|
||||
|
||||
<h3 id="sharing-accounts">Sharing accounts</h3>
|
||||
|
||||
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.
|
||||
|
||||
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](https://github.com/tmeasday/multi-app-accounts).
|
||||
822
content/testing.md
Normal file
@@ -0,0 +1,822 @@
|
||||
---
|
||||
title: "Testing"
|
||||
description: How to test your Meteor application
|
||||
discourseTopicId: 20191
|
||||
---
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
|
||||
Testing allows you to ensure your application works the way you think it does, especially as your codebase changes over time. If you have good tests, you can refactor and rewrite code with confidence. Tests are also the most concrete form of documentation of expected behavior, since other developers can figure out how to use your code by reading the tests.
|
||||
|
||||
Automated testing is critical because it allows you to run a far greater set of tests much more often than you could manually, allowing you to catch regression errors immediately.
|
||||
|
||||
<h3 id="testing-types">Types of tests</h3>
|
||||
|
||||
Entire books have been written on the subject of testing, so we will touch on some basics of testing here. The important thing to consider when writing a test is what part of the application you are trying to test, and how you are verifying the behavior works.
|
||||
|
||||
- **Unit test**: If you are testing one small module of your application, you are writing a unit test. You'll need to *stub* and *mock* other modules that your module usually leverages in order to *isolate* each test. You'll typically also need to *spy* on actions that the module takes to verify that they occur.
|
||||
|
||||
- **Integration test**: If you are testing that multiple modules behave properly in concert, you are writing an integration test. Such tests are much more complex and may require running code both on the client and on the server to verify that communication across that divide is working as expected. Typically an integration test will still isolate a part of the entire application and directly verify results in code.
|
||||
|
||||
- **Acceptance test**: If you want to write a test that can be run against any running version of your app and verifies at the browser level that the right things happen when you push the right buttons, then you are writing an acceptance test (sometimes called "end to end test"). Such tests typically try to hook into the application as little as possible, beyond perhaps setting up the right data to run a test against.
|
||||
|
||||
- **Load test**: Finally you may wish to test that your application works under typical load or see how much load it can handle before it falls over. This is called a load test or stress test. Such tests can be challenging to set up and typically aren't run often but are very important for confidence before a big production launch.
|
||||
|
||||
<h3 id="challenges-with-meteor">Challenges of testing in Meteor</h3>
|
||||
|
||||
In most ways, testing a Meteor app is no different from testing any other full stack JavaScript application. However, compared to more traditional backend or front-end focused frameworks, two factors can make testing a little more challenging:
|
||||
|
||||
- **Client/server data**: Meteor's data system makes it possible to bridge the client-server gap and often allows you to build your application without thinking about how data moves around. It becomes critical to test that your code does actually work correctly across that gap. In traditional frameworks where you spend a lot of time thinking about interfaces between client and server you can often get away with testing both sides of the interface in isolation, but Meteor's [full app test mode](#test-modes) makes it possible to write [integration tests](#full-app-integration-test) that cover the full stack. Another challenge here is creating test data in the client context; we'll discuss ways to do this in the [section on generating test data](#generating-test-data) below.
|
||||
|
||||
- **Reactivity**: Meteor's reactivity system is "eventually consistent" in the sense that when you change a reactive input to the system, you'll see the user interface change to reflect this some time later. This can be a challenge when testing, but there are some ways to wait until those changes happen to verify the results, for example `Tracker.afterFlush()`.
|
||||
|
||||
<h2 id="test-modes">The 'meteor test' command</h2>
|
||||
|
||||
The primary way to test your application in Meteor is the `meteor test` command.
|
||||
|
||||
This loads your application in a special "test mode". What this does is:
|
||||
|
||||
1. *Doesn't* eagerly load *any* of our application code as Meteor normally would.
|
||||
2. *Does* eagerly load any file in our application (including in `imports/` folders) that look like `*.test[s].*`, or `*.spec[s].*`
|
||||
3. Sets the `Meteor.isTest` flag to be true.
|
||||
4. Starts up the test driver package ([see below](#driver-packages)).
|
||||
|
||||
> The [Meteor build tool](build-tool.html#what-it-does) and the `meteor test` command ignore any files located in any `tests/` directory. This allows you to put tests in this directory that you can run using a test runner outside of Meteor's built-in test tools and still not have those files loaded in your application. See Meteor's [default file load order](structure.html#load-order) rules.
|
||||
|
||||
What this means is that you can write tests in files with a certain filename pattern and know they'll not be included in normal builds of your app. When your app runs in test mode, those files will be loaded (and nothing else will), and they can import the modules you want to test. As we'll see this is ideal for [unit tests](#unit-testing) and [simple integration tests](#simple-integration-test).
|
||||
|
||||
Additionally, Meteor offers a "full application" test mode. You can run this with `meteor test --full-app`.
|
||||
|
||||
This is similar to test mode, with key differences:
|
||||
|
||||
1. It loads test files matching `*.app-test[s].*` and `*.app-spec[s].*`.
|
||||
2. It **does** eagerly load our application code as Meteor normally would.
|
||||
3. Sets the `Meteor.isAppTest` flag to be true (instead of the `Meteor.isTest` flag).
|
||||
|
||||
This means that the entirety of your application (including for instance the web server and client side router) is loaded and will run as normal. This enables you to write much more [complex integration tests](#full-app-integration-test) and also load additional files for [acceptance tests](#acceptance-testing).
|
||||
|
||||
Note that there is another test command in the Meteor tool; `meteor test-packages` is a way of testing Atmosphere packages, which is discussed in the [Writing Packages article](writing-packages.html#testing).
|
||||
|
||||
<h3 id="driver-packages">Driver packages</h3>
|
||||
|
||||
When you run a `meteor test` command, you must provide a `--driver-package` argument. A test driver is a mini-application that runs in place of your app and runs each of your defined tests, whilst reporting the results in some kind of user interface.
|
||||
|
||||
There are two main kinds of test driver packages:
|
||||
|
||||
- **Web reporters**: Meteor applications that display a special test reporting web UI that you can view the test results in.
|
||||
|
||||
<img src="images/mocha-test-results.png">
|
||||
|
||||
- **Console reporters**: These run completely on the command-line and are primary used for automated testing like [continuous integration](#ci).
|
||||
|
||||
<h3 id="mocha">Recommended: Mocha</h3>
|
||||
|
||||
In this article, we'll use the popular [Mocha](https://mochajs.org) test runner alongside the [Chai](http://chaijs.com) assertion library to test our application. In order to write and run tests in Mocha, we need to add an appropriate test driver package.
|
||||
|
||||
There are several options. Choose the ones that makes sense for your app. You may depend on more than one and set up different test commands for different situations.
|
||||
|
||||
* [meteortesting:mocha](https://atmospherejs.com/meteortesting/mocha) Runs client and/or server package or app tests and reports all results in the server console. Supports various browsers for running client tests, including PhantomJS, Selenium ChromeDriver, and Electron. Can be used for running tests on a CI server. Has a watch mode.
|
||||
|
||||
These packages don't do anything in development or production mode. They declare themselves `testOnly` so they are not even loaded outside of testing. But when our app is run in [test mode](#test-modes), the test driver package takes over, executing test code on both the client and server, and rendering results to the browser.
|
||||
|
||||
Here's how we can add the [`meteortesting:mocha`](https://atmospherejs.com/meteortesting/mocha) package to our app:
|
||||
|
||||
```bash
|
||||
meteor add meteortesting:mocha
|
||||
```
|
||||
|
||||
<h2 id="test-files">Test Files</h2>
|
||||
|
||||
Test files themselves (for example a file named `todos-item.test.js` or `routing.app-specs.coffee`) can register themselves to be run by the test driver in the usual way for that testing library. For Mocha, that's by using `describe` and `it`:
|
||||
|
||||
```js
|
||||
describe('my module', function () {
|
||||
it('does something that should be tested', function () {
|
||||
// This code will be executed by the test driver when the app is started
|
||||
// in the correct mode
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Note that arrow function use with Mocha [is discouraged](http://mochajs.org/#arrow-functions).
|
||||
|
||||
<h2 id="test-data">Test data</h2>
|
||||
|
||||
When your app is run in test mode, it is initialized with a clean test database.
|
||||
|
||||
If you are running a test that relies on using the database, and specifically the content of the database, you'll need to perform some *setup* steps in your test to ensure the database is in the state you expect. There are some tools you can use to do this.
|
||||
|
||||
To ensure the database is clean, the [`xolvio:cleaner`](https://atmospherejs.com/xolvio/cleaner) package is useful. You can use it to reset the database in a `beforeEach` block:
|
||||
|
||||
```js
|
||||
import { resetDatabase } from 'meteor/xolvio:cleaner';
|
||||
|
||||
describe('my module', function () {
|
||||
beforeEach(function () {
|
||||
resetDatabase();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This technique will only work on the server. If you need to reset the database from a client test, you can use a method to do so:
|
||||
|
||||
```js
|
||||
import { resetDatabase } from 'meteor/xolvio:cleaner';
|
||||
|
||||
// NOTE: Before writing a method like this you'll want to double check
|
||||
// that this file is only going to be loaded in test mode!!
|
||||
Meteor.methods({
|
||||
'test.resetDatabase': () => resetDatabase(),
|
||||
});
|
||||
|
||||
describe('my module', function (done) {
|
||||
beforeEach(function (done) {
|
||||
// We need to wait until the method call is done before moving on, so we
|
||||
// use Mocha's async mechanism (calling a done callback)
|
||||
Meteor.call('test.resetDatabase', done);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
As we've placed the code above in a test file, it *will not* load in normal development or production mode (which would be an incredibly bad thing!). If you create a Atmosphere package with a similar feature, you should mark it as `testOnly` and it will similarly only load in test mode.
|
||||
|
||||
<h3 id="generating-test-data">Generating test data</h3>
|
||||
|
||||
Often it's sensible to create a set of data to run your test against. You can use standard `insert()` calls against your collections to do this, but often it's easier to create *factories* which help encode random test data. A great package to use to do this is [`dburles:factory`](https://atmospherejs.com/dburles/factory).
|
||||
|
||||
In the [Todos](https://github.com/meteor/todos) example app, we define a factory to describe how to create a test todo item, using the [`faker`](https://www.npmjs.com/package/faker) npm package:
|
||||
|
||||
```js
|
||||
import faker from 'faker';
|
||||
|
||||
Factory.define('todo', Todos, {
|
||||
listId: () => Factory.get('list'),
|
||||
text: () => faker.lorem.sentence(),
|
||||
createdAt: () => new Date(),
|
||||
});
|
||||
```
|
||||
|
||||
To use the factory in a test, we call `Factory.create`:
|
||||
|
||||
```js
|
||||
// This creates a todo and a list in the database and returns the todo.
|
||||
const todo = Factory.create('todo');
|
||||
|
||||
// If we have a list already, we can pass in the id and avoid creating another:
|
||||
const list = Factory.create('list');
|
||||
const todoInList = Factory.create('todo', { listId: list._id });
|
||||
```
|
||||
|
||||
<h3 id="mocking-the-database">Mocking the database</h3>
|
||||
|
||||
As `Factory.create` directly inserts documents into the collection that's passed into the `Factory.define` function, it can be a problem to use it on the client. However there's a neat isolation trick that you can do to replace the server-backed `Todos` [client collection](collections.html#client-collections) with a mocked out [local collection](#collections.html#local-collections), that's encoded in the [`hwillson:stub-collections`](https://atmospherejs.com/hwillson/stub-collections) package.
|
||||
|
||||
```js
|
||||
import StubCollections from 'meteor/hwillson:stub-collections';
|
||||
import { Todos } from 'path/to/todos.js';
|
||||
|
||||
StubCollections.stub(Todos);
|
||||
|
||||
// Now Todos is stubbed to a simple local collection mock,
|
||||
// so for instance on the client we can do:
|
||||
Todos.insert({ a: 'document' });
|
||||
|
||||
// Restore the `Todos` collection
|
||||
StubCollections.restore();
|
||||
```
|
||||
|
||||
In a Mocha test, it makes sense to use `stub-collections` in a `beforeEach`/`afterEach` block.
|
||||
|
||||
<h2 id="unit-testing">Unit testing</h2>
|
||||
|
||||
Unit testing is the process of isolating a section of code and then testing that the internals of that section work as you expect. As [we've split our code base up into ES2015 modules](structure.html) it's natural to test those modules one at a time.
|
||||
|
||||
By isolating a module and testing its internal functionality, we can write tests that are *fast* and *accurate*---they can quickly tell you where a problem in your application lies. Note however that incomplete unit tests can often hide bugs because of the way they stub out dependencies. For that reason it's useful to combine unit tests with slower (and perhaps less commonly run) integration and acceptance tests.
|
||||
|
||||
<h3 id="simple-blaze-unit-test">A simple Blaze unit test</h3>
|
||||
|
||||
In the [Todos](https://github.com/meteor/todos) example app, thanks to the fact that we've split our User Interface into [smart and reusable components](ui-ux.html#components), it's natural to want to unit test some of our reusable components (we'll see below how to [integration test](#simple-integration-test) our smart components).
|
||||
|
||||
To do so, we'll use a very simple test helper that renders a Blaze component off-screen with a given data context. As we place it in `imports`, it won't load in our app by in normal mode (as it's not required anywhere).
|
||||
|
||||
[`imports/ui/test-helpers.js`](https://github.com/meteor/todos/blob/master/imports/ui/test-helpers.js):
|
||||
|
||||
```js
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
|
||||
const withDiv = function withDiv(callback) {
|
||||
const el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
try {
|
||||
callback(el);
|
||||
} finally {
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
};
|
||||
|
||||
export const withRenderedTemplate = function withRenderedTemplate(template, data, callback) {
|
||||
withDiv((el) => {
|
||||
const ourTemplate = _.isString(template) ? Template[template] : template;
|
||||
Blaze.renderWithData(ourTemplate, data, el);
|
||||
Tracker.flush();
|
||||
callback(el);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
An example of a reusable component to test is the [`Todos_item`](https://github.com/meteor/todos/blob/master/imports/ui/components/todos-item.html) template. Here's what a unit test looks like (you can see some [others in the app repository](https://github.com/meteor/todos/blob/master/imports/ui/components/client)).
|
||||
|
||||
[`imports/ui/components/client/todos-item.tests.js`](https://github.com/meteor/todos/blob/master/imports/ui/components/client/todos-item.tests.js):
|
||||
|
||||
```js
|
||||
/* eslint-env mocha */
|
||||
/* eslint-disable func-names, prefer-arrow-callback */
|
||||
|
||||
import { Factory } from 'meteor/dburles:factory';
|
||||
import chai from 'chai';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { $ } from 'meteor/jquery';
|
||||
import { Todos } from '../../../api/todos/todos';
|
||||
|
||||
|
||||
import { withRenderedTemplate } from '../../test-helpers.js';
|
||||
import '../todos-item.js';
|
||||
|
||||
describe('Todos_item', function () {
|
||||
beforeEach(function () {
|
||||
Template.registerHelper('_', key => key);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Template.deregisterHelper('_');
|
||||
});
|
||||
|
||||
it('renders correctly with simple data', function () {
|
||||
const todo = Factory.build('todo', { checked: false });
|
||||
const data = {
|
||||
todo: Todos._transform(todo),
|
||||
onEditingChange: () => 0,
|
||||
};
|
||||
|
||||
withRenderedTemplate('Todos_item', data, el => {
|
||||
chai.assert.equal($(el).find('input[type=text]').val(), todo.text);
|
||||
chai.assert.equal($(el).find('.list-item.checked').length, 0);
|
||||
chai.assert.equal($(el).find('.list-item.editing').length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Of particular interest in this test is the following:
|
||||
|
||||
<h4 id="unit-test-importing">Importing</h4>
|
||||
|
||||
When we run our app in test mode, only our test files will be eagerly loaded. In particular, this means that in order to use our templates, we need to import them! In this test, we import `todos-item.js`, which itself imports `todos.html` (yes, you do need to import the HTML files of your Blaze templates!)
|
||||
|
||||
<h4 id="unit-test-stubbing">Stubbing</h4>
|
||||
|
||||
To be a unit test, we must stub out the dependencies of the module. In this case, thanks to the way we've isolated our code into a reusable component, there's not much to do; principally we need to stub out the `{% raw %}{{_}}{% endraw %}` helper that's created by the [`tap:i18n`](ui-ux.html#i18n) system. Note that we stub it out in a `beforeEach` and restore it the `afterEach`.
|
||||
|
||||
If you're testing code that makes use of globals, you'll need to import those globals. For instance if you have a global `Todos` collection and are testing this file:
|
||||
|
||||
```js
|
||||
// logging.js
|
||||
export function logTodos() {
|
||||
console.log(Todos.findOne());
|
||||
}
|
||||
```
|
||||
|
||||
then you'll need to import `Todos` both in that file and in the test:
|
||||
|
||||
```js
|
||||
// logging.js
|
||||
import { Todos } from './todos.js'
|
||||
export function logTodos() {
|
||||
console.log(Todos.findOne());
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// logging.test.js
|
||||
import { Todos } from './todos.js'
|
||||
Todos.findOne = () => {
|
||||
return {text: "write a guide"}
|
||||
}
|
||||
|
||||
import { logTodos } from './logging.js'
|
||||
// then test logTodos
|
||||
...
|
||||
```
|
||||
|
||||
<h4 id="unit-test-data">Creating data</h4>
|
||||
|
||||
We can use the [Factory package's](#test-data) `.build()` API to create a test document without inserting it into any collection. As we've been careful not to call out to any collections directly in the reusable component, we can pass the built `todo` document directly into the template.
|
||||
|
||||
<h3 id="simple-react-unit-test">A simple React unit test</h3>
|
||||
|
||||
We can also apply the same structure to testing React components and recommend the [Enzyme](https://github.com/airbnb/enzyme) package, which simulates a React component's environment and allows you to query it using CSS selectors. A larger suite of tests is available in the [react branch of the Todos app](https://github.com/meteor/todos/tree/react), but let's look at a simple example for now:
|
||||
|
||||
```js
|
||||
import { Factory } from 'meteor/dburles:factory';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import chai from 'chai';
|
||||
import TodoItem from './TodoItem.jsx';
|
||||
|
||||
describe('TodoItem', () => {
|
||||
it('should render', () => {
|
||||
const todo = Factory.build('todo', { text: 'testing', checked: false });
|
||||
const item = shallow(<TodoItem todo={todo} />);
|
||||
chai.assert(item.hasClass('list-item'));
|
||||
chai.assert(!item.hasClass('checked'));
|
||||
chai.assert.equal(item.find('.editing').length, 0);
|
||||
chai.assert.equal(item.find('input[type="text"]').prop('defaultValue'), 'testing');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
The test is slightly simpler than the Blaze version above because the React sample app is not internationalized. Otherwise, it's conceptually identical. We use Enzyme's `shallow` function to render the `TodoItem` component, and the resulting object to query the document, and also to simulate user interactions. And here's an example of simulating a user checking the todo item:
|
||||
|
||||
```js
|
||||
import { Factory } from 'meteor/dburles:factory';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import TodoItem from './TodoItem.jsx';
|
||||
import { setCheckedStatus } from '../../api/todos/methods.js';
|
||||
|
||||
describe('TodoItem', () => {
|
||||
it('should update status when checked', () => {
|
||||
sinon.stub(setCheckedStatus, 'call');
|
||||
const todo = Factory.create('todo', { checked: false });
|
||||
const item = shallow(<TodoItem todo={todo} />);
|
||||
|
||||
item.find('input[type="checkbox"]').simulate('change', {
|
||||
target: { checked: true },
|
||||
});
|
||||
|
||||
sinon.assert.calledWith(setCheckedStatus.call, {
|
||||
todoId: todo._id,
|
||||
newCheckedStatus: true,
|
||||
});
|
||||
|
||||
setCheckedStatus.call.restore();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
In this case, the `TodoItem` component calls a [Meteor Method](/methods.html) `setCheckedStatus` when the user clicks, but this is a unit test so there's no server running. So we stub it out using [Sinon](http://sinonjs.org). After we simulate the click, we verify that the stub was called with the correct arguments. Finally, we clean up the stub and restore the original method behavior.
|
||||
|
||||
<h3 id="running-unit-tests">Running unit tests</h3>
|
||||
|
||||
To run the tests that our app defines, we run our app in [test mode](#test-modes):
|
||||
|
||||
```txt
|
||||
TEST_WATCH=1 meteor test --driver-package meteortesting:mocha
|
||||
```
|
||||
|
||||
As we've defined a test file (`imports/todos/todos.tests.js`), what this means is that the file above will be eagerly loaded, adding the `'builds correctly from factory'` test to the Mocha registry.
|
||||
|
||||
To run the tests, visit http://localhost:3000 in your browser. This kicks off `meteortesting:mocha`, which runs your tests both in the browser and on the server. It will display the test results in a div with ID mocha.
|
||||
|
||||
Usually, while developing an application, it makes sense to run `meteor test` on a second port (say `3100`), while also running your main application in a separate process:
|
||||
|
||||
```bash
|
||||
# in one terminal window
|
||||
meteor
|
||||
|
||||
# in another
|
||||
meteor test --driver-package meteortesting:mocha --port 3100
|
||||
```
|
||||
|
||||
Then you can open two browser windows to see the app in action while also ensuring that you don't break any tests as you make changes.
|
||||
|
||||
<h3 id="isolation-techniques">Isolation techniques</h3>
|
||||
|
||||
In the [unit tests above](#simple-blaze-unit-test) we saw a very limited example of how to isolate a module from the larger app. This is critical for proper unit testing. Some other utilities and techniques include:
|
||||
|
||||
- The [`velocity:meteor-stubs`](https://atmospherejs.com/velocity/meteor-stubs) package, which creates simple stubs for most Meteor core objects.
|
||||
|
||||
- Alternatively, you can also use tools like [Sinon](http://sinonjs.org) to stub things directly, as we'll see for example in our [simple integration test](#simple-integration-test).
|
||||
|
||||
- The [`hwillson:stub-collections`](https://atmospherejs.com/hwillson/stub-collections) package we mentioned [above](#mocking-the-database).
|
||||
|
||||
There's a lot of scope for better isolation and testing utilities.
|
||||
|
||||
<h4 id="testing-publications">Testing publications</h4>
|
||||
|
||||
Using the [`johanbrook:publication-collector`](https://atmospherejs.com/johanbrook/publication-collector) package, you're able to test individual publication's output without needing to create a traditional subscription:
|
||||
|
||||
```js
|
||||
describe('lists.public', function () {
|
||||
it('sends all public lists', function (done) {
|
||||
// Set a user id that will be provided to the publish function as `this.userId`,
|
||||
// in case you want to test authentication.
|
||||
const collector = new PublicationCollector({userId: 'some-id'});
|
||||
|
||||
// Collect the data published from the `lists.public` publication.
|
||||
collector.collect('lists.public', (collections) => {
|
||||
// `collections` is a dictionary with collection names as keys,
|
||||
// and their published documents as values in an array.
|
||||
// Here, documents from the collection 'Lists' are published.
|
||||
chai.assert.typeOf(collections.Lists, 'array');
|
||||
chai.assert.equal(collections.Lists.length, 3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that user documents – ones that you would normally query with `Meteor.users.find()` – will be available as the key `users` on the dictionary passed from a `PublicationCollector.collect()` call. See the [tests](https://github.com/johanbrook/meteor-publication-collector/blob/master/tests/publication-collector.test.js) in the package for more details.
|
||||
|
||||
<h2 id="integration-testing">Integration testing</h2>
|
||||
|
||||
An integration test is a test that crosses module boundaries. In the simplest case, this means something very similar to a unit test, where you perform your isolation around multiple modules, creating a non-singular "system under test".
|
||||
|
||||
Although conceptually different to unit tests, such tests typically do not need to be run any differently to unit tests and can use the same [`meteor test` mode](#running-unit-tests) and [isolation techniques](#isolation-techniques) as we use for unit tests.
|
||||
|
||||
However, an integration test that crosses the client-server boundary of a Meteor application (where the modules under test cross that boundary) requires a different testing infrastructure, namely Meteor's "full app" testing mode.
|
||||
|
||||
Let's take a look at example of both kinds of tests.
|
||||
|
||||
<h3 id="simple-integration-test">Simple integration test</h3>
|
||||
|
||||
Our reusable components were a natural fit for a unit test; similarly our smart components tend to require an integration test to really be exercised properly, as the job of a smart component is to bring data together and supply it to a reusable component.
|
||||
|
||||
In the [Todos](https://github.com/meteor/todos) example app, we have an integration test for the `Lists_show_page` smart component. This test ensures that when the correct data is present in the database, the template renders correctly -- that it is gathering the correct data as we expect. It isolates the rendering tree from the more complex data subscription part of the Meteor stack. If we wanted to test that the subscription side of things was working in concert with the smart component, we'd need to write a [full app integration test](#full-app-integration-test).
|
||||
|
||||
[`imports/ui/components/client/todos-item.tests.js`](https://github.com/meteor/todos/blob/master/imports/ui/components/client/todos-item.tests.js):
|
||||
|
||||
```js
|
||||
/* eslint-env mocha */
|
||||
/* eslint-disable func-names, prefer-arrow-callback */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Factory } from 'meteor/dburles:factory';
|
||||
import { Random } from 'meteor/random';
|
||||
import chai from 'chai';
|
||||
import StubCollections from 'meteor/hwillson:stub-collections';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { $ } from 'meteor/jquery';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { withRenderedTemplate } from '../../test-helpers.js';
|
||||
import '../lists-show-page.js';
|
||||
|
||||
import { Todos } from '../../../api/todos/todos.js';
|
||||
import { Lists } from '../../../api/lists/lists.js';
|
||||
|
||||
describe('Lists_show_page', function () {
|
||||
const listId = Random.id();
|
||||
|
||||
beforeEach(function () {
|
||||
StubCollections.stub([Todos, Lists]);
|
||||
Template.registerHelper('_', key => key);
|
||||
sinon.stub(FlowRouter, 'getParam').returns(listId);
|
||||
sinon.stub(Meteor, 'subscribe').returns.({
|
||||
subscriptionId: 0,
|
||||
ready: () => true,
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
StubCollections.restore();
|
||||
Template.deregisterHelper('_');
|
||||
FlowRouter.getParam.restore();
|
||||
Meteor.subscribe.restore();
|
||||
});
|
||||
|
||||
it('renders correctly with simple data', function () {
|
||||
Factory.create('list', { _id: listId });
|
||||
const timestamp = new Date();
|
||||
const todos = _.times(3, i => Factory.create('todo', {
|
||||
listId,
|
||||
createdAt: new Date(timestamp - (3 - i)),
|
||||
}));
|
||||
|
||||
withRenderedTemplate('Lists_show_page', {}, el => {
|
||||
const todosText = todos.map(t => t.text).reverse();
|
||||
const renderedText = $(el).find('.list-items input[type=text]')
|
||||
.map((i, e) => $(e).val())
|
||||
.toArray();
|
||||
chai.assert.deepEqual(renderedText, todosText);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Of particular interest in this test is the following:
|
||||
|
||||
<h4 id="simple-integration-test-importing">Importing</h4>
|
||||
|
||||
As we'll run this test in the same way that we did our unit test, we need to `import` the relevant modules under test in the same way that we [did in the unit test](#simple-integration-test-importing).
|
||||
|
||||
<h4 id="simple-integration-test-stubbing">Stubbing</h4>
|
||||
|
||||
As the system under test in our integration test has a larger surface area, we need to stub out a few more points of integration with the rest of the stack. Of particular interest here is our use of the [`hwillson:stub-collections`](#mocking-the-database) package and of [Sinon](http://sinonjs.org) to stub out Flow Router and our Subscription.
|
||||
|
||||
<h4 id="simple-integration-test-data">Creating data</h4>
|
||||
|
||||
In this test, we used [Factory package's](#test-data) `.create()` API, which inserts data into the real collection. However, as we've proxied all of the `Todos` and `Lists` collection methods onto a local collection (this is what `hwillson:stub-collections` is doing), we won't run into any problems with trying to perform inserts from the client.
|
||||
|
||||
This integration test can be run the exact same way as we ran [unit tests above](#running-unit-tests).
|
||||
|
||||
<h3 id="full-app-integration-test">Full-app integration test</h3>
|
||||
|
||||
In the [Todos](https://github.com/meteor/todos) example application, we have a integration test which ensures that we see the full contents of a list when we route to it, which demonstrates a few techniques of integration tests.
|
||||
|
||||
[`imports/startup/client/routes.app-test.js`](https://github.com/meteor/todos/blob/master/imports/startup/client/routes.app-test.js):
|
||||
|
||||
```js
|
||||
/* eslint-env mocha */
|
||||
/* eslint-disable func-names, prefer-arrow-callback */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { DDP } from 'meteor/ddp-client';
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { Promise } from 'meteor/promise';
|
||||
import { $ } from 'meteor/jquery';
|
||||
|
||||
import { denodeify } from '../../utils/denodeify';
|
||||
import { generateData } from './../../api/generate-data.app-tests.js';
|
||||
import { Lists } from '../../api/lists/lists.js';
|
||||
import { Todos } from '../../api/todos/todos.js';
|
||||
|
||||
|
||||
// Utility -- returns a promise which resolves when all subscriptions are done
|
||||
const waitForSubscriptions = () => new Promise(resolve => {
|
||||
const poll = Meteor.setInterval(() => {
|
||||
if (DDP._allSubscriptionsReady()) {
|
||||
Meteor.clearInterval(poll);
|
||||
resolve();
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// Tracker.afterFlush runs code when all consequent of a tracker based change
|
||||
// (such as a route change) have occured. This makes it a promise.
|
||||
const afterFlushPromise = denodeify(Tracker.afterFlush);
|
||||
|
||||
if (Meteor.isClient) {
|
||||
describe('data available when routed', () => {
|
||||
// First, ensure the data that we expect is loaded on the server
|
||||
// Then, route the app to the homepage
|
||||
beforeEach(() => generateData()
|
||||
.then(() => FlowRouter.go('/'))
|
||||
.then(waitForSubscriptions)
|
||||
);
|
||||
|
||||
describe('when logged out', () => {
|
||||
it('has all public lists at homepage', () => {
|
||||
assert.equal(Lists.find().count(), 3);
|
||||
});
|
||||
|
||||
it('renders the correct list when routed to', () => {
|
||||
const list = Lists.findOne();
|
||||
FlowRouter.go('Lists.show', { _id: list._id });
|
||||
|
||||
return afterFlushPromise()
|
||||
.then(waitForSubscriptions)
|
||||
.then(() => {
|
||||
assert.equal($('.title-wrapper').html(), list.name);
|
||||
assert.equal(Todos.find({ listId: list._id }).count(), 3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Of note here:
|
||||
|
||||
- Before running, each test sets up the data it needs using the `generateData` helper (see [the section on creating integration test data](#creating-integration-test-data) for more detail) then goes to the homepage.
|
||||
|
||||
- Although Flow Router doesn't take a done callback, we can use `Tracker.afterFlush` to wait for all its reactive consequences to occur.
|
||||
|
||||
- Here we wrote a little utility (which could be abstracted into a general package) to wait for all the subscriptions which are created by the route change (the `todos.inList` subscription in this case) to become ready before checking their data.
|
||||
|
||||
<h3 id="running-full-app-tests">Running full-app tests</h3>
|
||||
|
||||
To run the [full-app tests](#test-modes) in our application, we run:
|
||||
|
||||
```txt
|
||||
meteor test --full-app --driver-package meteortesting:mocha
|
||||
```
|
||||
|
||||
When we connect to the test instance in a browser, we want to render a testing UI rather than our app UI, so the `mocha-web-reporter` package will hide any UI of our application and overlay it with its own. However the app continues to behave as normal, so we are able to route around and check the correct data is loaded.
|
||||
|
||||
<h3 id="creating-integration-test-data">Creating data</h3>
|
||||
|
||||
To create test data in full-app test mode, it usually makes sense to create some special test methods which we can call from the client side. Usually when testing a full app, we want to make sure the publications are sending through the correct data (as we do in this test), and so it's not sufficient to stub out the collections and place synthetic data in them. Instead we'll want to actually create data on the server and let it be published.
|
||||
|
||||
Similar to the way we cleared the database using a method in the `beforeEach` in the [test data](#test-data) section above, we can call a method to do that before running our tests. In the case of our routing tests, we've used a file called [`imports/api/generate-data.app-tests.js`](https://github.com/meteor/todos/blob/master/imports/api/generate-data.app-tests.js) which defines this method (and will only be loaded in full app test mode, so is not available in general!):
|
||||
|
||||
```js
|
||||
// This file will be auto-imported in the app-test context,
|
||||
// ensuring the method is always available
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Factory } from 'meteor/dburles:factory';
|
||||
import { resetDatabase } from 'meteor/xolvio:cleaner';
|
||||
import { Random } from 'meteor/random';
|
||||
import { _ } from 'meteor/underscore';
|
||||
|
||||
import { denodeify } from '../utils/denodeify';
|
||||
|
||||
const createList = (userId) => {
|
||||
const list = Factory.create('list', { userId });
|
||||
_.times(3, () => Factory.create('todo', { listId: list._id }));
|
||||
return list;
|
||||
};
|
||||
|
||||
// Remember to double check this is a test-only file before
|
||||
// adding a method like this!
|
||||
Meteor.methods({
|
||||
generateFixtures() {
|
||||
resetDatabase();
|
||||
|
||||
// create 3 public lists
|
||||
_.times(3, () => createList());
|
||||
|
||||
// create 3 private lists
|
||||
_.times(3, () => createList(Random.id()));
|
||||
},
|
||||
});
|
||||
|
||||
let generateData;
|
||||
if (Meteor.isClient) {
|
||||
// Create a second connection to the server to use to call
|
||||
// test data methods. We do this so there's no contention
|
||||
// with the currently tested user's connection.
|
||||
const testConnection = Meteor.connect(Meteor.absoluteUrl());
|
||||
|
||||
generateData = denodeify((cb) => {
|
||||
testConnection.call('generateFixtures', cb);
|
||||
});
|
||||
}
|
||||
|
||||
export { generateData };
|
||||
```
|
||||
|
||||
Note that we've exported a client-side symbol `generateData` which is a promisified version of the method call, which makes it simpler to use this sequentially in tests.
|
||||
|
||||
Also of note is the way we use a second DDP connection to the server in order to send these test "control" method calls.
|
||||
|
||||
<h2 id="acceptance-testing">Acceptance testing</h2>
|
||||
|
||||
Acceptance testing is the process of taking an unmodified version of our application and testing it from the "outside" to make sure it behaves in a way we expect. Typically if an app passes acceptance tests, we have done our job properly from a product perspective.
|
||||
|
||||
As acceptance tests test the behavior of the application in a full browser context in a generic way, there are a range of tools that you can use to specify and run such tests. In this guide we'll demonstrate using [Chimp](https://chimp.readme.io), an acceptance testing tool with a few neat Meteor-specific features that makes it easy to use.
|
||||
|
||||
Chimp requires node version 4 or 5. You can check your node version by running:
|
||||
|
||||
```sh
|
||||
node -v
|
||||
```
|
||||
|
||||
You can install version 4 from [nodejs.org](https://nodejs.org/en/download/) or version 5 with `brew install node`. Then we can install the Chimp tool globally using:
|
||||
|
||||
```sh
|
||||
npm install --global chimp
|
||||
```
|
||||
|
||||
> Note that you can also install Chimp as a `devDependency` in your `package.json` but you may run into problems deploying your application as it includes binary dependencies. You can avoid such problems by running `meteor npm prune` to remove non-production dependencies before deploying.
|
||||
|
||||
Chimp has a variety of options for setting it up, but we can add some npm scripts which will run the currently tests we define in Chimp's two main modes. We can add them to our `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests",
|
||||
"chimp-test": "chimp --mocha --path=tests"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Chimp will now look in the `tests/` directory (otherwise ignored by the Meteor tool) for files in which you define acceptance tests. In the [Todos](https://github.com/meteor/todos) example app, we define a simple test that ensures we can click the "create list" button.
|
||||
|
||||
[`tests/lists.js`](https://github.com/meteor/todos/blob/master/tests/lists.js):
|
||||
|
||||
```js
|
||||
/* eslint-env mocha */
|
||||
/* eslint-disable func-names, prefer-arrow-callback */
|
||||
|
||||
// These are Chimp globals
|
||||
/* globals browser assert server */
|
||||
|
||||
function countLists() {
|
||||
browser.waitForExist('.list-todo');
|
||||
const elements = browser.elements('.list-todo');
|
||||
return elements.value.length;
|
||||
};
|
||||
|
||||
describe('list ui', function () {
|
||||
beforeEach(function () {
|
||||
browser.url('http://localhost:3000');
|
||||
server.call('generateFixtures');
|
||||
});
|
||||
|
||||
it('can create a list @watch', function () {
|
||||
const initialCount = countLists();
|
||||
|
||||
browser.click('.js-new-list');
|
||||
|
||||
assert.equal(countLists(), initialCount + 1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="running-acceptance-tests">Running acceptance tests</h3>
|
||||
|
||||
To run acceptance tests, we need to start our Meteor app as usual, and point Chimp at it.
|
||||
|
||||
In one terminal, we can do:
|
||||
|
||||
```bash
|
||||
meteor
|
||||
```
|
||||
|
||||
In another:
|
||||
|
||||
```bash
|
||||
meteor npm run chimp-watch
|
||||
```
|
||||
|
||||
The `chimp-watch` command will then run the test in a browser, and continue to re-run it as we change the test or the application. (Note that the test assumes we are running the app on port `3000`).
|
||||
|
||||
|
||||
Thus it's a good way to develop the test---this is why chimp has a feature where we mark tests with a `@watch` in the name to call out the tests we want to work on (running our entire acceptance test suite can be time consuming in a large application).
|
||||
|
||||
The `chimp-test` command will run all of the tests *once only* and is good for testing that our suite passes, either as a manual step, or as part of a [continuous integration](#ci) process.
|
||||
|
||||
|
||||
<h3 id="creating-acceptance-test-data">Creating data</h3>
|
||||
|
||||
Although we can run the acceptance test against our "pure" Meteor app, as we've done above, it often makes sense to start our meteor server with a special test driver, `tmeasday:acceptance-test-driver`. (You'll need to `meteor add` it to your app):
|
||||
|
||||
```txt
|
||||
meteor test --full-app --driver-package tmeasday:acceptance-test-driver
|
||||
```
|
||||
|
||||
The advantage of running our acceptance test suite pointed at an app that runs in full app test mode is that all of the [data generating methods](#creating-integration-test-data) that we've created remain available. Otherwise the `acceptance-test-driver` does nothing.
|
||||
|
||||
In Chimp tests, you have a DDP connection to the server available on the `server` variable. You can thus use `server.call()` (which is wrapped to be synchronous in Chimp tests) to call these methods. This is a convenient way to share data preparation code between acceptance and integration tests.
|
||||
|
||||
|
||||
<h2 id="ci">Continuous Integration</h2>
|
||||
|
||||
Continuous integration testing is the process of running tests on every commit of your project.
|
||||
|
||||
There are two principal ways to do it: on the developer's machine before allowing them to push code to the central repository, and on a dedicated CI server after each push. Both techniques are useful, and both require running tests in a commandline-only fashion.
|
||||
|
||||
<h3 id="command-line">Command line</h3>
|
||||
|
||||
We've seen one example of running tests on the command line, using our `meteor npm run chimp-test` mode.
|
||||
|
||||
We can also use a command-line driver for Mocha [`meteortesting:mocha`](https://atmospherejs.com/meteortesting/mocha) to run our standard tests on the command line.
|
||||
|
||||
Adding and using the package is straightforward:
|
||||
|
||||
```bash
|
||||
meteor add meteortesting:mocha
|
||||
meteor test --once --driver-package meteortesting:mocha
|
||||
```
|
||||
|
||||
(The `--once` argument ensures the Meteor process stops once the test is done).
|
||||
|
||||
We can also add that command to our `package.json` as a `test` script:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test": "meteor test --once --driver-package meteortesting:mocha"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we can run the tests with `meteor npm test`.
|
||||
|
||||
<h3 id="using-circle-ci">CircleCI</h3>
|
||||
|
||||
[CircleCI](https://circleci.com) is a great continuous integration service that allows us to run (possibly time consuming) tests on every push to a repository like GitHub. To use it with the commandline test we've defined above, we can follow their standard [getting started tutorial](https://circleci.com/docs/2.0/getting-started/#section=getting-started) and use a `circle.yml` file similar to this:
|
||||
|
||||
```
|
||||
machine:
|
||||
node:
|
||||
version: 0.10.43
|
||||
dependencies:
|
||||
override:
|
||||
- curl https://install.meteor.com | /bin/sh
|
||||
- npm install
|
||||
checkout:
|
||||
post:
|
||||
- git submodule update --init
|
||||
```
|
||||
24
content/ui-ux.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: User Interfaces
|
||||
description: General tips for structuring your UI code, independent of your view rendering technology.
|
||||
discourseTopicId: 19665
|
||||
---
|
||||
|
||||
<h2 id="view-layers">View layers</h2>
|
||||
|
||||
Meteor supports many view layers.
|
||||
|
||||
The most popular are:
|
||||
- [React](react.html): official [page](http://reactjs.org/)
|
||||
- [Blaze](blaze.html): official [page](http://blazejs.org/)
|
||||
- [Angular](http://www.angular-meteor.com): official [page](https://angular.io/)
|
||||
- [Vue](vue.html): official [page](https://vuejs.org/)
|
||||
- [Svelte](https://www.meteor.com/tutorials/svelte/creating-an-app): official [page](https://svelte.dev/)
|
||||
|
||||
If you are starting with web development we recommend that you use Blaze as it's very simple to learn.
|
||||
|
||||
Now if you are an advanced developer or already have a view layer library that you prefer Meteor is not going to get in your way. Just go ahead with your preferred one.
|
||||
|
||||
Each view layer library has trade-offs. Check the official page of each one to understand more or try all of them yourself with Meteor.
|
||||
|
||||
As Meteor simplifies a lot the set up of new apps you can try them all in a short time, follow our [tutorials](https://www.meteor.com/developers/tutorials) for a step-by-step guide.
|
||||
109
content/using-atmosphere-packages.md
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: Using Atmosphere Packages
|
||||
discourseTopicId: 20193
|
||||
---
|
||||
|
||||
<h2 id="atmosphere-searching">Searching for packages</h2>
|
||||
|
||||
There are a few ways to search for Meteor packages published to Atmosphere:
|
||||
|
||||
1. Search on the [Atmosphere website](https://atmospherejs.com/).
|
||||
2. Use `meteor search` from the command line.
|
||||
3. Use a community package search website like [Fastosphere](http://fastosphere.meteor.com/).
|
||||
|
||||
The main Atmosphere website provides additional curation features like trending packages, package stars, and flags, but some of the other options can be faster if you're trying to find a specific package. For example, you can use `meteor show ostrio:flow-router-extra` from the command line to see the description of that package and different available versions.
|
||||
|
||||
<h3 id="atmosphere-naming">Package naming</h3>
|
||||
|
||||
You may notice that, with the exception of Meteor platform packages, all packages on Atmosphere have a name of the form `prefix:package-name`. The prefix is the Meteor Developer username of the organization or user that published the package. Meteor uses such a convention for package naming to make sure that it's clear who has published a certain package, and to avoid an ad-hoc namespacing convention. Meteor platform packages do not have any `prefix:`.
|
||||
|
||||
<h2 id="installing-atmosphere">Installing Atmosphere Packages</h2>
|
||||
|
||||
To install an Atmosphere package, you run `meteor add`:
|
||||
|
||||
```bash
|
||||
meteor add ostrio:flow-router-extra
|
||||
```
|
||||
|
||||
This will add the newest version of the desired package that is compatible with the other packages in your app. If you want to specify a particular version, you can specify it by adding a suffix to the package name like: `meteor add ostrio:flow-router-extra@3.5.0`.
|
||||
|
||||
Regardless of how you add the package to your app, its actual version will be tracked in the file at `.meteor/versions`. This means that anybody collaborating with you on the same app is guaranteed to have the same package versions as you. If you want to update to a newer version of a package after installing it, use `meteor update`. You can run `meteor update` without any arguments to update all packages and Meteor itself to their latest versions, or pass a specific package to update just that one, for example `meteor update ostrio:flow-router-extra`.
|
||||
|
||||
If your app is running when you add a new package, Meteor will automatically download it and restart your app for you.
|
||||
|
||||
> The actual files for a given version of an Atmosphere package are stored in your local `~/.meteor/packages` directory.
|
||||
|
||||
To see all the Atmosphere packages installed run:
|
||||
|
||||
```bash
|
||||
meteor list
|
||||
```
|
||||
|
||||
To remove an unwanted Atmosphere package run:
|
||||
|
||||
```bash
|
||||
meteor remove ostrio:flow-router-extra
|
||||
```
|
||||
|
||||
You can get more details on all the package commands in the [Meteor Command line documentation](http://docs.meteor.com/#/full/meteorhelp).
|
||||
|
||||
<h2 id="using-atmosphere">Using Atmosphere Packages</h2>
|
||||
|
||||
To use an Atmosphere Package in your app you can import it with the `meteor/` prefix:
|
||||
|
||||
```js
|
||||
import { Mongo } from "meteor/mongo";
|
||||
```
|
||||
|
||||
Typically a package will export one or more symbols, which you'll need to reference with the destructuring syntax. You can find these exported symbols by either looking in that package's `package.js` file for [`api.export`](http://docs.meteor.com/#/full/pack_export) calls or by looking in that package's main JavaScript file for ES2015 `export ` calls like `export const packageName = 'package-name';`.
|
||||
|
||||
Sometimes a package will have no exports and have side effects when included in your app. In such cases you don't need to import the package at all after installing.
|
||||
|
||||
> For backwards compatibility with Meteor 1.2 and early releases, Meteor by default makes available directly to your app all symbols referenced in `api.export` in any packages you have installed. However, it is recommended that you import these symbols first before using them.
|
||||
|
||||
<h3 id="importing-atmosphere-styles">Importing styles from Atmosphere packages</h3>
|
||||
|
||||
Using any of Meteor's supported CSS pre-processors you can import other style files using the `{package-name}` syntax as long as those files are designated to be lazily evaluated as "import" files. To get more details on how to determine this see [CSS source versus import](build-tool.html#css-source-vs-import) files.
|
||||
|
||||
```less
|
||||
@import '{prefix:package-name}/buttons/styles.import.less';
|
||||
```
|
||||
|
||||
> CSS files in an Atmosphere package are declared with `api.addFiles`, and therefore will be eagerly evaluated by default, and then bundled with all the other CSS in your app.
|
||||
|
||||
<h3 id="peer-npm-dependencies">Peer npm dependencies</h3>
|
||||
|
||||
Atmosphere packages can ship with contained [npm dependencies](writing-packages.html#npm-dependencies), in which case you don't need to do anything to make them work. However, some Atmosphere packages will expect that you have installed certain "peer" npm dependencies in your application.
|
||||
|
||||
Typically the package will warn you if you have not done so. For example, if you install the [`react-meteor-data`](https://atmospherejs.com/meteor/react-meteor-data) package into your app, you'll also need to [install](#installing-npm) the [`react`](https://www.npmjs.com/package/react) and the [`react-addons-pure-render-mixin`](https://www.npmjs.com/package/react-addons-pure-render-mixin) packages:
|
||||
|
||||
```bash
|
||||
meteor npm install --save react react-addons-pure-render-mixin
|
||||
meteor add react-meteor-data
|
||||
```
|
||||
|
||||
<h2 id="package-namespacing">Atmosphere package namespacing</h2>
|
||||
|
||||
Each Atmosphere package that you use in your app exists in its own separate namespace, meaning that it sees only its own global variables and any variables provided by the packages that it specifically uses. When a top-level variable is defined in a package, it is either declared with local scope or package scope.
|
||||
|
||||
```js
|
||||
/**
|
||||
* local scope - this variable is not visible outside of the block it is
|
||||
* declared in and other packages and your app won't see it
|
||||
*/
|
||||
const alicePerson = {name: "alice"};
|
||||
|
||||
/**
|
||||
* package scope - this variable is visible to every file inside of the
|
||||
* package where it is declared and to your app
|
||||
*/
|
||||
bobPerson = {name: "bob"};
|
||||
```
|
||||
|
||||
Notice that this is just the normal JavaScript syntax for declaring a variable that is local or global. Meteor scans your source code for global variable assignments and generates a wrapper that makes sure that your globals don't escape their appropriate namespace.
|
||||
|
||||
In addition to local scope and package scope, there are also package exports. A package export is a "pseudo global" variable that a package makes available for you to use when you install that package. For example, the `email` package exports the `Email` variable. If your app uses the `email` package (and _only_ if it uses the `email` package!) then your app can access the `Email` symbol and you can call `Email.send`. Most packages have only one export, but some packages might have two or three (for example, a package that provides several classes that work together).
|
||||
|
||||
> It is recommended that you use the `ecmascript` package and first call `import { Email } from 'meteor/email';` before calling `Email.send` in your app. It is also recommended that package developers now use ES2015 `export` from their main JavaScript file instead of `api.export`.
|
||||
|
||||
Your app sees only the exports of the packages that you use directly. If you use package A, and package A uses package B, then you only see package A's exports. Package B's exports don't "leak" into your namespace just because you used package A. Each app or package only sees their own globals plus the APIs of the packages that they specifically use and depend upon.
|
||||
255
content/using-npm-packages.md
Normal file
@@ -0,0 +1,255 @@
|
||||
---
|
||||
title: Using npm Packages
|
||||
discourseTopicId: 20193
|
||||
---
|
||||
|
||||
<h2 id="npm-searching">Searching for packages</h2>
|
||||
|
||||
You can use the official search at [npmjs.com](https://www.npmjs.com/) or see results sorted by package quality (code quality, maintenance status, development velocity, popularity etc.) at [npms.io](https://npms.io/). There are also sites that search certain types of packages, like [js.coach](https://js.coach/)'s [React](https://js.coach/react) and [React Native](https://js.coach/react-native) sections.
|
||||
|
||||
<h2 id="client-npm">npm on the client</h2>
|
||||
|
||||
Tools like [browserify](http://browserify.org) and [webpack](https://webpack.github.io) are designed to provide a Node-like environment on the client so that many npm packages, even ones originally intended for the server, can run unmodified. In most cases, you can import npm dependencies from a client file, just as you would on the server.
|
||||
|
||||
When creating a new application Meteor installs the `meteor-node-stubs` npm package to help provide this client browser compatibility. If you are upgrading an application to Meteor 1.3 you may have to run `meteor npm install --save meteor-node-stubs` manually.
|
||||
|
||||
The `meteor-node-stubs` npm package provides browser-friendly implementations of Node's built-in modules, like `path`, `buffer`, `util`, etc. Meteor's module system avoids actually bundling any stub modules (and their dependencies) if they are not used, so there is no cost to keeping `meteor-node-stubs` in the dependencies. In other words, leave `meteor-node-stubs` installed unless you really know what you're doing.
|
||||
|
||||
<h2 id="installing-npm">Installing npm packages</h2>
|
||||
|
||||
npm packages are configured in a `package.json` file at the root of your project. If you create a new Meteor project, you will have such a file created for you. If not you can run `meteor npm init` to create one.
|
||||
|
||||
To install a package into your app you run the `npm install` command with the `--save` flag:
|
||||
|
||||
```bash
|
||||
meteor npm install --save moment
|
||||
```
|
||||
|
||||
This will both update your `package.json` with information about the dependency and download the package into your app's local `node_modules` directory. Typically, you don't check the `node_modules` directory into source control and your teammates run `meteor npm install` to get up to date when dependencies change:
|
||||
|
||||
```bash
|
||||
meteor npm install
|
||||
```
|
||||
|
||||
If the package is just a development dependency (i.e. it's used for testing, linting or the like) then you should use `--save-dev`. That way if you have some kind of build script, it can do `npm install --production` and avoid installing packages it doesn't need.
|
||||
|
||||
For more information about `npm install`, check out the [official documentation](https://docs.npmjs.com/getting-started/installing-npm-packages-locally).
|
||||
|
||||
> Meteor comes with npm bundled so that you can type `meteor npm` without worrying about installing it yourself. If you like, you can also use a globally installed npm to manage your packages.
|
||||
|
||||
<h2 id="using-npm">Using npm packages</h2>
|
||||
|
||||
To use an npm package from a file in your application you `import` the name of the package:
|
||||
|
||||
```js
|
||||
import moment from 'moment';
|
||||
|
||||
// this is equivalent to the standard node require:
|
||||
const moment = require('moment');
|
||||
```
|
||||
|
||||
This imports the default export from the package into the symbol `moment`.
|
||||
|
||||
You can also import specific functions from a package using the destructuring syntax:
|
||||
|
||||
```js
|
||||
import { isArray } from 'lodash';
|
||||
```
|
||||
|
||||
You can also import other files or JS entry points from a package:
|
||||
|
||||
```js
|
||||
import { parse } from 'graphql/language';
|
||||
```
|
||||
|
||||
Some Meteor apps contain local Meteor packages (packages defined in the `packages/` directory of your app tree); this was an older recommendation from before Meteor had full ECMAScript support. If your app is laid out this way, you can also `require` or `import` npm packages installed in your app from within your local Meteor packages.
|
||||
|
||||
<h3 id="npm-styles">Importing styles from npm</h3>
|
||||
|
||||
Using any of Meteor's [supported CSS pre-processors](build-tool.html#css) you can import other style files provided by an NPM into your application using both relative and absolute paths. However, this will only work for the top-level app and will not work inside an Atmosphere package.
|
||||
|
||||
Importing styles from an npm package with an absolute path using the `{}` syntax, for instance with Less:
|
||||
|
||||
```less
|
||||
@import '{}/node_modules/npm-package-name/button.less';
|
||||
```
|
||||
|
||||
Importing styles from an npm package with a relative path:
|
||||
|
||||
```less
|
||||
@import '../../node_modules/npm-package-name/colors.less';
|
||||
```
|
||||
|
||||
You can also import CSS directly from a JavaScript file to control load order if you have the `ecmascript` package installed:
|
||||
|
||||
```js
|
||||
import 'npm-package-name/stylesheets/styles.css';
|
||||
```
|
||||
|
||||
> When importing CSS from a JavaScript file, that CSS is not bundled with the rest of the CSS processed with the Meteor build tool, but instead is put in your app's `<head>` tag inside `<style>...</style>` after the main concatenated CSS file.
|
||||
|
||||
<h3 id="npm-assets">Building with other assets from npm</h3>
|
||||
|
||||
Meteor also supports building other assets into your app, such as fonts, that are located in your `node_modules` directory by symbolic linking to those assets from either the `/public` or `/private` directories. For example, `font-awesome` is a very popular font library that provides lots of font-based icons. New icons appear frequently as the library is developed and it would be difficult to manage all the updates if you were to copy the entire `font-awesome` code base to your own app and git repository. Instead use the following to include these fonts:
|
||||
|
||||
```
|
||||
cd /public
|
||||
ln -ls ../node_modules/font-awesome/fonts ./fonts
|
||||
```
|
||||
|
||||
Any assets made available via symlinks in the `/public` and `/private` directories of an application will be copied into the Meteor application bundles when using the `meteor build` command.
|
||||
|
||||
|
||||
<h2 id="recompile">Recompiling npm packages</h2>
|
||||
|
||||
Meteor does not recompile packages installed in your `node_modules` by default. However, compilation of specific npm packages (for example, to support older browsers that the package author neglected), can be achieved through the `meteor.nodeModules.recompile` configuration object in your `package.json` file.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "your-application",
|
||||
...
|
||||
"meteor": {
|
||||
"mainModule": ...,
|
||||
"testModule": ...,
|
||||
"nodeModules": {
|
||||
"recompile": {
|
||||
"very-modern-package": ["client", "server"],
|
||||
"alternate-notation-for-client-and-server": true,
|
||||
"somewhat-modern-package": "legacy",
|
||||
"another-package": ["legacy", "server"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The keys of the `meteor.nodeModules.recompile` configuration object are npm package names, and the values specify for which bundles those packages should be recompiled using the Meteor compiler plugins system, as if the packages were part of the Meteor application.
|
||||
|
||||
For example, if an npm package uses const/let syntax or arrow functions, that's fine for modern and server code, but you would probably want to recompile the package when building the legacy bundle. To accomplish this, specify `"legacy"` or `["legacy"]` as the value of the package's property, similar to `somewhat-modern-package` above. These strings and arrays of strings have the same meaning as the second argument to `api.addFiles(files, where)` in a `package.js` file.
|
||||
|
||||
This configuration serves pretty much the same purpose as symlinking an application directory into `node_modules/`, but without any symlinking: https://forums.meteor.com/t/litelement-import-litelement-html/45042/8?u=benjamn
|
||||
|
||||
The `meteor.nodeModules.recompile` configuration currently applies to the application `node_modules/` directory only (not to `Npm.depends` dependencies in Meteor packages). Recompiled packages must be direct dependencies of the application.
|
||||
|
||||
Meteor will compile the exposed code as if it was part of your application, using whatever compiler plugins you have installed. You can influence this compilation using `.babelrc` files or any other techniques you would normally use to configure compilation of application code.
|
||||
|
||||
<h2 id="npm-shrinkwrap">npm Shrinkwrap</h2>
|
||||
|
||||
`package.json` typically encodes a version range, and so each `npm install` command can sometimes lead to a different result if new versions have been published in the meantime. In order to ensure that you and the rest of your team are using the same exact same version of each package, it's a good idea to use `npm shrinkwrap` after making any dependency changes to `package.json`:
|
||||
|
||||
```bash
|
||||
# after installing
|
||||
meteor npm install --save moment
|
||||
meteor npm shrinkwrap
|
||||
```
|
||||
|
||||
This will create an `npm-shrinkwrap.json` file containing the exact versions of each dependency, and you should check this file into source control. For even more precision (the contents of a given version of a package *can* change), and to avoid a reliance on the npm server during deployment, you should consider using [`npm shrinkpack`](#npm-shrinkpack).
|
||||
|
||||
<h2 id="async-callbacks">Asyncronous callbacks</h2>
|
||||
|
||||
Many npm packages rely on an asynchronous, callback or promise-based coding style. For several reasons, Meteor is currently built around a synchronous-looking but still non-blocking style using [Fibers](https://github.com/laverdet/node-fibers).
|
||||
|
||||
The global Meteor server context and every method and publication initialize a new fiber so that they can run concurrently. Many Meteor APIs, for example collections, rely on running inside a fiber. They also rely on an internal Meteor mechanism that tracks server "environment" state, like the currently executing method. This means you need to initialize your own fiber and environment to use asynchronous Node code inside a Meteor app. Let's look at an example of some code that won't work, using the code example from the [node-github repository](https://github.com/mikedeboer/node-github):
|
||||
|
||||
```js
|
||||
// Inside a Meteor method definition
|
||||
updateGitHubFollowers() {
|
||||
github.user.getFollowingFromUser({
|
||||
user: 'stubailo'
|
||||
}, (err, res) => {
|
||||
// Using a collection here will throw an error
|
||||
// because the asynchronous code is not in a fiber
|
||||
Followers.insert(res);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at a few ways to resolve this issue.
|
||||
|
||||
<h3 id="bind-environment">`Meteor.bindEnvironment`</h3>
|
||||
|
||||
In most cases, wrapping the callback in `Meteor.bindEnvironment` will do the trick. This function both wraps the callback in a fiber, and does some work to maintain Meteor's server-side environment tracking. Here's the same code with `Meteor.bindEnvironment`:
|
||||
|
||||
```js
|
||||
// Inside a Meteor method definition
|
||||
updateGitHubFollowers() {
|
||||
github.user.getFollowingFromUser({
|
||||
user: 'stubailo'
|
||||
}, Meteor.bindEnvironment((err, res) => {
|
||||
// Everything is good now
|
||||
Followers.insert(res);
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
However, this won't work in all cases - since the code runs asynchronously, we can't use anything we got from an API in the method return value. We need a different approach that will convert the async API to a synchronous-looking one that will allow us to return a value.
|
||||
|
||||
<h3 id="wrap-async">`Meteor.wrapAsync`</h3>
|
||||
|
||||
Many npm packages adopt the convention of taking a callback that accepts `(err, res)` arguments. If your asynchronous function fits this description, like the one above, you can use `Meteor.wrapAsync` to convert to a fiberized API that uses return values and exceptions instead of callbacks, like so:
|
||||
|
||||
```js
|
||||
// Setup sync API
|
||||
const getFollowingFromUserFiber =
|
||||
Meteor.wrapAsync(github.user.getFollowingFromUser, github.user);
|
||||
|
||||
// Inside a Meteor method definition
|
||||
updateGitHubFollowers() {
|
||||
const res = getFollowingFromUserFiber({
|
||||
user: 'stubailo'
|
||||
});
|
||||
|
||||
Followers.insert(res);
|
||||
|
||||
// Return how many followers we have
|
||||
return res.length;
|
||||
}
|
||||
```
|
||||
|
||||
If you wanted to refactor this and create a completely fiber-wrapper GitHub client, you could write some logic to loop over all of the methods available and call `Meteor.wrapAsync` on them, creating a new object with the same shape but with a more Meteor-compatible API.
|
||||
|
||||
<h3 id="promises">Promises</h3>
|
||||
|
||||
Recently, a lot of npm packages have been moving to Promises instead of callbacks for their API. This means you actually get a return value from the asynchronous function, but it's just an empty shell where the real value is filled in later.
|
||||
|
||||
The good news is that Promises can be used with the new ES2015 `async/await` syntax (available in the `ecmascript` package since Meteor 1.3) in a natural and synchronous-looking style on both the client and the server.
|
||||
|
||||
If you declare your function `async` (which ends up meaning it returns a Promise itself), then you can use the `await` keyword to wait on other promise inside. This makes it very easy to serially call Promise-based libraries:
|
||||
|
||||
|
||||
```js
|
||||
async function sendTextMessage(user) {
|
||||
const toNumber = await phoneLookup.findFromEmail(user.emails[0].address);
|
||||
return await client.sendMessage({
|
||||
to: toNumber,
|
||||
from: '+14506667788',
|
||||
body: 'Hello world!'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
<h2 id="npm-shrinkpack">Shrinkpack</h2>
|
||||
|
||||
[Shrinkpack](https://github.com/JamieMason/shrinkpack) is a tool that gives you more bulletproof and repeatable builds than you get by using [`npm shrinkwrap`](#npm-shrinkwrap) alone.
|
||||
|
||||
Essentially it copies a tarball of the contents of each of your npm dependencies into your application source repository. This is essentially a more robust version of the `npm-shrinkwrap.json` file that shrinkwrap creates, because it means your application's npm dependencies can be assembled without the need or reliance on the npm servers being available or reliable. This is good for repeatable builds especially when deploying.
|
||||
|
||||
To use shrinkpack, first globally install it:
|
||||
|
||||
```bash
|
||||
npm install -g shrinkpack
|
||||
```
|
||||
|
||||
Then use it directly after you shrinkwrap
|
||||
|
||||
```bash
|
||||
meteor npm install --save moment
|
||||
meteor npm shrinkwrap
|
||||
shrinkpack
|
||||
```
|
||||
|
||||
You should then check the generated `node_shrinkwrap/` directory into source control, but ensure it is ignored by your text editor.
|
||||
|
||||
> NOTE: Although this is a good idea for projects with a lot of npm dependencies, it will not affect Atmosphere dependencies, even if they themselves have direct npm dependencies.
|
||||
1
content/using-packages.html
Normal file
@@ -0,0 +1 @@
|
||||
<!-- This file is simply here as a placeholder for redirects (see https://github.com/meteor/guide/pull/410) -->
|
||||
549
content/vue.md
Normal file
@@ -0,0 +1,549 @@
|
||||
---
|
||||
title: Vue
|
||||
description: How to use the Vue frontend rendering library, with Meteor.
|
||||
---
|
||||
|
||||
After reading this guide, you'll know:
|
||||
|
||||
1. [What Vue is, and why you should consider using it with Meteor](#introduction)
|
||||
2. [How to install Vue in your Meteor application, and how to use it correctly](#integrating-vue-with-meteor)
|
||||
3. [How to integrate Vue with Meteor's realtime data layer](#using-meteors-data-system)
|
||||
4. [How to structure Meteor with Vue](#style-guide)
|
||||
5. [How to Server-side Render (SSR) Vue with Meteor](#ssr-code-splitting)
|
||||
|
||||
Vue already has an excellent guide with many advanced topics already covered. Some of them are [SSR (Server-side Rendering)](https://ssr.vuejs.org/), [Routing](https://router.vuejs.org/), [Code Structure and Style Guide](https://vuejs.org/v2/style-guide/) and [State Management with Vuex](https://vuex.vuejs.org/).
|
||||
|
||||
This documentation is purely focused on integrating it with Meteor.
|
||||
|
||||
> Meteor has a Vue skeleton which will prepare a basic Vue Meteor app for you.
|
||||
> You can create one by running `meteor create vue-meteor-app --vue`
|
||||
> There is also a [Vue tutorial](https://vue-tutorial.meteor.com/) which covers the basics of this section.
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
[Vue](https://vuejs.org/v2/guide/) (pronounced /vjuː/, like view) is a progressive framework for building user interfaces.
|
||||
Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable.
|
||||
The core library is focused on the view layer only, and is easy to
|
||||
pick up and integrate with other libraries or existing projects. On the other hand, Vue is also
|
||||
perfectly capable of powering sophisticated Single-Page Applications when used in combination
|
||||
with [modern tooling](https://vuejs.org/v2/guide/single-file-components.html) and
|
||||
[supporting libraries](https://github.com/vuejs/awesome-vue#components--libraries).
|
||||
|
||||
Vue has an excellent [guide and documentation](https://vuejs.org/v2/guide/). This guide is about integrating it with Meteor.
|
||||
|
||||
<h3 id="why-use-vue-with-meteor">Why use Vue with Meteor</h3>
|
||||
|
||||
Vue is a frontend library, like React, Blaze and Angular.
|
||||
|
||||
Some really nice frameworks are built around Vue. [Nuxt.js](https://nuxtjs.org) for example, aims to create a framework flexible enough that you can use it as a main project base or in addition to your current project based on Node.js. Though Nuxt.js is full-stack and very pluggable, it lacks an API to communicate data from and to the server. Also unlike Meteor, Nuxt still relies on a configuration file.
|
||||
|
||||
Meteor's build tool and Pub/Sub API (or Apollo) provides Vue with this API that you would normally have to integrate yourself, greatly reducing the amount of boilerplate code you have to write.
|
||||
|
||||
<h3 id="integrating-vue-with-meteor">Integrating Vue With Meteor</h3>
|
||||
|
||||
To start a new project:
|
||||
|
||||
```sh
|
||||
meteor create vue-meteor-app
|
||||
```
|
||||
|
||||
To install Vue in Meteor, you should add it as an npm dependency:
|
||||
|
||||
```sh
|
||||
meteor npm install --save vue
|
||||
```
|
||||
|
||||
To support [Vue's Single File Components](https://vuejs.org/v2/guide/single-file-components.html)
|
||||
with the .vue file extensions, install the following Meteor package created by Vue Core developer [Akryum (Guillaume Chau)](https://github.com/meteor-vue/vue-meteor/tree/master/packages/vue-component).
|
||||
|
||||
```sh
|
||||
meteor add akryum:vue-component
|
||||
```
|
||||
|
||||
You will end up with at least 3 files:
|
||||
|
||||
1. a `/client/App.vue` The root component of your app
|
||||
2. a `/client/main.js` Initializing the Vue app in Meteor startup
|
||||
3. a `/client/main.html` containing the body with the #app div
|
||||
|
||||
We need a base HTML document that has the `app` id. If you created a new project from `meteor create .`, put this in your `/client/main.html`.
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
```
|
||||
|
||||
You can now start writing .vue files in your app with the following format. If you created a new project from `meteor create .`, put this in your `/client/App.vue`.
|
||||
|
||||
```vuejs
|
||||
<template>
|
||||
<div>
|
||||
<p>This is a Vue component and below is the current date:<br />{{date}}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
date: new Date(),
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
You can render the Vue component hierarchy to the DOM by using the below snippet in you client startup file. If you created a new project from `meteor create .`, put this in your `/client/main.js`.
|
||||
|
||||
```javascript
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import './main.html';
|
||||
|
||||
Meteor.startup(() => {
|
||||
new Vue({
|
||||
el: '#app',
|
||||
...App,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run your new Vue+Meteor app with this command: `NO_HMR=1 meteor`
|
||||
|
||||
<h2 id="using-meteors-data-system">Using Meteor's data system</h2>
|
||||
|
||||
One of the biggest advantages of Meteor is definitely it's realtime data layer. It allows
|
||||
for so called full-stack reactivity and [optimistic UI](https://blog.meteor.com/optimistic-ui-with-meteor-67b5a78c3fcf) functionality.
|
||||
To accomplish full-stack reactivity, Meteor uses [Tracker](https://docs.meteor.com/api/tracker.html). In this section we will explain how to integrate Meteor Tracker
|
||||
with Vue to leverage the best of both tools.
|
||||
|
||||
1. Install the [vue-meteor-tracker](https://github.com/meteor-vue/vue-meteor-tracker) package from NPM:
|
||||
|
||||
```sh
|
||||
meteor npm install --save vue-meteor-tracker
|
||||
```
|
||||
|
||||
Next, the package needs to be plugged into Vue as a plugin.
|
||||
Add the following to your `/client/main.js`:
|
||||
|
||||
```javascript
|
||||
import Vue from 'vue';
|
||||
import VueMeteorTracker from 'vue-meteor-tracker'; // import the integration package!
|
||||
import App from './App.vue';
|
||||
import './main.html';
|
||||
|
||||
Vue.use(VueMeteorTracker); // Add the plugin to Vue!
|
||||
|
||||
Meteor.startup(() => {
|
||||
new Vue({
|
||||
el: '#app',
|
||||
...App,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
<h3>Example app</h3>
|
||||
|
||||
If you've followed the [integration guide](#integrating-vue-with-meteor), then your Vue application shows the time it was loaded.
|
||||
|
||||
Let's add some functionality that makes this part dynamic. To flex Meteor's plumbing, we'll create:
|
||||
|
||||
1. A [Meteor Collection](https://docs.meteor.com/api/collections.html) called `Time` with a `currentTime` doc.
|
||||
2. A [Meteor Publication](https://guide.meteor.com/data-loading.html#publications-and-subscriptions) called `Time` that sends all documents
|
||||
3. A [Meteor Method](https://guide.meteor.com/methods.html#what-is-a-method) called `UpdateTime` to update the `currentTime` doc.
|
||||
4. A [Meteor Subscription](https://docs.meteor.com/api/pubsub.html) to `Time`
|
||||
5. [Vue/Meteor Reactivity](https://github.com/meteor-vue/vue-meteor-tracker) to update the Vue component
|
||||
|
||||
The first 3 steps are basic Meteor:
|
||||
|
||||
1) In `/imports/collections/Time.js`
|
||||
|
||||
``` javascript
|
||||
Time = new Mongo.Collection("time");
|
||||
```
|
||||
|
||||
2) In `/imports/publications/Time.js`
|
||||
|
||||
``` javascript
|
||||
Meteor.publish('Time', function () {
|
||||
return Time.find({});
|
||||
});
|
||||
```
|
||||
|
||||
3) In `/imports/methods/UpdateTime.js`
|
||||
|
||||
``` javascript
|
||||
Meteor.methods({
|
||||
UpdateTime() {
|
||||
Time.upsert('currentTime', { $set: { time: new Date() } });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now, let's add these to our server. First [remove autopublish](https://guide.meteor.com/security.html#checklist) so our publications matter:
|
||||
|
||||
``` bash
|
||||
meteor remove autopublish
|
||||
```
|
||||
|
||||
For fun, let's make a [`settings.json` file](https://galaxy-guide.meteor.com/environment-variables.html#settings-example):
|
||||
|
||||
``` json
|
||||
{ "public": { "hello": "world" } }
|
||||
```
|
||||
|
||||
Now, let's update our `/server/main.js` to use our new stuff:
|
||||
|
||||
``` javascript
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import '/imports/collections/Time';
|
||||
import '/imports/publications/Time';
|
||||
import '/imports/methods/UpdateTime';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// Update the current time
|
||||
Meteor.call('UpdateTime');
|
||||
// Add a new doc on each start.
|
||||
Time.insert({ time: new Date() });
|
||||
// Print the current time from the database
|
||||
console.log(`The time is now ${Time.findOne().time}`);
|
||||
});
|
||||
```
|
||||
|
||||
Start your Meteor app, your should see a message pulling data from Mongo. We haven't made any changes to the client, so you should just see some startup messages.
|
||||
|
||||
```sh
|
||||
meteor
|
||||
```
|
||||
4) and 5) Great, let's integrate this with Vue using [Vue Meteor Tracker](https://github.com/meteor-vue/vue-meteor-tracker) and update our `/client/App.vue` file:
|
||||
|
||||
```javascript
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!$subReady.Time">Loading...</div>
|
||||
<div v-else>
|
||||
<p>Hello {{hello}},
|
||||
<br>The time is now: {{currentTime}}
|
||||
</p>
|
||||
<button @click="updateTime">Update Time</button>
|
||||
<p>Startup times:</p>
|
||||
<ul>
|
||||
<li v-for="t in TimeCursor">
|
||||
{{t.time}} - {{t._id}}
|
||||
</li>
|
||||
</ul>
|
||||
<p>Meteor settings</p>
|
||||
<pre><code>
|
||||
{{settings}}
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '/imports/collections/Time';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
console.log('Sending non-Meteor data to Vue component');
|
||||
return {
|
||||
hello: 'World',
|
||||
settings: Meteor.settings.public, // not Meteor reactive
|
||||
}
|
||||
},
|
||||
// Vue Methods
|
||||
methods: {
|
||||
updateTime() {
|
||||
console.log('Calling Meteor Method UpdateTime');
|
||||
Meteor.call('UpdateTime'); // not Meteor reactive
|
||||
}
|
||||
},
|
||||
// Meteor reactivity
|
||||
meteor: {
|
||||
// Subscriptions - Errors not reported spelling and capitalization.
|
||||
$subscribe: {
|
||||
'Time': []
|
||||
},
|
||||
// A helper function to get the current time
|
||||
currentTime () {
|
||||
console.log('Calculating currentTime');
|
||||
var t = Time.findOne('currentTime') || {};
|
||||
return t.time;
|
||||
},
|
||||
// A Minimongo cursor on the Time collection is added to the Vue instance
|
||||
TimeCursor () {
|
||||
// Here you can use Meteor reactive sources like cursors or reactive vars
|
||||
// as you would in a Blaze template helper
|
||||
return Time.find({}, {
|
||||
sort: {time: -1}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 2em;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
Restart your server to use the `settings.json` file.
|
||||
|
||||
```sh
|
||||
meteor --settings=settings.json
|
||||
```
|
||||
|
||||
Then refresh your browser to reload the client.
|
||||
|
||||
You should see:
|
||||
|
||||
- the current time
|
||||
- a button to Update the current time
|
||||
- startup times for the server (added to the Time collection on startup)
|
||||
- The Meteor settings from your settings file
|
||||
|
||||
Excellent! That's a tour of some of Meteor's features, and how to integrate with Vue. Have a better approach? Please send a PR.
|
||||
|
||||
|
||||
<h2 id="style-guide">Style Guide and File Structure</h2>
|
||||
|
||||
Like code linting and style guides are tools for making code easier and more fun to work with.
|
||||
|
||||
These are practical means to practical ends.
|
||||
|
||||
1. Leverage existing tools
|
||||
2. Leverage existing configurations
|
||||
|
||||
[Meteor's style guide](https://guide.meteor.com/code-style.html) and [Vue's style guide](https://vuejs.org/v2/style-guide/) can be overlapped like this:
|
||||
|
||||
1. [Configure your Editor](https://guide.meteor.com/code-style.html#eslint-editor)
|
||||
2. [Configure eslint for Meteor](https://guide.meteor.com/code-style.html#eslint-installing)
|
||||
3. [Review the Vue Style Guide](https://vuejs.org/v2/style-guide/#Rule-Categories)
|
||||
4. Open up the [ESLint rules](https://eslint.org/docs/rules/) as needed.
|
||||
|
||||
Application Structure is documented here:
|
||||
|
||||
1. [Meteor's Application Structure](https://guide.meteor.com/structure.html#example-app-structure) is the default start.
|
||||
2. [Vuex's Application Structure](https://vuex.vuejs.org/guide/structure.html) may be interesting.
|
||||
|
||||
|
||||
<h2 id="ssr-code-splitting">SSR and Code Splitting</h2>
|
||||
Vue has [an excellent guide on how to render your Vue application on the server](https://vuejs.org/v2/guide/ssr.html). It includes code splitting, async data fetching and many other practices that are used in most apps that require this.
|
||||
|
||||
<h3 id="basic-example">Basic Example</h3>
|
||||
Making Vue SSR to work with Meteor is not more complex then for example with [Express](https://expressjs.com/).
|
||||
However instead of defining a wildcard route, Meteor uses its own [server-render](https://docs.meteor.com/packages/server-render.html) package that exposes an `onPageLoad` function. Every time a call is made to
|
||||
the server side, this function is triggered. This is where we should put our code like how its described on the [VueJS SSR Guide](https://ssr.vuejs.org/guide/#integrating-with-a-server).
|
||||
|
||||
To add the packages, run:
|
||||
|
||||
```sh
|
||||
meteor add server-render
|
||||
meteor npm install --save vue-server-renderer
|
||||
```
|
||||
then connect to Vue in `/server/main.js`:
|
||||
|
||||
```javascript
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Vue from 'vue';
|
||||
import { onPageLoad } from 'meteor/server-render';
|
||||
import { createRenderer } from 'vue-server-renderer';
|
||||
|
||||
const renderer = createRenderer();
|
||||
|
||||
onPageLoad(sink => {
|
||||
console.log('onPageLoad');
|
||||
|
||||
const url = sink.request.url.path;
|
||||
|
||||
const app = new Vue({
|
||||
data: {
|
||||
url
|
||||
},
|
||||
template: `<div>The visited URL is: {{ url }}</div>`
|
||||
});
|
||||
|
||||
renderer.renderToString(app, (err, html) => {
|
||||
if (err) {
|
||||
res.status(500).end('Internal Server Error');
|
||||
return
|
||||
}
|
||||
console.log('html', html);
|
||||
|
||||
sink.renderIntoElementById('app', html);
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Luckily [Akryum](https://github.com/akryum) has us covered and provided us with a Meteor package for this:
|
||||
[akryum:vue-ssr](https://github.com/meteor-vue/vue-meteor/tree/master/packages/vue-ssr) allows us to write our server-side code like below:
|
||||
|
||||
```javascript
|
||||
import { VueSSR } from 'meteor/akryum:vue-ssr';
|
||||
import createApp from './app';
|
||||
|
||||
VueSSR.createApp = function () {
|
||||
// Initialize the Vue app instance and return the app instance
|
||||
const { app } = createApp();
|
||||
return { app };
|
||||
}
|
||||
```
|
||||
|
||||
<h4 id="serverside-routes">Server-side Routing</h4>
|
||||
|
||||
Sweet, but most apps have some sort of routing functionality. We can use the VueSSR context parameter
|
||||
for this. It simply passes the Meteor server-render request url which we need to push into our router instance:
|
||||
|
||||
```javascript
|
||||
import { VueSSR } from 'meteor/akryum:vue-ssr';
|
||||
import createApp from './app';
|
||||
|
||||
VueSSR.createApp = function (context) {
|
||||
// Initialize the Vue app instance and return the app + router instance
|
||||
const { app, router } = createApp();
|
||||
|
||||
// Set router's location from the context
|
||||
router.push(context.url);
|
||||
|
||||
return { app };
|
||||
}
|
||||
```
|
||||
|
||||
<h3 id="async-data-and-hydration">Async data and Hydration</h3>
|
||||
|
||||
Hydration is the the word for loading state into components on the serverside and then reusing that data on the clientside.
|
||||
This allows components to fully render their markup on the server and prevents a 're-render' on the clientside when the bundle is loaded.
|
||||
|
||||
[Nuxt](https://nuxtjs.org/) solves this gracefully with a feature called [asyncData](https://nuxtjs.org/guide/async-data).
|
||||
|
||||
Meteor's pub/sub system is at this moment not suitable for SSR which means that if we want the same functionality, we will have to implement it ourselves.
|
||||
How that can be done is exactly what we are going to explain here!
|
||||
|
||||
> Important reminder here is the fact that Server Rendering on its own is already worth a guide - [which is exactly what the guys from Vue did](https://ssr.vuejs.org/). Most of the code is needed in any platform except Nuxt (Vue based) and Next (React based). We simply describe the best way to do this for Meteor. To really understand what is happening read that SSR guide from Vue.
|
||||
|
||||
SSR follows a couple of steps that are almost always the same for any frontend library (React, Vue or Angular).
|
||||
|
||||
1. Resolve the url with the router
|
||||
2. Fetch any matching components from the router
|
||||
3. Filter out components that have no asyncData
|
||||
4. Map the components into a list of promises by return the asyncData method's result
|
||||
5. Resolve all promises
|
||||
6. Store the resulting data in the HTML for later hydration of the client bundle
|
||||
7. Hydrate the clientside
|
||||
|
||||
Its better documented in code:
|
||||
|
||||
```javascript
|
||||
VueSSR.createApp = function (context) {
|
||||
|
||||
// Wait with sending the app to the client until the promise resolves (thanks Akryum)
|
||||
return new Promise((resolve, reject) => {
|
||||
const { app, router, store } = createApp({
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
// 1. Resolve the URL with the router
|
||||
router.push(context.url);
|
||||
|
||||
router.onReady(async () => {
|
||||
// 2, Fetch any matching components from the router
|
||||
const matchedComponents = router.getMatchedComponents();
|
||||
|
||||
const route = router.currentRoute;
|
||||
|
||||
// No matched routes
|
||||
if (!matchedComponents.length) {
|
||||
reject(new Error('not-found'));
|
||||
}
|
||||
|
||||
// 3. Filter out components that have no asyncData
|
||||
const componentsWithAsyncData = matchedComponents.filter(component => component.asyncData);
|
||||
|
||||
// 4. Map the components into a list of promises
|
||||
// by returning the asyncData method's result
|
||||
const asyncDataPromises = componentsWithAsyncData.map(component => (
|
||||
component.asyncData({ store, route })
|
||||
));
|
||||
|
||||
// You can have the asyncData methods resolve promises with data.
|
||||
// However to avoid complexity its recommended to leverage Vuex
|
||||
// In our case we're simply calling Vuex actions in our methods
|
||||
// that do the fetching and storing of the data. This makes the below
|
||||
// step really simple
|
||||
|
||||
// 5. Resolve all promises. (that's it)
|
||||
await Promise.all(asyncDataPromises);
|
||||
|
||||
// From this point on we can assume that all the needed data is stored
|
||||
// in the Vuex store. Now we simply need to grap it and push it into
|
||||
// the HTML as a "javascript string"
|
||||
|
||||
// 6. Store the data in the HTML for later hydration of the client bundle
|
||||
const js = `window.__INITIAL_STATE__=${JSON.stringify(store.state)};`;
|
||||
|
||||
// Resolve the promise with the same object as the simple version
|
||||
// Push our javascript string into the resolver.
|
||||
// The VueSSR package takes care of the rest
|
||||
resolve({
|
||||
app,
|
||||
js,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Awesome. When we load our app in the browser you should see a weird effect. The app seems to load correctly.
|
||||
That's the server-side rendering doing its job well. However, after a split second the app suddenly is empty again.
|
||||
|
||||
That's because when the client-side bundle takes over, it doesn't have its data yet. It will override the HTML with an empty app!
|
||||
We need to hydrate the bundle with the JSON data in the HTML.
|
||||
|
||||
If you inspect the HTML via the source code view, you will see the HTML source of your app accompanied by the `__INITIAL_STATE=""` filled with the JSON string. We need to use this to hydrate the clientside. Luckily this is fairly easy, because we have only one place that needs hydration: the Vuex store!
|
||||
|
||||
```javascript
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import createApp from './app';
|
||||
|
||||
Meteor.startup(() => {
|
||||
const { store, router } = createApp({ // Same function as the server
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Hydrate the Vuex store with the JSON string
|
||||
if (window.__INITIAL_STATE__) {
|
||||
store.replaceState(window.__INITIAL_STATE__);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Now when we load our bundle, the components should have data from the store. All fine. However there is one more thing to do. If we navigate, our newly rendered clientside components will again not have any data. This is because the `asyncData` method is not yet being called on the client side. We can fix this using a mixin like below as documented in the [Vue SSR Guide](https://ssr.vuejs.org/guide/data.html#client-data-fetching).
|
||||
|
||||
```javascript
|
||||
Vue.mixin({
|
||||
beforeMount () {
|
||||
const { asyncData } = this.$options
|
||||
if (asyncData) {
|
||||
// assign the fetch operation to a promise
|
||||
// so that in components we can do `this.dataPromise.then(...)` to
|
||||
// perform other tasks after data is ready
|
||||
this.dataPromise = asyncData({
|
||||
store: this.$store,
|
||||
route: this.$route
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
We now have a fully functioning and server-rendered Vue app in Meteor!
|
||||
320
content/writing-atmosphere-packages.md
Normal file
@@ -0,0 +1,320 @@
|
||||
---
|
||||
title: Writing Atmosphere Packages
|
||||
discourseTopicId: 20194
|
||||
---
|
||||
|
||||
To get started writing a package, use the Meteor command line tool:
|
||||
|
||||
```bash
|
||||
meteor create --package my-package
|
||||
```
|
||||
> It is required that your `my-package` name take the form of `username:my-package`, where `username` is your Meteor Developer username, if you plan to publish your package to Atmosphere.
|
||||
|
||||
If you run this inside an app, it will place the newly generated package in that app's `packages/` directory. Outside an app, it will just create a standalone package directory. The command also generates some boilerplate files for you:
|
||||
|
||||
```txt
|
||||
my-package
|
||||
├── README.md
|
||||
├── package.js
|
||||
├── my-package-tests.js
|
||||
└── my-package.js
|
||||
```
|
||||
|
||||
The `package.js` file is the main file in every Meteor package. This is a JavaScript file that defines the metadata, files loaded, architectures, npm packages, and Cordova packages for your Meteor package.
|
||||
|
||||
In this guide article, we will go over some important points for building packages, but we won't explain every part of the `package.js` API. To learn about all of the options, [read about the `package.js` API in the Meteor docs.](http://docs.meteor.com/#/full/packagejs)
|
||||
|
||||
> Don't forget to run [`meteor add [my-package]`](http://docs.meteor.com/#/full/meteoradd) once you have finished developing your package in order to use it; this applies if the package is a local package for internal use only or if you have published the package to Atmosphere.
|
||||
|
||||
<h2 id="adding-files">Adding files and assets</h2>
|
||||
|
||||
The main function of an Atmosphere package is to contain source code (JS, CSS, and any transpiled languages) and assets (images, fonts, and more) that will be shared across different applications.
|
||||
|
||||
<h3 id="adding-javascript">Adding JavaScript</h3>
|
||||
|
||||
To add JavaScript files to a package, specify an entrypoint with [`api.mainModule()`](http://docs.meteor.com/#/full/pack_mainModule) in the package's `onUse` block (this will already have been done by `meteor create --package` above):
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.mainModule('my-package.js');
|
||||
});
|
||||
```
|
||||
|
||||
From that entrypoint, you can `import` other files within your package, [just as you would in an application](structure.html).
|
||||
|
||||
If you want to include different files on the client and server, you can specify multiple entry points using the second argument to the function:
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.mainModule('my-package-client.js', 'client');
|
||||
api.mainModule('my-package-server.js', 'server');
|
||||
});
|
||||
```
|
||||
|
||||
You can also add any source file that would be compiled to a JS file (such as a CoffeeScript file) in a similar way, assuming you [depend](#dependencies) on an appropriate build plugin.
|
||||
|
||||
<h3 id="adding-css">Adding CSS</h3>
|
||||
|
||||
To include CSS files with your package you can use [`api.addFiles()`](http://docs.meteor.com/#/full/pack_addFiles):
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.addFiles('my-package.css', 'client');
|
||||
});
|
||||
```
|
||||
|
||||
The CSS file will be automatically loaded into any app that uses your package.
|
||||
|
||||
<h3 id="adding-style">Adding Sass, Less, or Stylus mixins/variables</h3>
|
||||
|
||||
Just like packages can export JavaScript code, they can export reusable bits of CSS pre-processor code. You can also have a package that doesn't actually include any CSS, but just exports different bits of reusable mixins and variables. To get more details see Meteor [build tool CSS pre-processors](build-tool.html#css):
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.addFiles('my-package.scss', 'client');
|
||||
});
|
||||
```
|
||||
|
||||
This Sass file will be eagerly evaluated and its compiled form will be added to the CSS of the app immediately.
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.addFiles([
|
||||
'stylesheets/_util.scss',
|
||||
'stylesheets/_variables.scss'
|
||||
], 'client', {isImport: true});
|
||||
});
|
||||
```
|
||||
|
||||
These two Sass files will be lazily evaluated and only included in the CSS of the app if imported from some other file.
|
||||
|
||||
<h3 id="adding-assets">Adding other assets</h3>
|
||||
|
||||
You can include other assets, such as fonts, icons or images, to your package using [`api.addAssets`](http://docs.meteor.com/#/full/PackageAPI-addAssets):
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
api.addAssets([
|
||||
'font/OpenSans-Regular-webfont.eot',
|
||||
'font/OpenSans-Regular-webfont.svg',
|
||||
'font/OpenSans-Regular-webfont.ttf',
|
||||
'font/OpenSans-Regular-webfont.woff',
|
||||
], 'client');
|
||||
});
|
||||
```
|
||||
|
||||
You can then access these files from the client from a URL `/packages/username_my-package/font/OpenSans-Regular-webfont.eot` or from the server using the [Assets API](http://docs.meteor.com/#/full/assets_getText).
|
||||
|
||||
<h2 id="exporting">Exporting</h2>
|
||||
|
||||
While some packages exist just to provide side effects to the app, most packages provide a reusable bit of code that can be used by the consumer with `import`. To export a symbol from your package, use the ES2015 `export` syntax in your `mainModule`:
|
||||
|
||||
```js
|
||||
// in my-package.js:
|
||||
export const myName = 'my-package';
|
||||
```
|
||||
|
||||
Now users of your package can import the symbol with:
|
||||
|
||||
```js
|
||||
import { myName } from 'meteor/username:my-package';
|
||||
```
|
||||
|
||||
<h2 id="dependencies">Dependencies</h2>
|
||||
|
||||
Chances are your package will want to make use of other packages. To ensure they are available, you can declare dependencies. Atmosphere packages can depend both on other Atmosphere packages, as well as packages from npm.
|
||||
|
||||
<h3 id="atmosphere-dependencies">Atmosphere dependencies</h3>
|
||||
|
||||
To depend on another Atmosphere package, use [`api.use`](http://docs.meteor.com/#/full/pack_use):
|
||||
|
||||
```js
|
||||
Package.onUse(function(api) {
|
||||
// This package depends on 1.2.0 or above of validated-method
|
||||
api.use('mdg:validated-method@1.2.0');
|
||||
});
|
||||
```
|
||||
|
||||
One important feature of the Atmosphere package system is that it is single-loading: no two packages in the same app can have dependencies on conflicting versions of a single package. Read more about that in the section about version constraints below.
|
||||
|
||||
<h4 id="meteor-version-dependencies">Depending on Meteor version</h4>
|
||||
|
||||
Note that the Meteor release version number is mostly a marketing artifact - the core Meteor packages themselves typically don't share this version number. This means packages can only depend on specific versions of the packages inside a Meteor release, but can't depend on a specific release itself. We have a helpful shorthand api called [`api.versionsFrom`](http://docs.meteor.com/#/full/pack_versions) that handles this for you by automatically filling in package version numbers from a particular release:
|
||||
|
||||
```js
|
||||
// Use versions of core packages from Meteor 1.2.1
|
||||
api.versionsFrom('1.2.1');
|
||||
|
||||
api.use([
|
||||
// Don't need to specify version because of versionsFrom above
|
||||
'ecmascript',
|
||||
'check',
|
||||
|
||||
// Still need to specify versions of non-core packages
|
||||
'mdg:validated-method@1.2.0',
|
||||
'mdg:validation-error@0.1.0'
|
||||
]);
|
||||
```
|
||||
|
||||
The above code snippet is equivalent to the code below, which specifies all of the version numbers individually:
|
||||
|
||||
```js
|
||||
api.use([
|
||||
'ecmascript@0.1.6',
|
||||
'check@1.1.0',
|
||||
'mdg:validated-method@1.2.0',
|
||||
'mdg:validation-error@0.1.0'
|
||||
]);
|
||||
```
|
||||
|
||||
Additionally, you can call `api.versionsFrom(<release>)` multiple times, or with
|
||||
an array (eg `api.versionsFrom([<release1>, <release2>])`. Meteor will interpret
|
||||
this to mean that the package will work with packages from all the listed releases.
|
||||
|
||||
```js
|
||||
api.versionsFrom('1.2.1');
|
||||
api.versionsFrom('1.4');
|
||||
api.versionsFrom('1.8');
|
||||
|
||||
// or
|
||||
|
||||
api.versionsFrom(['1.2.1', '1.4', '1.8']);
|
||||
```
|
||||
|
||||
This usually isn't necessary, but can help in cases where you support more than
|
||||
one major version of a core package.
|
||||
|
||||
|
||||
<h4 id="version-constraints">Semantic versioning and version constraints</h4>
|
||||
|
||||
Meteor's package system relies heavily on [Semantic Versioning](http://semver.org/), or SemVer. When one package declares a dependency on another, it always comes with a version constraint. These version constraints are then solved by Meteor's industrial-grade Version Solver to arrive at a set of package versions that meet all of the requirements, or display a helpful error if there is no solution.
|
||||
|
||||
The mental model here is:
|
||||
|
||||
1. **The major version must always match exactly.** If package `a` depends on `b@2.0.0`, the constraint will only be satisfied if the version of package `b` starts with a `2`. This means that you can never have two different major versions of a package in the same app.
|
||||
2. **The minor and patch version numbers must be greater or equal to the requested version.** If the dependency requests version `2.1.3`, then `2.1.4` and `2.2.0` will work, but `2.0.4` and `2.1.2` will not.
|
||||
|
||||
The constraint solver is necessary because Meteor's package system is **single-loading** - that is, you can never have two different versions of the same package loaded side-by-side in the same app. This is particularly useful for packages that include a lot of client-side code, or packages that expect to be singletons.
|
||||
|
||||
Note that the version solver also has a concept of "gravity" - when many solutions are possible for a certain set of dependencies, it always selects the oldest possible version. This is helpful if you are trying to develop a package to ship to lots of users, since it ensures your package will be compatible with the lowest common denominator of a dependency. If your package needs a newer version than is currently being selected for a certain dependency, you need to update your `package.js` to have a newer version constraint.
|
||||
|
||||
If your package supports multiple major versions of a dependency, you can supply
|
||||
both versions to `api.use` like so:
|
||||
|
||||
```js
|
||||
api.use('blaze@1.0.0 || 2.0.0');
|
||||
```
|
||||
|
||||
Meteor will use whichever major version is compatible with your other packages,
|
||||
or the most recent of the options given.
|
||||
|
||||
<h3 id="npm-dependencies">npm dependencies</h3>
|
||||
|
||||
Meteor packages can include [npm packages](https://www.npmjs.com/) to use JavaScript code from outside the Meteor package ecosystem or to include JavaScript code with native dependencies. Use [Npm.depends](http://docs.meteor.com/#/full/Npm-depends) at the top level of your `package.js` file. For example, here's how you would include the `github` npm package:
|
||||
|
||||
```js
|
||||
Npm.depends({
|
||||
github: '0.2.4'
|
||||
});
|
||||
```
|
||||
|
||||
If you want to use a local npm package, for example during development, you can give a directory instead of a version number:
|
||||
|
||||
```js
|
||||
Npm.depends({
|
||||
my-package: 'file:///home/user/npms/my-package'
|
||||
});
|
||||
```
|
||||
|
||||
You can import the dependency from within you package code in the same way that you would inside an [application](using-npm-packages.html#using-npm):
|
||||
|
||||
```js
|
||||
import github from 'github';
|
||||
```
|
||||
|
||||
<h3 id="peer-npm-dependencies">Peer npm dependencies</h3>
|
||||
|
||||
`Npm.depends()` is fairly rigid (you can only depend on an exact version), and will typically result in multiple versions of a package being installed if many different Atmosphere packages depend on the same npm package. This makes it less than ideal to use on the client, where it's impractical to ship multiple copies of the same package code to the browser. Client-side packages are also often written with the assumption that only a single copy will be loaded. For example, React will complain if it is included more than once in an application bundle.
|
||||
|
||||
To avoid this problem as a package author, you can request that users of your package have installed the npm package you want to use at the application level. This is similar to a [peer dependency](https://nodejs.org/en/blog/npm/peer-dependencies/) of an npm package (although with less support in the tool). You can use the [`tmeasday:check-npm-versions`](https://atmospherejs.com/tmeasday/check-npm-versions) package to ensure that they've done this, and to warn them if not.
|
||||
|
||||
For instance, if you are writing a React package, you should not directly depend on [`react`](https://www.npmjs.com/package/react), but instead use `check-npm-versions` to check the user has installed it:
|
||||
|
||||
```js
|
||||
import { checkNpmVersions } from 'meteor/tmeasday:check-npm-versions';
|
||||
|
||||
checkNpmVersions({
|
||||
'react': '0.14.x'
|
||||
}, 'my:awesome-package');
|
||||
|
||||
// If you are using the dependency in the same file, you'll need to use require, otherwise
|
||||
// you can continue to `import` in another file.
|
||||
const React = require('react');
|
||||
```
|
||||
|
||||
> Note that `checkNpmVersions` will only output a warning if the user has installed a incompatible version of the npm package. So your `require` call may not give you what you expect. This is consistent with npm's handling of [peer dependencies](http://blog.npmjs.org/post/110924823920/npm-weekly-5).
|
||||
|
||||
<h2 id="cordova-plugins">Cordova plugins</h2>
|
||||
|
||||
Atmosphere packages can include [Cordova plugins](http://cordova.apache.org/plugins/) to ship native code for the Meteor mobile app container. This way, you can interact with the native camera interface, use the gyroscope, save files locally, and more.
|
||||
|
||||
Include Cordova plugins in your Meteor package by using [Cordova.depends](http://docs.meteor.com/#/full/Cordova-depends).
|
||||
|
||||
Read more about using Cordova in the [mobile guide](mobile.html).
|
||||
|
||||
<h2 id="testing">Testing packages</h2>
|
||||
|
||||
Meteor has a test mode for packages called `meteor test-packages`. If you are in a package's directory, you can run
|
||||
|
||||
```bash
|
||||
meteor test-packages ./ --driver-package meteortesting:mocha
|
||||
```
|
||||
|
||||
This will run a special app containing only a "test" version of your package and start a Mocha [test driver package](testing.html#driver-packages).
|
||||
|
||||
When your package starts in test mode, rather than loading the `onUse` block, Meteor loads the `onTest` block:
|
||||
|
||||
```js
|
||||
Package.onTest(function(api) {
|
||||
// You almost definitely want to depend on the package itself,
|
||||
// this is what you are testing!
|
||||
api.use('my-package');
|
||||
|
||||
// You should also include any packages you need to use in the test code
|
||||
api.use(['ecmascript', 'random', 'meteortesting:mocha']);
|
||||
|
||||
// Finally add an entry point for tests
|
||||
api.mainModule('my-package-tests.js');
|
||||
});
|
||||
```
|
||||
|
||||
From within your test entry point, you can import other files as you would in the package proper.
|
||||
|
||||
You can read more about testing in Meteor in the [Testing article](testing.html).
|
||||
|
||||
<h2 id="publishing-atmosphere">Publishing your package</h2>
|
||||
|
||||
To publish your package to Atmosphere, run [`meteor publish`](http://docs.meteor.com/#/full/meteorpublish) from the package directory. To publish a package the package name must follow the format of `username:my-package` and the package must contain a [SemVer version number](#version-constraints).
|
||||
|
||||
> Note that if you have a local `node_modules` directory in your package, remove it before running `meteor publish`. While local `node_modules` directories are allowed in Meteor packages, their paths can collide with the paths of `Npm.depends` dependencies when published.
|
||||
|
||||
<h3 id="local-vs-published">Cache format</h3>
|
||||
|
||||
If you've ever looked inside Meteor's package cache at `~/.meteor/packages`, you know that the on-disk format of a built Meteor package is completely different from the way the source code looks when you're developing the package. The idea is that the target format of a package can remain consistent even if the API for development changes.
|
||||
|
||||
<h2 id="local-packages">Local packages</h2>
|
||||
|
||||
As an alternative to publishing your package on Atmosphere, if you want to keep your package private, you can place it in your Meteor app in the `packages/` directory, for instance `packages/foo/`, and then add it to your app with `meteor add foo`.
|
||||
|
||||
<h3 id="overriding-atmosphere-packages">Overriding published packages with a local version</h3>
|
||||
|
||||
If you need to modify an Atmosphere package to do something that the published version doesn't do, you can edit a local version of the package on your computer.
|
||||
|
||||
A Meteor app can load Atmosphere packages in one of three ways, and it looks for a matching package name in the following order:
|
||||
|
||||
1. Package source code in the `packages/` directory inside your app.
|
||||
2. Package source code in directories indicated by setting a `METEOR_PACKAGE_DIRS` environment variable before running any `meteor` command. You can add multiple directories by separating the paths with a `:` on OSX or Linux, or a `;` on Windows. For example: `METEOR_PACKAGE_DIRS=../first/directory:../second/directory`, or on Windows: `set PACKAGE_DIRS=..\first\directory;..\second\directory`.
|
||||
> Note: Prior to Meteor 1.4.2, `METEOR_PACKAGE_DIRS` was `PACKAGE_DIRS`. For compatibility reasons, developers should use `METEOR_PACKAGE_DIRS` going forward.
|
||||
3. Pre-built package from Atmosphere. The package is cached in `~/.meteor/packages` on Mac/Linux or `%LOCALAPPDATA%\.meteor\packages` on Windows, and only loaded into your app as it is built.
|
||||
|
||||
You can use (1) or (2) to override the version from Atmosphere. You can even do this to load patched versions of Meteor core packages - just copy the code of the package from [Meteor's GitHub repository](https://github.com/meteor/meteor/tree/devel/packages), and edit away.
|
||||
100
content/writing-npm-packages.md
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Writing npm Packages
|
||||
discourseTopicId: 20194
|
||||
---
|
||||
|
||||
To create a new npm package:
|
||||
|
||||
```bash
|
||||
mkdir my-package
|
||||
cd my-package/
|
||||
meteor npm init
|
||||
```
|
||||
|
||||
The last command creates a `package.json` file and prompts you for the package information. You may skip everything but `name`, `version`, and `entry point`. You can use the default `index.js` for `entry point`. This file is where you set your package's exports:
|
||||
|
||||
```js
|
||||
// my-package/index.js
|
||||
exports.myPackageLog = function() {
|
||||
console.log("logged from my-package");
|
||||
};
|
||||
```
|
||||
|
||||
Now apps that include this package can do:
|
||||
|
||||
```js
|
||||
import { myPackageLog } from 'my-package'
|
||||
|
||||
myPackageLog(); // > "logged from my-package"
|
||||
```
|
||||
|
||||
When choosing a name for your npm package, be sure to follow the [npm guidelines](https://docs.npmjs.com/files/package.json#name).
|
||||
|
||||
<h2 id="including-in-app">Including in your app</h2>
|
||||
|
||||
When you are developing a new npm package for your app, there are a couple methods for including the package in your app:
|
||||
|
||||
- **Inside node_modules**: Place the package in your app's `node_modules/` directory, and add the package to source control. Do this when you want everything in a single repository.
|
||||
|
||||
```bash
|
||||
cd my-app/node_modules/
|
||||
mkdir my-package
|
||||
cd my-package/
|
||||
meteor npm init
|
||||
git add -f ./ # or use a git submodule
|
||||
```
|
||||
|
||||
- **npm link**: Place the package outside your app's directory in a separate repository and use [`npm link`](https://docs.npmjs.com/cli/link). Do this when you want to use the package in multiple apps.
|
||||
|
||||
```bash
|
||||
cd ~/
|
||||
mkdir my-package
|
||||
cd my-package/
|
||||
meteor npm init
|
||||
cd ~/my-app/
|
||||
meteor npm link ~/my-package
|
||||
```
|
||||
|
||||
Other developers will also need to run the `npm link` command.
|
||||
|
||||
After either method, edit the `dependencies` attribute of `my-app/package.json`, adding `"my-package": "1.0.0"` (use the same version number you chose during `meteor npm init`).
|
||||
|
||||
<h2 id="publishing-npm">Publishing your package</h2>
|
||||
|
||||
You can share your package with others by publishing it to the npm registry. While most packages are public, you can control who may view and use your package with [private modules](https://docs.npmjs.com/private-modules/intro)).
|
||||
|
||||
To publish publicly, [follow these instructions](https://docs.npmjs.com/getting-started/publishing-npm-packages). When you're done, anyone can add your package to their app with `npm install --save your-package`.
|
||||
|
||||
If you want to share packages during development, we recommend using the [above methods](#including-in-app) instead of the registry. If you use the registry, then every time you change the package, you need to increment the version number, publish, and then `npm update my-package` inside your app.
|
||||
|
||||
<h2 id="overriding-npm-packages">Overriding packages with a local version</h2>
|
||||
|
||||
If you need to modify a package to do something that the published version doesn't do, you can edit a local version of the package on your computer.
|
||||
|
||||
Let's say you want to modify the `left-pad` npm package. If you haven't already, run inside your app directory:
|
||||
|
||||
```bash
|
||||
meteor npm install --save left-pad
|
||||
```
|
||||
|
||||
Now `left-pad` is included in your `package.json`, and the code has been downloaded to `node_modules/left_pad/`. Add the new directory to source control with:
|
||||
|
||||
```bash
|
||||
git add -f node_modules/left_pad/
|
||||
```
|
||||
|
||||
Now you can edit the package, commit, and push, and your teammates will get your version of the package. To ensure that your package doesn't get overwritten during an `npm update`, change the default [caret version range](https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004) in your `package.json` to an exact version.
|
||||
|
||||
Before:
|
||||
|
||||
```json
|
||||
"left-pad": "^1.0.2",
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```json
|
||||
"left-pad": "1.0.2",
|
||||
```
|
||||
|
||||
An alternative method is maintaining a separate repository for the package and changing the `package.json` version number [to a git URL or tarball](http://debuggable.com/posts/how-to-fork-patch-npm-modules:4e2eb9f3-e584-44be-b1a9-3db7cbdd56cb), but every time you edit the separate repo, you'll need to commit, push, and `npm update left-pad`.
|
||||
1
content/writing-packages.html
Normal file
@@ -0,0 +1 @@
|
||||
<!-- This file is simply here as a placeholder for redirects (see https://github.com/meteor/guide/pull/410) -->
|
||||
53
meta/charter.md
Normal file
@@ -0,0 +1,53 @@
|
||||
## Meteor Guide “Charter”
|
||||
|
||||
### **Vision**
|
||||
|
||||
Let's get on the path to having a “Meteor Guide” – the most authoritative, honest, up-to-date, and reliable source of best practices for Meteor. This is not the docs page that describes the technology components of Meteor and their APIs. Instead, the idea is to eventually answer all questions of the form: “How do I do X in Meteor?”
|
||||
|
||||
### **Values**
|
||||
|
||||
1. Honesty - the content in the guide should be the same advice you would give a trusted friend.
|
||||
2. Community - the guide should represent the aggregate experience of the Meteor community when it comes to the best way to build an app with Meteor, weighted by the vision of the platform as set by MDG.
|
||||
3. Comprehensiveness - anything belongs in the guide as long as it's a reasonable thing for people to want to do with the platform. Running in Electron - yes; running on FreeBSD - no.
|
||||
4. Content-focused - the guide should be in the format best optimized for general consumption, and it should be easy to access the information you want quickly and efficiently. Focus on content over technology.
|
||||
5. Curation - every guide bakes in a certain set of opinions. There's no way to get around this, so MDG needs to be comfortable taking a clear and well-supported stand on certain issues.
|
||||
6. Up-to-date - it should be easy to update so that we don't drag our feet on deploying new changes.
|
||||
7. Realistic - the guide should address real-life use-cases and developer goals first, and avoid contrived examples. It's not about demonstrating how to use technology, it's about showing people how to get their work done.
|
||||
8. Focus on the intermediate developer - the guide is not for beginners who are writing their first app, and it's not for the super hackers that can code their way out of anything. The guide is for normal developers trying to get the job done.
|
||||
|
||||
### **Methods**
|
||||
|
||||
1. :white_check_mark: Put the guide in a new repository so that it can be discussed and contributed to independently of the Meteor framework.
|
||||
2. :white_check_mark: Set up a standard static site generator that will be easy for people to understand and contribute to.
|
||||
3. :white_check_mark: Set up a continuous deployment system to automatically deploy changes from GitHub, so that we can't forget to deploy a new version.
|
||||
4. :white_check_mark: Build in support for versioning docs via branches so that people can easily access documentation for previous Meteor releases.
|
||||
5. Ensure great SEO so that people can find documentation items through Google or other search engines.
|
||||
6. Don't put guide-appropriate information anywhere else, like the GitHub Wiki, READMEs, or similar, so that people don't have to guess where information might be hidden.
|
||||
7. :white_check_mark: Make contributing to the guide the easiest way to contribute to the Meteor project, by adding “edit this page” buttons everywhere.
|
||||
8. Be proactive in evaluating and accepting community contributions.
|
||||
9. Be clear about why contributions are and are not accepted, and write these down in a living document about how to craft a great contribution to the guide.
|
||||
10. Hire a technical writer to help efficiently produce great content and ensure everything is nice to read, consistent in style, and free of grammatical errors or stylistic issues.
|
||||
11. :white_check_mark: Organize guide content by developer goals rather than API methods. It's possible that some sections will lack Meteor API information entirely, if the Meteor API doesn't help with any of those goals. API documentation is a separate app we'll need to built some other time.
|
||||
12. :white_check_mark: Put community packages and MDG packages on a more even footing by sometimes endorsing the community solution over the MDG one when it's better.
|
||||
13. Figure out a way to user test the guide to see if it helps developers achieve their goals.
|
||||
14. Loop in every framework developer to get input on the parts of the framework with which they are most familiar.
|
||||
15. Add a section to each article that has related links that people can submit - for example links to packages that people have written to help with a certain task, or how-to articles on someone's blog. Heading could be “other articles on the web”.
|
||||
|
||||
### **Obstacles**
|
||||
|
||||
1. Historical reluctance to endorse community packages
|
||||
2. Changes to the Meteor API can make large parts of the guide outdated quickly
|
||||
3. Large amount of existing source material in random locations - GitHub Wiki, etc
|
||||
4. Maintaining high quality and cohesion in the face of numerous authors and contributions
|
||||
5. Getting engineers to write docs/guides as part of our process and culture
|
||||
6. Lack of clear guidance from the framework on many topics, such as testing and app architecture
|
||||
|
||||
### **Goals**
|
||||
|
||||
1. :white_check_mark: Develop a skeleton for a guide app, with nice styles and a clear path to adding more content [firm]
|
||||
2. :white_check_mark: Have continuous deployment set up for at least two branches of the new guide repository [firm]
|
||||
3. Migrate all existing content onto the guide website from docs.meteor.com, GitHub Wiki, and READMEs (I'm looking at you, Spacebars) [firm]
|
||||
4. Hire one technical writer
|
||||
5. :white_check_mark: Have an “edit on GitHub” button on every single page
|
||||
6. Review every guide contribution within one week of submission and give a conclusive response
|
||||
7. Accept 50 community pull requests
|
||||
586
meta/meeting-notes.md
Normal file
@@ -0,0 +1,586 @@
|
||||
## Agenda for next standup
|
||||
|
||||
- Look over changes detailed here: https://github.com/meteor/guide/blob/1.3-migration-content/content/1.3-migration.md
|
||||
- i18n - should we do more in the Todos app (add spanish? translation / UI to change language)
|
||||
- Can we / should we bin: basic docs; Meteor Projects
|
||||
- Should we recommend a richer community package in the Blaze chapter? (https://forums.meteor.com/t/meteor-guide-regarding-blaze/17506/32)
|
||||
|
||||
## Standup 3rd Feb
|
||||
|
||||
With Zol, Sashko, Tom.
|
||||
|
||||
### Notes
|
||||
|
||||
- React outline draft done by Evan.
|
||||
- View team to fill in the rest.
|
||||
- Uri will mirror the same structure for Angular.
|
||||
- Martijn trying to get tech stuff finished before guide. (not essential for 1.3)
|
||||
- If we're delaying the mobile release, we should delay it a bit more to build good documentation (e.g the wikis).
|
||||
- Tom has spent a lot of time on testing.
|
||||
- Run into a roadbump late yesterday trying to convert velocity example app to the new system.
|
||||
- Tom is organizing conversation with Workpop to find out what's feasible for them.
|
||||
- Updating sketch of an article as framework changes.
|
||||
- Avi is pretty close to finishing on his work.
|
||||
- Sashko is adamanent we worry about velocity users.
|
||||
- Do we want migration path for an app that's completely made up of packages to be super smooth? We should mention the path, step 1 move to modules, step 2 incrementally move tests to mocha, step 3 delete your package.js file.
|
||||
- We should message that people shouldn't use tinytest anymore.
|
||||
- We should add a migration guide section to the guide whenever we do a new release.
|
||||
- Sashko - Useful parts from Code Style are Meteor specific.
|
||||
|
||||
### Decisions
|
||||
|
||||
- We're definitely reaching out to Workpop to test tests.
|
||||
- Consider having Workpop write a blog post for transitioning to the new tests.
|
||||
- Don't kill the UI for integration tests, add a single node in the app for the reporter and inline the css.
|
||||
- Add a migration from 1.2 -> 1.3 article.
|
||||
- Tom is good with owning App Structure and Testing till the 1.3 release.
|
||||
- Need to update example apps and possible Meteor create skeleton.
|
||||
- Use a linter config (whichever) for example apps.
|
||||
- Merge app structure and code style articles with just the useful Meteor specific parts.
|
||||
- Tom to get outlines for App Structure and the Migration article in the next few days.
|
||||
|
||||
|
||||
## Standup 20th Jan
|
||||
|
||||
With Zol, Sashko, Tom, Matt.
|
||||
|
||||
### Notes
|
||||
|
||||
- Guide should reflect view layer strategy. Blaze is in there and we document best practices. Guide should also document our recommendations with respect to React and Angular.
|
||||
- Modules and Testing are critical.
|
||||
- Modules and Blaze integration points should tie in well, make sure the situation is reasonable.
|
||||
- Blaze, people have been wondering how to use the same template name, i.e this is what modules would solve.
|
||||
- Might make sense to leave Blaze as it is, have a sane way to put js+css in the same dir as your html.
|
||||
- Currently Templates are still global and if you use a helper that hasn't been imported code blows up at run time.
|
||||
- Where do template go in an Angular 2 app? Maybe it would inform what we should do.
|
||||
- Deal with how Blaze works right now, work on adding React and Angular to the guide later.
|
||||
- 100% start writing about application structure.
|
||||
- Avi is working on the testing stuff as we speak, Tom to sync up with him.
|
||||
- Time to validate testing implementation and guide article and validate it with bigger Meteor users. Workpop. Asaf in Israel (medical app).
|
||||
- What to do about forms? We're still talking about autoform and Blaze. Reason it's not in the guide is the todos app doesn't have enough forms to show much. Are we going to build new example app with lot's of forms? Definitely relevant to the view layer question. We don't have autoform for React.
|
||||
- It's ok if the first version of the React chapter in the guide doesn't address as much about forms as the blaze version and/or requires more manual labour than autoform.
|
||||
- Ideal outcome is in time for 1.3 we had some chapter for React and Angular 2, even if it's a short chapter. We need to document the Meteor/Angular/React bindings.
|
||||
- Sashko non-negotiable article: application structure, testing (complete and awesome). Want to have: barebones React/Angular articles (to replace existing websites). Question Mark: mobile, es2015 code style, forms. Less clear that we need them immediately. We should enlist Martijn to help write mobile article.
|
||||
- 1.3 is probably toward the end of February.
|
||||
- This adds up according to Tom.
|
||||
- Thoughts on long term maintenance? Don't know yet. In the medium term Tom feels like the natural person to be point on that, probably as a coordinator.
|
||||
|
||||
### Decisions
|
||||
|
||||
- Matt priority order: App structure, Testing, Mobile <-> React/Angular, Forms - items 1&2 are essential, others could slip to after 1.3 (good chance we'll defer mobile improvements to 1.3.1), could lean on the view team for React/Angular.
|
||||
- Tom on point for app structure and testing (including updating the todos example app), view team writing Angular/React stuff with Tom's guidance. Martijn to write the mobile chapter. This is the goal for 1.3. Forms is a nice to have which can come later.
|
||||
- There's more work to do with fully integrating the guide into the website, tutorials, etc.
|
||||
|
||||
## Standup 4th Jan
|
||||
|
||||
With Zol, Sashko.
|
||||
|
||||
### Notes
|
||||
|
||||
- Testing, forms and app structure blocked on more 1.3 stuff.
|
||||
|
||||
### Decisions
|
||||
|
||||
- Ignore code style article this week.
|
||||
- Sashko to work on 'soft launch meta issues' cleanup ticket in the next week, then do data stuff till Tom gets back.
|
||||
- Sashko to see what is left for us to be able to 1.3 preview release.
|
||||
- Sashko to try and massage todos to work with 1.3 preview, commenting out tests if necessary.
|
||||
|
||||
## Standup 14th Dec
|
||||
|
||||
With Zol, Tom, Sashko.
|
||||
|
||||
### Agenda
|
||||
- Progress, soft launch?
|
||||
|
||||
### Notes
|
||||
- If Accounts, Deployment finished, we could be done.
|
||||
- Forms sticks out.
|
||||
- Reasonable to message that certain articles are blocked on 1.3
|
||||
- Email to the big mailing list to see what people think. We're pretty sure on all the articles that are drafted.
|
||||
- How do we make the example app presentable?
|
||||
- Unknowns in forms article?
|
||||
- Janky packages in todos right now, mostly around testing.
|
||||
- Example app should address the set of articles we'll promote.
|
||||
- Sashko can write the content but not maintain the linting config, need a lint config maintainer (Tim).
|
||||
|
||||
### Action Items
|
||||
|
||||
- 'Soft Launch' becomes promoting content so far for holiday reading, with a disclaimer that more exciting content is in the works aligning with 1.3
|
||||
- Clean up todos app wrt to XXX's. Rename packages not part of the app to fork-something.
|
||||
- Everything in drafts-in-progress will be drafted and promoted by the end of the week.
|
||||
- Order the articles in the website.
|
||||
|
||||
## Standup 9th Dec
|
||||
|
||||
With Zol, Matt, Tom, Sashko.
|
||||
|
||||
Agenda:
|
||||
|
||||
1. Guide social promotion tasks from Dan
|
||||
1. Fix URLs [done]
|
||||
2. See if we can put up a banner reminding people it’s in progress and we’re looking for contributions
|
||||
3. Make a list of interesting nuggets to tweet about
|
||||
1. Ever wanted to know what happens when a Meteor Method is called? http://guide.meteor.com/methods.html#call-lifecycle
|
||||
2. Read our recommendations about routing in Meteor: http://guide.meteor.com/routing.html
|
||||
3. Learn how to secure your Meteor app: http://guide.meteor.com/security.html
|
||||
4. Figure out when we should do a big marketing push with the initial release
|
||||
2. Talk about 7 principles with Matt
|
||||
3. Mid-dec soft release, what is it?
|
||||
4. Blaze?
|
||||
|
||||
### Notes
|
||||
|
||||
- App Structure and Code Style split.
|
||||
- Sashko to work with Tim on code style.
|
||||
- Forms not started yet.
|
||||
- Tom is working on welcome outline.
|
||||
- Split up testing article into Test Runner CI, and how to test methods/other individual components will go into their own section.
|
||||
- Definitely in the Guide is for 1.3 mindset.
|
||||
- Line up guide preview release roughly with 1.3 preview release.
|
||||
- Stick to our goals of initial draft releases by mid Dec.
|
||||
- Eventually move to a world where API docs are purely generated from jsdoc
|
||||
- We're recommending Blaze, you can use React & Angular, not recommending them as a first choice because of existing packages.
|
||||
- Where do we tell people how to use javascript? Don't need to do it in the guide, link to a great resource.
|
||||
- Testing: We'll have guidance for end to end tests. Assuming modules get done nicely and have everything we need we'll have testing for the guide.
|
||||
- What happens to specs for modules and testing so that guide can sync up with features that are coming.
|
||||
|
||||
### Action Items
|
||||
|
||||
- Take a crack at 250-400 words at what Meteor is from the point of a Developer. Maybe distill some updated principles.
|
||||
- Quietly drop 7 prinicples under a deprecated commit.
|
||||
- Matt to get design docs for testing and modules.
|
||||
|
||||
## Standup 2nd Dec
|
||||
|
||||
With Tom, Zol, Sashko and Matt
|
||||
|
||||
## Notes
|
||||
|
||||
- Outstanding product questions: Blaze, Methods, Linting.
|
||||
- Setting up a process to resolve these questions.
|
||||
- Matt re: 1.3. We ought to write the Guide for 1.3 . Use it to promote the things that are exciting in 1.3, e.g ES2015 modules, things that come up in a larger app,
|
||||
- Dream scenario would be when we release 1.3, there is a guide in place written for it that steers people on the leading edge of what’s in 1.3 . That guide has been floating around for a while and we’ve been iterating on it and getting comments on it as the lead up to 1.3 . Feedback loop between the guide, community and product.
|
||||
- Guide to steer polish of modules rather than large efforts.
|
||||
- Theme that works for 1.3 - it’s the Meteor release for large apps. Targetted at the things that come up when you have a larger app.
|
||||
- How do we systemize the collaboration of the Guide Team.
|
||||
- Platform team is to drive the 1.3 release cycle.
|
||||
- The platform team (Ben, Avi, Martin) are keen to work with the guide team.
|
||||
- Guide team become customers for the platform team.
|
||||
- Take plan to existing customers.
|
||||
- Should we be writing a Blaze article?
|
||||
- Complicated question.
|
||||
- Is it a blog post?
|
||||
- Bookmark the Blaze conversation - the urgency is high.
|
||||
- Make a decision by the end of the week.
|
||||
- Linting: Existing linter config. Would be cool to put the config and meteor integration into the Guide.
|
||||
- Politics of applying linting config to the entire Meteor codebase?
|
||||
- Matt is in favor of advocating for one code style.
|
||||
- Leave meteor/meteor unlinted.
|
||||
- Maybe style guide, linter chapter.
|
||||
|
||||
## Action Items
|
||||
|
||||
- Make a decision on Blaze by the end of the week.
|
||||
- Maintain Meteor eslint style config in npm.
|
||||
- Linting to go into Guide.
|
||||
- Move weekly check-in to Wednesdays (this same time works).
|
||||
|
||||
## Standup 1st Dec
|
||||
|
||||
With Tom, Zol and Sashko
|
||||
|
||||
### Agenda
|
||||
|
||||
- Progress?
|
||||
- Website QA
|
||||
|
||||
### Notes
|
||||
|
||||
- We'll all do a round of QA on the website before Evan completely moves onto other things.
|
||||
- Merge Security and Data article in the next 24 hours.
|
||||
- Sashko to clean up other cruft.
|
||||
- Article draft velocity is good, easy articles are done.
|
||||
- Should we write a guide on Blaze?
|
||||
- Testing and App structure require too many decisions.
|
||||
- Taking these 3 out will maybe have the other articles drafted by the end of the week (*Tom optimism*)
|
||||
- Demoting publishing a package to an 'article idea' rather than a first class article.
|
||||
- Mini-app with forms in it?
|
||||
- Split Forms/Methods article into 2, Methods and Forms. Definitely get the Methods done and maybe get the Forms done, note Forms requires a mini example app.
|
||||
- Remember we should make some nice diagrams some time.
|
||||
|
||||
### Action Items
|
||||
|
||||
- Tom+Sashko - UI/UX, what should we do about internationalization?
|
||||
- Do we write a Blaze article? - part of discussion with Matt tomorrow.
|
||||
- Sit down and resolve tiny decisions in each article to get them to draft stage.
|
||||
- Tom to do Mobile (until we know more).
|
||||
- Sashko to do Methods.
|
||||
|
||||
## Standup 23rd Nov
|
||||
|
||||
With Evan, Tom, Sashko, Zol
|
||||
|
||||
### Agenda
|
||||
|
||||
- Article writing cadence, how did we go last week?
|
||||
- Website progress, when can we expect the TODOs finished?
|
||||
- Modules collabororation with Ben, does this require more structure than ad-hoc. Are there blockers?
|
||||
|
||||
### Notes
|
||||
|
||||
- Sashko got help up with customer day and publishing method package.
|
||||
- Sashko has done more than half of the security article.
|
||||
- Tom has done collections and data loading along with some other code things.
|
||||
- Some XXX's still around.
|
||||
- Tom feels confident on everything apart from the testing article and structure article.
|
||||
- Perhaps Tom and Sashko should swap on the form article.
|
||||
- Tom suggests we could drop the testing and app structure articles because they're hard to write and in the air.
|
||||
- The other is blaze.
|
||||
- Prioritize 1.3 related articles to the end and let's calibrate our writing velocity on the less nebulus articles.
|
||||
- Trade methods<->routing.
|
||||
- Tom will do Routing, UI/UX this week.
|
||||
- Sashko focused on Security, Build Tool, Accounts.
|
||||
- Most of the static stuff is good, 3 things left on the TODO
|
||||
- Use swifttype for search integration.
|
||||
- Headings should function as table of contents in a book.
|
||||
- Add one more level of nesting.
|
||||
- Deploy guide to guide.meteor.com, add Do Not Read banner.
|
||||
- Evan will work on todos tomorrow, check most of them before holiday (goal for this week).
|
||||
- Ideally after this week Evan wants to focus primarily on blaze.
|
||||
- We'll QA it next week, then after another pass over these we'll be done.
|
||||
- Modules converstion with Ben not an urgent issue right now because of our priorties.
|
||||
- Testing is a big body of work without ownership.
|
||||
- Shared document(s) regarding features Ben is planning to build. Currently no two way channel between the guide and Ben
|
||||
- Let's figure out this channel of comms (to be realtime).
|
||||
- Who is the product manager and who is the customer. Zol to ask Matt.
|
||||
|
||||
### Action Items
|
||||
|
||||
- Zol meet with Matt re: modules/testing product plan.
|
||||
|
||||
## Modules meeting 18th Nov
|
||||
|
||||
With Ben, Matt, Tom, Zol, and Sashko.
|
||||
|
||||
We talked about what would need to be supported by ES2015 modules in Meteor 1.3 for us to switch to that for the guide.
|
||||
|
||||
Forum post here: https://forums.meteor.com/t/ideas-from-mdg-meteor-1-3-the-meteor-guide-and-es2015-modules/13591
|
||||
|
||||
- **Testing**: Realistically we need a sensible way to unit test a module in a reasonably isolated way.
|
||||
- Ben: Perhaps testing just comes down to not eagerly-loading application code, and just loading the modules you want to test?
|
||||
- Tom: Whole app testing might not actually be related to modules. We should just find out if we can simulate the `meteor test-packages` experience with a module. This is basically “compile the package as usual, but also include the `onTest` files and dependencies”.
|
||||
- Ben: We could just have a “sub-app” in `test/` that is loaded when you are running tests, and those could have modules that are test-only.
|
||||
- Could we have `.meteor/packages` just specify which packages are test-only, dev-only, etc. For example, you could have `mocha` as an app dependency, but only in test mode.
|
||||
- You’d also have an eagerly-loaded test runner where you can register tests to be run.
|
||||
- `meteor test` could accept an argument that filters the tests based on the name they were registered with, or the module in which the tests are defined. Should also start a test database, etc.
|
||||
- Matt would like to see the hypothetical guide article that outlines how you write and run your tests in the module world. Then we can make sure the module system supports all of that.
|
||||
- Tom will write the testing article against an idealized module system, if we ship the guide before modules ship, we’ll just omit that section or put it in some “future” area.
|
||||
- **Blaze**: How do blaze templates, which don't have any concept of `import`/`export`, work with this? Can you co-locate templates and their JS logic?
|
||||
- We could make these lazily evaluated, and only be executed when you import them
|
||||
- `{{> templateName}}` means `Template.templateName`. In module world, do we need to first `import templateName from ‘../module/templateName.html’` somehow? Answer: no, given the other discussions about Blaze/React, it's not worth making major changes to the Spacebars syntax at the moment.
|
||||
- Sashko/Matt say - it’s OK if all template files are eagerly evaluated and work as they do now. Sashko says it’s important to him that all code can be put in the `imports` directory, including templates, so that we can make the code structure nice for the guide.
|
||||
- For further discussion: How important is it that feature code (JS, HTML, CSS, images, etc) can be co-located?
|
||||
- **Build plugins**: Do build plugins work as-is? how do you use modules with CoffeeScript, does this open up TypeScript? Have we compared what we are building to what is needed by the Angular/Angular2 community? What about CSS pre-processors?
|
||||
- The dependency tree is analyzed based on the output code - so as long as the CoffeeScript compiler outputs `require(‘myFile.coffee’)`, we’ll detect that dependency.
|
||||
- LESS does all of its own importing and exports CSS, which could either be added as a style tag or a JavaScript resource. The CSS pre-processor stuff isn't affected by ES2015 module work for Meteor 1.3, and will work just as before.
|
||||
- **Meteor package system**: What’s it for? Should people start shipping reusable Meteor code on npm as of 1.3?
|
||||
- Meteor packages are really good at doing totally different things on the client and the server - you can set up the full stack just by adding the package
|
||||
- For example, for DDP, you’d need to `import ‘ddp/client’` on the client and `import ‘ddp/server’` on the server if you want different functionality, but in a Meteor package you can just use `ddp` and it does the right thing.
|
||||
- `client/` and `server/` directories in npm packages don’t have a special meaning at the moment, but they could - or we could have a special `package.json` format. Probably won't tackle this for 1.3.
|
||||
- If you don’t have the above special build system requirements, there is basically no reason you need to publish your package on Atmosphere over npm - this also removes the need for robotic wrapper packages that just re-bundle the same code that's already on npm.
|
||||
- **Assets**: What about assets like fonts and images? In the current package system, you can organize them in `package.js`, but in a Meteor app today you'd have to put everything in `public/`. What do you do in module world?
|
||||
- Modules are about loading things that compile to JavaScript code
|
||||
- Images aren’t something you would ‘require’ since there is no JavaScript content involved
|
||||
- Perhaps you should be able to stick images anywhere in the app (not just `public/`), and have a sane way of referring to them
|
||||
- Idea from Sashko: Perhaps we want `getPathToAsset(‘../relative/path’);`, so that you can put images next to templates and refer to them without having to know the absolute path to the containing directory.
|
||||
- **Apps with multiple entry points/UIs/services**: Right now, you can have lots of packages and lots of apps, is this possible with modules? Do you need to make modules into npm packages for this? Are Meteor packages still the way to go? If so, do you still need to list all of the files?
|
||||
- Didn't have time to address this in the meeting.
|
||||
- **Cordova**: Can you import Cordova plugins?
|
||||
- We don’t yet have a good mental model here yet. It would be nice if you could import a cordova plugin, and make sure the necessary side effects run.
|
||||
- These things can work like “legacy” meteor packages - the dependency scanner doesn’t need to understand them. This is one advantage over using Webpack in Meteor 1.3 - it assumes everything is a node-style JavaScript module.
|
||||
- **File structure**: Do we want to keep the `imports` directory convention proposed in the PR?
|
||||
- Ben says backwards compatibility with the existing loading logic was important to him, and `imports` is just one way to get that.
|
||||
- Other ways are special file names, special comments, etc.
|
||||
- Matt says this directory should sound more “default” and less “opting into a fancy new feature”
|
||||
- Ben said one big reason was that the LESS package picked this name. `modules` is another possibility, feels like `node_modules`. This is kind of a bike-shedding topic overall.
|
||||
- We could also have an app “control file” that just specifies entry point. Either a JSON file we can statically read, or a JavaScript file that just imports stuff inside conditionals somehow.
|
||||
|
||||
## Standup 16th Nov
|
||||
|
||||
With Tom, Zol and Sashko.
|
||||
|
||||
### Agenda:
|
||||
|
||||
- Progress, will we get this done by mid Dec?
|
||||
- Aligning the guide with M1.3 and vice versa.
|
||||
- Plan around testing & M1.3, what's the low hanging fruit and who does it?
|
||||
- Blaze.
|
||||
|
||||
### Notes:
|
||||
|
||||
- Some chapter are easier and will definitely be done on time.
|
||||
- Tests, i8n, etc - shouldn't try to have real world coverage.
|
||||
- Cut scope instead of pushing back the date.
|
||||
- Do we cut scope by not doing articles?
|
||||
- Let's re-visit next week when we've had a chance to do some writing.
|
||||
- Incorporate Matt's feedback on the app structure chapter by revisiting outlines (ex modules).
|
||||
- Current guide is sort of a requirements for modules.
|
||||
- Proceed as plan on the Blaze chapter.
|
||||
- Method package. Good feedback from Robert on the README.
|
||||
|
||||
### Action items:
|
||||
|
||||
- Setup a meeting with Ben to go over the implications of modules both on the guide and vice versa. Start with Sashko's highlevel questions then work through points found in the outlines that are impacted by modules. (Sashko + Tom working on doc with questions, Zol to schedule meeting)
|
||||
- Should clean up the guide repo a little bit. Make sure github issues are updated with the outlines. Get the community people back into the conversation. Post on the forums. (Sashko)
|
||||
- Polish up the Method package readme and docs and post it for feedback on the forums. (Sashko)
|
||||
- Create package for validation error format, get feedback. (Tom) (Blocking publishing methods)
|
||||
- Ask if something like cursor-utils exists (on the forums). (Tom)
|
||||
|
||||
|
||||
## Standup 12th Nov
|
||||
|
||||
With Zol, Tom, Sashko and Matt
|
||||
|
||||
### Agenda:
|
||||
|
||||
- Feedback and sign off on outlines so far.
|
||||
|
||||
### Notes
|
||||
|
||||
- Matt found it hard to read the outlines and conclude if it was correct.
|
||||
- No mention of jobs or workers.
|
||||
- Move deployment to a more prominent place in the guide.
|
||||
- Feels like about the right amount of stuff.
|
||||
- Maybe there's a good way to prioritize the issues.
|
||||
- Guide is sort of targetted at 1.3 but since modules aren't there yet guide still targets 1.2.
|
||||
- We should be opinionated in the guide.
|
||||
- Clear point of view about what kind of app you're building (e.g MVP) is missing.
|
||||
- Rails guide has something like this, maybe we're missing an intro section.
|
||||
- Be aggressive about ES2015, a lot of people don't know it so we should be really upfront. Highlight the linter and our linting rules.
|
||||
- Looking at article ordering...
|
||||
- Flag save() in Collections & Modles and come back to it
|
||||
- Should forms and methods go in the same section?
|
||||
|
||||
*Matt's Ordering for articles (TBD):*
|
||||
- App Structure
|
||||
- Collections and Schemas (migrations)
|
||||
- Data Loading/Publications
|
||||
- Methods (stubs, optimistic UI)
|
||||
- Routing
|
||||
- UI-UX / Blaze in here somewhere
|
||||
- Everything else...
|
||||
|
||||
- Detailed walk through.
|
||||
- *App Structure:*
|
||||
- 1. Sounds great.
|
||||
- Things seem reasonable but we're just going to have to see the text.
|
||||
- 2. ok.
|
||||
- 3. This is a bummer for Matt, that the small & medium apps are different. Modules could unify this. Matt wishes instead of S, M, L we had a taxonomy that didn't make you classify your app in one of these categories.
|
||||
- We should fold modules into the mix early on.
|
||||
- 1. Let's not target something that doesn't exist yet
|
||||
- 2. We believe modules are the correct thing to document in the guide.
|
||||
- 3. Let's make sure we aren't making any bad decisions on modules that would be obvious if we applied the guide to our current module plan.
|
||||
- Rather than small medium large, Matt would be comfortable if there was a recommended app structure and progressive enhancement.
|
||||
- ** Speak to Ben ** Matt would delay M1.3 into Jan if it meant writing the guide first would tell us how to solve the load order problem and hook we need to write app tests.
|
||||
- 7ii) Tell people just to use submodules for private packages with local package. Put PACKAGE_DIRS in the package chapter (if at all).
|
||||
- We can link out to other articles on the web, e.g for large app structure.
|
||||
- Section 7 seems like a separate article to Matt.
|
||||
- *Collections:*
|
||||
- Matt thinks we didn't like Collection2
|
||||
- Can we put Collection2 in core?
|
||||
- Matt is surprised that designing your schema doesn't come earlier.
|
||||
- 4. custom mutators belongs in the methods chapter.
|
||||
- 8,9 are all just links. Matt and Tom are confused whether we're linking to things we use or are popular.
|
||||
- Consider removing 9.
|
||||
- Does 7i) go higher up?
|
||||
- 8i) SimpleSchema link should go in it's own section.
|
||||
- *Data Loading*
|
||||
- 1. Spot on.
|
||||
- 3. Switch ii & iii.
|
||||
- 4 i, ii) About right.
|
||||
- ! The guide should have a server performance article. E.g it's really easy to write slow queries. !
|
||||
- 5. Looks good. Perhaps shouldn't be in this section.
|
||||
- Move 5&6 out of here.
|
||||
- 7ii) Investigate, actually use publish-composite
|
||||
- Remove 7iii)
|
||||
- Matt suggests a section called using the low level publish API.
|
||||
- Turn 9ii) into 11 so it's side by side with 10.
|
||||
- *Forms and Methods*
|
||||
- Maybe there's two parts to this, the distinction between defining them and calling them.
|
||||
- ! 1ii) Get the package out to the broader community. ! Prioritize wrapping it up along with a forum post.
|
||||
- 1-7 seems about right. Better discussed with some content around it.
|
||||
- Thing Matt would push on hard is seeing what we can line up with 1.3, especially a half reasonable story with modules as the best structure for all apps, and if this happens to give us a good story for testing that's great.
|
||||
- ! write up a sketch/proposal for fixing app testing !
|
||||
- Go all in on ES15, this is the opportunity for us to lay down what a proper ES15 app looks like.
|
||||
- Let's get another 2 hours on the calendar for next week.
|
||||
|
||||
|
||||
## Standup 9th Nov
|
||||
|
||||
With Zol, Tom, Evan and Sashko
|
||||
|
||||
### Agenda:
|
||||
|
||||
- Progress.
|
||||
- Thoughts on testing.
|
||||
- Meeting Matt on Thu for feedback.
|
||||
|
||||
### Notes
|
||||
|
||||
- Deployment is merged to master. CirclCI is set up to push to s3 on a push to master. Branches with the version prefix will be pushed to deploy. Edit circle.yaml to push a specific branch.
|
||||
- QA push to deploy.
|
||||
- Open issues in Github for feature requests. Use the 'website' label.
|
||||
- Each article should have the title meta block. Use relative links when linking to content.
|
||||
- Functional site with navbars and decent styling by the end of the week. Presentable.
|
||||
- Tom: most of the big ticket conversion is done. Big ticket item left on todos is testing - unfortunately there is actually not much out there in for testing, lack of mocks. We could build stuff that would fill significant gaps. PR against Meteor out there that enables method mocking, this is required in addition to mocking publications as well as ... Todos app has some unit tests but not great coverage. End-end tests with Gagarin seems like a good approach.
|
||||
- Open question on building unit test infrastructure?
|
||||
- Leave testing questions till after the guide.
|
||||
- Still on track to have todos done by Wed.
|
||||
- Remember we want an iterative process.
|
||||
- Tom to get gagarin, selenium, CI done. Identify the risks and try to eliminate the largest one.
|
||||
- On track for 2 articles drafted this week.
|
||||
|
||||
## Standup 5th Nov
|
||||
|
||||
With Zol, Tom, Evan and Sashko
|
||||
|
||||
* Only a couple hours work for the code.
|
||||
* Stick to 2 articles per person per week schedule.
|
||||
* Aim to have complete but rough articles by mid Dec rather than better articles with some incomplete.
|
||||
* Two articles will be complete by the end of next week.
|
||||
* Evan has made a staging s3 bucket - http://meteor-guide-staging.s3-website-us-west-1.amazonaws.com/
|
||||
* SEO is important, when folks search for something they should find hits from the latest version of the guide unless they explicitly search for a specific version.
|
||||
* Master at /, other versions/branch at /version/
|
||||
* If the user is not on a page for the latest version, display a banner telling them so (with a link to the latest).
|
||||
* Guide will live at guide.meteor.com
|
||||
* Mid next week for infrastructure part of Guide website (push to deploy)
|
||||
* Use css classes for components
|
||||
* We can (should) use themes to customize.
|
||||
* Don't worry about Markdown consistency for the first pass. Do it later.
|
||||
* We realized there are no good form examples in the Todos app.
|
||||
* The two options are rewrite Microscope with forms and the guided approach or build something else.
|
||||
* Revisit this Monday week after having built 2 articles.
|
||||
* Schedule 2 hour meeting with Matt once Todos is done to walk through the code and outlines.
|
||||
|
||||
|
||||
## Meeting about example app and post-outline plans 4th Nov
|
||||
|
||||
#### Meeting part 1 with Tom
|
||||
|
||||
1. Do modules solve testing the same way packages do? If so, this means we don't need package-focused apps anymore
|
||||
2. Let's unprefix the file names in packages - so it's OK to just have a file called methods.js and not lists-methods.js, since the package is already called lists
|
||||
3. Prefix the package names with app- instead of todos- it's silly to have a package called todos-todos
|
||||
4. should stub collections be debugOnly? Open question
|
||||
5. Tom has the linter running in his editor, Sashko should add it to his Atom
|
||||
|
||||
#### Splitting up tasks for the todos example app:
|
||||
|
||||
* Application Structure - Sashko
|
||||
* Less / CSS / PostCSS - Sashko
|
||||
* Methods - remove allow/deny - Sashko
|
||||
* Using Stores + Template level subscription - Tom
|
||||
* Simple Schema / Collection 2 - Tom
|
||||
* Autoform - Sashko
|
||||
* User Accounts - Sashko
|
||||
* LaunchScreen (add to mobile article) - Sashko
|
||||
* Momentum - Tom
|
||||
* “Componentize” Blaze templates - Tom
|
||||
* Tests - Tom
|
||||
* Deploy to Galaxy w/ Kadira - Tom
|
||||
|
||||
**Action item:** File all of these as issues on meteor/todos
|
||||
|
||||
#### Meeting part 2 with Zol
|
||||
|
||||
1. When is an article moved to the example app column? When it is fully reflected in meteor/todos
|
||||
2. Todos is the only complete example app we care about - all other code snippets can be written as we go, and don't need to be from a particular app. They will be filled in during the first draft phase.
|
||||
3. Sashko took some time for on-call stuff, so we can push back the example app deadline to Monday/Tuesday
|
||||
4. Zol should look at https://github.com/meteor/guide/blob/master/meeting-notes.md#initial-meeting-about-guide-website-29th-oct and meet with Evan about planning
|
||||
|
||||
#### Quick discussion about code tasks (labeled `code` in the guide repo)
|
||||
|
||||
1. Testing packages - we'll find out more as we test Todos
|
||||
2. Cursor utils - shouldn't be hard
|
||||
3. Complex authorization means a way to re-run publications when authorization data changes - could be easy to add a small API
|
||||
4. Badge against master and devel - sashko is working on it
|
||||
5. Update meteor create - we can do this after we finish the initial guide
|
||||
6. Methods package - sashko is already doing this as part of Todos
|
||||
7. Best JS validation library - we are going with SS, let's close it
|
||||
8. Modify simple-schema to work like check, make autoform accept errors
|
||||
9. Validation error format + there needs to be a “core” error package that supports it
|
||||
10. Remove/rename mutator methods - not necessary if we just split by dots
|
||||
11. Make dotted names is not a big deal
|
||||
|
||||
## Initial meeting about guide website 29th Oct
|
||||
|
||||
With Evan and Sashko
|
||||
|
||||
1. Evan has experience with Hexo, so we can get going the fastest using that
|
||||
1. You can see an example at http://vuejs.org/guide/
|
||||
2. Deployment
|
||||
1. Continuously deployed from one or more branches
|
||||
1. Different branches will eventually be different versions/languages
|
||||
2. Deployment from PRs is a nice to have
|
||||
3. Deployment location - whatever is easiest
|
||||
1. Digital ocean
|
||||
2. S3
|
||||
3. UI components
|
||||
1. A component for representing an external packages
|
||||
1. https://www.dropbox.com/s/w5229hslbcl7gql/Screenshot%202015-10-29%2013.46.40.png?dl=0
|
||||
2. Citing an external source or article
|
||||
3. Table of contents
|
||||
1. Could be in the sidebar, or at the top of the page
|
||||
4. Navigation structure
|
||||
1. To start, two types of content
|
||||
1. Guide articles
|
||||
2. Random articles like how to install mobile stuff on Mac OS
|
||||
2. In the future, an API reference section
|
||||
5. Get a design from Dom
|
||||
4. Ask him if he has time
|
||||
5. We want the navbar from meteor.com (http://meteor.com/) eventually
|
||||
5. Invite Evan to checkin meeting
|
||||
|
||||
## Standup 26th Oct
|
||||
|
||||
With Zol, Tom, and Sashko
|
||||
|
||||
### Decisions
|
||||
|
||||
- Sashko/Tom will create Pull Requests with completed outlines and the other person will merge and/or discuss first. The outlines will stay in a separate
|
||||
file to the finished articles.
|
||||
- Todos will live in a new repo.
|
||||
- All outlines dones this week.
|
||||
- Example code the week after.
|
||||
- Outlines will be completed before example code is written.
|
||||
- Zol: Figure out how/when to get Matt's buy in.
|
||||
- Zol: Check whether it's true that Evan can build the website.
|
||||
- Zol: Find someone to proof/edit the english.
|
||||
|
||||
|
||||
## Standup 16th Oct
|
||||
|
||||
With Zol, Tom, and Sashko
|
||||
|
||||
### Decisions
|
||||
|
||||
* Will be tracked via waffle on Github: [done](https://waffle.io/meteor/guide?label=article)
|
||||
* Tool to build the website will be: MD, static website, continuous deployment, github backed, public, accept pull requests supports multiple versions. Sidebar, versions selector.
|
||||
* Design/Branding will be done as a pass over the content, rather than a top-down design process.
|
||||
|
||||
### Approach
|
||||
|
||||
1. Deciding what the articles are and first pass of outline. [Done]
|
||||
2. Second pass on outlines/polish - publishing decisions so far, soliciting community response with decisions and feedback. Action items/etc in GH issues. Setup waffle/etc [In progress].
|
||||
3. Apply decisions to example app.
|
||||
4. Content - Per article states (empty, outline, RFC, rough content, first draft). All articles in 'first draft' state == First draft milestone.
|
||||
|
||||
### Milestones
|
||||
|
||||
* All ideations initiated
|
||||
* All outlines written
|
||||
* First draft -> concerted effort to get feedback from everyone (Mid Dec)
|
||||
* Website, CI, and tooling set up (Mid Dec)
|
||||
* Visual design finalized (Mid Dec)
|
||||
* Soft launch of entire site (Christmas)
|
||||
* Content edited in detail, link on the homepage (Early Feb)
|
||||
196
meta/outlines.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# Meteor guide outlines
|
||||
|
||||
This is an attempt to write a comprehensive outline of all of the guides one would need to read to build a professional-quality application with Meteor. Each guide has a title, a tagline that explains why this concept is important in Meteor, and 5-10 sections which are named in the format: “After reading this guide, you'll know...”
|
||||
|
||||
### Meteor guide: Application structure
|
||||
|
||||
Since everything is JavaScript and code can be shared between all parts of your app, Meteor presents new opportunities for code organization. But with great power comes great responsibility.
|
||||
|
||||
1. What are the different parts of a Meteor app
|
||||
2. How to build your app around features instead of stack layers
|
||||
3. How to split your code into the right number of files and how to organize those files in directories, in a way that will scale as your project grows
|
||||
4. Best practices for making your app modular so that you can work on one part of the codebase without fear that some other part will break unexpectedly
|
||||
5. How to split your app into smaller apps that each have a smaller surface area while sharing code and data
|
||||
6. The Meteor style guide
|
||||
|
||||
[See the document.](content/structure.md)
|
||||
|
||||
Status: Initial draft 50% done.
|
||||
|
||||
### Meteor guide: Collections and models
|
||||
|
||||
We'll explain how to deal with collections across client and server. Then we'll reduce code repetition by extending database documents with model classes.
|
||||
|
||||
1. How to define and use MongoDB collections in Meteor
|
||||
2. The distinctions between collections on the client and collections on the server
|
||||
3. How to define a model with schemas and validations for a collection
|
||||
4. How to add setters and getters to your model to centralize your database logic
|
||||
5. How to design a schema that works well with Meteor's data system and can be extended over time
|
||||
6. How to migrate data when you want to change the structure or schema of your collections
|
||||
7. How to model relational data, even when your database is not relational
|
||||
|
||||
### Meteor guide: Data loading and management
|
||||
|
||||
Meteor lets you write your UI as if the database is present on the client while maintaining security and the ability to have a decoupled data model. Sound like a contradiction? We'll explain how to use all of the tools together to get the best balance of fast development and maintainability.
|
||||
|
||||
1. How to load and use data from the database over DDP
|
||||
2. How to load just enough data to display your UI while using caching to make sure the UI is as fast as possible
|
||||
3. When to use local component state and when to have a global store
|
||||
4. How to build your own client-side reactive data stores with ReactiveVar, ReactiveDict, and Tracker
|
||||
5. Modifying data stores using Methods
|
||||
6. How to use data from external APIs on the client and server
|
||||
1. HTTP
|
||||
2. DDP
|
||||
3. Webhooks
|
||||
7. How to publish and use relational data
|
||||
8. How to do pagination or infinite scroll so that you can load data incrementally as the user needs it
|
||||
|
||||
### Meteor guide: UI/UX
|
||||
|
||||
Meteor supports many different UI frameworks in addition to having its own default framework, Blaze. While all of these frameworks have their own documentation, there's a lot to learn about building a UI for a large app that is UI-framework-agnostic.
|
||||
|
||||
1. When to build reusable components and when not to
|
||||
2. Optimizing event handling for realtime input by throttling effectively
|
||||
3. Good patterns for revealing new data without startling the user
|
||||
4. Using animations effectively for a great user experience
|
||||
5. Designing a responsive application that works across different devices
|
||||
6. Make your app appeal to a wider user base with internationalization
|
||||
7. How to make your app more accessible to people with disabilities
|
||||
|
||||
### Meteor guide: Accounts
|
||||
|
||||
Meteor's login system works pretty well out of the box, but there are a few tips and tricks to set things up just the way you want them.
|
||||
|
||||
1. Picking an accounts UI package
|
||||
2. Setting up password reset, email verification, and enrollment emails
|
||||
3. Setting up OAuth login services
|
||||
4. Building your own login service
|
||||
4. Adding custom fields to the users collection and using them
|
||||
|
||||
### Meteor guide: Security
|
||||
|
||||
Meteor apps can be very easy to secure if you follow a few simple principles, and there are some packages that streamline the process for you.
|
||||
|
||||
1. The security surface area of a Meteor app
|
||||
1. Methods
|
||||
2. Publications
|
||||
3. Served files
|
||||
2. How to set up roles and permissions for user accounts
|
||||
3. How and why to use SSL
|
||||
4. How to manage sensitive API keys and configuration
|
||||
5. Common mistakes and misconceptions
|
||||
|
||||
### Meteor guide: Forms, user input, and methods
|
||||
|
||||
How to build your C~~R~~UD with a stellar user experience, with no extra effort.
|
||||
|
||||
1. How to define a method with optimistic UI and validation
|
||||
2. How to wire up a button or single UI control to a method
|
||||
3. How to wire up a form to a method
|
||||
4. Error handling
|
||||
5. Realtime validation
|
||||
6. Optimistic UI and when to use it
|
||||
7. Saving intermediate inputs in case the user closes the tab accidentally
|
||||
8. How to use a method to write data to external APIs
|
||||
9. How to enable users to upload files
|
||||
|
||||
### Meteor guide: Routing
|
||||
|
||||
What do URLs mean in a mobile and client-rendering world, and how does one use them properly?
|
||||
|
||||
1. What role URLs play in a client-rendered app, and how it's different from a traditional server-rendered app
|
||||
2. How to define client and server routes for your app using Flow Router
|
||||
3. How to have your app display different content depending on the URL
|
||||
4. How to construct links to routes and go to routes programmatically
|
||||
5. How to handle URLs in your app that should only be accessible to certain users
|
||||
6. How and why to use a UI framework native router, like Angular router or React Router
|
||||
|
||||
[See the document.](content/routing.md)
|
||||
|
||||
Status: Initial draft 80% done.
|
||||
|
||||
### Meteor guide: Testing
|
||||
|
||||
Write some extra code now to make sure you don't break your code when you add more code later. Add features and refactor your app with no fear.
|
||||
|
||||
1. How to test:
|
||||
1. Methods
|
||||
2. Publications
|
||||
3. Models
|
||||
4. Routes
|
||||
5. UI components
|
||||
2. How to structure your code with modules so that it can be tested more easily
|
||||
3. How to stub parts of the Meteor framework so that you can test a small part of your app at a time
|
||||
4. How to mock data in a realistic way
|
||||
5. How to set up continuous integration so that you can't forget to run the tests
|
||||
|
||||
### Meteor guide: Mobile
|
||||
|
||||
Build a really good mobile experience with just a little bit of extra effort.
|
||||
|
||||
1. How to set up your development environment for Android and iOS
|
||||
2. How to enable mobile debugging, logging, testing
|
||||
3. How to optimize your user experience on mobile to make your app feel smooth and usable
|
||||
4. How to use native mobile features through Cordova plugins
|
||||
5. How to add push notifications, intents, and other mobile operating system integrations
|
||||
6. How to use hot code push effectively to update your app outside of the normal app store review process while maintaining a great user experience
|
||||
|
||||
### Meteor guide: The build tool
|
||||
|
||||
Meteor brings sane, zero-configuration defaults to the previously tedious tasks of compiling, concatenating, minifying, and transforming assets.
|
||||
|
||||
1. How to use popular transpiled languages in Meteor out of the box:
|
||||
1. ES2015+
|
||||
2. LESS
|
||||
3. SASS
|
||||
4. Coffee
|
||||
5. TypeScript
|
||||
2. How to use packages from other packaging systems:
|
||||
1. Compatibility directory and 'bare' files
|
||||
2. Bower
|
||||
3. npm
|
||||
|
||||
### Blaze guide: The Tracker-based reactive templating system
|
||||
|
||||
Write “HTML with holes” just like you're used to, and get a fast, fine-grained, reactively updating page with no sweat.
|
||||
|
||||
1. Spacebars syntax, and how to use the built-in helpers
|
||||
2. Building reusable components with Blaze by avoiding global data
|
||||
3. How to use reactivity in a principled way
|
||||
4. Writing maintainable helpers and event handlers that aren't tightly coupled to HTML
|
||||
5. Reusing logic and HTML snippets between templates
|
||||
|
||||
|
||||
|
||||
## Advanced
|
||||
|
||||
### Meteor production guide: Deployment, monitoring, and analytics
|
||||
|
||||
Now that you've built a sweet app, give it to the world. It can be hard to run a production app, that's where Galaxy comes in.
|
||||
|
||||
1. Integrating with popular analytics platforms to track method calls, publications, and URL hits
|
||||
2. Profiling your app locally
|
||||
3. Monitoring your app in production (with Kadira)
|
||||
3. Escape hatches for performance issues
|
||||
4. Staging and testing
|
||||
5. Rolling updates with hot code push
|
||||
6. Debugging production apps (with Kadira Debug)
|
||||
6. Galaxy
|
||||
|
||||
### Meteor guide: Building a great package
|
||||
|
||||
The Meteor package ecosystem is a unique place where many of the packages are designed to work together around a standard, well-defined stack. Learn how you can easily and effectively contribute!
|
||||
|
||||
1. Creating a simple package and publishing it
|
||||
2. How to deliver different code for different platforms and architectures
|
||||
3. How to integrate an existing Cordova plugin or write your own
|
||||
4. How to use monkey-patching to add new features to existing APIs
|
||||
5. Biting off what you can chew - what's the perfect size for a package?
|
||||
6. How to test your package and set up CI
|
||||
7. How to document your package
|
||||
8. How to publicize your package and become famous in the Meteor community
|
||||
9. How to deal with pull requests and issues
|
||||
10. How to pick a license for your package
|
||||
11. How make a build plugin
|
||||
1. Compiling single files into other single files
|
||||
2. Compiling many files at once that can be inter-related
|
||||
0
meta/outlines/.gitignore
vendored
Normal file
31
meta/outlines/1.3-migration
Normal file
@@ -0,0 +1,31 @@
|
||||
1. Breaking changes (as we'd normally do this)
|
||||
|
||||
2. Recommended changes : Modules [link to app structure guide]
|
||||
1. Applications: Move all files to imports, initialize in one spot, link via `import`
|
||||
2. Import *all* symbols from packages, including meteor core packages.
|
||||
3. No longer use `api.exports()` in packages (?), publish to npm if not Meteor specific (?)
|
||||
4. Install npm packages directly, instead of using wrapper packages (unless the wrapper package adds a synchronous API).
|
||||
5. Install React from npm (remove `react` package?), see React guide for more.
|
||||
|
||||
3. Recommended changes: Testing [link to testing guide]
|
||||
1. Integration testing: Migrating from velocity
|
||||
1. You can move your tests alongside your modules with the `*.tests.*` naming convention.
|
||||
2. Depending on your testing library, you may need to upgrade or replace your test package (link to libraries)
|
||||
3. Run integration tests with `meteor test-app --integration`
|
||||
2. Unit testing: Migrating from package tests
|
||||
1. If you were using in-app packages for testing, you can now use the unit test mode to test your modules.
|
||||
2. If you were using tinytest, you will need to migrate your test code (Mocha is recommended)
|
||||
3. If you were using Mocha/Jasmine/..?, you can now place your tests in the imports folder alongside the unit to test and they'll automatically be added to your unit test suite.
|
||||
4. Run unit tests with `meteor test-app --unit`
|
||||
|
||||
4. Recommended changes: Mobile [link to mobile guide]
|
||||
1. Are there any things people should do differently?
|
||||
|
||||
|
||||
5. What's new in the 1.3 guide:
|
||||
1. App structure article
|
||||
2. Testing article
|
||||
3. React article
|
||||
4. Angular article
|
||||
5. Mobile article (?)
|
||||
6. i18n section of UI guide (?)
|
||||
49
meta/outlines/accounts.md
Normal file
@@ -0,0 +1,49 @@
|
||||
1. What does Meteor do for you?
|
||||
1. Standardized concept of userId in DDP
|
||||
2. accounts-base package that has a standard user database, and can be plugged with different login systems
|
||||
2. The fastest way to get set up for a prototype: accounts-ui
|
||||
1. List of easy to set up login providers, code examples
|
||||
2. Read more about accounts user interfaces later
|
||||
5. The useraccounts package for a production-grade login UI
|
||||
1. Pick the right package based on your CSS framework
|
||||
2. Get help from splendido to flesh out this section - just the basics, then link to the docs
|
||||
3. Figure out what to do about useraccounts and adding fields to profile
|
||||
3. Password login
|
||||
1. accounts-password gives you password login with username, email, or both
|
||||
2. How to require username, email, or both
|
||||
3. Dealing with multiple email addresses
|
||||
4. The case insensitivity issues - working with Meteor 1.2+ password accounts. Basically, don't access Meteor.users directly.
|
||||
5. Email flows
|
||||
1. Enrollment email
|
||||
2. Reset password
|
||||
3. Verify email
|
||||
4. How to customize emails
|
||||
5. Generating HTML or text emails on the server using template strings
|
||||
4. OAuth login
|
||||
1. Meteor has core packages for some of the most common login services
|
||||
2. Facebook
|
||||
3. GitHub
|
||||
4. Google
|
||||
5. Twitter
|
||||
6. Meetup
|
||||
7. Meteor Developer Accounts
|
||||
8. Getting extra data from the OAuth services
|
||||
8. Building your own OAuth login handler [XXX not done, perhaps to be filled in later]
|
||||
3. Accessing user data
|
||||
1. Meteor.userId() and Meteor.user() on the client
|
||||
2. this.userId on the server
|
||||
3. Meteor.users collection
|
||||
6. Adding custom data about users
|
||||
1. Adding new top-level fields onto the users collection
|
||||
2. How and why to disable profile
|
||||
3. Publishing custom user data
|
||||
4. Why top-level fields are better than nested objects in DDP (link to collections/schema article)
|
||||
5. Security concerns - don't accidentally publish secret data to the client
|
||||
7. Allowing the same user to log in through different methods - “account merging”
|
||||
1. Update the existing account using requestCredential
|
||||
2. accounts-meld in case you want to do more complicated things, but make sure you understand the risk!
|
||||
1. Check out this package for security
|
||||
2. There's a community project in progress to make this simpler
|
||||
8. Authentication, roles, and permissions
|
||||
1. alanning:roles
|
||||
2. Read about hiding certain views from people in the Routing chapter
|
||||
60
meta/outlines/blaze.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Blaze
|
||||
|
||||
1. Introduction to Spacebars -- the tracker-backed handlebars like templating syntax
|
||||
1. Example of spacebars syntax, data context + helpers
|
||||
2. Data contexts and access - name lookup (`.`, `this`, `..`, `[0]`), and null values
|
||||
3. Helpers, arguments, options
|
||||
4. Inclusion of other templates + arguments, passing data contexts
|
||||
5. Helpers inside tags -- returning strings and objects, `checked={{isChecked}}`
|
||||
6. Nested helpers + sub expressions
|
||||
7. Block helpers (you can create them with templates, see 10. below)
|
||||
8. Safestrings and `{{{`
|
||||
9. Builtin block helpers
|
||||
1. `{{#if/unless}}`
|
||||
2. `{{#each .. in ..}}`
|
||||
3. `{{#let}}`
|
||||
4. Explain `{{#each}}` and `{{#with}}`, indicate that it's better not to use them.
|
||||
1. NOTE: we need to ensure that issues around lexical scope and event handlers are resolved for `{{#each .. in ..}}` and `{{#let}}`.
|
||||
10. Comments
|
||||
11. Strictness
|
||||
12. Escaping
|
||||
2. Creating reusable "pure" components with Blaze / best practice (a lot of this is repeating @sanjo's boilerplate)
|
||||
1. Validating data context fits a schema
|
||||
2. Always set data contexts to `{name: doc}` rather than just `doc`. Always set a d.c on an inclusion.
|
||||
3. Use `{{#each .. in .. }}` to achieve the above
|
||||
4. Use the template instance as a component -- adding a `{{instance}}` helper to access it
|
||||
5. Use a (named / scoped on `_id` if possible) reactive dict for instance state
|
||||
6. Attach functions to the template instance (in `onCreated`) to sensibly modify state
|
||||
7. Place `const instance = Template.instance()` at the top of all helpers/event handlers that care about state
|
||||
8. Always scope DOM lookups with `this.$`
|
||||
9. Use `.js-X` in event maps
|
||||
10. Pass extra content to components with `Template.contentBlock`, or named template arguments
|
||||
11. Use the `onRendered` callback to integrate w/ 3rd party libraries
|
||||
1. Waiting on subscriptions w/ `autorun` pattern: https://github.com/meteor/guide/pull/75/files#r43545240
|
||||
3. Writing "smart" components with Blaze
|
||||
1. All of section 2.
|
||||
2. Use `this.autorun` and `this.subscribe`, listening to `FlowRouter` and `this.state`
|
||||
3. Set up cursors in helpers to be passed into pure sub-components, and filtered with `cursor-utils`
|
||||
1. Why cursors are preferred to arrays.
|
||||
4. Access stores directly from helpers.
|
||||
4. Reusing code between templates
|
||||
1. Prefer utilities/composition to mixins or inheritance
|
||||
2. How to write global helpers
|
||||
5. Understanding Blaze
|
||||
1. When does a template re-render (when its data context changes)
|
||||
2. When does a helper-rerun? (when its data context or reactive deps change)
|
||||
1. So be careful, this can happen a lot! If your helper is expensive, consider something like https://github.com/peerlibrary/meteor-computed-field
|
||||
3. How does an each tag re-run / decide when new data should appear?
|
||||
1. How does the `{{attrs}` syntax work, some tricks on how to use it.
|
||||
4. How do name lookups work?
|
||||
5. What does the build system do exactly?
|
||||
6. What is a view?
|
||||
6. Testing Blaze templates
|
||||
1. Rendering a template in a unit test
|
||||
2. Querying the DOM (general but just a pointer here)
|
||||
3. Triggering reactivity and waiting for re-rendering
|
||||
4. Simulating events w/ JQ
|
||||
7. Useful Blaze utilities / other approaches
|
||||
1. https://github.com/peerlibrary/meteor-blaze-components
|
||||
2. https://github.com/raix/Meteor-handlebar-helpers
|
||||
3. Much, much more...
|
||||
52
meta/outlines/build-tool.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Build tool outline
|
||||
|
||||
1. What does the Meteor build tool do?
|
||||
1. Runs constantly and watches files to be served
|
||||
2. Runs build plugins to convert “source” files to “compiled” files
|
||||
3. Combines all of the packages you're using
|
||||
4. Builds for production by applying build plugins and then minifiers
|
||||
5. The built format of a Meteor app is completely different from the source code, you definitely don't deploy a Meteor app just by putting up your source code and typing meteor run
|
||||
2. The build tool enables seamless, zero-configuration usage of many popular transpiled languages
|
||||
1. Build plugins transpile only the package or app where they are directly added
|
||||
2. Only one source handler per file extension
|
||||
3. Supports source maps so that debugging works great
|
||||
3. JavaScript transpilation
|
||||
1. ES2015+ turned on by default with the ecmascript package
|
||||
1. Link to docs of all features turned on
|
||||
2. Link to the “cost” of ecmascript, and other relevant blog posts
|
||||
2. CoffeeScript in the coffeescript package
|
||||
1. Basically works out of the box, is very popular in the Meteor community
|
||||
3. Typescript is more experimental, link to some of the different options out there but we might not be able to recommend it as a super stable, first-class approach at the moment. This should improve once we have better support for modules
|
||||
1. Ask Uri how this ties into Angular 2
|
||||
4. Templating
|
||||
1. The most common one is templating which compiles .html files into Blaze code - you might be using build plugins without knowing it. It comes inside the blaze-html-templates package
|
||||
2. You can replace Spacebars with Jade using mquandalle:jade
|
||||
3. For React developers there are two main options:
|
||||
1. JSX in the jsx package
|
||||
1. Works exactly like ecmascript, but with the jsx option enabled.
|
||||
2. Compilers that convert HTML templates into React elements, for example timbrandin:sideburns - figure out how mature this is - perhaps this is just a link to the documentation if it's not quite done yet
|
||||
4. Angular comes with its own template pre-compiler that works with .ng.html files, comes in the angular package
|
||||
5. CSS pre-processors
|
||||
1. LESS, SASS and Stylus all work very similarly, and have overlapping feature sets - it's up to you which syntax you want to use
|
||||
2. “main” files vs. “import” files
|
||||
1. Main files get converted to CSS eagerly
|
||||
2. Import files are only evaluated when imported from a main file, and should probably only contain mixins/variables, so that if you import them multiple times you don't get multiple copies of the CSS
|
||||
3. How to import mixins and variables from packages (enabled in Meteor 1.2)
|
||||
4. How to customize Bootstrap/Semantic UI
|
||||
6. CSS post-processors
|
||||
1. In the current Meteor build system, there is not a separate step for CSS post-processors, so they need to be built either into the minifier or compiler
|
||||
2. **BIG CODE ITEM: POST-CSS IN MINIFIERS**
|
||||
7. Minification
|
||||
1. Done by default using standard-minifiers
|
||||
8. Packaging
|
||||
1. Meteor packages
|
||||
1. Packages on Atmosphere vs. local packages
|
||||
1. The built format is totally different
|
||||
2. See an example of a built package in `~/.meteor/packages` (we'll pick a specific example and compare the source code and output)
|
||||
2. Link to chapter about building a package
|
||||
2. npm
|
||||
1. In your app with meteorhacks:npm
|
||||
2. This just makes a local package, read about npm in packages in the package article
|
||||
3. Bundle for client-side with cosmos:browserify, link to React guide for React components
|
||||
3. Bower
|
||||
1. mquandalle:bower, learn more about this. Perhaps it's just a link
|
||||
30
meta/outlines/code-style.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Code style
|
||||
|
||||
1. Benefits of strict code style
|
||||
1. Integration with default linters, checkers, transpilers, etc
|
||||
2. Easy for new people to get started on your code
|
||||
3. All Meteor code samples can follow your style
|
||||
2. JavaScript and ES2015
|
||||
1. Use JavaScript and compile all of your code with the `ecmascript` package
|
||||
1. Follow the Meteor style guide, based on the Airbnb style guide
|
||||
2. Use ESLint using the standard config, which is made to work with `ecmascript`
|
||||
1. Running ESLint
|
||||
1. Setting up linting in your editor
|
||||
1. Setting up a linter commit hook
|
||||
1. Adding linting to your CI alongside tests
|
||||
3. Meteor components
|
||||
1. Collections
|
||||
1. Name is plural
|
||||
1. Instance variable is capitalized camel case
|
||||
1. Collection name in DB is same as the instance variable
|
||||
1. Fields in MongoDB should be camel-cased
|
||||
2. Methods and publications
|
||||
1. Camel cased, namespaced with dot separators
|
||||
1. Use mdg:validated-method to reference methods by JavaScript scope
|
||||
3. Packages, files, and exports
|
||||
1. Use ES2015 exports
|
||||
1. Each file should represent one logical module, rather than having a file called `utils.js` that exports a variety of unrelated things
|
||||
1. If a file defines a class or component, the file should be named the same as the class, down to the case
|
||||
4. Templates and components
|
||||
1. Blaze templates should be namespaced via dots since they can't be exported via modules; the HTML, CSS, and JS file related to the template should have the same name.
|
||||
1. React components should be treated as you would normal JavaScript modules/functions
|
||||
52
meta/outlines/collections.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Collections and Models
|
||||
|
||||
1. Mongo Collections in Meteor
|
||||
1. Server side Mongo "real" collections backed by a DB
|
||||
2. Client side Minimongo "remote-backed" collections backed by a DDP connection
|
||||
3. Local Minimongo Collections backed by nothing.
|
||||
2. Definining a Collection with a Schema
|
||||
1. Why schemas are important in a schema-less db
|
||||
1. Controlling the database
|
||||
2. Avoiding "writing schemas in code" -- which is what you end up doing if you don't have a schema
|
||||
2. The Simple Schema package and how to define a schema
|
||||
3. Using schemas -- running a validation, getting errors back
|
||||
4. The `ValidationError` and how it relates to the form chapter.
|
||||
3. Mutating data -- writing insert/update/remove functions
|
||||
1. Using an instance of a `Collection2` to force Schema checks.
|
||||
2. Using `autovalue` and `defaultValue` to "define" more complex insert/update code.
|
||||
3. Subclassing `Collection2` to do arbitrary things on mutations.
|
||||
4. "Hooking" data by subclassing Collection2.
|
||||
1. Description of the need for hooks
|
||||
2. How the careful use of utilities can allow readable mutators that have hooks
|
||||
5. EG: Denormalization patterns
|
||||
1. Define your denormalizer in a different file
|
||||
2. Hook the denormalizer in various `insert/update/remove` functions
|
||||
4. Custom mutators
|
||||
1. In a public API it's best to be *less* general rather than *more* general (see security article)
|
||||
2. Your methods are your public API.
|
||||
3. So write a `bar.addFoo` mutator rather than allowing `bar.update` to add `foo`.
|
||||
4. Using the `Method` pattern to wrap a mutator in a public API of the same name.
|
||||
1. Reference to Dave Weldon's post on the subject / see also Form chapter.
|
||||
4. Designing your data schema
|
||||
1. "Impure" mongo -- i.e. things that Meteor will force you to do that you might not have done otherwise
|
||||
- Avoid subdocuments and large changing properties
|
||||
- Use more collections, normalize more
|
||||
2. Thinking ahead to future database changes
|
||||
- Don't try to predict the future but be flexible
|
||||
5. Changing data schema - how to use migrations
|
||||
1. percolate:migrations package
|
||||
2. How to run migrations against a production db
|
||||
[is our best advice run locally pointing at the production db, use Meteor shell?]
|
||||
3. Multiple stage deploys which can handle both new and old format
|
||||
6. Relational data and other helpers
|
||||
1. Using `dburles:collection-helpers` to add "methods" to your documents
|
||||
2. Returning a cursors from a helper to get related documents
|
||||
3. Using a `cursor-utils` package to narrow down cursors etc [HELP NEEDED?]
|
||||
7. Advanced schema usage
|
||||
1. https://github.com/aldeed/meteor-simple-schema
|
||||
4. Using JSONSchema with SS
|
||||
8. Other packages / approaches
|
||||
1. Astronomy
|
||||
1. Brings the "ORM-y" `.save()` to your models.
|
||||
2. Collection hooks
|
||||
1. Allows you to follow a hook/aspect oriented patterns you don't need to fully describe your mutators in one go.
|
||||
57
meta/outlines/data-loading.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Data Loading and Management
|
||||
|
||||
1. Publications + Subscriptions
|
||||
1. What are they? - compare REST endpoint
|
||||
2. How do they work? - talk about bridging data from server-client collections
|
||||
3. What's a pub / what's a sub
|
||||
2. Defining a publication on the server
|
||||
1. Rules around what arguments it should take
|
||||
2. Where should it go? (which package -- depends on universality)
|
||||
3. Subscribing on the client
|
||||
1. Subscriptions should be initiated by templates/components that need the data
|
||||
2. Global required data should be subscribed by an always there "layout" template
|
||||
3. Retrieve the data from the sub at the same point as subscribing, pass down and filter via `cursor-utils`
|
||||
4. Data loading patterns
|
||||
4. Monitoring subscription readiness + errors
|
||||
1. Using `Template.subscriptionsReady`
|
||||
2. Passing subscription readiness into sub-components alongside data (see UI/UX chapter)
|
||||
5. Subscriptions + changing inputs, how autoruns can help.
|
||||
1. Basic techniques using `this.autorun`/other reactive contexts and `Template.currentData()`/other reactive sources
|
||||
2. How it works
|
||||
1. The subscription realizes it's called from within a reactive context
|
||||
2. When invalidated, subscription marks itself invalid
|
||||
3. When re-running, if re-run with the same arguments, the sub is a no-op
|
||||
4. Otherwise the new sub starts, *goes ready*, then the old sub is stopped.
|
||||
6. Paginating subscription data -- combining the above
|
||||
1. A basic paginated publication
|
||||
2. A publication that returns a count
|
||||
3. Passing pagination info into a template/component
|
||||
1. `totalCount`, `requested`, `currentItems`
|
||||
4. Passing a `loadMore` callback into a template/component, using it to increment `requested`.
|
||||
4. Other data -- global client only data
|
||||
1. Concept of a "store"
|
||||
2. Types of store:
|
||||
1. If it's a single dimension, use a reactive var
|
||||
2. If it's a few dimensions (or you need HCR), use a named reactive dict
|
||||
3. If you need to query it, use a local collection.
|
||||
3. How to listen to a store (autorun / helper / getMeteorData / angular version?)
|
||||
4. How to update a store:
|
||||
1. Built in APIs
|
||||
2. Adding APIs to stores via `XStore.foo = () => {}` (they are singletons, so no need to make class)
|
||||
5. Updating data via methods
|
||||
1. See forms chapter, advanced methods section for details
|
||||
2. Method call flow chart
|
||||
6. Publishing relational data
|
||||
1. Common misconceptions about publication reactivity + naive implementation
|
||||
1. There's no reactivity in a publish function apart from:
|
||||
1. `userId`
|
||||
2. The way that `publishCursor` works.
|
||||
2. Using publish-composite to get it done the way you'd expect.
|
||||
3. Paginating lists (UI/UX chapter)
|
||||
7. Complex authorization in publications
|
||||
1. Is kind of impossible to do correctly - https://github.com/meteor/meteor/issues/5529 (unelss we recommend a fully reactive publish solution, which we don't)
|
||||
8. Publishing non-mongo data
|
||||
1. Custom publication patterns - how to decouple your backend data format from your frontend (if you want!)
|
||||
2. Poll-publish [can we publish our Galaxy package here? or has someone made this?]
|
||||
3. Be super careful about leaking!! (How to detect this)
|
||||
9. Turning pubs into REST endpoints (via `simple:rest`)
|
||||
69
meta/outlines/deployment.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Deployment, monitoring and analytics
|
||||
|
||||
1. Deploying a web application
|
||||
1. A web application is not necessarily like any other piece of released software
|
||||
1. Can be updated frequently
|
||||
2. Still QA is important!
|
||||
2. Deployment environments: production vs staging vs development
|
||||
1. Staging should be as close as possible to production
|
||||
3. Meteor ENV + settings
|
||||
1. Use ENV for "host specific" things -- usually just MONGO_URLs and MAIL_URLs
|
||||
2. Use settings for environment specific things, like 3rd party secrets.
|
||||
3. Settings *should not* be part of the repository (too much trust in 3rd parties). Ideally stored in some secure location.
|
||||
2. Other things to consider when deploying
|
||||
1. Domain names
|
||||
2. SSL certificates
|
||||
3. CDNs
|
||||
1. What they are for
|
||||
2. How they are usually used -- e.g. CloudFront (find a useful general article to link to)
|
||||
3. Deployment options
|
||||
1. Deploying to .meteor.com via `meteor deploy`.
|
||||
1. How to do it
|
||||
2. Managing a deployment / deleting
|
||||
3. Accessing mongo
|
||||
4. Performance characteristics => don't use for production
|
||||
2. Deploying with MUP
|
||||
1. Description of what MUP does
|
||||
2. Link to some articles w/ example content
|
||||
3. Deploying with Modulus
|
||||
1. Description of what Modulus is
|
||||
2. Link to some articles w/ example content
|
||||
4. Deploying with Galaxy
|
||||
1. Description of Galaxy
|
||||
2. How to do it
|
||||
3. Management on the commandline
|
||||
4. The Galaxy UI -- link to galaxy docs
|
||||
5. How rolling app updates work, and why they are important
|
||||
4. Deployment process
|
||||
1. Why it's important to have a deployment process (it's easy to mess up a web application deployment)
|
||||
2. The steps in getting a release to production
|
||||
1. Deploy to staging + migrate data
|
||||
2. QA on staging
|
||||
3. Fix and repeat
|
||||
4. Deploy to production + migrate
|
||||
5. Final QA
|
||||
3. Automating QA via acceptance testing
|
||||
4. Understanding what happens during a (rolling deployment)
|
||||
1. Multiple versions running concurrently
|
||||
2. Therefore you app needs to (temporarily) be resistent to both data formats
|
||||
3. How to do a 2 stage deployment to allow schema changes
|
||||
5. Monitoring users via analytics
|
||||
1. It's useful to understand who is using your application
|
||||
2. Using `okgrow:analytics`
|
||||
3. A sample service -- probably GA
|
||||
6. Monitoring your application via APM
|
||||
1. Understanding the typical performance profile of a Meteor application
|
||||
1. observers x mutations ~== total CPU usage
|
||||
2. When the CPU is pegged, many other problems can occur that aren't necessarily related to the root problem.
|
||||
3. Finding observer leaks
|
||||
4. Using CPU detective to find out which observers are guilty [is this a thing yet avi?]
|
||||
2. Using Galaxy's APM
|
||||
1. Metrics
|
||||
2. Logging
|
||||
3. Using Kadira
|
||||
1. What Kadira is
|
||||
2. Monitoring resource usage
|
||||
3. Monitoring Method + Publication latency -- and what this means.
|
||||
1. Over time
|
||||
2. Getting traces to help discover bottlenecks
|
||||
4. Monitoring observer re-use
|
||||
28
meta/outlines/forms.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Forms
|
||||
|
||||
1. Forms call methods
|
||||
1. Read the method article first
|
||||
2. The basic API of a "form" method -- throwing `ValidationError`s
|
||||
3. Using mdg:validated-method to build a simple form method
|
||||
2. Building a basic form to call a method
|
||||
1. HTML example with no frameworks
|
||||
2. Catching submit event
|
||||
3. Parsing data
|
||||
4. Using method to validate
|
||||
5. Displaying errors
|
||||
3. Autoform
|
||||
1. Building a simple form with inputs + a submit button
|
||||
2. Pointers to documentation explaining other standard inputs + other packages
|
||||
2. Hooking it up + displaying errors
|
||||
3. What to do if the form succeed + when (cf UX chapter)
|
||||
1. Disabling submit button during submission
|
||||
4. "Realtime" form validation
|
||||
1. The Method.validate() property
|
||||
2. UX considerations -- dirty fields, tracking state (cf Blaze / UI/UX chapter)
|
||||
5. Building a "quickform" by passing a schema to autoform
|
||||
1. Attaching a schema to methods
|
||||
2. [Ideally building a form for a method with a schema is now 1 line of code!]
|
||||
6. Advanced: file uploads
|
||||
1. Adding binary support to a method + using FileReader (briefly -- find an article)
|
||||
2. Using CFS to upload better [should we just skip 1?]
|
||||
3. Uploading to a 3rd party server (just an sketch, eg. s3 signed URLs)
|
||||
45
meta/outlines/methods.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Methods
|
||||
|
||||
1. What are Methods?
|
||||
1. Methods are your app's data input API methods, like a POST request in REST
|
||||
2. One of the most advanced RPC systems for the web, with lots of "hidden" benefits that make your life easier
|
||||
2. Defining a method
|
||||
1. Simple Meteor core version
|
||||
2. Meteor core version with boilerplate
|
||||
3. mdg:validated-method version
|
||||
4. What it means to define a method on the server only vs. client and server
|
||||
3. Calling a method
|
||||
1. Call in the console
|
||||
2. Call in an event handler
|
||||
1. How to indicate errors to users outside of forms (flash notification pattern UX chapter)
|
||||
3. Call from a form submit
|
||||
4. Calling methods serially (best to combine to a single method)
|
||||
4. Error handling
|
||||
1. Throwing errors from a method
|
||||
2. Throwing an error from the simulation prevents the server call
|
||||
3. Catching errors on the client from the callback
|
||||
4. ValidationError
|
||||
5. Wiring up a method to a simple form
|
||||
1. Basically, the task adding code from the todos app
|
||||
2. Read more in the forms article
|
||||
6. Methods can be used for data loading, but have disadvantages compared to publications
|
||||
7. Advanced concepts
|
||||
1. Method life cycle
|
||||
1. Method simulation executed on client
|
||||
2. Method message sent over DDP: name, arguments
|
||||
3. Method executed on the server
|
||||
4. Return value sent to the client
|
||||
5. After all DB operations are complete and published, updated message sent to the client
|
||||
6. Method callback fires with the result
|
||||
2. Benefits of Methods over REST
|
||||
1. Non-blocking but synchronous-looking (other methods from the same client won't run at the same time unless you use this.unblock)
|
||||
2. Methods always execute in the same order, and results come back in the same order
|
||||
3. Deeply optimized for optimistic UI because of change tracking
|
||||
4. Calling a method from inside another method on the server
|
||||
5. Consistent ID generation and optimistic UI
|
||||
6. The `onResultReceived` callback, and when to use it
|
||||
7. Method retries, and figuring out that a method didn't succeed
|
||||
8. Comparison with allow/deny
|
||||
1. Methods are better in every way
|
||||
2. You still get Optimistic UI by default
|
||||
3. How to factor your security into a global place if you really want to, recommend something like Wekan's pattern: https://github.com/wekan/wekan/blob/8d5d7ee678d9a396a0427329200e5bf0573eb730/models/boards.js#L124
|
||||
73
meta/outlines/mobile.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Mobile outline
|
||||
|
||||
1. Introduction to Meteor's built-in mobile integration
|
||||
1. Based on Cordova
|
||||
2. UI and logic are still written in HTML and JavaScript, but it's better than a mobile website since all of the code is running locally!
|
||||
3. You can use Cordova plugins as a bridge to native functionality
|
||||
4. Update your app outside of the regular app store process
|
||||
5. Downside: UI can sometimes feel less than native if you aren't careful
|
||||
2. Installing prerequisites
|
||||
1. Meteor prompts you automatically
|
||||
2. Link to the appropriate pages:
|
||||
1. iOS on Mac
|
||||
2. Android on Mac
|
||||
3. Android on Linux
|
||||
4. Windows support in the pipeline, in the meantime the best bet is to... install a Linux VM? Investigate the Vagrant build solution. Question here.
|
||||
3. Development environment
|
||||
1. Running your app in a simulator/emulator for development
|
||||
1. iOS on Mac, your best bet is to use Xcode via meteor run ios-device - ios-sim is convenient for a demo, but doesn't let you configure anything
|
||||
2. Android, run meteor run android, make sure you have acceleration installed or it will be ultra slow.
|
||||
1. How to configure different emulators
|
||||
2. Logging
|
||||
1. See server-side logs in the terminal as usual
|
||||
2. Android
|
||||
1. Meteor prints them for you in the terminal, but for more in-depth stuff, see below
|
||||
2. See client-side JS logs and debug stuff in the Chrome inspector with the inspect devices tool: https://developers.google.com/web/tools/chrome-devtools/debug/remote-debugging/remote-debugging?hl=en
|
||||
3. Native logs for Cordova plugins and Meteor native code exceptions with Android Device Monitor: http://developer.android.com/tools/help/monitor.html
|
||||
3. iOS
|
||||
1. JavaScript logging in Safari web inspector
|
||||
2. Native logs in Xcode when running your app
|
||||
4. Question: where exactly do we capture logs correctly and log them to terminal?
|
||||
3. Testing
|
||||
4. Debugging
|
||||
4. Designing for mobile - link to external resources or UX guide for all points
|
||||
1. Credit: https://github.com/awatson1978/meteor-cookbook/blob/master/cookbook/mobile.md
|
||||
2. Media queries/CSS libraries
|
||||
3. Scroll bounce with cordova settings and CSS properties
|
||||
4. Swiping - Hammer.js
|
||||
5. Animations - link to UX article about animations, mention that they are especially important on mobile
|
||||
6. Fastclick, installed by default, explain what it does
|
||||
7. Loading screens, installed by default, explain how to control it
|
||||
5. Native functionality with Cordova plugins
|
||||
1. How to add a plugin with meteor add cordova:something, or depending on it in a package
|
||||
2. Make sure to wait until Meteor.startup for plugins to load
|
||||
3. Cordova plugins can be a bit of a gamble, since it's hard to make native code that works on a wide variety of devices. Your best bet is to test on a certain set of devices/OS versions yourself
|
||||
4. Search for plugins on https://cordova.apache.org/plugins/
|
||||
5. Useful lists of plugins
|
||||
1. Core plugins [at least camera + geolocation]
|
||||
2. http://docs.telerik.com/platform/appbuilder/creating-your-project/using-plugins/using-core-plugins/using-core-plugins
|
||||
6. Hot code push on mobile
|
||||
1. Be careful when analyzing the outcome of hot code push - for example, you need a new app to use new Cordova plugins; so if you add any you need to make sure their app shell has the right plugins
|
||||
2. Controlling when reload happens
|
||||
1. Different states your app can be in, and how to detect them
|
||||
1. Never hot code pushed, fresh
|
||||
2. New update currently downloading
|
||||
3. New update downloaded, not reloaded yet
|
||||
4. Reloaded to a new updated version
|
||||
2. reload-on-resume
|
||||
3. Special case: when your app needs to hot code push right after the user downloaded it; you should display an upgrade screen in this case
|
||||
3. https://github.com/meteor/meteor/issues/5973 - solve or document
|
||||
8. Accessing local files
|
||||
1. Files/assets from the app bundle
|
||||
2. Local files (not possible in Meteor 1.2, we're working on it)
|
||||
10. Configuring your project
|
||||
1. App icons
|
||||
2. Preferences
|
||||
3. Overriding parts of the Xcode project
|
||||
11. Deploying to the app store
|
||||
1. Android: https://github.com/meteor/meteor/wiki/How-to-submit-your-Android-app-to-Play-Store
|
||||
2. iOS: https://github.com/meteor/meteor/wiki/How-to-submit-your-iOS-app-to-App-Store
|
||||
1. TestFlight
|
||||
|
||||
Useful links:
|
||||
http://blog.differential.com/easily-connect-react-native-to-a-meteor-server/
|
||||
33
meta/outlines/old-structure.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Application structure and code style
|
||||
|
||||
XXX will change majorly before Guide release due to ES2015 modules
|
||||
|
||||
1. What's different between Meteor and other kinds of apps?
|
||||
1. All JavaScript, enables code sharing
|
||||
2. Structure is flexible
|
||||
2. Client vs. server vs. both
|
||||
2. General JavaScript stucture
|
||||
1. Directory structure around features, not client/server
|
||||
2. LESS/SCSS files are in the same directory as components
|
||||
3. One file per unit - template, method, collection, etc
|
||||
4. Example app structure, model after todos XXX
|
||||
7. Splitting your project into multiple apps/entry points
|
||||
1. Why you want this structure
|
||||
1. Lots of different totally separate UIs, and you want to avoid intersecting the code
|
||||
1. Admin app
|
||||
2. Mobile vs. desktop
|
||||
3. Different classes of users
|
||||
2. Independently scaled and secured services
|
||||
3. Independent development teams
|
||||
2. Sharing code between different apps
|
||||
1. Local packages
|
||||
2. Git submodules
|
||||
3. PACKAGE_DIRS
|
||||
4. One or many repositories
|
||||
3. Sharing data between different apps
|
||||
1. Through database directly
|
||||
2. Through DDP API
|
||||
3. Through REST API
|
||||
4. Sharing user accounts between different apps
|
||||
1. AccountsClient/AccountsServer
|
||||
2. Accounts connection URL
|
||||
37
meta/outlines/packages.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Building a package
|
||||
|
||||
1. Why you might want to build a package
|
||||
1. You have a package-based app
|
||||
2. You want to publish some code for people to use in the community
|
||||
3. You want to share code within your organization between different apps
|
||||
2. Creating a package using the command line tool
|
||||
3. Parts of a Meteor package
|
||||
1. What different parts of package.js mean, point to the docs
|
||||
2. Depending on other packages and build plugins
|
||||
1. You can't depend on a specific version of Meteor; that's not what versionsFrom does
|
||||
3. Adding files and assets
|
||||
4. About architectures
|
||||
5. Semver and the constraint solver
|
||||
6. Cordova plugins
|
||||
7. npm packages
|
||||
1. npm on the client with Browserify
|
||||
2. Converting asynchronous Node APIs to synchronous-looking Fiber APIs
|
||||
1. Meteor.bindEnvironment
|
||||
1. Meteor.wrapAsync
|
||||
2. Promise and Promise.await
|
||||
3. (Probably in Meteor 1.3) `async`/`await`
|
||||
8. Local packages vs. published packages, and the Isopack format
|
||||
9. Testing your package (basically just link to testing guide)
|
||||
9. Structuring your package
|
||||
1. Standard template for package.js file
|
||||
1. Try to export only one symbol, that matches the name of your package
|
||||
2. Different things you might want to have in a package
|
||||
1. LESS mixins
|
||||
2. Templates
|
||||
3. Collections - don't expose the collection directly, have an API for talking to it in case you need to change the schema or guarantees later
|
||||
10. Build plugins
|
||||
1. Build plugins are the way to extend Meteor's build system. If you find yourself writing a package that does something with an app's source code, it almost certainly should be implemented as a build plugin to take advantage of Meteor's built in features, like caching
|
||||
2. A basic build plugin that compiles files one-to-one
|
||||
3. A caching build plugin that compiles files one-to-one
|
||||
4. Compiling inter-related files
|
||||
5. Some good examples of build plugins to build off of
|
||||
27
meta/outlines/publish-packages.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Publishing a Package
|
||||
|
||||
1. Pros/cons of publishing a package
|
||||
1. Considering what's involved in publishing a package.
|
||||
2. Do you think you'll be able to maintain it properly?
|
||||
3. Is the code complete enough to be useful, or does it need work to be made generic?
|
||||
2. How to decide what's in a package / more than one
|
||||
1. Is there any core functionality that could be reused elsewhere? (e.g. route matching)
|
||||
2. Is there pure JS code that could be published to npm?
|
||||
3. Principle of a single symbol per package could help (see structure chapter).
|
||||
3. How to write great documentation.
|
||||
1. README vs longer documentation/guide
|
||||
2. Is the documentation too complicated? Maybe the package should be simpler?
|
||||
3. Maintaining a changelog
|
||||
4. Licensing
|
||||
4. Testing your package now and in the future
|
||||
1. See testing chapter
|
||||
2. Use travis + special badge to test against core + devel
|
||||
5. Publishing!
|
||||
1. Semvar + version number changes
|
||||
2. Tracking meteor + other package versions
|
||||
3. Changing your profile on Atmosphere
|
||||
4. Publicizing your package on forums + relevant issues on GH/forums
|
||||
5. Using CI: https://medium.com/@sakulstra/meteor-ci-test-and-deploy-e779b62109a3#.7e6y8e3et
|
||||
6. Maintaining packages
|
||||
1. Dealing with issues and PRs in a respectful and efficient way
|
||||
2. Getting help from the community + finding co-maintainers
|
||||
22
meta/outlines/react.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# React
|
||||
|
||||
1. Introduction to React
|
||||
1. Background: developed by Facebook, used widely in production, vibrant ecosystem
|
||||
2. Installing and using React in Meteor 1.3
|
||||
3. A simple example
|
||||
|
||||
2. Meteor and React
|
||||
1. JSX and the Meteor build tool (link to build tool article)
|
||||
2. Using 3rd party react libs in apps and packages
|
||||
|
||||
3. Getting Meteor Data into React Components
|
||||
1. The `ReactMeteorData` mixin
|
||||
2. The `ReactMeteorDataContainer` higher order component
|
||||
3. Containers vs. presentational components [call out to UX guide]
|
||||
4. Optimizing re-renders with `shouldComponentUpdate`
|
||||
|
||||
6. Routing with React
|
||||
2. FlowRouter
|
||||
1. Using mounte
|
||||
1. `react-router`
|
||||
1. Basic demo, "spirit" of our routing guide
|
||||