Code Splitting / Preloading Content while user is browsing? - javascript

Using tools like Webpack we can enable code splitting and only
load our application code asynchronously when required.
Example in the context of a react application with react-router.
Load initial page.
-> go to new route
---> webpack loads in the component file required asynchronous.
Webpack waits until the code is required in order to initiate the request.
My question is, once the base application code load, can we start loading the rest of the code, even before the user initiates the transition to the new route?
My view is that will prevent the user from waiting for the webpack chunk to download.
-> Load initial page
--> user sitting idle or browsing on home page
----> Start loading application code for rest of the application
---> user goes to new route (faster UX because code has already download in the background)
I hope this makes sense

Yes you can achieve this. I will show one of the possible solutions.
Firstly let's create backgroundLoader for queuing required chunks:
const queue = [];
const delay = 1000;
let isWaiting = false;
function requestLoad() {
if (isWaiting) {
return;
}
if (!queue.length) {
return;
}
const loader = queue.pop();
isWaiting = true;
loader(() => {
setTimeout(() => {
isWaiting = false;
requestLoad();
}, delay)
});
}
export default (loader) => {
queue.push(loader);
requestLoad();
}
This function will load your chunks in background with 1 second delay (you can tweak it - for example start popping queue after for example 5 second or shuffle array of chunks).
Next you must register your require.ensure in queuing function backgroundLoader:
import render from './render'; // not relevant in this example
import backgroundLoader from './backgroundLoader';
let lightTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeA.css'));
}, 'light');
}
let darkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeB.css'));
}, 'dark');
}
let pinkTheme = (cb) => {
require.ensure([], () => {
cb(require('./themeC.css'));
}, 'pink');
}
backgroundLoader(lightTheme);
backgroundLoader(darkTheme);
backgroundLoader(pinkTheme);
export default (themeName) => { // router simulation
switch(themeName) {
case 'light':
lightTheme(render);
break;
case 'dark':
darkTheme(render);
break;
case 'pink':
pinkTheme(render);
break;
}
};
Once you require your chunk in switch statement you pass render function containing resolve function. In backgroundLoader this function will be empty resulting only loading chunk to head of your app.
Full code for this example you can see on WebpackBin (you can check network to see how chunks are loaded in background)

Related

Dynamically add all components in a folder

