πŸ“œ ⬆️ ⬇️

AngularJS + Webpack = lazyLoad

AngularJS + Webpack = lazyLoad

Introduction


When writing Single Page Application, developers in most cases face one very common problem, namely the creation of lazyLoad modules and their subsequent loading on the client side. Those. For some action or for navigating to a URL (in most cases), we must download a specific set of dependencies β€” JavaScript, CSS, HTML, etc. In the realities of modern Front-End development, this will be a huge JavaScript file. In this article I want to share my experience and show how to implement lazyLoad modules for AngularJS and thereby reduce the total amount of code when the application is first loaded.

Why AngularJS 1.x


Probably, you, dear reader, have a reasonable question: β€œStop, which AngularJS 1.x, because just recently there was the release of Angular v5.2 ”. The question is more than relevant. It's simple, quite a lot of projects that use AngularJS 1.x and still feel good about themselves. There are a number of industries for which the transition to the new version is very costly, both in man-hours and in monetary terms. AngularJS 1.x is still in high demand in the market.

Bicycles that already have


It so happened that in the world of the Front-End development of bicycle tools / approaches to solve the same issue - the car and a small cart. Everyone chooses to suit their needs, skills and knowledge. And there is no one verified solution that will work in 99.99% of cases. This is neither good nor bad. You just need to take it and continue to develop further. Someone chooses RequireJS , someone curl.js , someone Browserify , someone <insert his>. We will look at how to implement lazyLoad module loading using Webpack , UI-Router and ocLazyLoad . All the backstage magic will be done by ocLazyLoad . If we try to make the lazyLoad module using the same require.ensure and without using ocLazyLoad, we will get something like this:
')
require.ensure error

Project structure


So let's get started. I cannot share the original project code for which I needed to implement lazyLoad modules. So I made a small application. I tried to make sure that the application did not look like a starship in which it is not clear where it comes from and in general how it all works. The main purpose is to make a working prototype that will be available online ( not only source code ). Below you can see the project structure for the require-ensure and system-import branches. For the import-es6 we will make a small addition.

 project-root β”œβ”€β”€ src β”‚ β”œβ”€β”€ core β”‚ β”‚ β”œβ”€β”€ bootstrap.js β”‚ β”œβ”€β”€ pages β”‚ β”‚ β”œβ”€β”€ home β”‚ β”‚ β”‚ β”œβ”€β”€ about β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ about.module.js β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ about.view.html β”‚ β”‚ β”‚ β”œβ”€β”€ index β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ index.module.js β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ index.view.html β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.js β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.routing.js β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.states.js β”œβ”€β”€ app.js β”œβ”€β”€ index.html 

Before we get to work with the code, let's discuss two important points that affect whether our lazyLoad module will be or not:

  1. In order for a module to become lazyLoad, it does not need to be specified as a dependency for other modules.
  2. The module should not be imported anywhere, except for the route for which you want to make this lazyLoad module.

If it is not very clear, do not worry - in practice, it will immediately become clear what’s what.

require.ensure () + $ ocLazyLoad


require.ensure was proposed by the webpack team in its first versions. This method allows developers to dynamically create separate files (chunks in the context of webpack terminology) with some part of the code that will later be uploaded on demand on the client side. This approach is less preferable for creating dynamically loaded modules, but if you really want it, then there is nothing wrong with that. This method is perfect for those who want to make lazyLoad modules without the high cost of refactoring. Below you can see an example of using require.ensure to load index.module.js :

const homeIndex = {
name: "home",
url: "/home",
component: "homeComponent",
lazyLoad: ($transition$) => {
const $ocLazyLoad = $transition$.injector().get("$ocLazyLoad");
return require.ensure([], () => {
// load whole module
const module = require("./index/index.module");
$ocLazyLoad.load(module.default);
}, "index.module");
}
};
view raw homeIndex.js hosted with ❀ by GitHub
For about.module.js code will be identical, except for the paths to the module and some other parameters. Below you can see an example of using require.ensure to load about.module.js :

const homeAbout = {
name: "home.about",
url: "/about",
component: "homeAboutComponent",
lazyLoad: ($transition$) => {
const $ocLazyLoad = $transition$.injector().get("$ocLazyLoad");
return require.ensure([], () => {
// load whole module
let module = require("./about/about.module");
$ocLazyLoad.load(module.HOME_ABOUT_MODULE);
}, "about.module");
}
};
view raw homeAbout.js hosted with ❀ by GitHub
As you can see from the code, all the magic happens in this term:

 $ocLazyLoad.load(module.HOME_ABOUT_MODULE); 

There is another option for specifying a module β€” via an object:

 $ocLazyLoad.load({ name: "home.module" }); 

But in this case, we limit ourselves in freedom of action. If we want to rename the module, we will need to change the code in several places. There is also a high probability to make a mistake when writing the name of the module. I strongly recommend you not to use this approach.

I would like to draw your attention to one important about.module.js regarding about.module.js and the subsequent download to the client side. Look at the screen below:


When you go to the Home/About link, two files are loaded at once: index.module.chunk.js and about.module.chunk.js . This happens because the home.about URL is a child of the home URL. This is worth remembering. Looking ahead, in the last section we will add another module with the new URL, and see that only one file will be loaded for it and nothing more.

System.import + $ ocLazyLoad


I thought for a long time to write about this approach or not. I think that you need to talk about it.
System.import is another construction from the webpack team, which was subsequently banned , but this approach continues to be offered as an implementation option. Moreover, this design continues to work in new versions of webpack. I suspect that this was done for compatibility reasons. If you use this design in your project, then I have bad news - it is in the status of deprecated . The code in this section will not be. Go ahead.

Dynamic imports + $ ocLazyLoad


You may have already heard that Chrome 63 and Safari Technology Preview 24 rolled out updates and now dynamic imports are available to developers. Yes, yes, the very dynamic imports that were proposed in the specification . Back in 2016 , the webpack team introduced support for dynamic imports.

In this section, we will add another module to the root of the pages directory to ensure that lazyLoad is working properly. The structure for the import-es6 is presented below:

 project-root β”œβ”€β”€ src β”‚ β”œβ”€β”€ core β”‚ β”‚ β”œβ”€β”€ bootstrap.js β”‚ β”œβ”€β”€ pages β”‚ β”‚ β”œβ”€β”€ blog β”‚ β”‚ β”‚ β”œβ”€β”€ blog.module.js β”‚ β”‚ β”‚ β”œβ”€β”€ blog.service.js β”‚ β”‚ β”‚ β”œβ”€β”€ blog.view.html β”‚ β”‚ β”œβ”€β”€ home β”‚ β”‚ β”‚ β”œβ”€β”€ about β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ about.module.js β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ about.view.html β”‚ β”‚ β”‚ β”œβ”€β”€ index β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ index.module.js β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ index.view.html β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.js β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.routing.js β”‚ β”‚ β”‚ β”œβ”€β”€ home.module.states.js β”œβ”€β”€ app.js β”œβ”€β”€ app.routing β”œβ”€β”€ app.states.js β”œβ”€β”€ index.html 

If you do not use Babel or TypeScript in your project, then everything will start from the box without any extra dancing with a tambourine. But both you and I know that in the realities of the modern Front-End it is very difficult to write code without Babel or TypeScript. Let's talk about Babel. First we need to install an additional Babel plugin that understands the syntax of dynamic imports: syntax-dynamic-import . Otherwise, we get an error:

Babel Syntax Error

Next we need to add .babelrc with settings:

{
"presets": [
[
"env",
{
"targets": {
"browsers": [
"last 2 versions",
"safari 7"
]
}
}
]
],
"plugins": [
"syntax-dynamic-import"
]
}
view raw .babelrc hosted with ❀ by GitHub
Now the second nasty mistake you can see below:

ESLint Syntax Error

Yes, you are not mistaken, ESLint also does not understand dynamic imports . To fix this you need to install a special parser for ESLint babel-eslint and then everything will work like clockwork. Add .eslintrc with settings:

{
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"impliedStrict": false
}
},
"env": {
"browser": true,
"node": true,
"es6": true
}
}
view raw .eslintrc hosted with ❀ by GitHub
Well, it's time to try dynamic imports in practice. Test them on the new module:

const appBlog = {
name: "blog",
url: "/blog",
component: "blogComponent",
lazyLoad: ($transition$) => {
const $ocLazyLoad = $transition$.injector().get("$ocLazyLoad");
// !!! Dynamic import !!!
return import(/* webpackChunkName: "blog.module" */ "./pages/blog/blog.module")
.then(mod => $ocLazyLoad.load(mod.BLOG_MODULE))
.catch(err => {
throw new Error("Ooops, something went wrong, " + err);
});
}
};
view raw appBlog.js hosted with ❀ by GitHub
As you can see from the code, the webpack team added some nice chips to dynamic imports. It is possible to specify the name of the final chunk that the webpack will create, and we can also specify the download method. Read more about it here . In the video below you can watch the work of dynamic imports:


Component vs. Template


To load modules, the component property was used. You can use the property named template instead of the component property. They work almost identically, with only one nuance. If you have sealed in the name of the component or for some other reason the component is not available, then you will get an error in the console. With the template you will not get such an error. And you can look for a very long time what the problem is.

useful links


  1. How to route to AngularJS 1.5 components
  2. Lazy loading in UI-Router
  3. GitHub source code
  4. Heroku Project

Instead of conclusion


lazyLoad modules in the context of an AngularJS application is a great opportunity to make your application more lightweight, more responsive and more distributed. Times change, requirements for applications grow, and with them grows the amount of code that we deliver to the client. If before it was enough to collect all the code in a single file, give it to the end user and everything was cool, now it is a luxury. There is a tendency to divide the application depending on the URL with the release of a common code.

That's all. Thanks for attention. Who read to the end, special thanks.

PS If you had a similar experience in the implementation of lazyLoad modules for AngularJS applications - share them in the comments.

Source: https://habr.com/ru/post/346406/


All Articles