How can I improve load performance of Angular2 apps? - javascript

Angular2 app is loading slow, how can I improve the performance on load?
I use Angular2, typescript with html5.
currently my app takes 4 seconds to load.
I host with Firebase and use cloudflare.
Things I'm doing / info:
I've compressed images.
I minify css
I minify js.
I use async on my scripts.
My scripts are in my .
The scripts are around 700kb
I used google speed test and get 65%
I used minified version of the libs I use e.g. bootstrap etc.
Using systemjs.
This is the seed app im using: https://github.com/mgechev/angular-seed
Flow:
When the app loads it shows a blue screen (this is the bootstrap css) and then 4 seconds later the app loads and works really fast. But takes 4 seconds to load. It seems the app.js file that systemjs minifies to is slowing the whole app, and not showing the views fast enough.
This is my website speed test:
https://www.webpagetest.org/result/161206_F5_N87/
This is my website:
https://thepoolcover.co.uk/
Let me know if you need more information about my app and any other things I can do.

A single page application generally takes more time while loading as it loads all necessary things at once.
I had also faced same problem and my team has optimized our project from loading in 8 seconds to 2 seconds by using following methods.
Lazy loading a module : Lazy loading modules helps to decrease the startup time. With lazy loading our application does not need to load everything at once, it only needs to load what the user expects to see when the app first loads. Modules that are lazily loaded will only be loaded when the user navigates to their routes. Angular2 has introduced modules in its final release RC5. See below for step-by-step guide.
Aot Compilation : With AoT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
It reduces the payload size : There's no need to download the Angular compiler if the app is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload. For more info see this.
Webpack : Webpack is a popular module bundler, a tool for bundling application source code in convenient chunks and for loading that code from a server into a browser. You can configure your Angular 2 web application with webpack (see this guide).
Remove scripts,stylesheet from index.html : Remove all scripts and stylesheet which are not needed in index.html. You can load these script dynamically in component itself by calling a service.
Make a file script.service.ts which can load any script on demand for that component
\script.service.ts
import { Injectable } from '#angular/core';
declare var document: any;
#Injectable()
export class Script {
loadScript(path: string) {
//load script
return new Promise((resolve, reject) => {
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = path;
if (script.readyState) { //IE
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
resolve({ loaded: true, status: 'Loaded' });
}
};
} else { //Others
script.onload = () => {
resolve({ loaded: true, status: 'Loaded' });
};
};
script.onerror = (error: any) => resolve({ loaded: false, status: 'Loaded' });
document.getElementsByTagName('head')[0].appendChild(script);
});
}
}
This is just a sample code to load script dynamically, you can customize and optimize it by yourself according to your need.
For stylesheet you should load it in component using styleUrl.
Use Browser Caching : Your webpage files will get stored in the browser cache when you use browser caching. Your pages will load much faster for repeat visitors and so will other pages that share those same resources. For more info https://varvy.com/pagespeed/leverage-browser-caching.html
minimize the code in app.component.ts : minimize the code present in app.component.ts which always run when the app loads or reloads.
set data on app Initialization : if you are using same api calls multiple times in your project or in components,
or you are dependent upon same data in multiple component, instead of calling api multiple times what you can do is save
the data as an object in service on app initialization. That service will act as a singleton throughout the project and you
can access that data without calling api.
Lazy loading of modules step by step
Modular structure : We have to divide our App into separate modules. For example an app may have a user side and an admin side and each will have its own different components and routes, so we will separate this two sides into modules admin.module.ts and user.module.ts.
Root Module : Every Angular app has a root module class. By convention it's a class called AppModule in a file named app.module.ts , this module will import the above two module and also the AppComponent for bootstrap. You can also declare multiple components according to your need. Sample code in app.module.ts:
\app.module.ts
import { NgModule } from '#angular/core';
import { UserModule } from './user/user.module';
import { AdminModule } from './admin/admin.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login.component';
#NgModule({
imports: [UserModule, AdminModule],
declarations: [AppComponent, LoginComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
Routes : Now in your routes you can specify like the following
\app.router.ts
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { LoginComponent } from './login.component';
const routes: Routes = [
{ path: 'login', component: 'LoginComponent' }, //eager loaded
{ path: 'admin', loadChildren: './admin/admin.module#AdminModule' }, // Lazy loaded module
{ path: 'user', loadChildren: './user/user.module#UserModule' } //lazy loaded module
];
Now when the application loads, it will only load LoginComponent and AppComponent code. These modules will only be loaded when we visit /admin or /user routes. Hence it will decrease the size of payload for loading into the browser, thus resulting in fast loading.
Nesting Modules : Just like app.module every module has its own set of components and routes. As your project becomes larger, the nesting of modules inside module is the best way to optimize because we can lazily load those modules whenever we require.
PLEASE NOTE
Above code is only for explanation, please refer for full example
https://angular-2-training-book.rangle.io/handout/modules/lazy-loading-module.html

How about "code splitting".
From the Webpack site:
"For big web apps it’s not efficient to put all code into a single file, especially if some blocks of code are only required under some circumstances. Webpack has a feature to split your codebase into “chunks” which are loaded on demand. Some other bundlers call them “layers”, “rollups”, or “fragments”. This feature is called “code splitting”.
Link here:
https://webpack.github.io/docs/code-splitting.html
Note that the Angular CLI uses Webpack.
Also, make sure that if your app bootstraps with data calls, that you are not holding up the rendering of your components waiting on those calls to return. Promises, async, etc.

It is difficult to diagnose the precise problem you are having without hands-on to your entire code base and backend (as others have suggested, the problem may not be angular at all).
Having said that, I HIGHLY recommend you start using the angular-cli. It was designed by the angular team to accomplish all of the things you need to do in a easy-to-use command line interface. So my answer is predicated on the use of the angular-cli.
Here are the general things you can do to optimize your ng2 project for production:
1) Ahead of Time (AoT) Compilation - Bundling/Minification/Tree-shaking
Look, forget about the headache of configuring a bunch of gulp tasks to accomplish all of these things. The angular-cli handles Bundling/Minification/Tree-shaking/AOT compilation in one easy step:
ng build -prod -aot
This will produce the following js files in your "dist" folder:
inline.d41d8cd98f00b204e980.bundle.js
vendor.d41d8cd98f00b204e980.bundle.js
main.d41d8cd98f00b204e980.bundle.js
styles.4cec2bc5d44c66b4929ab2bb9c4d8efa.bundle.css
You will also get the gzipped versions of these files for MORE optimization:
inline.d41d8cd98f00b204e980.bundle.js.gz
vendor.d41d8cd98f00b204e980.bundle.js.gz
main.d41d8cd98f00b204e980.bundle.js.gz
Angular's AOT compilation will automatically do "tree shaking" on your code and get rid of any unused references. For example, you may use lodash in your project, but you probably only use a few lodash functions; tree shaking will trim away all the unused parts of lodash that are not needed in your final build. And most importantly, AOT compilation will pre-compile all of your code and views which means LESS time the browser needs to get the ng2 app rolling. Click here for more info on angular's AOT compilation.
2) Lazy loading parts of your app
If you further carve up your app into different parts, you do not need to load EVERYING when your app first loads. You can specify different modules for your application that then can be bundled (by the angular-cli aot compiler) into different chunks. Read up here to learn how to organize your project into modules that you can compile into chucks that are only loaded AS NEEDED. Angular-cli will manage the creation of these chunks for you.
3) Angular Universal
Now if you really want to make your load time EXTREMELY fast then you will want to consider implementing Angular Universal, which is when you compile your initial view ON THE SERVER. I have not used Angular Universal as I have been able to achieve fast load times with steps 1 and 2. But it is an exciting option in the ng2 toolset. Keep in mind you don't compile or run the ng2 app on the server, you compile the initial view serverside so the user quickly receives a jolt of html and thus the user PERCEIVES the load time to be very fast (even though a full load will still lag behind a little bit). This step does not obviate the need for the other steps. As a bonus, Angular Universal also is supposed to help with SEO.
4) Secondary bundling
If I am not using lazy loading, I usually go ahead and further bundle the distribution files that are generated from AOT compilation. Thus I create one main.bundle.js file that concats inline.js, vendor.js and main.js files. I use gulp for this.