So I'm currently building a tutorial, where the number of pages will continuously expand as more features are added, currently I am manually adding each file to the displaying file, i.e.
const Page0 = () => import("../../components/tutorial/Page0/index.vue");
const Page1 = () => import("../../components/tutorial/Page1/index.vue");
but obviously if this isn't very well handled once it gets really big i.e.
const Page0 = () => import("../../components/tutorial/Page0/index.vue");
...
const Page100 = () => import("../../components/tutorial/Page100/index.vue");
So I was wondering if there was a way to know let vue.js know that it should be fetching all files/folders in a certain folder and render each of them as a component with 'Page' + number name.
Ordering matters.
full code sandbox here https://codesandbox.io/s/serene-curie-it7xo?file=/pages/tutorial/_page.vue:102-247
use dynamic loading then.
in your _page.vue
function mapComponents() {
let components = {};
for (let i = 0; i < 2; i++) { // 2 should be your pages amount
components["Page" + i] = () =>
import(`../../components/tutorial/Page${i}/index.vue`);
}
return components;
}
export default {
components: mapComponents(),
computed: {
current() {
//.... other code
in your tutorial.vue
data() {
return {
pages: [...Array(2).keys()] // same here, 2 should be your pages amount
};
},
maybe just use another function to get the amount of the page, but you got the idea :)
working sample : https://codesandbox.io/s/clever-feynman-vgm93?file=/pages/tutorial.vue:280-347

Edit HTML through Javascript function real time

I am trying to show a "spinner loader" when a user clicks a button that fires a function which takes some time, so the user knows that stuff is being done in the back-end. I am writing in VueJS the front-end. The spinner is being shown only after the function has finished its job, which is something I do not want as my goal is that the spinner is shown as long as the function is run and the user wait.
// Unlock is a prompt page that contains a "Success" button and a "Cancel" button
<unlock v-model="password" #cancel="cancel" #success="add"/>
<icon type="spinner" class="spin" v-if="loading"></icon>
methods: {
async add() {
this.loading = true
this.error = ''
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
this.loading = false
this.$store.dispatch('TOGGLE_PROMPT', '')
}
...
...
}
I expected the spinner to show up as soon as the function is fired up, until it finishes when another page loads either way. I would like the spinner to be shown as long as we wait for the await this.$store.dispatch('DO_SOMETHING') to execute completely. Problem is, it is shown only after the whole function gets finished.
I tried playing around with the async and await, no results. Is it possible to show the element in HTML-VueJS directly, or this is something that just we cannot do it through Javascript?
Thank you in advance.
EDIT:
The action
DO_SOMETHING: ({ commit, dispatch, state }, { users, password }) => {
commit('SET_LOADING', true)
const results = JSON.stringify(users.map( x => {
if (...) delete ...
if (...) delete ...
return x
}))
API.addUser(results, password || state.password)
// refresh page
return dispatch('FETCH_PAGE')
},
The API.addUser, makes a call to the backend, where a Java function is run that makes the modifications in the database.
EDIT v2:
I added the following console.log()
console.log("1")
await this.$store.dispatch('DO_SOMETHING', { users: this.selected_users, password: this.password })
console.log("6")
console.log("2")
API.addUsers(results, password || state.password)
console.log("5")
The below is in the API/addUsers() function.
console.log("3")
const results = JSON.parse(window.UserbaseApplication.addUsers(users, password))
console.log("4")
As expected, the results are:
1
2
3
4
5
6
So the function is awaiting the return of the API as expected.
Not sure about Vue, but in vanilla JS:
Lets say you have a user press a button, which calls a function that runs some computation. During the computation, you have a loader ready to go.
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
myfunction(...).then(
res => {
// hide the loader after successful promise call
loader.style.display = "none";
});
});
This example assumes that your function returns a promise
Same can be achieved with async/await
const myfunction = async function() {
const res = await .... some computation
return res
}
const button = document.querySelector(".mybutton")
button.addEventListener('click', () => {
const loader = document.querySelector(".myloader");
loader.style.display = "inline-block";
// we are assuming the loader covers the whole width & height, with a black filter in the background
// therefore, we don't have to hide other elements
const result = myfunction();
loader.style.display="none";
});

Get Header's OOXML

I am unable to get the OOXML of a Header. According to the documentation getHeader" method will return Body type. The Body has a method to get OOXML. But it looks like it is not returning the OOXML. Maybe I am missing something?
Here's my code:
Word.run(function (context) {
// Create a proxy sectionsCollection object.
var mySections = context.document.sections;
// Queue a commmand to load the sections.
context.load(mySections, 'body/style');
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
// header
var headerBody = mySections.items[0].getHeader("primary");
// header OOXML
//// NOT GETTING OOXML HERE
var headerOOXML = headerBody.getOoxml();
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
// modify header
var headerOOXMLValue = ModifyHeaderMethod(headerOOXML.value);
headerBody.clear();
headerBody.insertOoxml(headerOOXMLValue, 'Start');
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
callBackFunc({
isError: false
});
});
});
});
})
The "art" of Office.js is to minimize the number of "syncs" you do. I know that is kind of an unnecessary burden, but that's how it is.
With that in mind, In this case you only need ONE sync.
this code works (assuming that you have only one section in the doc).
btw you can try it in script lab with this yaml.
if this does not work, please indicate if this is Word for Windows (and what build) or Online, or Mac... thanks!
async function run() {
await Word.run(async (context) => {
let myOOXML = context.document.sections.getFirst()
.getHeader("primary").getOoxml();
await context.sync();
console.log(myOOXML.value);
});
}
You have a lot of extra code here but the gist of your problem is that headerOOXML won't be populated until you sync():
Word.run(function (context) {
var header = context.document.sections // Grabv
.getFirst() // Get the first section
.getHeader("primary"); // Get the header
var ooxml = header.getOoxml();
return context.sync().then(function () {
console.log(ooxml.value);
});
});

