I have a knockout/require app and am struggling with the caching of one particular file. Sadly it is the file that busts the cache for all other javascript files. The setup may be slightly odd:
Each view simply binds a knockout view model. It requires the require library and the main script for the particular area of the system:
<script data-main="scripts/user" src="~/Scripts/lib/require.js"></script>
The scripts/user.js file required above requires the common file (containing the require setup) and the main viewmodel script:
require(['./Common'], function (common) {
require(['userMain']);
})
The scripts/user/userMain.js file binds the viewmodel and requires anything needed at the view level (such as custom binding handlers).
define(function (require) {
require(['ko', 'userViewModel'], function (ko, userViewModel) {
var userVm = new userViewModel(false);
userVm.initialise();
// bound to the wrapper to stop jquery dialog bindings being applied twice
ko.applyBindings(userVm, document.getElementById('pageWrapper'));
});
});
Then we have common.js:
require.config({
baseUrl: './',
paths: {
'userMain': './Scripts/user/Main',
'userAjax': './Scripts/user/userAjax',
'userBasicDetails': './Scripts/user/userBasicDetails',
'userExchangesModel': './Scripts/user/userExchangesModel',
'userModel': './Scripts/user/userModel',
'userReportAccessModel': './Scripts/user/userReportAccessModel',
'usersModel': './Scripts/user/usersModel',
'userViewModel': './scripts/user/userViewModel',
... etc
,
urlArgs: "bust=" + (new Date()).getTime()
each script within the folder then requires anything it needs within its own model.
The script structure is then setup as so:
scripts\common.js
scripts\user.js
scripts\user\main.js
scripts\user\userAjax
scripts\user\etc...
This setup allows me to reference scripts from other folders without specifying where the file is anywhere other than in common.js. The downside is that all js files have a reference in common but I can live with that.
As an example there are 4 or 5 folders at the same level as 'user' ('scripts\report\', 'scripts\client' etc) and if I want to create
a user model from any of the scripts within those folders I can simply "define (['userModel'], function (userModel)" and common will tell require where to go and find that file. This system works well, allowing me to move files around at will and only change their path in one place.
The problem comes when I add new scripts or change paths in common.js. Whilst all others are bust every request due to the setup
in common the common file itself gets cached so I have to bust users' chrome caches before the new common.js file gets picked up. This is obviously a big issue at delivery time - pages fail as they cannot find the new script because it doesn't exist in the same folder and common has been cached.
Can anyone suggest a way of automatically busting common.js or moving the path config into a separate required file so that the urlArgs bust will do it for me?
Many thanks.
Before the script element that loads RequireJS, add the following code:
<script>
require = {
urlArgs: "bust=" + (new Date()).getTime()
};
</script>
RequireJS will pick this up as its initial configuration and any module it loads, through data-main or any other way, will be required with a bust parameter.
It is probably best to remove urlArgs from your subsequent call to require.config. It will override the earlier option so the value of the bust will change. Usually, modules are loaded once and only once by RequireJS so it should not happen that the same module is loaded by the same page with two different bust values. But there are scenarios I'm unsure about (for instance, using require.undef to undefine a module). Removing the later urlArgs would avoid bad surprises.
Related
Question
Is it possible to precache a file using a different strategy? i.e. Stale While Revalidate?
Or, should I just load the script in the DOM and then add a route for it in the worker with the correct strategy?
Background
This is quite a weird case so I will try to explain it as best I can...
We have two repos; The PWA and The Games
Both are statically hosted on the same CDN
Due to the Games repo being separate, the PWA has no access to the versioning of the game js bundles
Therefore, the solution I have come up with is to generate an unversioned manifest (game-manifest.js) in the Games build
The PWA will then precache this file, loop through it's contents, and append each entry to the existing precache manifest
However, given the game-manifest.js has no revision and is not hashed, we need to apply either a Network First, or Stale While Revalidate strategy in order for the file to be updated when new versions become available
See the following code as a clearer example of what I am trying to do:
import { precacheAndRoute } from 'workbox-precaching';
// Load the game manifest
// THIS FILE NEEDS TO BE PRECACHED, but under the strategy
// of stale while revalidate, or network first.
importScripts('example.cdn.com/games/js/game-manifest.js');
// Something like...
self.__gameManifest.forEach(entry => {
self.__precacheManifest.push({
url: entry
});
});
// Load the assets to be precached
precacheAndRoute(self.__precacheManifest);
Generally speaking, it's not possible to swap in an alternative strategy when using workbox-precaching. It's always going to be cache-first, with the versioning info in the precache manifest controlling how updates take place.
There's a larger discussion of the issue at https://github.com/GoogleChrome/workbox/issues/1767
The recommended course of action is to explicitly set up runtime caching routes using the strategy that you'd prefer, and potentially "prime" the cache by adding entries to it in advance during the install step.
We have at our company a react app (built with create-react-app) which is served today via an iframe, which works well.
A need has risen to serve the app (which is always embedded within other pages), with a script tag alone (no iframe tag).
I thought of something like:
<div id="app-placeholder"></div>
<script src="https://our-app.com/init.js"></script> // this will create a function called window.InitMyApp
<script>
InitMyApp('#app-placeholder', 'token', otherOptions)
</script>
I've tried to create init.js file in the react app's public folder. I can access the file.
From that file, how can I render the react app itself to the given selector (#app-placeholder)?
Because this file is served as-is, and doesn't get transpiled by webpack/babel, I cannot use import/jsx/require() and other stuff.
Am i on the right track?
Should I manually transpile this file?
Are there any other solutions to this rendering method?
You should try configuring the compiler with { output.library }. This should produce a compilation output that's ready for distribution, which means you can easily reference it in another document (and not need to worry about, say, transpiling/optimizing sources, because this was already performed by webpack).
Here's an example of a multi-part library produced by webpack. As you can see, the entrypoint exports are being assigned to window.
In a NodeJS application I would like to load configuration data (reports to be generated) from external files dynamically. I can load them statically by using require('path/config');
But I do have parts of the configuration that need to be refreshed on a regular schedule and, to make it all more complicated, these configuration files contain a function that must be executable.
One such report looks as follows:
const report = {
name : 'Report 3',
description : 'Very simple report.',
// Some properties
preprocessor : function() {
},
// Some more properties
};
module.exports = report;
When using require to re-load the report it is basically not reloaded. Even if I change something, it stays the same. (Reason: require() uses caching and rightfully it does.)
What is a good way (maybe an external library) to re-load external configuration files that contain executable functions?
I would use fs. If you have complete control over the configuration files (otherwise it's dangerous) you can use eval.
var fs = require('fs');
var file = fs.readFileSync(filename);
var module = {}
eval(file);
// You can access report in module.exports
If you don't want to block your application (usually recommended) you should use the async version and provide callbacks.
To circument caching problems, I now use the library require-without-cache. Seems to do the job.
I am using sw-precache along with sw-toolbox to allow offline browsing of cached pages of an Angular app.
The app is served through a node express server.
One problem we ran into is that the index.html sometimes doesn't seem to be updated in the cache although other assets has been updated on activation of new service worker.
This leaves users with an outdated index.html that is trying to load no longer existing versioned asset in this case /scripts/a387fbeb.modules.js.
I am not entirely sure what's happening, because it seems that on different browsers where the index.html has been correctly updated have the same hash.
On one browser outdated (problematic) Index.html
(cached with 2cdd5371d1201f857054a716570c1564 hash) includes:
<script src="scripts/a387fbeb.modules.js"></script>
in its content. (this file no longer exists in the cache or on remote).
On another browser updated (good) index.html
(cached with the same 2cdd5371d1201f857054a716570c1564) includes:
<script src="scripts/cec2b711.modules.js"></script>
These two have the same cache, although the content that is returned to the browsers are different!
What should I make of this? Does this mean that sw-precache doesn't guarantee atomic cache busting when new SW activates? How can one protect from this?
If these help, this is the generated service-worker.js file from sw-precache.
Note: I realize I can use remoteFirst strategy (at least for index.html) to avoid this. But I'd still like to understand and figure out a way to use cacheFirst strategy to get the most out of performance.
Note 2: I saw in other related questions that one can change the name of the cache to force bust all the old cache. But this seems to beat the idea of sw-precache only busting updated content? Is this the way to go?
Note 3: Note that even if I hard reload the browser where the website is broken. The site would work because it would skip service worker cache but the cache would still be wrong - the service worker doesn't seem to activate - my guess because this specific SW has been activated already but failed at busting the cache correctly. Subsequent non-hard-refresh visits would still see the broken index.html.
(The answers here are specific to the sw-precache library. The details don't apply to service workers in general, but the concepts about cache maintenance may still apply to a wider audience.)
If the content of index.html is dynamically generated by a server and depends on other resources that are either inlined or referenced via <script> or <link> tags, then you need to specify those dependencies via the dynamicUrlToDependencies option. Here's an example from the app-shell-demo that ships as part of the library:
dynamicUrlToDependencies: {
'/shell': [
...glob.sync(`${BUILD_DIR}/rev/js/**/*.js`),
...glob.sync(`${BUILD_DIR}/rev/styles/all*.css`),
`${SRC_DIR}/views/index.handlebars`
]
}
(/shell is used there instead of /index.html, since that's the URL used for accessing the cached App Shell.)
This configuration tells sw-precache that any time any of the local files that match those patterns change, the cache entry for the dynamic page should be updated.
If your index.html isn't being generated dynamically by the server, but instead is updated during build time using something like this approach, then it's important to make sure that the step in your build process that runs sw-precache happens after all the other modifications and replacements have taken place. This means using something like run-sequence to ensure that the service worker generation isn't run in parallel with other tasks.
If the above information doesn't help you, feel free to file a bug with more details, including your site's URL.
I am doing this to load all JS files in app folder
ss.client.define('main', {
view: 'app.jade',
css: [
'libs/reset.css',
'app.styl'
],
code: [
'libs/jquery-2.1.0.min.js',
'libs/angular-1.2.10.min.js',
'libs/lodash-2.4.1.min.js',
'app'
],
tmpl: '*'
});
There are 3 files in app, 2 that came with the default project and 1 that I added. The first 2 work fine, but the one I added does not get executed!
The funny thing is that when there are errors in that file, I set them in the Chrome console, but no function gets executed or variable added to the page.
Any ideas why?
It will need access to the window variable/global-object.
Therefore you need to require it from your entry file. Typically this means having the lodash code file in your actual code (/client/code/[...]) directory. I.e. you wouldn't put it in your libs folder, but in your main app folder, although you can make another libs folder there.
This is what I've always had to do in order to require --for example-- bootstrapJS. It defies the organisation of the client side as they set it up, but it's the way things need to be done for stuff like this.
An alternative is to require it remotely (from CDN) from your main app.jade view file.
script(src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js")