because its SPA and angular 2 init too slow. thats it. plus RXjs, tons of polifills etc. AOT can help but a lot of angular2 libs do not work with it. angular universal realy helps

Build your angular app using following command to get maximum optimization benefits.
ng build --prod --aot --optimization --build-optimizer --vendor-chunk --common-chunk --extract-licenses --extract-css --source-map=false
Basically you will build in aot mode and use treeshaking with some optimization.
optimization: Enables optimization of the build output.
vendor-chunk: Use a separate bundle containing only vendor libraries.
common-chunk: Use a separate bundle containing code used across multiple bundles.
extract-css: Extract css from global styles onto css files instead of js ones.
build-optimizer: Enables #angular-devkit/build-optimizer optimizations when using the 'aot' option.
source-map=false: remove source map will also reduce bundle size

Try disabling source maps by running ng serve --sourcemap=false

Related

How to load javascript es6 module determined at runtime?

I am developing a SPA which contains multiple dashboards. Each dashboard is a set of Web Components respecting a layout. These components are not known statically. They are determined at run-time.
What I want to achieve is the following scenario:
Application starts.
The application performs XHR to fetch dashboards representations from a REST service.
User sees dashboards listed by name.
User clicks on a dashboard item. (say dashboard 1)
The application determines the necessary components needed to load dashboard 1 (web components)
Load web components and their dependencies.
I am pretty aware of loading web components dynamically which was answered here
My actual problem is
how to load a web component with their dependencies at run-time
without having to load duplicate dependencies?
Are there any module formats or practices to follow when packaging the
web components?
If you have a mapping from dashboard to the module that implements it, then you can just dynamic import() the necessary module. The JS module system will take care of not loading modules that have already been loaded, so common dependencies like lit-element will only be loaded once.
This deduplication works based on URL, so you have to make sure that you have one copy of a dependency installed via npm. npm dedupe will attempt to deduplicate your node_modules/ folder, and npm ls will let you see the dependency tree.
All current browsers support dynamic import(), and most bundlers do too - as long as the argument is a string constant. This means that you can't compute the module URL based on the dashboard if you want to use a bundler. You need something like:
const dashboardLoaders = {
'dash-1': () => import('./dashboards/dash-1.js'),
'dash-2': () => import('./dashboards/dash-2.js'),
// etc...
};

