Meteor Guideで紹介されているコマンド・パッケージ・ソースコード一覧です。個人的に使わなさそうなものは除外しています。また検索性重視で1ページにまとめています。随時更新。
Introduction
# Meteor インストール
curl https://install.meteor.com/ | sh
# 新規 Meteor アプリ作成
meteor create myapp
# ローカルで Meteor 起動
cd myapp
meteor npm install
meteor
Code Style
meteor npm install --save-dev eslint-config-airbnb eslint-plugin-import eslint-plugin-meteor eslint-plugin-react eslint-plugin-jsx-a11y eslint
package.json
{
...
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint --silent"
},
"eslintConfig": {
"plugins": [
"meteor"
],
"extends": [
"airbnb",
"plugin:meteor/recommended"
],
"rules": {
"meteor/eventmap-params": [
2, { "templateInstanceParamName": "instance" }
],
"import/no-unresolved": [
2, { "ignore": ["^meteor/"] }
]
}
}
}
# ESLint 実行
meteor npm run lint
# for Atom
apm install language-babel
apm install linter
apm install linter-eslint
Application Structure
Introduction to using import and export
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
export const listRenderHold = LaunchScreen.hold(); // named export
export { Todos }; // named export
export default Lists; // default export
export default new Collection('lists'); // default export
Example directory layout
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
Migrating to Meteor 1.3
飛ばします。
Collections and Schemas
// コレクションの作成
Todos = new Mongo.Collection('Todos');
// ローカルコレクションの作成
SelectedTodos = new Mongo.Collection(null);
SimpleSchema
meteor add aldeed:simple-schema
Lists.schema = new SimpleSchema({
name: {type: String},
incompleteCount: {type: Number, defaultValue: 0},
userId: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true}
});
Collection2
meteor add aldeed:collection2
Lists.attachSchema(Lists.schema);
defaultValue and data cleaning
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 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');
Hooks on insert/update/remove
class ListsCollection extends Mongo.Collection {
// ...
remove(selector, callback) {
Package.todos.Todos.remove({listId: selector});
return super.remove(selector, callback);
}
}
Migrating to a new schema
meteor add percolate:migrations
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}});
}
});
Bulk changes
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();
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}});
});
// We need to wrap the async function to get a synchronous API that migrations expects
const execute = Meteor.wrapAsync(batch.execute, batch);
return execute();
},
down() {
Lists.update({}, {$unset: {todoCount: true}});
}
});
Running migrations
// After running `meteor shell` on the command line:
Migrations.migrateTo('latest');
Breaking schema changes
Migrations.migrateTo(0);
Collection helpers
meteor add dburles:collection-helpers
Lists.helpers({
// A list is considered to be private if it has a userId set
isPrivate() {
return !!this.userId;
}
});
const list = Lists.findOne();
if (list.isPrivate()) {
console.log('The first list is private!');
}
Association helpers
Lists.helpers({
todos() {
return Todos.find({listId: this._id}, {sort: {createdAt: -1}});
}
});
const list = Lists.findOne();
console.log(`The first list has ${list.todos().count()} todos`);
Publications and Data Loading
Defining a publication
Meteor.publish('lists.public', function() {
return Lists.find({
userId: {$exists: false}
}, {
fields: Lists.publicFields
});
});
Meteor.publish('lists.private', function() {
if (!this.userId) {
return this.ready();
}
return Lists.find({
userId: this.userId
}, {
fields: Lists.publicFields
});
});
Meteor.publish('todos.inList', function(listId) {
// We need to check the `listId` is the type we expect
new SimpleSchema({
listId: {type: String}
}).validate({ listId });
// ...
});
Subscribing to data
const handle = Meteor.subscribe('lists.public');
const handle2 = Meteor.subscribe('todos.inList', list._id);
Subscribe in UI components
Template.Lists_show_page.onCreated(function() {
this.getListId = () => FlowRouter.getParam('_id');
this.autorun(() => {
this.subscribe('todos.inList', this.getListId());
});
});
Subscription readiness
const handle = Meteor.subscribe('lists.public');
Tracker.autorun(() => {
const isReady = handle.ready();
console.log(`Handle is ${isReady ? 'ready' : 'not ready'}`);
});
FindFromPublication
meteor add percolate:find-from-publication
PublishCounts
meteor add tmeasday:publish-counts
Meteor.publish('Lists.todoCount', function({ listId }) {
new SimpleSchema({
listId: {type: String}
}).validate({ listId });
Counts.publish(this, `Lists.todoCount.${listId}`, Todos.find({listId}));
});
Counts.get(`Lists.todoCount.${listId}`)
ReactiveVar / ReactiveDict
meteor add reactive-var
meteor add reactive-dict
PublishComposite
meteor add reywood:publish-composite
Loading data from a REST endpoint with a publication
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, for simplicity
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;
if (publishedKeys[doc._id]) {
this.added(COLLECTION_NAME, doc._id, doc);
}
}
});
};
poll();
this.ready();
const interval = Meteor.setInterval(poll, POLL_INTERVAL);
this.onStop(() => {
Meteor.clearInterval(interval);
});
});
simple:rest パッケージ
meteor add simple:rest
Methods
Defining
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 }
});
}
});
Calling
Meteor.call('todos.updateText', {
todoId: '12345',
newText: 'This is a todo item.'
}, (err, res) => {
if (err) {
alert(err);
} else {
// success!
}
});
Advanced Methods with mdg:validated-method
meteor add 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 }
});
}
});
Handling errors
// 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!
}
});
Users and Accounts
meteor add accounts-ui
{{> loginButtons}}
# 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
meteor add useraccounts:core useraccounts:unstyled
meteor add useraccounts:flow-routing
Publishing custom data
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);
});
Roles and permissions
alanning:roles パッケージ
meteor add alanning:roles
// 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);
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);
Per-document permissions
Lists.helpers({
// ...
editableBy(userId) {
if (!this.userId) {
return true;
}
return this.userId === userId;
},
// ...
});
const list = Lists.findOne(listId);
if (! list.editableBy(userId)) {
throw new Meteor.Error('unauthorized',
'Only list owners can edit private lists.');
}
Testing | Meteor Guide
meteor add practicalmeteor:mocha
meteor add xolvio:cleaner
meteor add dburles:factory
meteor add hwillson:stub-collections
meteor add velocity:meteor-stubs
# for React
meteor npm install -D enzyme
# Testing publications
meteor add johanbrook:publication-collector
# Running unit tests
meteor test --driver-package practicalmeteor:mocha --port 3100
# Running full-app tests
meteor test --full-app --driver-package practicalmeteor:mocha
Acceptance testing
# node >= 4
npm install --global chimp
package.json
{
"scripts": {
"chimp-watch": "chimp --ddp=http://localhost:3000 --watch --mocha --path=tests",
"chimp-test": "chimp --mocha --path=tests"
}
}
Running acceptance tests
# In one terminal
meteor
# In another
meteor npm run chimp-watch
Creating data
meteor add tmeasday:acceptance-test-driver
meteor test --full-app --driver-package tmeasday:acceptance-test-driver
Continuous Integration
Command line
meteor add dispatch:mocha-phantomjs
meteor test --once --driver-package dispatch:mocha-phantomjs
{
"scripts": {
"test": "meteor test --once --driver-package dispatch:mocha-phantomjs"
}
}
CircleCI
circle.yml
machine:
node:
version: 0.10.43
dependencies:
override:
- curl https://install.meteor.com | /bin/sh
- npm install
checkout:
post:
- git submodule update --init
URLs and Routing
meteor add kadira:flow-router
meteor add zimme:active-route
meteor add arillo:flow-router-helpers
# for Blaze
meteor add kadira:blaze-layout
User Interfaces
# Internationalization
meteor add tap:i18n
# Animating changes in visiblity
meteor add percolate:momentum
Blaze
Use a reactive dict for state
meteor add reactive-dict
Template.Lists_show.onCreated(function() {
this.state = new ReactiveDict();
this.state.setDefault({
editing: false,
editingTodo: false
});
});
Attach functions to the instance
import {
updateName,
} from '../../api/lists/methods.js';
Template.Lists_show.onCreated(function() {
this.saveList = () => {
this.state.set('editing', false);
updateName.call({
listId: this.data.list._id,
newName: this.$('[name=name]').val()
}, (err) => {
err && alert(err.error);
});
};
});
Template.Lists_show.events({
'submit .js-edit-form'(event, instance) {
event.preventDefault();
instance.saveList();
}
});
Scope DOM lookups to the template instance
Template.Lists_show.events({
'click .js-todo-add'(event, instance) {
instance.$('.js-todo-new input').focus();
}
});
Passing HTML content as a template argument
meteor add kadira:blaze-layout
{{> Template.dynamic templateName dataContext}}
Controlling re-rendering
meteor add peerlibrary:computed-field
React
# npm で React をインストール
meteor npm install --save react react-dom
# Using 3rd party packages
meteor npm install --save griddle-react
# Blaze を使わない場合
meteor remove blaze-html-templates
meteor add static-html
# React 内で Blaze テンプレートを使用する場合
meteor add gadicc:blaze-react-component
# ReactMeteorData
meteor add react-meteor-data
meteor npm install --save react-addons-pure-render-mixin
# Routing
meteor add kadira:flow-router
meteor npm install --save react-mounter
## or
meteor npm install --save react-router
# Using React in Atmosphere Packages
meteor add tmeasday:check-npm-versions
Angular
飛ばします。
Atmosphere vs. npm
飛ばします。
Using Atmosphere Packages
# Searching for packages
meteor search <WORD>
meteor show kadira:flow-router
# Installing Atmosphere Packages
meteor add <PACKAGE>
# See all the Atmosphere packages
meteor list
# Remove an unwanted Atmosphere package
meteor remove <PACKAGE>
Writing Atmosphere Packages
# Create a package
meteor create --package my-package
# Testing packages
meteor test-packages ./ --driver-package practicalmeteor:mocha
# Publish a package
meteor publish
Using npm Packages
meteor npm install
meteor npm install --save meteor-node-stubs
meteor npm install --save moment
meteor npm shrinkwrap
Shrinkpack
npm install -g shrinkpack
meteor npm install moment
meteor npm shrinkwrap
shrinkpack
Writing npm Packages
mkdir my-package
cd my-package/
meteor npm init
Including in your app
# Inside node_modules
cd my-app/node_modules/
mkdir my-package
cd my-package/
meteor npm init
git add -f ./ # or use a git submodule
# npm link
cd ~/
mkdir my-package
cd my-package/
meteor npm init
cd ~/my-app/
meteor npm link ~/my-package
Mobile
meteor add-platform ios
sudo xcodebuild -license accept
meteor add-platform android
meteor remove-platform ios android
meteor list-platforms
meteor run ios
meteor run ios-device
meteor run android
meteor run android-device
# Installing plugins
meteor add cordova:cordova-plugin-camera@1.2.0
# Installing a plugin from Git
meteor add cordova:com.phonegap.plugins.facebookconnect@https://github.com/Wizcorp/phonegap-facebook-plugin.git#5dbb1583168558b4447a13235283803151cb04ec
# Installing a plugin from the local file system
meteor add cordova:cordova-plugin-underdevelopment@file://../plugins/cordova-plugin-underdevelopment
# Removing directly installed plugins
meteor remove cordova:cordova-plugin-camera
meteor remove cordova:com.phonegap.plugins.facebookconnect
meteor remove cordova:cordova-plugin-underdevelopment
# Domain whitelisting
Build System
meteor add ecmascript
# CoffeeScript
meteor add coffeescript
# Sass/SCSS
meteor add fourseven:scss
# LESS
meteor add less
# Stylus
metoer add stylus
PostCSS and AutoPrefixer
meteor remove standard-minifier-css
meteor add juliancwirko:postcss
package.json
{
"devDependencies": {
"autoprefixer": "^6.3.1"
},
"postcss": {
"plugins": {
"autoprefixer": {"browsers": ["last 2 versions"]}
}
}
}
Security
meteor add check
meteor add aldeed:simple-schema
meteor remove autopublish insecure
Securing API keys
{
"facebook": {
"clientId": "12345",
"secret": "1234567"
}
}
# 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
API keys for OAuth
meteor add service-configuration
ServiceConfiguration.configurations.upsert({
service: "facebook"
}, {
$set: {
clientId: Meteor.settings.facebook.clientId,
loginStyle: "popup",
secret: Meteor.settings.facebook.secret
}
});
SSL
meteor add force-ssl
Deployment and Monitoring
Custom deployment
# for example if deploying to a Ubuntu linux server:
npm install --production
meteor build /path/to/build --architecture os.linux.x86_64
cd my_directory
(cd programs/server && npm install)
MONGO_URL=mongodb://localhost:27017/myapp ROOT_URL=http://my-app.com node main.js
Monitoring users via analytics
meteor add okgrow:analytics
settings.json
{
"public": {
"analyticsSettings": {
// Add your analytics tracking id's here
"Google Analytics" : {"trackingId": "Your tracking ID"}
}
}
}
Enabling SEO
# for Prerender.io
meteor add dfischer:prerenderio
# for Galaxy
meteor add mdg:seo
# To set <title> tags and other <head> content
meteor add kadira:dochead
ひとまずここまで。