mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'appcache' into devel
Conflicts: History.md
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
|
||||
## vNEXT
|
||||
|
||||
* Add new `appcache` package. Add this package to your project to speed
|
||||
up page load and make hot code reload smoother using the HTML5
|
||||
AppCache API. See http://docs.meteor.com/#appcache for details.
|
||||
|
||||
* You can now provide a `transform` option to collections, which is a
|
||||
function that documents coming out of that collection are passed
|
||||
through. `find`, `findOne`, `allow`, and `deny` now take `transform` options,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// - manifest [list of resources in load order, each consists of an object]:
|
||||
// {
|
||||
// "path": relative path of file in the bundle, normalized to use forward slashes
|
||||
// "where": "client" [could also be "server" in future]
|
||||
// "where": "client", "internal" [could also be "server" in future]
|
||||
// "type": "js", "css", or "static"
|
||||
// "cacheable": (client) boolean, is it safe to ask the browser to cache this file
|
||||
// "url": (client) relative url to download the resource, includes cache
|
||||
@@ -231,11 +231,19 @@ var Bundle = function () {
|
||||
// list of filenames
|
||||
self.css = [];
|
||||
|
||||
// images and other static files added from packages
|
||||
// map from environment, to list of filenames
|
||||
self.static = {client: [], server: []};
|
||||
|
||||
// Map from environment, to path name (server relative), to contents
|
||||
// of file as buffer.
|
||||
self.files = {client: {}, client_cacheable: {}, server: {}};
|
||||
|
||||
// See description of manifest at the top
|
||||
// See description of the manifest at the top.
|
||||
// Note that in contrast to self.js etc., the manifest only includes
|
||||
// files which are in the final bundler output: for example, if code
|
||||
// is minified, the manifest includes the minify output file but not
|
||||
// the individual input files that were combined.
|
||||
self.manifest = [];
|
||||
|
||||
// list of segments of additional HTML for <head>/<body>
|
||||
@@ -315,6 +323,7 @@ var Bundle = function () {
|
||||
self[options.type].push(data);
|
||||
} else if (options.type === "static") {
|
||||
self.files[w][options.path] = data;
|
||||
self.static[w].push(options.path);
|
||||
} else {
|
||||
throw new Error("Unknown type " + options.type);
|
||||
}
|
||||
@@ -529,6 +538,25 @@ _.extend(Bundle.prototype, {
|
||||
|
||||
// --- Static assets ---
|
||||
|
||||
var addClientFileToManifest = function (filepath, contents, type, cacheable, url) {
|
||||
if (! contents instanceof Buffer)
|
||||
throw new Error('contents must be a Buffer');
|
||||
var normalized = filepath.split(path.sep).join('/');
|
||||
if (normalized.charAt(0) === '/')
|
||||
normalized = normalized.substr(1);
|
||||
self.manifest.push({
|
||||
// path is normalized to use forward slashes
|
||||
path: (cacheable ? 'static_cacheable' : 'static') + '/' + normalized,
|
||||
where: 'client',
|
||||
type: type,
|
||||
cacheable: cacheable,
|
||||
url: url || '/' + normalized,
|
||||
// contents is a Buffer and so correctly gives us the size in bytes
|
||||
size: contents.length,
|
||||
hash: self._hash(contents)
|
||||
});
|
||||
};
|
||||
|
||||
if (is_app) {
|
||||
if (fs.existsSync(path.join(project_dir, 'public'))) {
|
||||
var copied =
|
||||
@@ -537,18 +565,8 @@ _.extend(Bundle.prototype, {
|
||||
|
||||
_.each(copied, function (fs_relative_path) {
|
||||
var filepath = path.join(build_path, 'static', fs_relative_path);
|
||||
var normalized = fs_relative_path.split(path.sep).join('/');
|
||||
self.manifest.push({
|
||||
// path is normalized to use forward slashes, so deliberately
|
||||
// not using path.sep here
|
||||
path: 'static/' + normalized,
|
||||
type: 'static',
|
||||
where: 'client',
|
||||
cacheable: false,
|
||||
url: '/' + normalized,
|
||||
size: fs.statSync(filepath).size,
|
||||
hash: self._hash(fs.readFileSync(filepath))
|
||||
});
|
||||
var contents = fs.readFileSync(filepath);
|
||||
addClientFileToManifest(fs_relative_path, contents, 'static', false);
|
||||
});
|
||||
}
|
||||
dependencies_json.app.push('public');
|
||||
@@ -573,17 +591,7 @@ _.extend(Bundle.prototype, {
|
||||
else
|
||||
throw new Error('unable to find file: ' + file);
|
||||
|
||||
self.manifest.push({
|
||||
// path is normalized to use forward slashes
|
||||
path: 'static_cacheable' + file.split(path.sep).join('/'),
|
||||
where: 'client',
|
||||
type: type,
|
||||
cacheable: true,
|
||||
url: url,
|
||||
// contents is a Buffer and so correctly gives us the size in bytes
|
||||
size: contents.length,
|
||||
hash: self._hash(contents)
|
||||
});
|
||||
addClientFileToManifest(file, contents, type, true, url);
|
||||
};
|
||||
|
||||
_.each(self.js.client, function (file) { processClientCode('js', file); });
|
||||
@@ -594,6 +602,7 @@ _.extend(Bundle.prototype, {
|
||||
var full_path = path.join(build_path, 'static', rel_path);
|
||||
files.mkdir_p(path.dirname(full_path), 0755);
|
||||
fs.writeFileSync(full_path, self.files.client[rel_path]);
|
||||
addClientFileToManifest(rel_path, self.files.client[rel_path], 'static', false);
|
||||
}
|
||||
|
||||
// -- Client cache forever code --
|
||||
@@ -613,8 +622,13 @@ _.extend(Bundle.prototype, {
|
||||
fs.writeFileSync(full_path, self.files.server[rel_path]);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(build_path, 'app.html'),
|
||||
self._generate_app_html());
|
||||
var app_html = self._generate_app_html();
|
||||
fs.writeFileSync(path.join(build_path, 'app.html'), app_html);
|
||||
self.manifest.push({
|
||||
path: 'app.html',
|
||||
where: 'internal',
|
||||
hash: self._hash(app_html)
|
||||
});
|
||||
dependencies_json.core.push(path.join('lib', 'app.html.in'));
|
||||
|
||||
fs.writeFileSync(path.join(build_path, 'unsupported.html'),
|
||||
|
||||
@@ -155,9 +155,9 @@ var appUrl = function (url) {
|
||||
if (url === '/app.manifest')
|
||||
return false;
|
||||
|
||||
// Avoid serving app HTML for declared network routes such as /sockjs/.
|
||||
// Avoid serving app HTML for declared routes such as /sockjs/.
|
||||
if (__meteor_bootstrap__._routePolicy &&
|
||||
__meteor_bootstrap__._routePolicy.classify(url) === 'network')
|
||||
__meteor_bootstrap__._routePolicy.classify(url))
|
||||
return false;
|
||||
|
||||
// we currently return app HTML on all URLs by default
|
||||
|
||||
@@ -10,3 +10,4 @@ code-prettify
|
||||
jquery-waypoints
|
||||
less
|
||||
spiderable
|
||||
appcache
|
||||
|
||||
@@ -291,6 +291,7 @@ var toc = [
|
||||
|
||||
"Packages", [ [
|
||||
"accounts-ui",
|
||||
"appcache",
|
||||
"amplify",
|
||||
"backbone",
|
||||
"bootstrap",
|
||||
|
||||
@@ -107,7 +107,7 @@ clean, classically beautiful APIs.
|
||||
<h2 id="resources">Developer Resources</h2>
|
||||
|
||||
<!-- https://github.com/blog/273-github-ribbons -->
|
||||
<a href="http://github.com/meteor/meteor"><img class="github-ribbon visible-desktop" style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
|
||||
<a href="http://github.com/meteor/meteor"><img class="github-ribbon visible-desktop" style="position: absolute; top: 0; right: 0; border: 0;" src="/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
If anything in Meteor catches your interest, we hope you'll get involved
|
||||
with the project!
|
||||
|
||||
@@ -17,6 +17,7 @@ and removed with:
|
||||
$ meteor remove <package_name>
|
||||
|
||||
{{> pkg_accounts_ui}}
|
||||
{{> pkg_appcache}}
|
||||
{{> pkg_amplify}}
|
||||
{{> pkg_backbone}}
|
||||
{{> pkg_bootstrap}}
|
||||
|
||||
92
docs/client/packages/appcache.html
Normal file
92
docs/client/packages/appcache.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<template name="pkg_appcache">
|
||||
{{#better_markdown}}
|
||||
|
||||
## `appcache`
|
||||
|
||||
The `appcache` package stores the static parts of a Meteor application
|
||||
(the client side Javascript, HTML, CSS, and images) in the browser's
|
||||
[application cache](https://en.wikipedia.org/wiki/AppCache). To enable
|
||||
caching simply add the `appcache` package to your project.
|
||||
|
||||
* Once a user has visited a Meteor application for the first time and
|
||||
the application has been cached, on subsequent visits the web page
|
||||
loads faster because the browser can load the application out of the
|
||||
cache without contacting the server first.
|
||||
|
||||
* Hot code pushes are loaded by the browser in the background while the
|
||||
app continues to run. Once the new code has been fully loaded the
|
||||
browser is able to switch over to the new code quickly.
|
||||
|
||||
* The application cache allows the application to be loaded even when
|
||||
the browser doesn't have an Internet connection, and so enables using
|
||||
the app offline.
|
||||
|
||||
(Note however that the `appcache` package by itself doesn't make
|
||||
*data* available offline: in an application loaded offline, a Meteor
|
||||
Collection will appear to be empty in the client until the Internet
|
||||
becomes available and the browser is able to establish a livedata
|
||||
connection).
|
||||
|
||||
The application cache works transparently in all supported browsers
|
||||
except for Firefox, which pops up a message saying "This website is
|
||||
asking to store data on your computer for offline use" and asks the
|
||||
user whether to allow or deny the request. The application cache is
|
||||
disabled on Firefox by default; to turn it on use:
|
||||
|
||||
Meteor.AppCache.config({firefox: true});
|
||||
|
||||
You can also disable the application cache for specific browsers:
|
||||
|
||||
Meteor.AppCache.config({
|
||||
chrome: false,
|
||||
firefox: true,
|
||||
ie: false
|
||||
});
|
||||
|
||||
The supported browsers that can be enabled or disabled are `android`,
|
||||
`chrome`, `firefox`, `ie`, `mobileSafari` and `safari`.
|
||||
|
||||
Browsers limit the amount of data they will put in the application
|
||||
cache, which can vary due to factors such as how much disk space is
|
||||
free. Unfortunately if your application goes over the limit rather
|
||||
than disabling the application cache altogether and running the
|
||||
application online, the browser will instead fail that particular
|
||||
*update* of the cache, leaving your users running old code.
|
||||
|
||||
Thus it's best to keep the size of the cache below 5MB. The
|
||||
`appcache` package will print a warning on the Meteor server console
|
||||
if the total size of the resources being cached is over 5MB.
|
||||
|
||||
If you have files too large to fit in the cache you can disable
|
||||
caching by URL prefix. For example,
|
||||
|
||||
Meteor.AppCache.config({onlineOnly: ['/online/']});
|
||||
|
||||
causes files in your `public/online` directory to not be cached, and
|
||||
so they will only be available online. You can then move your large
|
||||
files into that directory and refer to them at the new URL:
|
||||
|
||||
<img src="/online/bigimage.jpg">
|
||||
|
||||
If you'd prefer not to move your files, you can use the file names
|
||||
themselves as the URL prefix:
|
||||
|
||||
Meteor.AppCache.config({
|
||||
onlineOnly: [
|
||||
'/bigimage.jpg',
|
||||
'/largedata.json'
|
||||
]
|
||||
});
|
||||
|
||||
though keep in mind that since the exclusion is by prefix (this is a
|
||||
limitation of the application cache manifest), excluding
|
||||
`/largedata.json` will also exclude such URLs as
|
||||
`/largedata.json.orig` and `/largedata.json/file1`.
|
||||
|
||||
For more information about how Meteor interacts with the application
|
||||
cache, see the
|
||||
[AppCache page](https://github.com/meteor/meteor/wiki/AppCache)
|
||||
in the Meteor wiki.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
BIN
docs/public/forkme_right_red_aa0000.png
Normal file
BIN
docs/public/forkme_right_red_aa0000.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
93
packages/appcache/QA.md
Normal file
93
packages/appcache/QA.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# QA Notes
|
||||
|
||||
## Viewing the app cache
|
||||
|
||||
Chrome: Navigate to chrome://appcache-internals/
|
||||
|
||||
Firefox: Open Tools / Advanced / Network. The section reading "The
|
||||
following websites are allowed to store data for offline use" will
|
||||
show the amount of data in the app cache ("1.2 MB"). If this number
|
||||
is 0 the app is permitted to use the app cache but the app cache is
|
||||
currently turned off.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Create a simple static app and add the appcache package.
|
||||
|
||||
static.html:
|
||||
|
||||
````
|
||||
<body>
|
||||
some static content
|
||||
</body>
|
||||
````
|
||||
|
||||
If you're testing with Firefox, enable it:
|
||||
|
||||
static.js:
|
||||
|
||||
````
|
||||
if (Meteor.isServer) {
|
||||
Meteor.AppCache.config({
|
||||
firefox: true
|
||||
});
|
||||
}
|
||||
````
|
||||
|
||||
|
||||
## App is cached offline
|
||||
|
||||
Run Meteor, load the app in the browser, stop Meteor. Reload the page
|
||||
in the browser and observe the content is still visible.
|
||||
|
||||
|
||||
## Hot code reload still works
|
||||
|
||||
Run Meteor, open the app in the browser. Make a change to
|
||||
static.html. Observe the change appear in the web page.
|
||||
|
||||
Note that it is normal when using the app cache for the page reload to
|
||||
be delayed a bit while the browser fetches the changed code in the
|
||||
background.
|
||||
|
||||
Without app cache: (page goes blank) -> (browser fetches) -> (page renders)
|
||||
|
||||
With app cache: (browser fetches) -> (page goes blank) -> (page renders)
|
||||
|
||||
|
||||
## Enabling / disabling the appcache turns the app cache on / off
|
||||
|
||||
Run Meteor, open the app in the browser.
|
||||
|
||||
Disable your browser in the appcache config. For example, if you're
|
||||
using Chrome:
|
||||
|
||||
````
|
||||
Meteor.AppCache.config({
|
||||
chrome: false
|
||||
});
|
||||
````
|
||||
|
||||
Observe following the hot code reload the app is no longer cached.
|
||||
|
||||
Enable your browser again:
|
||||
|
||||
````
|
||||
Meteor.AppCache.config({
|
||||
chrome: true
|
||||
});
|
||||
````
|
||||
|
||||
Observe following the hot code reload the app is cached again.
|
||||
|
||||
|
||||
## Removing the appcache package turns off app caching
|
||||
|
||||
Start Meteor, open the app in the browser.
|
||||
|
||||
Stop Meteor, remove the appcache package, remove or comment out the
|
||||
call to Meteor.AppCache.config in static.js, start Meteor again.
|
||||
|
||||
Wait for the browser to reestablish its livedata connection. Observe
|
||||
following the hot code reload that the app is no longer cached.
|
||||
71
packages/appcache/appcache-client.js
Normal file
71
packages/appcache/appcache-client.js
Normal file
@@ -0,0 +1,71 @@
|
||||
(function() {
|
||||
|
||||
if (! window.applicationCache)
|
||||
return;
|
||||
|
||||
var appCacheStatuses = [
|
||||
'uncached',
|
||||
'idle',
|
||||
'checking',
|
||||
'downloading',
|
||||
'updateready',
|
||||
'obsolete'
|
||||
];
|
||||
|
||||
var updatingAppcache = false;
|
||||
var reloadRetry = null;
|
||||
var appcacheUpdated = false;
|
||||
|
||||
Meteor._reload.onMigrate('appcache', function(retry) {
|
||||
if (appcacheUpdated)
|
||||
return [true];
|
||||
|
||||
// An uncached application (one that does not have a manifest) cannot
|
||||
// be updated.
|
||||
if (window.applicationCache.status === window.applicationCache.UNCACHED)
|
||||
return [true];
|
||||
|
||||
if (!updatingAppcache) {
|
||||
try {
|
||||
window.applicationCache.update();
|
||||
} catch (e) {
|
||||
Meteor._debug('applicationCache update error', e);
|
||||
// There's no point in delaying the reload if we can't update the cache.
|
||||
return [true];
|
||||
}
|
||||
updatingAppcache = true;
|
||||
}
|
||||
|
||||
// Delay migration until the app cache has been updated.
|
||||
reloadRetry = retry;
|
||||
return false;
|
||||
});
|
||||
|
||||
// If we're migrating and the app cache is now up to date, signal that
|
||||
// we're now ready to migrate.
|
||||
var cacheIsNowUpToDate = function() {
|
||||
if (!updatingAppcache)
|
||||
return;
|
||||
appcacheUpdated = true;
|
||||
reloadRetry();
|
||||
};
|
||||
|
||||
window.applicationCache.addEventListener('updateready', cacheIsNowUpToDate, false);
|
||||
window.applicationCache.addEventListener('noupdate', cacheIsNowUpToDate, false);
|
||||
|
||||
// We'll get the obsolete event on a 404 fetching the app.manifest:
|
||||
// we had previously been running with an app cache, but the app
|
||||
// cache has now been disabled or the appcache package removed.
|
||||
// Reload to get the new non-cached code.
|
||||
|
||||
window.applicationCache.addEventListener('obsolete', (function() {
|
||||
if (reloadRetry) {
|
||||
cacheIsNowUpToDate();
|
||||
}
|
||||
else {
|
||||
appcacheUpdated = true;
|
||||
Meteor._reload.reload();
|
||||
}
|
||||
}), false);
|
||||
|
||||
})();
|
||||
185
packages/appcache/appcache-server.js
Normal file
185
packages/appcache/appcache-server.js
Normal file
@@ -0,0 +1,185 @@
|
||||
(function() {
|
||||
|
||||
var app = __meteor_bootstrap__.app;
|
||||
var bundle = __meteor_bootstrap__.bundle;
|
||||
var crypto = __meteor_bootstrap__.require('crypto');
|
||||
var fs = __meteor_bootstrap__.require('fs');
|
||||
var path = __meteor_bootstrap__.require('path');
|
||||
|
||||
var knownBrowsers = ['android', 'chrome', 'firefox', 'ie', 'mobileSafari', 'safari'];
|
||||
|
||||
var browsersEnabledByDefault = ['android', 'chrome', 'ie', 'mobileSafari', 'safari'];
|
||||
|
||||
var enabledBrowsers = {};
|
||||
_.each(browsersEnabledByDefault, function (browser) {
|
||||
enabledBrowsers[browser] = true;
|
||||
});
|
||||
|
||||
Meteor.AppCache = {
|
||||
config: function(options) {
|
||||
_.each(options, function (value, option) {
|
||||
if (option === 'browsers') {
|
||||
enabledBrowsers = {};
|
||||
_.each(value, function (browser) {
|
||||
enabledBrowsers[browser] = true;
|
||||
});
|
||||
}
|
||||
else if (_.contains(knownBrowsers, option)) {
|
||||
enabledBrowsers[option] = value;
|
||||
}
|
||||
else if (option === 'onlineOnly') {
|
||||
_.each(value, function (urlPrefix) {
|
||||
Meteor._routePolicy.declare(urlPrefix, 'static-online');
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid AppCache config option: ' + option);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var browserEnabled = function(request) {
|
||||
return enabledBrowsers[request.browser.name];
|
||||
};
|
||||
|
||||
__meteor_bootstrap__.htmlAttributeHooks.push(function (request) {
|
||||
if (browserEnabled(request))
|
||||
return 'manifest="/app.manifest"';
|
||||
else
|
||||
return null;
|
||||
});
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
if (req.url !== '/app.manifest') {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Browsers will get confused if we unconditionally serve the
|
||||
// manifest and then disable the app cache for that browser. If
|
||||
// the app cache had previously been enabled for a browser, it
|
||||
// will continue to fetch the manifest as long as it's available,
|
||||
// even if we now are not including the manifest attribute in the
|
||||
// app HTML. (Firefox for example will continue to display "this
|
||||
// website is asking to store data on your computer for offline
|
||||
// use"). Returning a 404 gets the browser to really turn off the
|
||||
// app cache.
|
||||
|
||||
if (!browserEnabled(__meteor_bootstrap__.categorizeRequest(req))) {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// After the browser has downloaded the app files from the server and
|
||||
// has populated the browser's application cache, the browser will
|
||||
// *only* connect to the server and reload the application if the
|
||||
// *contents* of the app manifest file has changed.
|
||||
//
|
||||
// So we have to ensure that if any static client resources change,
|
||||
// something changes in the manifest file. We compute a hash of
|
||||
// everything that gets delivered to the client during the initial
|
||||
// web page load, and include that hash as a comment in the app
|
||||
// manifest. That way if anything changes, the comment changes, and
|
||||
// the browser will reload resources.
|
||||
|
||||
var hash = crypto.createHash('sha1');
|
||||
hash.update(JSON.stringify(__meteor_runtime_config__), 'utf8');
|
||||
_.each(bundle.manifest, function (resource) {
|
||||
if (resource.where === 'client' || resource.where === 'internal') {
|
||||
hash.update(resource.hash);
|
||||
}
|
||||
});
|
||||
var digest = hash.digest('hex');
|
||||
|
||||
var manifest = "CACHE MANIFEST\n\n";
|
||||
manifest += '# ' + digest + "\n\n";
|
||||
|
||||
manifest += "CACHE:" + "\n";
|
||||
manifest += "/" + "\n";
|
||||
_.each(bundle.manifest, function (resource) {
|
||||
if (resource.where === 'client' &&
|
||||
! Meteor._routePolicy.classify(resource.url)) {
|
||||
manifest += resource.url;
|
||||
// If the resource is not already cacheable (has a query
|
||||
// parameter, presumably with a hash or version of some sort),
|
||||
// put a version with a hash in the cache.
|
||||
//
|
||||
// Avoid putting a non-cacheable asset into the cache, otherwise
|
||||
// the user can't modify the asset until the cache headers
|
||||
// expire.
|
||||
if (!resource.cacheable)
|
||||
manifest += "?" + resource.hash;
|
||||
|
||||
manifest += "\n";
|
||||
}
|
||||
});
|
||||
manifest += "\n";
|
||||
|
||||
manifest += "FALLBACK:\n";
|
||||
manifest += "/ /" + "\n";
|
||||
// Add a fallback entry for each uncacheable asset we added above.
|
||||
//
|
||||
// This means requests for the bare url (/image.png instead of
|
||||
// /image.png?hash) will work offline. Online, however, the browser
|
||||
// will send a request to the server. Users can remove this extra
|
||||
// request to the server and have the asset served from cache by
|
||||
// specifying the full URL with hash in their code (manually, with
|
||||
// some sort of URL rewriting helper)
|
||||
_.each(bundle.manifest, function (resource) {
|
||||
if (resource.where === 'client' &&
|
||||
! Meteor._routePolicy.classify(resource.url) &&
|
||||
!resource.cacheable) {
|
||||
manifest += resource.url + " " + resource.url +
|
||||
"?" + resource.hash + "\n";
|
||||
}
|
||||
});
|
||||
|
||||
manifest += "\n";
|
||||
|
||||
manifest += "NETWORK:\n";
|
||||
// TODO adding the manifest file to NETWORK should be unnecessary?
|
||||
// Want more testing to be sure.
|
||||
manifest += "/app.manifest" + "\n";
|
||||
_.each(
|
||||
[].concat(
|
||||
Meteor._routePolicy.urlPrefixesFor('network'),
|
||||
Meteor._routePolicy.urlPrefixesFor('static-online')
|
||||
),
|
||||
function (urlPrefix) {
|
||||
manifest += urlPrefix + "\n";
|
||||
}
|
||||
);
|
||||
manifest += "*" + "\n";
|
||||
|
||||
// content length needs to be based on bytes
|
||||
var body = new Buffer(manifest);
|
||||
|
||||
res.setHeader('Content-Type', 'text/cache-manifest');
|
||||
res.setHeader('Content-Length', body.length);
|
||||
return res.end(body);
|
||||
});
|
||||
|
||||
var sizeCheck = function() {
|
||||
var totalSize = 0;
|
||||
_.each(bundle.manifest, function (resource) {
|
||||
if (resource.where === 'client') {
|
||||
totalSize += resource.size;
|
||||
}
|
||||
});
|
||||
if (totalSize > 5 * 1024 * 1024) {
|
||||
Meteor._debug(
|
||||
"** You are using the appcache package but the total size of the\n" +
|
||||
"** cached resources is " +
|
||||
(totalSize / 1024 / 1024).toFixed(1) + "MB.\n" +
|
||||
"**\n" +
|
||||
"** This is over the recommended maximum of 5 MB and may break your\n" +
|
||||
"** app in some browsers! See http://docs.meteor.com/#appcache\n" +
|
||||
"** for more information and fixes.\n"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
sizeCheck();
|
||||
|
||||
})();
|
||||
11
packages/appcache/package.js
Normal file
11
packages/appcache/package.js
Normal file
@@ -0,0 +1,11 @@
|
||||
Package.describe({
|
||||
summary: "enable the application cache in the browser"
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('reload', 'client');
|
||||
api.use('routepolicy', 'server');
|
||||
api.use('startup', 'client');
|
||||
api.add_files('appcache-client.js', 'client');
|
||||
api.add_files('appcache-server.js', 'server');
|
||||
});
|
||||
@@ -1,3 +1,25 @@
|
||||
// In addition to listing specific files to be cached, the browser
|
||||
// application cache manifest allows URLs to be designated as NETWORK
|
||||
// (always fetched from the Internet) and FALLBACK (which we use to
|
||||
// serve app HTML on arbitrary URLs).
|
||||
//
|
||||
// The limitation of the manifest file format is that the designations
|
||||
// are by prefix only: if "/foo" is declared NETWORK then "/foobar"
|
||||
// will also be treated as a network route.
|
||||
//
|
||||
// Meteor._routePolicy is a low-level API for declaring the route type
|
||||
// of URL prefixes:
|
||||
//
|
||||
// "network": for network routes that should not conflict with static
|
||||
// resources. (For example, if "/sockjs/" is a network route, we
|
||||
// shouldn't have "/sockjs/red-sock.jpg" as a static resource).
|
||||
//
|
||||
// "static-online": for static resources which should not be cached in
|
||||
// the app cache. This is implemented by also adding them to the
|
||||
// NETWORK section (as otherwise the browser would receive app HTML
|
||||
// for them because of the FALLBACK section), but static-online routes
|
||||
// don't need to be checked for conflict with static resources.
|
||||
|
||||
(function () {
|
||||
|
||||
// The route policy is a singleton in a running application, but we
|
||||
@@ -17,8 +39,8 @@
|
||||
},
|
||||
|
||||
checkType: function (type) {
|
||||
if (! _.contains(['network'], type))
|
||||
return 'the route type must be "network"';
|
||||
if (! _.contains(['network', 'static-online'], type))
|
||||
return 'the route type must be "network" or "static-online"';
|
||||
return null;
|
||||
},
|
||||
|
||||
@@ -35,6 +57,8 @@
|
||||
|
||||
checkForConflictWithStatic: function (urlPrefix, type, _testManifest) {
|
||||
var self = this;
|
||||
if (type === 'static-online')
|
||||
return null;
|
||||
var manifest = _testManifest || __meteor_bootstrap__.bundle.manifest;
|
||||
var conflict = _.find(manifest, function (resource) {
|
||||
return (resource.type === 'static' &&
|
||||
|
||||
@@ -2,9 +2,8 @@ Tinytest.add("routepolicy", function (test) {
|
||||
var policy = new Meteor.__RoutePolicyConstructor();
|
||||
|
||||
policy.declare('/sockjs/', 'network');
|
||||
// App routes might look like this...
|
||||
// policy.declare('/posts/', 'app');
|
||||
// policy.declare('/about', 'app');
|
||||
policy.declare('/bigphoto.jpg', 'static-online');
|
||||
policy.declare('/anotherphoto.png', 'static-online');
|
||||
|
||||
test.equal(policy.classify('/'), null);
|
||||
test.equal(policy.classify('/foo'), null);
|
||||
@@ -13,11 +12,14 @@ Tinytest.add("routepolicy", function (test) {
|
||||
test.equal(policy.classify('/sockjs/'), 'network');
|
||||
test.equal(policy.classify('/sockjs/foo'), 'network');
|
||||
|
||||
// test.equal(policy.classify('/posts/'), 'app');
|
||||
// test.equal(policy.classify('/posts/1234'), 'app');
|
||||
test.equal(policy.classify('/bigphoto.jpg'), 'static-online');
|
||||
test.equal(policy.classify('/bigphoto.jpg.orig'), 'static-online');
|
||||
|
||||
test.equal(policy.urlPrefixesFor('network'), ['/sockjs/']);
|
||||
// test.equal(policy.urlPrefixesFor('app'), ['/about', '/posts/']);
|
||||
test.equal(
|
||||
policy.urlPrefixesFor('static-online'),
|
||||
['/anotherphoto.png', '/bigphoto.jpg']
|
||||
);
|
||||
});
|
||||
|
||||
Tinytest.add("routepolicy - static conflicts", function (test) {
|
||||
@@ -26,9 +28,14 @@ Tinytest.add("routepolicy - static conflicts", function (test) {
|
||||
"path": "static/sockjs/socks-are-comfy.jpg",
|
||||
"type": "static",
|
||||
"where": "client",
|
||||
"cacheable": false,
|
||||
"url": "/sockjs/socks-are-comfy.jpg"
|
||||
},
|
||||
{
|
||||
"path": "static/bigphoto.jpg",
|
||||
"type": "static",
|
||||
"where": "client",
|
||||
"url": "/bigphoto.jpg"
|
||||
}
|
||||
];
|
||||
var policy = new Meteor.__RoutePolicyConstructor();
|
||||
|
||||
@@ -36,4 +43,9 @@ Tinytest.add("routepolicy - static conflicts", function (test) {
|
||||
policy.checkForConflictWithStatic('/sockjs/', 'network', manifest),
|
||||
"static resource /sockjs/socks-are-comfy.jpg conflicts with network route /sockjs/"
|
||||
);
|
||||
|
||||
test.equal(
|
||||
policy.checkForConflictWithStatic('/bigphoto.jpg', 'static-online', manifest),
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user