How to split code into several bundles with Vue CLI3

I have a Vue project using TypeScript and built by Vue-CLI3.
What I'm trying to achieve is to get Webpack to build separate bundles for my workers. I've read about Webpack Code Splitting and about configureWebpack in vue.config.js, but so far had no luck in putting them together.
The project setup is the standard vue create type. I have a ./src/main.ts as the main entry point and a bunch of TypeScript modules, I want as separate bundles with their own dependency trees (I'm fine with code duplication if it can't be avoided).
I'd like to get
./dist/js/all main stuff
./dist/js/workers/worker1.6e3ebec8.js
./dist/js/workers/worker2.712f2df5.js
./dist/js/workers/worker3.83041b4b.js
So I could do new Worker(worker1.6e3ebec8.js) in the main code.
I could launch workers from the main package by generating javascript code, putting it into a blob and instantiating from that, but it looks rather awkward. Besides, my worker code import other modules, so it doesn't seem to be an option anyway.
I'm quite new to all of this, so maybe I'm not even heading in the right direction.
What is the usual way of doing that on this stack?
You can use import(), it will return a Promise and will resolve your module.
As you are using Vue-CLI 3, webpack is ready and it should split your bundle automatically.
const moduleName = 'coolModuleName'
import (
/* webpackChunkName: "[moduleName]" */
`#/my/module/path/${moduleName}.js`
).then(moduleCode => {
// use your module
})
// load them in parallel
const getModuleDynamically(path, moduleName) => import(
/* webpackChunkName: "[moduleName]" */
`#/${path}/${moduleName}.js`
)
Promise.all([
getModuleDynamically(path, moduleName1),
getModuleDynamically(path, moduleName2),
getModuleDynamically(path, moduleName3)
])
Got there! #aquilesb's answer did help, although I've failed to get getModuleDynamically() from the answer working after plenty of experimenting.
Update: Looks like with this solution I'm not able to use imports of npm modules. I've tried experimenting with worker-loader but haven't got anywhere so far.
Here are a few takeaways:
Create a separate webpack config for packing workers. The target: 'webworker' must be there. Call it with webpack --config ./webpack.config.workers.js, as Vue wouldn't know about that.
Create a separate tsconfig.json for workers TypeScript. The lib setting for workers must be there: "lib": ["esnext","webworker","scripthost"] as well as the proper include:[...]/exclude:[...] settings.
You may need to tell Vue to use the main tsconfig.json that has it's own "lib":["esnext","dom","dom.iterable","scripthost"] and include/exclude. This is done in vue.config.js, you will probably need to create it. I use chainWebpack configuration option of Vue config.
Let Webpack know you have dynamic loading by making calls to import() with static (i.e. not variable) names. I haven't found a way to do so in config file, but it doesn't matter: you can't help hardcoding the names somewhere, how else Webpack would know it has to bundle and split the code?
Somehow get the name(s) of generated files, as you must have at least one of them at runtime to do new Worker(filename). I used the --json option of Webpack CLI for that.
There are many ways all of this can be achieved. This is what this ended up looking like in my project:
Folder structure:
webpack.config.workers.js
vue.config.js
tsconfig.base.json
src/main/
src/main/tsconfig.json -- extends tsconfig.base.json
src/shared/ -- this code may be duplicated by the Vue app bundles and by workers bundle
src/workers/
src/workers/tsconfig.json -- extends tsconfig.base.json
webpack.config.workers.js: contains a single entry – the main worker file, that loads the other stuff.
entry: {
worker: './src/workers/worker.ts'
}
build.workers.sh: the script calls Webpack CLI and produces a JSON file with the resulting workers names (trivial actions on folders are omitted). The only one I need is called "worker". The rest is to be dynamically loaded by it.
#!/bin/bash
# Map entry name -> bundle file name
# "assetsByChunkName":{"entryN":"entryN.[hash].js", ...}
json=$(webpack --config ./webpack.config.workers.js --json $#|tr -d "\n\r\t "|grep -Eo '"assetsByChunkName":.+?}')
# Remove "assetsByChunkName"
json=$(echo "${json:20}")
echo $json
echo $json > "$target/$folder/workers.json"
Load workers.json at runtime. The other option would be to use it at compile time by providing Vue config with const VUE_APP_MAIN_WORKER = require("path to/workers.json").worker and using this env constant.
Now that we have the name of the main worker file, we can do new Worker("main worker file path we've got from webpack").
The main worker file contains the function that statically references other modules and dynamically loads them. This way Webpack knows what to bundle and how to split the code.
enum WorkerName {
sodium = "sodium",
socket = "socket"
}
function importModule(name: WorkerName): Promise<any> {
switch (name) {
case WorkerName.sodium:
return import(
/* webpackChunkName: "sodium" */
"workers/sodium"
);
case WorkerName.socket:
return import(
/* webpackChunkName: "socket" */
"workers/socket"
);
}
}
Use the postMessage/message event API to tell your main worker code what to load.
const messageHandler = (e: MessageEvent) => {
// here goes app-specific implementation of events
// that gets you the moduleName in the end
importModule(moduleName).then((work) => {
// do smth
});
};
Now, to the correct answer.
To achieve the following:
Using webworkers
Using both dynamic imports and normal imports in webworker code
Sharing code between webworkers and main app
I had to add a separate rule for worker-loader in vue.config.js and also to add babel-loader. It took me some time to find the correct solution, but I dropped the previous one (in my other answer) in the end. I still use separate tsconfig.js for main and for webworkers.
What I'm still not happy with, is that vue-cli–or rather fork-ts-checker plugin–doesn't seem to know the webworker-specific types in my worker classes (so I can't use DedicatedWorkerScope, for instance).

How to bundle react components which would be needed dynamically?

We have several applications deployed which were with plain html and js.
These applications should be completely independent modules without affecting other applications deployed.
But we should be able to load from one application to another if the situation needs which will be dynamically decided by business.
We were able to load different application screens by simply giving the relative path as there was no dependency bundling previously.
Now we are converting them to react applications with webpack as bundler.
Here we have to use dynamic import if we are going to need something dynamically. That also works on patterns which should be given at the build time.
So is there any way to achieve this kind of dynamic pattern using webpack bundler?
For importing screen components, code is something like below,
Promise.all(reqList.map(modulePath =>
{
return import(/* webpackMode: "lazy-once" */`../../../${modulePath}.jsx`)
})).then(modules => {doStuff()})
As they all are parallelly deployed applications we are trying to go back 3 folders (i.e, root folder like webapps in tomcat) and access the other application path, which will be derived dynamically in modulePath variable.
So while importing, webpack tries to import from the chunks which has already been loaded on first application launch. But this loaded chunks are not having the screens from other applications yet.
We have tried giving each jsx files as entry points in webpack which did created independent files but if we make them as entry files, they should be attached to the index.html manually, Which would cease export in jsx to work.
My wepack config is something like below,
function getEntries(pattern) {
const entries = {};
glob.sync(pattern).forEach((file) => {
let fileName = file.substr(0,file.indexOf("."));
entries[fileName.replace('src/', '')] = path.join(__dirname, file);
});
return entries;
}
let jsxFiles = getEntries('src/**/*.jsx');
console.log(Object.keys(jsxFiles));
module.exports = {
entry: jsxFiles,
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
chunkFilename: "AppId"+'.js'
}
}
The first application is loading fine as webpack is able to find the bundled chunks but when we try to load different application screen components, dynamic importing fails saying module not found.
Is there any way we can achieve this kind of dynamic imports?
Thanks for going through such a long post, I didn't had a choice but to explain.. :)
Essentially, you are looking for dynamic module loading. You would need to webpack each module separately.
To load them, you have several options
Use the browser's native import(), but IE, Edge, and Firefox don't support it, yet
Use a module loader library
SystemJS
RequireJS, although it's rather old
In case of going with a module loader, you need to webpack your modules to the corresponding System.register or AMD formats

Using Vue without bundlers?

I am creating some experimental web sites that are using JavaScript without bundling. For dependency management I have used RequireJS until now, but I have started to use SystemJS recently since it has some very nice support for HTTP2. I have done some experiments so far on my custom set web server and results are great for the web sites I am creating. For example, first page render happens around 400ms, full page load at 800ms etc.
I am doing this because I want to take full advantage of HTTP2 and I want to lazy load only scripts that I am using at a certain moment. Code like that is easier to maintain, it’s cached better etc. At the moment there is a total craze about bundlers like Webpack, but that is not something I want to use.
Here is the question: is there a way to compile single Vue file / components by using Gulp and then load them with SystemJS as AMD or CommonJS modules?
edit:
This is what I want to achieve with SystemJS and Vue:
entry point JS file:
// SystemJS config
SystemJS.config({
/* ... */
baseURL: './',
map: {
// App
'app': 'scripts/app.min.js',
// Utils
'axios': 'scripts/vendor/axios/axios.min.js',
'modernizr': 'scripts/vendor/modernizr/modernizr.min.js',
// Framework
'vue': 'scripts/vendor/vue/vue.min.js',
// Components
'vueHelloWorld': 'scripts/components/hello/vueHelloWorld.js', // <- Compiled VUE component
'vueMenu': 'scripts/components/menu/vueMenu.js'
},
depcache: {
'vueHelloWorld': ['vue'],
'vueMenu': ['vue', 'vueHelloWorld']
}
/* ... */
)};
// Initially Load default scripts
require(['modernizr', 'axios', 'app']);
Vue components vueHelloWorld.js and vueMenu.js are end result, compiled into pure JS from single file templates vueHelloWorld.vue and vueMenu.vue.
After that initial file, app.min.js is loaded and it will have declarations to load rendered vue components.
This is what I don't know how to do - how to render separate files for each Vue component that I want to load in this manner?
If I understand the question correctly, all you are looking for is something that takes a single *.vue file and returns a single compiled *.js file. You could either try and write your own thing using https://github.com/vuejs/vue-component-compiler, or what I ended up doing is misusing rollup as my vue compiler, configuring it, so that it ignores all dependencies and therefore takes one vue component in and only compiles that one component. Here you can see the config that achieves that: https://github.com/ecosia/bazel_rules_nodejs_contrib/blob/master/internal/vue_component/rollup.config.js
It seems that Async components and webpacks code splitting are what you are looking for.
Here you find an article about using them:
https://vuejsdevelopers.com/2017/07/03/vue-js-code-splitting-webpack/
No, because single Vue file (*.vue) can only be recognized by vue-loader through webpack, SystemJS or AMD or CommonJS are totally unrelated to it, these three are modularity standards or ways to make your javascipts files organized.
Also u can write in this way.
a.component.js
var ComponentA = {
template: '#view-a',
}
b.component.js
var ComponentB = {
template: '#view-b',
}
then in your html file
<script type="text/x-template" id="view-a">
<div> .... </div>
</script>
<script type="text/x-template" id="view-b">
<div> .... </div>
</script>

Attempting to load a JavaScript sdk into an Angular2 application. Can't find all dependencies

I'm attempting to make use of this library: https://github.com/MagicTheGathering/mtg-sdk-javascript in an Angular2 application.
Unfortunately, I've been going in circles trying to load it into my application.
Firstly, on the TypeScript side if I import it using:
import { } from 'mtgsdk';
there are no types to load into the {}.
If I attempt to load it using something similar to:
import * as mtg from 'mtgsdk'
I'm unable to because it says that it's unable to find a module named mtgsdk.
I've installed the module using
npm install --save mtgsdk
Also, npm installs work fine for other modules.
The application compiles fine if I load it in using require via something similar to this:
var mtg = require('mtgsdk');
Taking that approach, I'm able to compile and launch but in the browser I get a number of errors about modules that it can't find. I figure they are prerequisites for the sdk that didn't get loaded so I start bringing them in via package.json.
For every one that I bring in, I then have to go to systemjs.config.js and add an entry pointing to the module's entry point and often have to specify a default extension using blocks like this:
pointer
'mtgsdk': 'npm:mtgsdk/lib/index.js',
'request-promise': 'npm:request-promise/lib/rp.js',
'ramda': 'npm:ramda/dist/ramda.js',
'emitter20': 'npm:emitter20/index.js',
'bluebird': 'npm:bluebird/js/browser/bluebird.js',
'request': 'npm:request/index.js'
default extension
'request-promise':
{
defaultExtension: 'js'
}
I'm not sure if that's the right approach though because the more dependencies I add, the more that I end up requiring. At one point I had literally gotten up to 50 extra dependencies added because every time I launched, the browser console would find more that were needed.
Is there any easier way to load all of these in?
Also, some of them (such as tough-cookie and request-promise-core) were very problematic to load and I couldn't get the browser console to stop complaining about them. Finally, some of them seemed very basic such as url, http, https. Those seem like they should already be present.
Using systemjs was utilized in the previous versions of Angular 2, However Angular 2 has evolved to Angular 4, with super new features like Angular CLI.
I recommend your use Angular CLI, with #angular/cli.
Importing Node modules
Since mtgsdk is a node-module, you can easily import it using
import * as mtg from 'mtgsdk'
However for your program to compile, you must install a type definition for it. or declare one for it in /typings.json or your app might not build.
Importing Client Scripts
For client scripts like firebase.js you won't need to add client scripts as entries in systemjs.config.js again.
Using #angular/cli, you would easily add them in the scripts[] array in your angular-cli.json for automatic compilation.
Then access them like this
declare const firebase: any;
Here is a quickstart tutorial to set up Angular with #angular/cli.

Categories

Resources