mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
docs: add webapp
This commit is contained in:
171
v3-docs/docs/packages/webapp.md
Normal file
171
v3-docs/docs/packages/webapp.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# WebApp
|
||||
|
||||
The `webapp` package is what lets your Meteor app serve content to a web
|
||||
browser. It is included in the `meteor-base` set of packages that is
|
||||
automatically added when you run `meteor create`. You can easily build a
|
||||
Meteor app without it - for example if you wanted to make a command-line
|
||||
tool that still used the Meteor package system and DDP.
|
||||
|
||||
This package also allows you to add handlers for HTTP requests.
|
||||
This lets other services access your app's data through an HTTP API, allowing
|
||||
it to easily interoperate with tools and frameworks that don't yet support DDP.
|
||||
|
||||
`webapp` exposes the [express](https://github.com/expressjs/express) API for
|
||||
handling requests through `WebApp.handlers`.
|
||||
Here's an example that will let you handle a specific URL:
|
||||
|
||||
```js
|
||||
// Listen to incoming HTTP requests (can only be used on the server).
|
||||
WebApp.handlers.use("/hello", (req, res, next) => {
|
||||
res.writeHead(200);
|
||||
res.end(`Hello world from: ${Meteor.release}`);
|
||||
});
|
||||
```
|
||||
|
||||
<ApiBox name="WebApp.handlers"/>
|
||||
<ApiBox name="expressHandlersCallback(req, res, next)" hasCustomExample/>
|
||||
|
||||
### Serving a Static Landing Page
|
||||
|
||||
One of the really cool things you can do with WebApp is serve static HTML for a landing page where TTFB (time to first byte) is of utmost importance.
|
||||
|
||||
The [Bundle Visualizer](https://docs.meteor.com/packages/bundle-visualizer.html) and [Dynamic Imports](https://docs.meteor.com/packages/dynamic-import.html) are great tools to help you minimize initial page load times. But sometimes you just need to skinny down your initial page load to bare metal.
|
||||
|
||||
The good news is that WebApp makes this is really easy to do.
|
||||
|
||||
Step one is to create a your static HTML file and place it in the _private_ folder at the root of your application.
|
||||
|
||||
Here's a sample _index.html_ you might use to get started:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [index.html]
|
||||
<head>
|
||||
<title>Fast Landing Page</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no" />
|
||||
<link rel="stylesheet" href="path to your style sheet etc">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- your content -->
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
// any functions you need to support your landing page
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
```javascript [index.js]
|
||||
/* global WebApp Assets */
|
||||
import crypto from "crypto";
|
||||
import express from "express";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/", function (req, res, next) {
|
||||
const buf = Assets.getText("index.html");
|
||||
|
||||
if (buf.length > 0) {
|
||||
const eTag = crypto.createHash("md5").update(buf).digest("hex");
|
||||
|
||||
if (req.headers["if-none-match"] === eTag) {
|
||||
res.writeHead(304, "Not Modified");
|
||||
return res.end();
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
ETag: eTag,
|
||||
"Content-Type": "text/html",
|
||||
});
|
||||
|
||||
return res.end(buf);
|
||||
}
|
||||
|
||||
return res.end("<html><body>Index page not found!</body></html>");
|
||||
});
|
||||
|
||||
WebApp.handlers.use(router);
|
||||
|
||||
```
|
||||
|
||||
:::
|
||||
There are a couple things to think about with this approach.
|
||||
|
||||
We're reading the contents of index.html using the [Assets](https://docs.meteor.com/api/assets.html) module that makes it really easy to read files out of the _private_ root folder.
|
||||
|
||||
We're using the [connect-route](https://www.npmjs.com/package/connect-route) NPM package to simplify WebApp route processing. But you can use any package you want to understand what is being requested.
|
||||
|
||||
And finally, if you decide to use this technique you'll want to make sure you understand how conflicting client side routing will affect user experience.
|
||||
|
||||
### Dynamic Runtime Configuration
|
||||
|
||||
In some cases it is valuable to be able to control the **meteor_runtime_config** variable that initializes Meteor at runtime.
|
||||
|
||||
#### Example
|
||||
|
||||
There are occasions when a single Meteor server would like to serve multiple cordova applications that each have a unique `ROOT_URL`. But there are 2 problems:
|
||||
|
||||
1. The Meteor server can only be configured to serve a single `ROOT_URL`.
|
||||
2. The `cordova` applications are build time configured with a specific `ROOT_URL`.
|
||||
|
||||
These 2 conditions break `autoupdate` for the cordova applications. `cordova-plugin-meteor-webapp` will fail the update if the `ROOT_URL` from the server does not match the build time configured `ROOT_URL` of the cordova application.
|
||||
|
||||
To remedy this problem `webapp` has a hook for dynamically configuring `__meteor_runtime_config__` on the server.
|
||||
|
||||
#### Dynamic Runtime Configuration Hook
|
||||
|
||||
```js
|
||||
WebApp.addRuntimeConfigHook(
|
||||
({ arch, request, encodedCurrentConfig, updated }) => {
|
||||
// check the request to see if this is a request that requires
|
||||
// modifying the runtime configuration
|
||||
if (request.headers.domain === "calling.domain") {
|
||||
// make changes to the config for this domain
|
||||
// decode the current runtime config string into an object
|
||||
const config = WebApp.decodeRuntimeConfig(current);
|
||||
// make your changes
|
||||
config.newVar = "some value";
|
||||
config.oldVar = "new value";
|
||||
// encode the modified object to the runtime config string
|
||||
// and return it
|
||||
return WebApp.encodeRuntimeConfig(config);
|
||||
}
|
||||
// Not modifying other domains so return undefined
|
||||
return undefined;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
<ApiBox name="WebApp.addRuntimeConfigHook"/>
|
||||
<ApiBox name="addRuntimeConfigHookCallback(options)" hasCustomExample/>
|
||||
|
||||
Additionally, 2 helper functions are available to decode the runtime config string and encode the runtime config object.
|
||||
|
||||
<ApiBox name="WebApp.decodeRuntimeConfig"/>
|
||||
<ApiBox name="WebApp.encodeRuntimeConfig"/>
|
||||
|
||||
### Updated Runtime Configuration Hook
|
||||
|
||||
```js
|
||||
const autoupdateCache;
|
||||
// Get a notification when the runtime configuration is updated
|
||||
// for each arch
|
||||
WebApp.addUpdatedNotifyHook(({arch, manifest, runtimeConfig}) => {
|
||||
// Example, see if runtimeConfig.autoupdate has changed and if so
|
||||
// do something
|
||||
if(!_.isEqual(autoupdateCache, runtimeConfig.autoupdate)) {
|
||||
autoupdateCache = runtimeConfig.autoupdate;
|
||||
// do something...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<ApiBox name="WebApp.addUpdatedNotifyHook"/>
|
||||
<ApiBox name="addUpdatedNotifyHookCallback(options)" hasCustomExample/>
|
||||
<ApiBox name="main"/>
|
||||
Reference in New Issue
Block a user