NodeJS dynamically load angular modules after login - javascript

first of all I'm sorry, but I don't have a lot of exp in npm so please forgive me ^^
I have an http-server that uses angular. Right now I load all the modules ever needed right on the index.html like:
<!-- internal js files -->
<script src="app.module.js"></script>
<script src="app.config.js"></script>
<script src="do/something.module.js"></script>
<script src="do/something.component.js"></script>
<script src="do/another_thing.module.js"></script>
<script src="do/another_thing.component.js"></script>
So when the website loads, it also loads all the modules without verifying that I am 'allowed to see them'. This in turn makes it (imho) a big security risk, since an attacker can see what the website does and how it calls a private API in the modules...
As said I'm really not thaat experienced in this area.. so I was just wondering: what are common practices? How would / did you solve this issue?

Lazy loading an AngularJs (v1.x) module is supported by $injector.loadNewModules (^v1.6.x).
This example assumes angularjs v1.6.x & #uirouter/angularjs v1.x.x
In order to prevent from an Angular module from being inside the app bundle, it should not be part of the app dependency tree, this means that the angular module admin won't be imported and used as part of the app angular module.
// router config
$stateProvider
.state('admin', {
abstract: true,
url: '/admin',
onEnter: /*#ngInject*/ (userService, $stateParams) => {
return userService.getUserFromToken($stateParams.propertyId);
},
lazyLoad: $transition$ => {
// this dynamic import tells webpack to split the admin-panel module (and it dependencies) from the app bundle
return import(/* webpackChunkName: "admin" */ '../admin-panel/admin-panel.module').then(
module => $transition$.injector().loadNewModules([module.AdminPanelModule],
);
},
})
.state('admin.main', { // inherits from admin abstract state
url: '/',
views: {
'#': {
component: 'dashboard',
},
},
});
with this state config, whenever your app will navigate to any of the admin pages (which inherits from the abstract admin state) the onEnter will check if the user has permission for the page & then lazyLoad will be called (at the first time only) in-order to load the admin.panel.module to the app module.

Related

"TypeError: Failed to fetch dynamically imported module" on Vue/Vite vanilla setup

We have a vanilla Vue/Vite setup and I'm receiving TypeError: Failed to fetch dynamically imported module on sentry logs.
It seems like the errors are correlated in time with new deployment to prod, although I don't have enough data to confirm. It doesn't happen on local and appears only on deployed code.
I've seen some similar questions for react's setups, but none with a satisfactory response.
I've also found a similar question regarding dynamically imported svgs, but our errors happen for full components.
The only place where we use dynamic imported components is on routing:
export const router = createRouter({
history: routerHistory,
strict: true,
routes: [
{
path: '/',
name: routes.homepage.name,
component: () => import('#/views/Home.vue'),
children: [
{
path: '/overview',
name: routes.overview.name,
component: () => import('#/views/Overview.vue'),
},
// other similar routes
],
},
],
});
Our deps versions:
"vue": "^3.0.9",
"vue-router": "^4.0.5",
"vite": "^2.0.5",
Any additional information on this issue and how to debug it would be much appreciated!
When you dynamically import a route/component, during build it creates a separate chunk. By default, chunk filenames are hashed according to their content – Overview.abc123.js. If you don't change the component code, the hash remains the same. If the component code changes, the hash changes too - Overview.32ab1c.js. This is great for caching.
Now this is what happens when you get this error:
You deploy the application
Your Home chunk has a link to /overview route, which would load Overview.abc123.js
Client visits your site
You make changes in your code, not necessarily to the Overview component itself, but maybe to some children components that Overview imports.
You deploy changes, and Overview is built with a different hash now - Overview.32ab1c.js
Client clicks on /overview link - gets the Failed to fetch dynamically imported module error, because Overview.abc123.js no longer exists
That is why the errors correlate with deployments. One way to fix it is to not use lazy loaded routes, but that's not a great solution when you have many heavy routes - it will make your main bundle large
In my case the error was caused by not adding .vue extension to module name.
import MyComponent from 'components/MyComponent'
It worked in webpack setup, but with Vite file extension is required:
import MyComponent from 'components/MyComponent.vue'
I had the exact same issue. In my case some routes worked and some didn't. The solution was relatively easy. I just restarted the dev server.
The accepted answer correctly explains when this error is triggered but does not really provide a good solution.
The way I fixed this is by using an error handler on the router. This error handler makes sure that when this error occurs (so thus when a new version of the app is deployed), the next route change triggers a hard reload of the page instead of dynamically loading the modules. The code looks like this:
router.onError((error, to) => {
if (error.message.includes('Failed to fetch dynamically imported module')) {
window.location = to.fullPath
}
})
Where router is your vue-router instance.
My situation was similar.
I found that my Quasar setup works fine on the initial page but not page that are loaded dynamically through an import('../pages/page.vue');.
Short response:
I replaced import('../pages/TestPage.vue') in the middle of the route file by import TestPage from '../pages/TestPage.vue' at the top.
More detailed response:
In my situation I don't expect to have much pages, a single bundle with no dynamic loading is fine with me.
The solution is to import statically every page I need.
In my routes.ts I import all the pages I need.
import IndexPage from '../pages/IndexPage.vue';
import TestPage from '../pages/TestPage.vue';
Then I serve them statically in my routes :
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: 'test', component: () => TestPage },
{ path: '', component: () => IndexPage }
],
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue'),
},
];
I recently expriencied this. The error was caused by an empty href inside an a tag: <a href="" #click="goToRoute">. You can either remove the href or change the a tag to something else, ie. button. Let me know if this helps.
I had the same problem. I found that I had not started my project.

