#10 Step By Step: Lazy Loading
Friday, November 2, 2018
If you have ever found yourself in the situation where your application supports functionality that not everyone uses all the time but adds a noticable amount to your page weight or everyone uses the functionality but it is not needed right away and you would like to defer loading the code until a future time, for example in response to a user action, then lazy loading may just be the answer to your prayers. In this article we will see how to lazy load code using webpack.
The print service
- WebUi
- Source
- lazy-loading
- print.service.ts
- lazy-loading
- Source
First off we are going to start by creating a print service. This class will sit between our components and the code
that we wish to lazy load (a). While we get everything up and running our print service
will just print out any messages sent to the print
method to the console.
print.service.ts
export class PrintService {
private static instance: PrintService;
private static isCreating = false;
constructor() {
if (PrintService.isCreating === false) {
throw new Error("You can't call new to create PrintService instances! Call PrintService.getInstance() instead.");
}
}
public static getInstance() {
if (typeof PrintService.instance === "undefined" || PrintService.instance === null) {
PrintService.isCreating = true;
PrintService.instance = new PrintService();
PrintService.isCreating = false;
}
return PrintService.instance;
}
public print = (message: string) => {
console.log(`[Print Service] ${message}`);
}
}
The components
- WebUi
- Source
- lazy-loading
- lazy-loading.component1.tsx
- lazy-loading.component2.tsx
- lazy-loading
- Source
Next up we will create a couple of components that will consume the print service. Both
(b) and (c)
are identical except of course for the class name and message that is being passed to the
print
method. The purpose for them is to show the
service being used in two separate files.
lazy-loading.component1.tsx
import * as React from "react";
import { PrintService } from "./print.service";
class Component1 extends React.Component {
public render() {
return <>
<div>Component 1</div>
<button onClick={this.print}>Print</button>
</>;
}
private print = () => {
const message = "Component 1";
PrintService.getInstance().print(message);
}
}
export default Component1;
lazy-loading.component2.tsx
import * as React from "react";
import { PrintService } from "./print.service";
class Component2 extends React.Component {
public render() {
return <>
<div>Component 2</div>
<button onClick={this.print}>Print</button>
</>;
}
private print = () => {
const message = "Component 2";
PrintService.getInstance().print(message);
}
}
export default Component2;
Adding our components to the page
- WebUi
- Source
- lazy-loading
- lazy-loading.page.tsx
- lazy-loading
- Source
Now that we have created our components we just need to add them to the page. To accomplish this we will use the minimal react code shown in (d).
lazy-loading.page.tsx
import * as React from "react";
import * as ReactDOM from "react-dom";
import Component1 from "./lazy-loading.component1";
import Component2 from "./lazy-loading.component2";
ReactDOM.render(
<>
<Component1/>
<Component2/>
</>,
document.getElementById("root"),
);
Lazy loading entry point
- WebUi
- webpack.lazy-loading.js
We are now ready to add an entry point to our lazy loading react page in our webpack configuration file (e).
webpack.lazy-loading.js
var common = require("./webpack.common");
common.entry = {
"lazy-loading": "./Source/lazy-loading/lazy-loading.page.tsx"
}
module.exports = common;
Speaking of our build script it is now time to run it. Using our usual npm run build
command we can kick off our webpack build process which should result in the output shown in
(f). For now we are just creating two bundles, again this is just so we can
get everything up and running and make sure it is all working.
Now we need a page
- WebUi
- Pages
- Lazy-Loading.cshtml
- Lazy-Loading.cshtml.cs
- Pages
For completeness I am including the code for the razor page (g) and the code behind (h). As part of the webpack config that I am using any code that is in the node_modules folder is placed in its own bundle named 'common' which you can see being referenced in the page.
Lazy-Loading.cshtml
@page
@model LazyLoadingModel
@{
ViewData["Title"] = "Lazy Loading";
}
@section Scripts {
<environment names="Development">
<script src="/js/common.bundle.js"></script>
<script src="/js/lazy-loading.bundle.js"></script>
</environment>
}
<h1>Lazy Loading</h1>
<div id="root"></div>
Lazy-Loading.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebUi.Pages
{
public class LazyLoadingModel : PageModel
{
public void OnGet()
{
}
}
}
With all of that done we are now in a position to make sure that everything is in fact working correctly. All we have to do is fire up which ever server we are using and navigate to our lazy loading page. If we do that we should see the page should look something like (i) with the console emitting the appropriate messages when the buttons are pushed.
Time for that lazy loading
- WebUi
- Source
- lazy-loading
- print.ts
- lazy-loading
- Source
Now that everything is working we need to create the code that we will be lazy loading. For our purposes we will use the simple function shown in (j).
print.ts
export default function print(message: string) {
console.log(`[Lazy Loaded Print Function]: ${message}`);
}
Modifying the print service
- WebUi
- Source
- lazy-loading
- print.service.ts
- lazy-loading
- Source
To accomplish what we are setting out to do we need to modify our print service so that it dynamically imports
the print function that we just created (k). In this code we see
a few non-obvious changes. Within the import we are adding a comment which tells webpack that we wish the code
associated with it should be added to a separate bundle, in this case, named 'lazy-loading-print'. The result of
the import will be a promise that we then can the then
method on. Webpack
will pass in the module
and to get our print function we need to
reference the default
property. A couple of points need to be mentioned
here. If you are supporting a browser that does not natively support promises you will need to polyfill it and
you should add code to handle the case when the promise rejects.
print.service.ts
export class PrintService {
...
public print = (message: string) => {
import(
/* webpackChunkName: "lazy-loading-print" */
"./print"
)
.then((module) => {
const print = module.default;
print(message);
});
}
}
Modifying our webpack config
- WebUi
- webpack.lazy-loading.js
The last thing we need to do is add a setting to the output object of our webpack configuration as shown in (l). This addition is what makes webpack use the name that we specified in the comment in our import statement for the name of the resulting bundle.
webpack.lazy-loading.js
...
common.output.chunkFilename = "[name].bundle.js";
...
Next up we need to build our bundles once again. When that is done you should see output that is very similar to what is shown in (m).
Once again if we return to the our browser and click our buttons we should see the console output shown in (n). In addition to this it is important that we notice that there is only one call to our server to get our lazy loaded code no matter how many times we click the buttons.