Merge branch 'modern-bundler-integration' into release-3.4

This commit is contained in:
Nacho Codoñer
2025-09-19 12:52:04 +02:00
12 changed files with 1004 additions and 158 deletions

View File

@@ -40,6 +40,10 @@ export default defineConfig({
text: "Meteor + Vue + vue-meteor-tracker",
link: "/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker",
},
{
link: "/tutorials/application-structure/index",
text: "Application structure",
},
],
},
],
@@ -272,108 +276,120 @@ export default defineConfig({
text: "Packages",
items: [
{
text: "accounts-ui",
link: "/packages/accounts-ui",
text: "Overview",
link: "/packages/index",
},
{
text: "accounts-passwordless",
link: "/packages/accounts-passwordless",
text: "Accounts and security",
items: [
{
text: "accounts-ui",
link: "/packages/accounts-ui",
},
{
text: "accounts-passwordless",
link: "/packages/accounts-passwordless",
},
{
text: "accounts-2fa",
link: "/packages/accounts-2fa",
},
{
text: "roles",
link: "/packages/roles",
},
{
text: "oauth-encryption",
link: "/packages/oauth-encryption",
},
{
text: "browser-policy",
link: "/packages/browser-policy",
},
]
},
{
text: "accounts-2fa",
link: "/packages/accounts-2fa",
text: "Developer tools",
items: [
{
text: "audit-arguments-checks",
link: "/packages/audit-argument-checks",
},
{
text: "bundler-visualizer",
link: "/packages/bundle-visualizer",
},
{
text: "hot-module-replacement",
link: "/packages/hot-module-replacement",
},
{
text: "fetch",
link: "/packages/fetch",
},
{
text: "logging",
link: "/packages/logging",
},
{
text: "underscore",
link: "/packages/underscore",
},
{
text: "autoupdate",
link: "/packages/autoupdate",
},
{
text: "modern-browsers",
link: "/packages/modern-browsers",
},
{
text: "modules",
link: "/packages/modules",
},
{
text: "random",
link: "/packages/random",
},
{
text: "server-render",
link: "/packages/server-render",
},
{
text: "standard-minifier-css",
link: "/packages/standard-minifier-css",
},
{
text: "url",
link: "/packages/url",
},
{
text: "webapp",
link: "/packages/webapp",
},
]
},
{
text: "appcache",
link: "/packages/appcache",
},
{
text: "audit-arguments-checks",
link: "/packages/audit-argument-checks",
},
{
text: "autoupdate",
link: "/packages/autoupdate",
},
{
text: "browser-policy",
link: "/packages/browser-policy",
},
{
text: "bundler-visualizer",
link: "/packages/bundle-visualizer",
},
{
text: "coffeescript",
link: "/packages/coffeescript",
},
{
text: "ecmascript",
link: "/packages/ecmascript",
},
{
text: "fetch",
link: "/packages/fetch",
},
{
text: "hot-module-replacement",
link: "/packages/hot-module-replacement",
},
{
text: "roles",
link: "/packages/roles",
},
{
text: "less",
link: "/packages/less",
},
{
text: "logging",
link: "/packages/logging",
},
{
text: "markdown",
link: "/packages/markdown",
},
{
text: "modern-browsers",
link: "/packages/modern-browsers",
},
{
text: "modules",
link: "/packages/modules",
},
{
text: "oauth-encryption",
link: "/packages/oauth-encryption",
},
{
text: "random",
link: "/packages/random",
},
{
text: "react-meteor-data",
link: "/packages/react-meteor-data",
},
{
text: "server-render",
link: "/packages/server-render",
},
{
text: "standard-minifier-css",
link: "/packages/standard-minifier-css",
},
{
text: "underscore",
link: "/packages/underscore",
},
{
text: "url",
link: "/packages/url",
},
{
text: "webapp",
link: "/packages/webapp",
text: "Framework compatibility",
items: [
{
text: "coffeescript",
link: "/packages/coffeescript",
},
{
text: "ecmascript",
link: "/packages/ecmascript",
},
{
text: "less",
link: "/packages/less",
},
{
text: "react-meteor-data",
link: "/packages/react-meteor-data",
},
]
},
{
link: "/packages/packages-listing",
@@ -382,41 +398,37 @@ export default defineConfig({
{
link: "/community-packages/index",
text: "Community Packages",
},
],
collapsed: true,
},
{
text: "Community Packages",
link: "/community-packages/index",
items: [
{
text: "Meteor RPC",
link: "/community-packages/meteor-rpc",
},
{
text: "jam:method",
link: "/community-packages/jam-method",
},
{
text: "jam:pub-sub",
link: "/community-packages/pub-sub",
},
{
text: "jam:mongo-transactions",
link: "/community-packages/mongo-transactions",
},
{
text: "jam:soft-delete",
link: "/community-packages/soft-delete",
},
{
text: "jam:archive",
link: "/community-packages/archive",
},
{
text: "jam:offline",
link: "/community-packages/offline",
items: [
{
text: "Meteor RPC",
link: "/community-packages/meteor-rpc",
},
{
text: "jam:method",
link: "/community-packages/jam-method",
},
{
text: "jam:pub-sub",
link: "/community-packages/pub-sub",
},
{
text: "jam:mongo-transactions",
link: "/community-packages/mongo-transactions",
},
{
text: "jam:soft-delete",
link: "/community-packages/soft-delete",
},
{
text: "jam:archive",
link: "/community-packages/archive",
},
{
text: "jam:offline",
link: "/community-packages/offline",
},
],
collapsed: true,
},
],
collapsed: true,
@@ -456,6 +468,10 @@ export default defineConfig({
link: "/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker",
text: "Meteor + Vue + vue-meteor-tracker",
},
{
link: "/tutorials/application-structure/index",
text: "Application structure",
},
],
collapsed: true,
},

View File

@@ -27,6 +27,10 @@ const props = defineProps({
from: {
type: String,
default: ''
},
isDefaultImport: {
type: Boolean,
default: false
}
})
@@ -99,7 +103,8 @@ const debug = (name) => {
<template>
<div>
<h2 :id="link">
<a class="header-anchor" :href="'#' + link" :aria-label="'Permalink to &quot;' + ui.longname + '&quot;'"></a>
<a class="header-anchor" :href="'#' + link"
:aria-label="'Permalink to &quot;' + ui.longname + '&quot;'"></a>
{{ showName(ui.longname) }}
<Locus v-if="ui.locus && ui.locus !== 'Anywhere'" :locus="ui.locus" />
@@ -112,10 +117,13 @@ const debug = (name) => {
<slot />
<ParamTable v-if="isFunction || isClass" :params="ui.params" :options="ui.options" :from="gitHubSource" />
<template v-if="!hasCustomExample">
<Booleans v-if="isBoolean" :memberof="ui.memberof" :from="ui.module" :longname="ui.longname" />
<Functions v-if="isFunction" :from="ui.module" :longname="ui.longname" :params="ui.params" :fnName="ui.name"
<Booleans v-if="isBoolean" :memberof="ui.memberof" :from="ui.module" :is-default-import="isDefaultImport"
:longname="ui.longname" />
<Functions v-if="isFunction" :from="ui.module" :is-default-import="isDefaultImport"
:longname="ui.longname" :params="ui.params" :fnName="ui.name"
:memberof="isInstance ? instanceName : ui.memberof" :scope="ui.scope" :returns="ui.returns" />
<Classes v-if="isClass" :params="ui.params" :from="ui.module" :longname="ui.longname" />
<Classes v-if="isClass" :params="ui.params" :from="ui.module" :is-default-import="isDefaultImport"
:longname="ui.longname" />
</template>
@@ -133,4 +141,4 @@ h2 {
align-content: center;
justify-content: space-between;
}
</style>
</style>

View File

@@ -4,14 +4,17 @@
<script setup lang="ts">
const props = defineProps<{
from: string;
isDefaultImport: boolean;
longname: string;
memberof: string;
}>()
}>();
import ImportStatement from './Import.vue';
</script>
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span>
<pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.memberof }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'meteor/{{ props.from }}'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<pre class="shiki shiki-themes github-light github-dark vp-code"><code><ImportStatement :what="props.memberof" :is-default-import="props.isDefaultImport" :from="props.from" :scope="'static'"/>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">/** </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@type</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> {Boolean}</span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> */</span></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ({{ props.longname }}) {</span></span>
@@ -19,6 +22,3 @@ const props = defineProps<{
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre>
</div>
</template>

View File

@@ -3,9 +3,11 @@
<script setup lang="ts">
import { makePrimitiveHTML } from '../scripts/make-primitive-html';
import ImportStatement from './Import.vue';
const props = defineProps<{
from: string;
isDefaultImport: boolean;
longname: string;
params: {
name: string;
@@ -29,7 +31,7 @@ const isOneLiner = props?.params?.length === 0;
</script>
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span> <pre class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.longname.split(".")[0] }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "meteor/{{ props.from }}""</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span> <pre class="shiki shiki-themes github-light github-dark vp-code"><code><ImportStatement :what="props.longname.split('.')[0]" :is-default-import="props.isDefaultImport" :from="props.from" :scope="'static'"/>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> {{ toCamelCase(props.longname) }}</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> {{ props.longname }}</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(<span v-show="isOneLiner">);</span></span>
<span class="line" v-for="(param, index) in props.params" :key="param.name"><span v-html="makePrimitiveHTML({ primitive: [param.type.names[0]], arr: props.params, index, isOptional: param.optional, name: param.name })"/></span>
@@ -37,6 +39,3 @@ const isOneLiner = props?.params?.length === 0;
</code></pre>
</div>
</template>

View File

@@ -3,8 +3,10 @@
<script setup lang="ts">
import { makePrimitiveHTML, comment, typeComment } from '../scripts/make-primitive-html';
import ImportStatement from './Import.vue';
const props = defineProps<{
from: string;
isDefaultImport: boolean;
longname: string;
memberof: string;
fnName: string;
@@ -43,7 +45,7 @@ function getReturnType(returns: typeof props.returns) {
<template>
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span>
<pre
class="shiki shiki-themes github-light github-dark vp-code"><code><span class="line" v-if="props.scope !== 'instance' && props.from"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { {{ props.memberof.split(".")[0] }} } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "meteor/{{ props.from }}"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
class="shiki shiki-themes github-light github-dark vp-code"><code><ImportStatement :what="props.memberof.split('.')[0]" :is-default-import="props.isDefaultImport" :from="props.from" :scope="props.scope"/>
<span class="line" v-if="props.scope === 'instance'" v-html="comment(`// ${props.memberof} is an instance of ${getInstanceName(props.longname)}`)"></span>
<span class="line" v-if="props.returns !== undefined" v-html="typeComment(getReturnType(props.returns))"></span>
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><span :style="{'display': props.returns === undefined ? 'none' : ''}"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">const </span>result = </span>{{ props.memberof }}.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">{{ trimFnName(props.fnName) }}</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(<span v-show="isOneLiner">);</span></span></span>
@@ -51,6 +53,3 @@ function getReturnType(returns: typeof props.returns) {
<span v-show="!isOneLiner" class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">);</span></span></code></pre>
</div>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
const props = defineProps<{
scope: string;
what: string;
from: string;
isDefaultImport: boolean;
}>()
const exportName = (what: string) => props.isDefaultImport ? ` ${what} ` : ` { ${what} } `
</script>
<template>
<span class="line" v-if="props.scope !== 'instance' && props.from">
<span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span>
<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {{ exportName(what) }} </span>
<span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span>
<span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "meteor/{{ props.from }}"</span>
<span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span>
</span>
</template>

View File

@@ -0,0 +1,14 @@
## Using Atmosphere packages
### When to use Atmosphere packages
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`, `mongo` or `accounts`
- Explicitly include non-javascript files including CSS, Less or static assets
- 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](#atmosphere-package-namespacing) and package global exports without having to explicitly use ES2015 `import`
- Enforce exact version dependencies between packages using Meteor's [constraint resolver](#semantic-versionning-and-version-constraints)
- Include [build plugins](https://docs.meteor.com/api/package.html#build-plugin-api) for Meteor's build system
- Include pre-built binary code for different server architectures, such as Linux or Windows
If your package depends on another Atmosphere package, or needs to take advantage of Meteor's [build system](https://docs.meteor.com/about/modern-build-stack.html#modern-build-stack), writing an Atmosphere package might be the best option for now.

View File

@@ -0,0 +1,94 @@
### Package naming
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:` or may use `mdg:`.
### Installing Atmosphere Packages
To install an Atmosphere package, you use `meteor add` inside your app directory:
```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 and your Meteor app version. 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.12.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](https://docs.meteor.com/cli/#meteorhelp).
### Using Atmosphere Packages inside your app
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/api/package.html#PackageAPI-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.
#### Importing styles from Atmosphere packages
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](https://guide.meteor.com/build-tool#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.
#### Peer npm dependencies
Atmosphere packages can ship with contained [npm dependencies](#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 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
```
### Atmosphere package namespacing
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.

View File

@@ -0,0 +1,321 @@
## Writing Atmosphere packages
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 tutorial, 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.](https://docs.meteor.com/api/package.html)
> Don't forget to run [`meteor add [my-package]`](https://docs.meteor.com/cli/#meteor-add) 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.
### Adding files and assets
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.
### Adding JavaScript
To add JavaScript files to a package, specify an entrypoint with [`api.mainModule()`](https://docs.meteor.com/packages/modules.html#modular-package-structure) 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](https://docs.meteor.com/packages/modules.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()`](https://docs.meteor.com/api/package.html#PackageAPI-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.
### Adding Sass, Less, or Stylus mixins/variables
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](https://guide.meteor.com/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.
### Adding other assets
You can include other assets, such as fonts, icons or images, to your package using [`api.addAssets`](https://docs.meteor.com/api/package.html#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](https://docs.meteor.com/api/assets.html#Assets-getTextAsync).
### Exporting
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';
```
### Dependencies
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.
#### Atmosphere dependencies
To depend on another Atmosphere package, use [`api.use`](https://docs.meteor.com/api/package.html#PackageAPI-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.
#### Depending on Meteor version
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`](https://docs.meteor.com/api/package.html#PackageAPI-versionsFrom) 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.
#### Semantic versioning and version constraints
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.
#### npm dependencies
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](https://docs.meteor.com/api/package.html#Npm-require) 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:
```js
import github from 'github';
```
#### Peer npm dependencies
`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).
### Cordova plugins
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](https://docs.meteor.com/api/package.html#PackageCordova-depends).
Read more about using Cordova in the [mobile guide](https://guide.meteor.com/mobile.html).
### Testing packages
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](https://guide.meteor.com/testing#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](https://guide.meteor.com/testing).
### Publishing your package
To publish your package to Atmosphere, run [`meteor publish`](https://docs.meteor.com/cli/#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](#semantic-versionning-and-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.
## Cache format
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.
## Local packages
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`.
Using `git submodules` to handle private packages is a common pattern.
### Overriding published packages with a local version
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.

View File

@@ -0,0 +1,18 @@
# Meteor packaging system: Atmosphere packages
In this tutorial we will present the Meteor packaging system: Atmosphere packages. Atmosphere packages are a way to create reusable code that can shared by multiple apps. They can contain both client and server code, as well as assets like images and stylesheets.
Of course, you can also use npm packages in your Meteor apps.
This tutorial only focuses on Meteor packages, which are a different system.
Two public repositories of Meteor packages exist:
- [Atmosphere](https://atmospherejs.com/): The original repository for Meteor packages, which hosts a wide variety of community-contributed packages.
- [Packosphere](https://packosphere.com/): A newer repository and community maintained alternative to Atmosphere. Packosphere has more information about package quality and maintenance status.
# Table of Contents
[[toc]]
<!-- @include: ./1.when-to-use-meteor-packages.md-->
<!-- @include: ./2.using-atmosphere-packages.md-->
<!-- @include: ./3.writing-atmosphere-packages.md-->

View File

@@ -1,6 +1,5 @@
# Modern-browsers
API for defining the boundary between modern and legacy JavaScript clients.
You can use this package to define the minimum browser versions for which
@@ -10,13 +9,13 @@ on using modern features that you need.
You can read more about this in [Meteor 1.7 announcement blog](https://blog.meteor.com/meteor-1-7-and-the-evergreen-dream-a8c1270b0901).
<ApiBox name="ModernBrowsers.isModern" />
<ApiBox name="ModernBrowsers.isModern" isDefaultImport/>
<ApiBox name="ModernBrowsers.setMinimumBrowserVersions" />
<ApiBox name="ModernBrowsers.setMinimumBrowserVersions" isDefaultImport/>
<ApiBox name="ModernBrowsers.getMinimumBrowserVersions" />
<ApiBox name="ModernBrowsers.getMinimumBrowserVersions" isDefaultImport/>
<ApiBox name="ModernBrowsers.calculateHashOfMinimumVersions" />
<ApiBox name="ModernBrowsers.calculateHashOfMinimumVersions" isDefaultImport/>
## Configure Unknown Browsers to default to Modern
@@ -24,10 +23,10 @@ Browsers not explicitly listed in `setMinimumBrowserVersions` are considered "le
To change this and treat unknown browsers as "modern," update the relevant option in your settings file:
``` js
```js
Meteor.settings.packages = {
"modern-browsers": {
"unknownBrowsersAssumedModern": true
}
"modern-browsers": {
unknownBrowsersAssumedModern: true,
},
};
```

View File

@@ -0,0 +1,357 @@
# Application Structure
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.
[[toc]]
## Universal JavaScript
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](https://guide.meteor.com/build-tool) allows you to specify what JavaScript code, including any supporting UI templates, CSS rules, and static assets, to run in each environment using a combination of ES2015 `import` and `export` and the Meteor build system [default file load order](#default-file-load-order) rules.
### ES2015 modules
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".
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.
### Introduction to using `import` and `export`
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](https://guide.meteor.com/build-tool#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
```
### Importing from packages
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](../../packages/#using-atmosphere-packages) tutorial.
### Using `require`
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`.
### Using CoffeeScript
See the Docs: [CoffeeScript](https://docs.meteor.com/packages/coffeescript.html#coffeescript)
```cs
// lists.coffee
export Lists = new Collection 'lists'
```
```cs
import { Lists } from './lists.coffee'
```
## File structure
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](#default-file-load-order) rules (also called "eager evaluation or loading"). It is recommended that you create exactly two eagerly loaded files, `client/main.js` and `server/main.js`, in order to define explicit entry points for both the client and the server. Meteor ensures that any file in any directory named `server/` will only be available on the server, and likewise for files in any directory named `client/`. This also precludes trying to `import` a file to be used on the server from any directory named `client/` even if it is nested in an `imports/` directory and vice versa for importing client files from `server/`.
These `main.js` files won't do anything themselves, but they should import some _startup_ modules which will run immediately, on client and server respectively, when the app loads. These modules should do any configuration necessary for the packages you are using in your app, and import the rest of your app's code.
### Example directory layout
To start, one can have a look to the [example applications](https://github.com/meteor/examples) provided. They are great examples to follow when structuring your app.
Below is an overview of an exemple directory structure. You can generate a new app with this structure using the command `meteor create appName --full`. The default frontend is Blaze, but you can change it later. Or use [another create option](https://docs.meteor.com/cli/#meteorcreate)
```sh
imports/
startup/
both/
index.js # single entry point to import isomorphic modules for client and server
client/
index.js # import client startup through a single index entry point
routes.js # set up all routes in the app
server/
fixtures.js # fill the DB with example data on startup
index.js # import server startup through a single index entry point
register-api.js # dedicated file to import server code for api
api/
lists/ # a unit of domain logic
server/
publications.js # all list-related publications
publications.tests.js # tests for the list publications
lists.js # definition of the Lists collection
lists.tests.js # tests for the behavior of that collection
methods.js # methods related to lists
methods.tests.js # tests for those methods
ui/
components/ # all reusable components in the application
# can be split by domain if there are many
layouts/ # wrapper components for behaviour and visuals
pages/ # entry points for rendering used by the router
stylesheets/ # global stylesheets
private/ # to store private assets for the server
public/ # to store public assets (pictures)
client/
main.js # client entry point, imports all client code
server/
main.js # server entry point, imports all server code
```
### Structuring imports
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. Each API folder typically has different files for isomorphic code and server-specific code. To ensure good isolation, server-specific code is put into a `server` folder.
> 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.
### Startup files
Some of your code isn't going to be a unit of business logic or UI, it's some setup or configuration code that needs to run in the context of the app when it starts up. In the above example, the `imports/startup/client/routes.js` configures all the routes and then imports *all* other code that is required on the client:
```js
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// Import needed templates
import '../../ui/layouts/body/body.js';
import '../../ui/pages/home/home.js';
import '../../ui/pages/not-found/not-found.js';
```
We then import both of these files in `imports/startup/client/index.js`:
```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
import './fixtures.js';
import './register-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.
### Importing Meteor "pseudo-globals"
For backwards compatibility Meteor still provides Meteor's global namespacing for the Meteor core package as well as for other Meteor packages you include in your application. You can also still directly call functions such as [`Meteor.publish`](https://docs.meteor.com/api/Meteor#Meteor-publish), as in previous versions of Meteor, without first importing them. However, it is recommended best practice that you first load all the Meteor "pseudo-globals" using the `import { Name } from 'meteor/package'` syntax before using them. For instance:
```js
import { Meteor } from 'meteor/meteor';
import { EJSON } from 'meteor/ejson';
```
## Default file load order
Even though it is recommended that you write your application to use ES2015 modules and the `imports/` directory, Meteor continues to support eager evaluation of files, using these default load order rules, to provide backwards compatibility with applications written for Meteor 1.2 and earlier. For a description of the difference between eager evaluation, lazy evaluation, and lazy loading, please see this Stack Overflow [article](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](https://docs.meteor.com/api/Meteor#Meteor-startup) to control when run code is run on both the server and the client.
### Special directories
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`](https://docs.meteor.com/api/Assets#Assets-getTextAsync) 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](https://guide.meteor.com/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](https://guide.meteor.com/mobile.html#advanced-build)
- `programs`: For legacy reasons
### Files outside special directories
All JavaScript files outside special directories are loaded on both the client and the server. Meteor provides the variables [`Meteor.isClient`](https://docs.meteor.com/api/Meteor#Meteor-isClient) and [`Meteor.isServer`](https://docs.meteor.com/api/Meteor#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.
## Splitting into multiple apps
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.
### Sharing code
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](https://guide.meteor.com/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](../../packages/#writing-atmosphere-packages), 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://docs.npmjs.com/about-private-packages)). 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.
### Sharing data
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()`](https://docs.meteor.com/api/meteor#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.
If you need a more traditional API, you can use the bundled `express` available in Meteor. See the [documentation of WebApp](https://docs.meteor.com/packages/webapp#webapp)
```js
import { Meteor } from "meteor/meteor";
import { WebApp } from "meteor/webapp";
import bodyParser from "body-parser";
const app = WebApp.handlers;
app.use(bodyParser.json());
app.use("/hello", (req, res, next) => {
res.writeHead(200);
res.end(`Hello world from: ${Meteor.release}`);
});
```
### Sharing accounts
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).
Another pattern is to have another app which acts as identity provider for your apps. The [leaonline:oauth2-server](https://atmospherejs.com/leaonline/oauth2-server) package can be used to create an OAuth2 server in one Meteor app, and then other apps can use the standard `accounts-oauth` packages to authenticate users against it.