General solution for pre-emptive background work scheduling on javascript

Here is the scenario:
When my web app starts, I want to load data from several tables in local storage (using indexedDB). I delegate this work to a web worker. It will load each table in turn, and fire a message with the data as it loads each one. On the main thread, a listener will receive the message and store the data in a cache.
But let's say the user presses a button to view the data for a specific table. The app calls a function that checks the cache, and sees that the data for that table has not been loaded yet.
How does this function wait until the data for that table has been cached so that it can return the data? Even more important, what if the table is scheduled to be loaded last? How can this function send a message to the web worker to prioritize loading that specific table so that its data will available as soon as possible?
What is a general pattern for a clean solution to this pre-emptive scheduling problem? I would like to avoid polling if at all possible.
The Worker may use an asynchronous queue that contains all the tables to be loaded and is sorted after a certain priority, so you can priorize certain tables and they get sorted to the front of the table. As you havent shown a real implementation here is a more generalized version:
class AsyncPriorityQueue {
constructor(task){
this.task = task;
this.queue = [];
}
push(element, priority = 0){
const pos = this.queue.findIndex(el => el.priority < priority) + 1;
this.queue.splice(pos, 0, {element, priority});
if(this.running) return;
this.running = true;
this._run();
}
prioritize(element, priority = 10){
const pos = this.queue.findIndex(el => el.element === element);
if(pos != -1) this.queue.splice(pos, 1);
this.push(element, priority);
}
async _run(){
while(this.queue.length)
await this.task(this.queue.shift().element);
}
}
Note: If the task is not asynchronous you should use sth like setTimeout(next, 0) to allow the process messaging to interrupt it...
A sample implementation could be an image loader:
class ImageLoader extends AsyncPriorityQueue {
constructor(){
super(function task(url){
const img = new Image();
img.src = url;
return new Promise(res => img.onload = res);
});
}
}
const loader = new ImageLoader;
loader.push("a.jpg");
loader.push("b.jpg", 1); // a bit more important
// Oh, wait:
loader.prioritize("a.jpg");

Windows 8 Javascript app activation/launch deferral

