We have project of around 100 pages. We are migrating our front end to some emerging technology. We almost have finalized Vue.js(with vue cli). Vue CLI is building project in one build.js. We have a problem with that. We have frequent requirement changes. So after every small change, we will have to upload whole build js and that will need regression testing of the whole project. Is there any way that build will be module wise? So that only changed module need to be uploaded on live after changes.
Using the Vue router:
The following approach will tell the compiler (Webpack) to "return" the component vs "including" it. Resulting in the given component being "chunked" into its own file for lazy loading.
e.g.
export default new Router({
routes: [
// Home component to be included in bundle.js
{
path: '/',
name: 'home',
component: Home
},
// code splitting - generate a separate unique chuck for about component.
{
path: '/about',
name: 'about',
component: () => import(/* webpackMode: "lazy" */ '#/views/About.vue')
}
]
})
Output = bundle.js and about.js or... 100 other files, given you have a component per page.
more on component lazy loading: https://router.vuejs.org/guide/advanced/lazy-loading.html
Using webpack:
You can extend and/or modify the default compiler (webpack) configuration by adding a vue.config.js file to the project root.
e.g.
// vue.config.js
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// mutate config for production...
} else {
// mutate for development...
}
}
}
Be sure to read all the documentation at https://cli.vuejs.org/guide/webpack.html as some settings should not be mutated directly.
more on webpack code splitting: https://webpack.js.org/guides/code-splitting
Related
I have a Nuxt project that is fully statically generated. But one part of this is pretty dynamic. Let's say it is items page and items/:id pages. Where on the items page we render the list of items as links to each items/:id page.
I would like to opt-out of a static generation for those 2 pages. For now, I used the following changes to the nuxt.config.js:
export default {
target: 'static',
router: {
extendRoutes (routes, resolve) {
routes.push({
name: 'items',
path: '/items/*',
component: resolve(__dirname, 'pages/items/_slug.vue')
})
}
},
generate: { exclude: [/^\/items/], fallback: '404.html' }
This works nicely when I do nuxt generate & nuxt start but only for navigation within the website. When I go directly to let's say /items/42 I get 404 rendered.
How should I adjust configuration or deployment to make it work?
I am developing an ecommerce application, and one major feature is that this app should have multiple themes. The total number of themes could be 100 or even more. However, these themes all have the same data (For example: all home page have same banner images, new product, feature product data.) .
I know I can use ng-template or TemplateRef to determine which piece of HTML should display. But since I have over 100 themes, both ng-template or TemplateRef methods will load a lot of extra files. So I think I need some sort of lazy load, when a component loads the data then lazy loads the correct HTML template. So how can I have this work?
Looks like it is possible, all our routes are handled by lazy loaded modules. This is our out-of-the-box route config:
const routes: Routes = [
{ path: '', loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule) }
];
While module lazy has this route config:
const routes: Routes = [
{ path: 'home', component: HomeComponent },
]
While HomeComponent is taken from the declarations of module lazy.
Then define another module, called for example lazy-two with the same route config, and its own HomeComponent.
Finally, you can switch between the modules by using this code:
lazyLoad() {
const routes: Routes = [
{
path: '',
loadChildren: () => import('./lazy-two/lazy-two.module')
.then(m => m.LazyTwoModule)
}
];
this.router.resetConfig(routes);
this.router.navigateByUrl('/home');
}
This will lazy load module lazy-two and refresh the route to /home - you will see the component of the new module displayed.
I couldn't create a stackblitz, some errors occurred probably because of lazy loading. So I ran it locally on my machine and pushed the code to GitHub
EDIT I managed to make a StackBlitz
I recommend used ComponentFactoryResolver to create the components that you need to render.
this.templates = [
{
id: "template-1",
component: Template1,
},
{
id: "template-2",
component: Template2,
},
];
ngOnInit() {
this.templates.forEach((element) => {
this.containerReference.createComponent(
this.factoryResolver.resolveComponentFactory(element.component)
);
});
}
in the .html you should have
<ng-container #containerReference><ng-container>
what about using the same component and styling it different when you select the template?
I'm following the tutorial here https://www.gatsbyjs.com/docs/using-gatsby-without-graphql/ and I have my gatsby-node.js filled in.
My current directory structure is:
public
src
-pages
-templates
--program_group.js
static
gatsby-config.js
gatsby-node.js
inside my gatsby-node.js file I have
programGroups.forEach(program => {
createPage({
path: `/programs/${program}/`,
component: require.resolve("src/templates/program_group.js"),
context: { program },
})
})
and when I run gatsby develop it says, "Cannot find module 'src/templates/program_group.js'"
I've tried changing the path to ./src/..... and ../src/ and every combination. The documentation says it should be relative to the config file, which the above path is...
Thoughts?
Try using relative paths using path.resolve. This should work:
const path = require(`path`); // import it outside the function
programGroups.forEach(program => {
createPage({
path: `/programs/${program}/`,
component: path.resolve("./src/templates/programGroup.js"),
context: { program },
})
})
You can't underscore the templates due to naming issues to, that's another point to avoid.
So, oddly, you cannot have underscores in the template file name. This should be fixed in the gatsby compiler.
Solution: don't use _ in your template names
I'm lazy loading an angular module and while trying to open my DatesModal I'm getting this error:
No component factory found for DatesModal. Did you add it to #NgModule.entryComponents?
My lazyModule looks like this:
declarations: [DatesModal]
entryComponents: [DatesModal]
Im definitely adding the DatesModal in the entryComponents array of the lazyModule. Anyone got any idea what I could be missing here?
Please let me know if you need more info to answer this question.
It looks like you want to get/import this module in other modules, however this module is not visible to others. Try to export your module:
exports: [DatesModal]
UPDATE:
As Angular docs says:
For production apps you want to load the smallest code possible. The
code should contain only the classes that you actually need and
exclude components that are never used. For this reason, the Angular
compiler only generates code for components which are reachable from
the entryComponents; This means that adding more references to
#NgModule.declarations does not imply that they will necessarily be
included in the final bundle.
If a component isn't an entry component and isn't found in a template,
the tree shaker will throw it away. So, it's best to add only the
components that are truly entry components to help keep your app as
trim as possible.
To make your module to be lzay loaded, you need to do in routes:
const routes: Routes = [
{
path: 'customers',
loadChildren: () => import('./customers/customers.module').then(mod => mod.CustomersModule)
},
{
path: 'orders',
loadChildren: () => import('./orders/orders.module').then(mod => mod.OrdersModule)
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
Read this docs about how to make your modules to be lazy loaded.
Have you used any third-party library (or yourself) to dynamic create this component in lazy-load module?
If so, you need to add this component in the AppModule's (or up-level non-lazy-load module) entrycomponents.
More detail you can see this issue
Currently I'm working on a project which is being hosted on a clients server. For new 'modules' there is no intention to recompile the entire application. That said, the client wants to update the router/lazy loaded modules in runtime. I've tried several things out but I can't get it to work. I was wondering if any of you knows what I could still try or what I missed.
One thing I noticed, most of the resources I tried, using angular cli, are being bundled into seperate chunks by webpack by default when building the application. Which seems logical as it makes use of the webpack code splitting. but what if the module is not known yet at compile time (but a compiled module is stored somewhere on a server)? The bundling does not work because it can't find the module to import. And Using SystemJS will load up UMD modules whenever found on the system, but are also bundled in a seperate chunk by webpack.
Some resources I already tried;
dynamic-remote-component-loader
module-loading
Loading modules from different server at runtime
How to load dynamic external components into Angular application
Implementing a plugin architecture / plugin system / pluggable framework in Angular 2, 4, 5, 6
Angular 5 - load modules (that are not known at compile time) dynamically at run-time
https://medium.com/#nikolasleblanc/building-an-angular-4-component-library-with-the-angular-cli-and-ng-packagr-53b2ade0701e
Some several other relating this topic.
Some code I already tried and implement, but not working at this time;
Extending router with normal module.ts file
this.router.config.push({
path: "external",
loadChildren: () =>
System.import("./module/external.module").then(
module => module["ExternalModule"],
() => {
throw { loadChunkError: true };
}
)
});
Normal SystemJS Import of UMD bundle
System.import("./external/bundles/external.umd.js").then(modules => {
console.log(modules);
this.compiler.compileModuleAndAllComponentsAsync(modules['External'])
.then(compiled => {
const m = compiled.ngModuleFactory.create(this.injector);
const factory = compiled.componentFactories[0];
const cmp = factory.create(this.injector, [], null, m);
});
});
Import external module, not working with webpack (afaik)
const url = 'https://gist.githubusercontent.com/dianadujing/a7bbbf191349182e1d459286dba0282f/raw/c23281f8c5fabb10ab9d144489316919e4233d11/app.module.ts';
const importer = (url:any) => Observable.fromPromise(System.import(url));
console.log('importer:', importer);
importer(url)
.subscribe((modules) => {
console.log('modules:', modules, modules['AppModule']);
this.cfr = this.compiler
.compileModuleAndAllComponentsSync(modules['AppModule']);
console.log(this.cfr,',', this.cfr.componentFactories[0]);
this.external.createComponent(this.cfr.componentFactories[0], 0);
});
Use SystemJsNgModuleLoader
this.loader.load('app/lazy/lazy.module#LazyModule')
.then((moduleFactory: NgModuleFactory<any>) => {
console.log(moduleFactory);
const entryComponent = (<any>moduleFactory.moduleType).entry;
const moduleRef = moduleFactory.create(this.injector);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(entryComponent);
});
Tried loading a module made with rollup
this.http.get(`./myplugin/${metadataFileName}`)
.map(res => res.json())
.map((metadata: PluginMetadata) => {
// create the element to load in the module and factories
const script = document.createElement('script');
script.src = `./myplugin/${factoryFileName}`;
script.onload = () => {
//rollup builds the bundle so it's attached to the window
//object when loaded in
const moduleFactory: NgModuleFactory<any> =
window[metadata.name][metadata.moduleName + factorySuffix];
const moduleRef = moduleFactory.create(this.injector);
//use the entry point token to grab the component type that
//we should be rendering
const compType = moduleRef.injector.get(pluginEntryPointToken);
const compFactory = moduleRef.componentFactoryResolver
.resolveComponentFactory(compType);
// Works perfectly in debug, but when building for production it
// returns an error 'cannot find name Component of undefined'
// Not getting it to work with the router module.
}
document.head.appendChild(script);
}).subscribe();
Example with SystemJsNgModuleLoader only works when the Module is already provided as 'lazy' route in the RouterModule of the app (which turns it into a chunk when built with webpack)
I found a lot of discussion about this topic on StackOverflow here and there and provided solutions seem really good of loading modules/components dynamically if known up front. but none is fitting for our use case of the project. Please let me know what I can still try or dive into.
Thanks!
EDIT: I've found; https://github.com/kirjs/angular-dynamic-module-loading and will give this a try.
UPDATE: I've created a repository with an example of loading modules dynamically using SystemJS (and using Angular 6); https://github.com/lmeijdam/angular-umd-dynamic-example
I was facing the same problem. As far as I understand it until now:
Webpack puts all resources in a bundle and replaces all System.import with __webpack_require__. Therefore, if you want to load a module dynamically at runtime by using SystemJsNgModuleLoader, the loader will search for the module in the bundle. If the module does not exist in the bundle, you will get an error. Webpack is not going to ask the server for that module. This is a problem for us, since we want to load a module that we do not know at build/compile time.
What we need is loader that will load a module for us at runtime (lazy and dynamic). In my example, I am using SystemJS and Angular 6 / CLI.
Install SystemJS: npm install systemjs –save
Add it to angular.json: "scripts": [ "node_modules/systemjs/dist/system.src.js"]
app.component.ts
import { Compiler, Component, Injector, ViewChild, ViewContainerRef } from '#angular/core';
import * as AngularCommon from '#angular/common';
import * as AngularCore from '#angular/core';
declare var SystemJS;
#Component({
selector: 'app-root',
template: '<button (click)="load()">Load</button><ng-container #vc></ng-container>'
})
export class AppComponent {
#ViewChild('vc', {read: ViewContainerRef}) vc;
constructor(private compiler: Compiler,
private injector: Injector) {
}
load() {
// register the modules that we already loaded so that no HTTP request is made
// in my case, the modules are already available in my bundle (bundled by webpack)
SystemJS.set('#angular/core', SystemJS.newModule(AngularCore));
SystemJS.set('#angular/common', SystemJS.newModule(AngularCommon));
// now, import the new module
SystemJS.import('my-dynamic.component.js').then((module) => {
this.compiler.compileModuleAndAllComponentsAsync(module.default)
.then((compiled) => {
let moduleRef = compiled.ngModuleFactory.create(this.injector);
let factory = compiled.componentFactories[0];
if (factory) {
let component = this.vc.createComponent(factory);
let instance = component.instance;
}
});
});
}
}
my-dynamic.component.ts
import { NgModule, Component } from '#angular/core';
import { CommonModule } from '#angular/common';
import { Other } from './other';
#Component({
selector: 'my-dynamic-component',
template: '<h1>Dynamic component</h1><button (click)="LoadMore()">LoadMore</button>'
})
export class MyDynamicComponent {
LoadMore() {
let other = new Other();
other.hello();
}
}
#NgModule({
declarations: [MyDynamicComponent],
imports: [CommonModule],
})
export default class MyDynamicModule {}
other.component.ts
export class Other {
hello() {
console.log("hello");
}
}
As you can see, we can tell SystemJS what modules already exist in our bundle. So we do not need to load them again (SystemJS.set). All other modules that we import in our my-dynamic-component (in this example other) will be requested from the server at runtime.
I've used the https://github.com/kirjs/angular-dynamic-module-loading solution with Angular 6's library support to create an application I shared on Github. Due to company policy it needed to be taken offline. As soon as discussions are over regarding the example project source I will share it on Github!
UPDATE: repo can be found ; https://github.com/lmeijdam/angular-umd-dynamic-example
I have tested in Angular 6, below solution works for dynamically loading a module from an external package or an internal module.
1. If you want to dynamically load a module from a library project or a package:
I have a library project "admin" (or you can use a package) and an application project "app".
In my "admin" library project, I have AdminModule and AdminRoutingModule.
In my "app" project:
a. Make change in tsconfig.app.json:
"compilerOptions": {
"module": "esNext",
},
b. In app-routing.module.ts:
const routes: Routes = [
{
path: 'admin',
loadChildren: async () => {
const a = await import('admin')
return a['AdminModule'];
}
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
2. if you want to load a module from the same project.
There are 4 different options:
a. In app-routing.module.ts:
const routes: Routes = [
{
path: 'example',
/* Options 1: Use component */
// component: ExampleComponent, // Load router from component
/* Options 2: Use Angular default lazy load syntax */
loadChildren: './example/example.module#ExampleModule', // lazy load router from module
/* Options 3: Use Module */
// loadChildren: () => ExampleModule, // load router from module
/* Options 4: Use esNext, you need to change tsconfig.app.json */
/*
loadChildren: async () => {
const a = await import('./example/example.module')
return a['ExampleModule'];
}
*/
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
``
Do it with angular 6 library and rollup do the trick. I've just experiment with it and i can share standalone angular AOT module with the main app without rebuild last.
In angular library set angularCompilerOptions.skipTemplateCodegen to false and after build library you will get module factory.
After that build an umd module with rollup like this:
rollup dist/plugin/esm2015/lib/plugin.module.ngfactory.js --file src/assets/plugin.module.umd.js --format umd --name plugin
Load text source umd bundle in main app and eval it with module context
Now you can access to ModuleFactory from exports object
Here https://github.com/iwnow/angular-plugin-example you can find how develop plugin with standalone building and AOT
I believe this is possible using SystemJS to load a UMD bundle if you build and run your main application using webpack. I used a solution that uses ng-packagr to build a UMD bundle of the dynamic plugin/addon module. This github demonstrates the procedure described:
https://github.com/nmarra/dynamic-module-loading
Yes, you can lazy load modules using by referring them as modules in the router. Here is an example https://github.com/start-angular/SB-Admin-BS4-Angular-6
First couple all the components that you are using into a single module
Now refer that module in the router and angular will lazy load your module into the view.