Importing single file components VueJS

everyone.
I have a trivial doubt on making vue components.
I don't want to use browserify or webpack , cause I am working in django and it has most of it's templates in static files , although I read this , which does describe how to take in account both ( but that's for some other day ).
Problem :
I am making a single file component which I have to import and use, using my router but I can't, as the import just doesn't happen.
My Hello.vue
<template>
Some HTML code here.
</template>
<script>
module.exports = {
data() {
return {
coin : []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-django-rest-api-url')
.then(response => {
next(vm => {
vm.data = response.data
})
})
}
}
</script>
I have it in the index.html file itself , no other .js file,
<script>
import Hello from '#/components/Hello.vue'
Vue.use(VueRouter);
const dashboard = {template:'<p>This is the base template</p>'};
const profile = {
template: '#profile_template',
data () {
return {
profile_details: []
}
},
beforeRouteEnter (to, from, next) {
axios.get('my-api-url')
.then(response => {
next(vm => {
vm.profile_details = response.data
})
})
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: dashboard },
{ path: '/profile', component: profile },
{ path: '/hello', component: Hello }
]
});
new Vue({
router : router,
}).$mount('#app');
</script>
What all I've tried :
1.<script src="../components/Hello.js" type="module"></script> and removing the import statement as suggested here
Replacing my Hello.js's code with this : export const Hello = { ...
Making a Hello.js file and importing it like this import Hello from '../components/Hello.js';
Error :
**Mozilla ( Quantum 57.0.4 64 bit ) ** : SyntaxError: import declarations may only appear at top level of a module
**Chrome ( 63.0.3239.108 (Official Build) (64-bit) ) ** :Uncaught SyntaxError: Unexpected identifier
P.S. : I have tried these in various combinations
Not a Vue.js guru, but here are a few perspectives that might help you.
Module loading is still not supported on modern browsers by default, and you'd need to set special flags in order to enable it (which the users of your app probably won't do).
If you insist on using import and export, you'd need Webpack. And most certainly Babel (or any other ES6 transpiler, e.g. Buble) as well.
If you prefer module.exports, then you'd need Browserify. It enables support for CommonJS in browser environments.
If neither is doable, then your best bet is defining Vue components in global scope. You can split them across separate files, and import each with a <script> individually. Definitely not the cleanest approach.
Single file components typically go inside of .vue files, but either way they require vue-loader which can be added and configured (again) with a bundler.
Last option is to just use an existing setup in place, if there is any (is there?). If you already have RequireJS, UMD, or something similar in place, adjust your components to fit that. Otherwise, use <script>s.
You are trying to do something which is not possible. Vue Single file components are not supported as raw component file by web browsers. The single file component is supposed to be compiled.
Please see this for more:
https://v2.vuejs.org/v2/guide/single-file-components.html
In Webpack, each file can be transformed by a “loader” before being included in the bundle, and Vue offers the vue-loader plugin to translate single-file (.vue) components.
A vue single file component is first "translated" (compiled) to pure javascript code which is use-able by browsers.