So currently in a windows 8 WinJS app I'm coding, I am trying to get the loading of an xml file to take place in the app startup sequence, while the splash screen is still showing, as this xmldoc element is needed for when the home page loads, and loading of the home page will fail without it.
This is my initiation sequence in default.js:
(function () {
"use strict";
var activation = Windows.ApplicationModel.Activation;
var app = WinJS.Application;
var nav = WinJS.Navigation;
var sched = WinJS.Utilities.Scheduler;
var ui = WinJS.UI;
app.addEventListener("activated", function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
console.log("Newly Launched!");
var localSettings = Windows.Storage.ApplicationData.current.localSettings;
WinJS.Namespace.define("MyGlobals", { localSettings: localSettings });
// APP RUN TYPE CHECK AND SEQUENCE (FIRST RUN / NOT FIRST RUN):
if (MyGlobals.localSettings.values['firstRunCompleted']) {
console.log("NOT FIRST RUN!");
// CACHE VERSION CHECK. IF APP HAS BEEN UPDATED, INITIATE NEWLY ADDED CACHE VALUES HERE:
} else {
console.log("FIRST RUN!")
MyGlobals.localSettings.values['firstRunCompleted'] = true;
};
//loadXML(); have tried many things with this. doesn't work.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
var currentVolume = app.sessionState.currentVolume;
if (currentVolume) {
console.log("RESTORE FROM SUSPENSION");
console.log(currentVolume);
};
}
nav.history = app.sessionState.history || {};
nav.history.current.initialPlaceholder = true;
// Optimize the load of the application and while the splash screen is shown, execute high priority scheduled work.
ui.disableAnimations();
var p = ui.processAll().then(function () {
return nav.navigate(nav.location || Application.navigator.home, nav.state);
}).then(function () {
return sched.requestDrain(sched.Priority.aboveNormal + 1);
}).then(function () {
ui.enableAnimations();
});
args.setPromise(p);
args.setPromise(WinJS.UI.processAll().then(function completed() {
loadSavedColour();
// Populate Settings pane and tie commands to Settings flyouts.
WinJS.Application.onsettings = function (e) {
e.detail.applicationcommands = {
"helpDiv": { href: "html/Help.html", title: WinJS.Resources.getString("settings_help").value },
"aboutDiv": { href: "html/About.html", title: WinJS.Resources.getString("settings_about").value },
"settingsDiv": { href: "html/Settings.html", title: WinJS.Resources.getString("settings_settings").value },
};
WinJS.UI.SettingsFlyout.populateSettings(e);
}
As you can see where I have the commented line of "loadXML()", that is where I need the loadXML() function to take place.
Here is my loadXML() function:
function loadXML() {
Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("foldername").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync(MyGlobals.localSettings.values['currentBook']).done(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file).then(function (doc) {
WinJS.Namespace.define("MyGlobals", {
xmlDoc: doc,
});
})
})
});
};
(loadXML is a working function and works in other scenarios)
However, the issue is that before the loadXML function finishes, the app splash screen goes away, and the next home.html home page loads, which starts the accompanying home.js, which has a function that requires the MyGlobals.xmlDoc object that loadXML should have made. This immediately crashes the app, as MyGlobals.xmlDoc is undefined/null.
I used to have this app working by running loadXML in home.js for the home.html page directly, but in that scenario the XML document is reloaded every time navigation is made to the page, wasting time and resources. As such, I'm trying to move the xmldocument loading into the app startup/initialization.
Thanks a lot!
loadXML has async functionality and you need to handle that.
You shouldn't expect that the loadFromFileAsync (or any of the other async functions) have completed before it returns to the caller. If your code doesn't wait, you'll find that the MyGlobals.xmlDoc value won't be set when you need it.
I've renamed it below to be more accurate as to its behavior. The big change is that it returns a Promise that can be used by the caller to properly wait for the Xml doc to be loaded. This Promise could be used with other Promises to wait on multiple conditions if you'd like (or in the case, other async work).
function loadXMLAsync() {
return new WinJS.Promise(function (complete, error, progress) {
var localSettings = MyGlobals.localSettings.values;
var installedLocation = Windows.ApplicationModel.Package.current.installedLocation;
installedLocation.getFolderAsync("foldername").then(function (externalDtdFolder) {
externalDtdFolder.getFileAsync(values['currentBook']).done(function (file) {
Windows.Data.Xml.Dom.XmlDocument.loadFromFileAsync(file).then(function (doc) {
complete(doc);
});
});
});
});
};
Then, in use:
loadXmlAsync().then(function(doc) {
WinJS.Namespace.define("MyGlobals", {
xmlDoc: doc,
});
// and any other code that should wait until this has completed
});
The code above does not handle errors.
I believe what you need to do is to extend the splash screen to give your app more time to initialize UI. For your scenario, it's loading the xml.
I will suggest you read How to extend the splash screen (HTML). The main idea is to display an extended splash screen(you can use the same image as the default one) in the activated event, then call your loadXML.
In addition to other comments, what you really need to do is include the promise from loadXml with what you pass to args.setPromise. As you know, setPromise is a way to tell the app loader to wait until that promise is fulfilled before removing the splash screen. However, in your code you're calling setPromise multiple times. What you should be doing is joining all the promises you care about (animations, loadXml, and setting loading) with WinJS.Promise.join, so that you get a single promise that's then waiting on all the other three, and when that one is fulfilled, then remove the splash screen.
Alan's suggestion for an extended splash screen is helpful if that whole loading process ends up taking too long, as doing an extended splash gives you total control over what's happening and when the transition happens to your main page.

Categories