Dynamic import splits code but doesn't lazy load

I want to introduce lazy loading to Vue Router, so that some parts of the code will be loaded only on demand.
I'm following the official documentation for Lazy Loading in Vue Router:
https://router.vuejs.org/en/advanced/lazy-loading.html
So for a test I've changed how the Vault module is imported in my router file:
import Vue from 'vue';
import Router from 'vue-router';
// Containers
import Full from '#/containers/Full';
// Views
// TODO: views should be imported dynamically
import Dashboard from '#/views/Dashboard';
const Vault = () => import('#/views/Vault');
import Page404 from '#/views/Page404';
import Page500 from '#/views/Page500';
import Login from '#/views/Login';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
redirect: '/dashboard',
name: 'VENE',
component: Full,
children: [
{
path: 'dashboard',
name: 'dashboard',
component: Dashboard
},
{
path: 'vault',
name: 'vault',
component: Vault
},
],
},
{
path: '/login',
name: 'Login',
component: Login,
},
{
path: '/404',
name: 'Page404',
component: Page404,
},
{
path: '/500',
name: 'Page500',
component: Page500,
},
],
});
All fine, however, when I open the app for the first time, the extracted bundle which was supposed to be lazy loaded, is loaded up front:
When I go to that view using router it appears in Dev Tools Network Tab again, but says it's loaded from the disk, so the bundle is clearly loaded on first page load, which is totally against the idea of lazy loading.
This is occurring for a couple reasons. I should say, you've set everything up correctly for lazy-loading the Vault component. One tip, I've found it helpful to add the webpack chunk name to the dynamic import:
const Vault = () => import(/* webpackChunkName: "vault" */ '#/views/Vault')
This would then show up in your network tab named with the chunkName "vault"
First, I'm guessing that you're using #vue-cli looking at your file structure and /src alias. Depending on the options you select when creating your project, #vue-cli uses a webpack config for progressive web apps that prefetches all resources. While the browser has mechanisms for prioritizing these downloads, I've found that some of the prefetching appears to block other resources. The benefit of prefetching is for browsers that don't support service-workers, you use idle browser time to put resources in the browser cache that the user will probably eventually use. When the user does need that resource, it is already cached and ready to go.
Second, you do have options for disabling the prefetch plugin. #vue-cli provides escape hatches for overriding the default config. Simply edit or add vue.config.js to the root of your project.
courtesy #LinusBorg
// vue.config.js
chainWebpack: (config) => {
// A, remove the plugin
config.plugins.delete('prefetch')
// or:
// B. Alter settings:
config.plugin('prefetch').tap(options => {
options.fileBlackList.push([/myasyncRoute(.)+?\.js$/])
return options
})
}
-- Be sure to only use either option A or option B; not both. --
Source: https://github.com/vuejs/vue-cli/issues/979
I've used option A with success, but you should definitely benchmark the results yourself and go with the option that best serves your users and application.
I appreciate the configurability of #vue-cli for these and many scenarios. It's definitely worth exploring to write the application you want, rather than coercing your app to the config.

Lazy loading Angular modules with Webpack

I'm trying to get lazy-loaded Angular modules working with Webpack, but I'm having some difficulties. Webpack appears to generate the split point correctly, because I see a 1.bundle.js getting created that contains the code for the child app, but I don't see any request for 1.bundle.js when I load the page, and the child app doesn't initialize. The console.log never seems to fire, and it doesn't even appear to get to the point where $oclazyload would initialize the module.
There are a few points where I am confused.
1) Will webpack make the request to the server, or do I have to load the second bundle manually? (I've tried both, and neither works)
2) If I do need to load the bundles manually, in what order should they be loaded?
3) The third argument to require.ensure supposedly lets you control the name of the bundle, but the bundle is named 1.bundle.js no matter what string I pass.
4) Why can't I step through the code inside the require.ensure block in the debugger? When I do so I end up looking at this in the Chrome source view:
undefined
/** WEBPACK FOOTER **
**
**/
(Code Below)
Main entry point code:
'use strict';
import angular from 'angular';
import 'angular-ui-router';
import 'oclazyload';
angular.module('parentApp', [
'ui.router',
])
.config(['$urlRouterProvider', '$locationProvider', ($urlRouterProvider, $locationProvider) => {
$urlRouterProvider
.otherwise('/');
$locationProvider.html5Mode(true);
}])
.config(['$stateProvider', ($stateProvider) => {
$stateProvider
.state('child-app', {
url: '/child-app',
template: '<child-app></child-app>',
resolve: {
loadAppModule: ($q, $ocLazyLoad) => {
return $q((resolve) => {
require.ensure(['./child-app/app.js'], (require) => {
let module = require('./child-app/app.js');
console.log(module);
$oclazyload.load({name: 'childApp'});
resolve(module.controller);
});
})
}
},
controller: function() {
}
})
}]);
Child app:
'use strict';
import childAppTemplateURL from '../templates/child-app.html';
import childAppController from './controllers/childAppController';
export default angular.module('parentApp.childApp', [])
.component('applicationListApp', {
templateUrl: childAppTemplateURL,
controller: childAppController
});
The problem was unrelated to the require.ensure implementation. It was caused by some weirdness in the way ocLazyLoad is packaged (https://github.com/ocombe/ocLazyLoad/issues/179). The fix in my case was simple, I just added 'oc.lazyLoad' to the module dependencies.
angular.module('parentApp', [
'ui.router',
'oc.lazyLoad'
])
To answer two of my own questions, Webpack does indeed make a request to the server for the bundle, and you do not have to manually load the bundle. One gotcha that really confused me: the resolve block will fail silently if it contains a promise that won't resolve. In my case $ocLazyLoad.load() was failing, but there was no indication of the failure. The only clue was that the state provider wasn't adding the <child-app></child-app> markup to the DOM, which meant that it was actually initializing the state.

Best practice for including scripts in Angular templates?

I have an Angular SPA with several tabs, which are served by templates. Each of these tabs requires different scripts (both local and CDN), so I'd only like to load scripts when needed, ie I will need to include them inside the templates (or associated controllers) themselves.
So far, I've tried this approach:
// Start template
<data-ng-include src="http://maps.googleapis.com/maps/api/js"></data-ng-include>
// Rest of stuff
// End template
This doesn't seem to be working. Yes, I have jQuery included in the header. What is the best way to go about this? Seems like it should be much simpler to include scripts inside templates.
You can use any of the lazy loader (on demand loader) library like requireJS, ocLazyLoad.
I have successfully implemented ocLazyLoad in one of our enterprise application, because it provide lots of usefull features if you are developing modular Single Page Application:
Easy to implement. You can lazy load any resource anywhere inside your application
Dependencies are automatically loaded
Debugger friendly (no eval code)
Can load any client side resouce like js/css/json/html
Compatible with AngularJS 1.2.x/1.3.x/1.4.x
You can also load any resource inside service, factory, directive also.See example
Resolve any resource before executing any state if you are using ui.router library for routing.See example
You can also lazy load resource in .config() of any module.See example
It also gives you the functionalty of logging module loading events.
All references are taken from official website of ocLazyLoad
If you want to include JS. I would tell you , please use :
cLazyLoad JS
Here is example:
var myapp = angular.module('myApp', ['oc.lazyLoad']);
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
template: '<div ui-view class="ngslide"></div>',
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([js/jquery.main.min.js','js/ie10-viewport-bug-workaround.js']);
}]
}
})
});
I use a simple directive for this (here :
interface AddScriptDirectiveAttributes extends IAttributes {
type: string;
src: string;
}
export function addScript(): IDirective {
return {
restrict: 'E',
link: function (scope: IScope, element: JQuery, attrs: AddScriptDirectiveAttributes) {
let script = document.createElement('script');
if (attrs.type) {
script.type = attrs.type;
}
script.src = attrs.src;
document.body.appendChild(script);
}
};
}
Usage would be something like this:
<add-script src="script" type="application/javascript"></add-script>

Categories