Webpack compilation fails throwing Cannot read property 'loadChildren' of undefined.
This Happens when i try to load routes from some function like below
export const routes: Routes = getRoutes();
function getRoutes() {
return [{ path: 'homepage', component: HomeComponent }];
}
When i export routes normally the webpack compilation will be successful.like below,
export const routes: Routes = [{ path: 'homepage', component: HomeComponent }];
And am mounting these routes in lazy loaded module.
As a guess, the getRoutes function should be exported as well. AoT doesn't know how to compile when declaration is hidden.
So try
export function getRoutes() {
return [{ path: 'homepage', component: HomeComponent }];
}
Related
I have a component which is part of a lazy load module.
Is there a way to matDialog.open() and lazy load the module and show the component?
export class testComponent implements OnInit {
constructor(
public matDialog: MatDialog,
private moduleLoader: NgModuleFactoryLoader
) {}
ngOnInit() {}
openModal() {
this.moduleLoader
.load("./modules/test-modal/test-modal.module#TestModalModule")
.then((module: NgModuleFactory<any>) => {
this.matDialog.open(/*insert component and load the module*/);
});
}
}
I found an example to lazy load module with component in mat-dialog.
Please see refer to:
https://medium.com/ngconf/routing-to-angular-material-dialogs-c3fb7231c177
Just in case the link is no longer available, i'd included a brief step and example to do it
1. Create a lazy load module
2. Create entry component(empty component) to launch your modal component
#Component({
template: ''
})
export class DialogEntryComponent {
constructor(public dialog: MatDialog, private router: Router,
private route: ActivatedRoute) {
this.openDialog();
}
openDialog(): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px'
});
dialogRef.afterClosed().subscribe(result => {
this.router.navigate(['../'], { relativeTo: this.route });
});
}
}
3. Create a route for the lazy load module
const routes: any = [
{
path: "",
component: modalComponent(actual component with content)
}
];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [DataResolver]
})
export class DialogEntryRoutingModule {}
4. At parent router module, include path to lazy load DialogEntryModule
RouterModule.forRoot([
{
path: 'home',
component: ParentComponent,
children: [
{
path: 'dialog',
loadChildren:
"../example/entry-dialog.module#DialogEntryModule"
}
]
},
{ path: '**', redirectTo: 'home' }
])
5. in ParentComponent open the modal by directing to the DialogEntryModule
<button mat-raised-button routerLink="dialog">Pick one</button>
Another alternative is to stick the mat dialog component in another module that has a route, assuming it isn't used by any other module.
For example, if you have app.module and a projects.module, and you have a mat dialog that displays project details, you could include the project details dialog component inside of projects.module instead of creating a separate module for it. The dialog code will load when the user navigates to the projects view.
#nicker's answer runs into issues when you close the dialog. This reloads the parent component and in some cases, you don't want the parent component view to be refreshed.
In our web app we have a few JSON files that are ~10-80k lines each. These are getting included in our main bundle. These are used by an animation plugin called react-lottie.
An example of our webpack.config.js
module.exports = {
entry: ["./src/index.js"],
module: {
rules: [
{ test: /\.(js|jsx)$/, exclude: /node_modules/, use: ["babel-loader"] },
{
test: /\.(jpg|png|gif|ico)$/,
use: {
loader: "file-loader",
options: { name: "[path][name].[hash].[ext]" }
}
}
]
},
resolve: { extensions: ["*", ".js", ".jsx"] },
output: {
path: __dirname + "/dist",
publicPath: "/",
filename: "[name].[hash].js"
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({ hash: false, template: "src/index.html" }),
new DashboardPlugin(),
new CopyWebpackPlugin([
{
from: "src/components/Assets/BookingBar.js",
to: "assets/BookingBar.js"
}
]),
new BundleAnalyzerPlugin()
],
devServer: {
contentBase: "./dist",
hot: true,
historyApiFallback: true,
port: 4000
}
};
What is the expected behavior?
There should be a way to exclude .json files from the main bundle. I've tried File-Loader, json-loader, and const someJson = require(./someJson)
Other relevant information:
webpack version: 4.16.1
Node.js version: 10.12.0
Operating System: Mac OS 10.14 Mojave
ANSWER BELOW (AT LEAST FOR HOW I SOLVED IT). I couldn't initialize the lottie without any data.
The expected behavior is that the JSON will get bundled because it's, presumably, needed synchronously at runtime. JSON data differs from something like image files which are loaded asynchronously by the browser as they are rendered on the page via src attributes etc.
As the comments mentioned, you should be using code splitting. The latest version of Webpack supports dynamic imports if you install and use the #babel/plugin-syntax-dynamic-import plugin.
npm install --save-dev #babel/plugin-syntax-dynamic-import
Then in babel.config.js:
module.exports = {
...
plugins: [
"#babel/plugin-syntax-dynamic-import"
]
...
};
Example
Say you have a React component that might need some JSON data, but doesn't need to load it synchronously as part of the bundle. Your non-code splitted version might look something like this:
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
render() {
return <div>{JSON.stringify(myJSON, null, 2)}</div>
}
}
Instead you can use a dynamic import - basically a runtime import that returns a Promise you can use to asynchronously load some data chunked separately from your bundle:
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
state = {data: {}};
componentDidMount() {
import(/* webpackChunkName: 'myJSON' */ './myJSON.json')
.then((data) => {
this.setState({data});
});
}
render() {
return <div>{JSON.stringify(this.state.data, null, 2)}</div>
}
}
Alternately, you can use React's new lazy and Suspense API (v16.6.0 and higher) to dynamically import React components that get chunked separately from the bundle. This might be preferable if you want to chunk a component and its corresponding JSON data together, but separately from the main bundle:
// MyComponent.jsx
import React from 'react';
import myJSON from './myJSON.json';
export default class MyComponent extends React.Component {
render() {
return <div>{JSON.stringify(myJSON, null, 2)}</div>
}
}
// SomeParent.jsx
import React, {lazy, Suspense} from 'react';
const MyComponent = lazy(() => import(/* webpackChunkName: 'MyComponent' */ './MyComponent'));
export default class SomeParent extends React.Component {
render() {
return <div>
<Suspense fallback={<div>Loading...<div>} >
<MyComponent />
</Suspense>
</div>;
}
}
In the above example, <MyComponent /> and its corresponding code -- including the JSON data -- will only be loaded when the component is actually rendered at runtime.
Ultimately I took the answer above below me but wasn't able to initialize the lottie without any JSON data. I ended up doing this:
import React, { PureComponent } from "react"
import Lottie from 'react-lottie'
export default class AnimationAutomatedCommunication extends PureComponent {
constructor(props) {
super(props)
this.state = {
animation: <div />
}
}
async componentDidMount() {
const animation = await import(/* webpackChunkName: "AnimationAutomatedCommunication" */ './JsonData/AnimationAutomatedCommunication.json')
const defaultOptions = {
loop: true,
autoplay: true,
animationData: animation.default
}
this.setState({
animation: <div className={this.props.className}>
<Lottie key="lottie-win-jobs" options={defaultOptions}
isStopped={this.props.isStopped} />
</div>
})
}
render() {
return (
<React.Fragment>
{this.state.animation}
</React.Fragment>
)
}
}
Hello I am using VueJS and the webpack template. I have a bunch of components I can easily display with Vue Router. However, my organization uses Robot Framework for testing and we generate an HTML page using the command:
python -m robot.testdoc /tests/directory /destination.html
This is basically how I am using the router:
import Vue from 'vue'
import Router from 'vue-router'
import Main from '#/components/Main.vue'
import Component1 from '#/components/Component1.vue'
import Component2 from '#/components/Component2.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
mode: history,
name: 'Main',
component: Main
},
{
path: '/component1',
mode: history,
name: 'Component1',
component: Component1
},
{
path: '/component2',
mode: history,
name: 'Component2',
component: Component2
}
]
})
Is there a way to route to an HTML file using Vue Router?
First you'll need html-loader:
yarn add html-loader | npm install html-loader
Then you need to update your webpack.config.js file and add an entry to your rules to handle .html extensions:
{
test: /\.(html)$/,
exclude: /(node_modules)/,
use: {
loader: "html-loader"
}
}
Then you can import your .html files like you would components:
import Destination from '/path/to/destination.html'
Now treat component as an Object and leverage the template property to serve static HTML files:
{
path: '/destination',
mode: history,
name: 'destination',
component: { template: Destination }
}
1.install html-loader
npm install --save-dev html-loader
2.use below code vue.config.js or Webpack.config.js
For webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
},
],
},
};
For Vue cli users vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('html')
.test(/\.html$/)
.use('html-loader')
.loader('html-loader')
}
}
just add router in your
{
path: '/print',
name: 'print',
component: () => import('../pages/print.html'),
},
more about vue
https://cli.vuejs.org/guide/webpack.html#replacing-loaders-of-a-rule
After starting webpack-dev-server, I can go directly to a static route (e.g. http://localhost:3456/one), but I cannot go directly to a dynamic route (e.g. http://localhost:3456/two/1234).
I believe I am missing something in my webpack-dev-server config, but not sure what.
The browser console outputs this error:
GET http://localhost:3456/two/dev-bundle.js 404 (Not Found)
Refused to execute script from 'http://localhost:3456/two/dev-bundle.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled
webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require("webpack")
module.exports = {
mode: "development",
devtool: "eval-source-map",
entry: [
"./index.js",
],
output: {
filename: "dev-bundle.js",
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, "dev.html")
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
historyApiFallback: true,
hot: true,
port: 3456,
stats: "minimal"
}
}
app.js
import React, { Component } from "react"
import { hot } from "react-hot-loader"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import ComponentOne from "./components/ComponentOne"
import ComponentTwo from "./components/ComponentTwo"
const MyApp = () => (
<div>
<Router>
<Switch>
<Route exact path="/" component={ComponentOne} />
<Route exact path="/one" component={ComponentOne} />
<Route path="/two/:id" component={ComponentTwo} />
</Switch>
</Router>
</div>
)
export default hot(module)(MyApp)
ComponentTwo.js
import React, { Component } from "react"
import { Link } from "react-router-dom"
export default class ComponentTwo extends Component {
render() {
return (
<div>
<h1>ComponentTwo for {this.props.match.params.id}</h1>
</div>
)
}
}
Any help is appreciated.
I was able to resolve this by updating part of the webpack config:
output: {
filename: "dev-bundle.js",
publicPath: "/", // added this line
},
The console error remains, but at least the page loads.
I caught this error when testing a component with templateUrl. I don't know how to fix it.
After reading a post I added TestBed.resetTestEnvironment() and TestBed.initTestEnvironment() in the test file, but it doesn't solve the problem.
It seems like something is missing in the config file and the html file cannot be loaded.
Here are the karma logs:
Here is my karma.config.js file:
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', 'karma-typescript'],
files: [
// Zone:
'./node_modules/zone.js/dist/zone.js', // 'Uncaught ReferenceError: Zone is not defined'
'./node_modules/zone.js/dist/proxy.js', // 'TypeError: Cannot read property 'assertPresent' of undefined'
'./node_modules/zone.js/dist/sync-test.js',
'./node_modules/zone.js/dist/async-test.js', // 'TypeError: Cannot read property 'assertPresent' of undefined'
'./node_modules/zone.js/dist/fake-async-test.js',
'./node_modules/zone.js/dist/jasmine-patch.js', // 'TypeError: Cannot read property 'assertPresent' of undefined'
// Angular:
'./node_modules/angular/angular.js',
'./node_modules/#uirouter/angularjs/release/angular-ui-router.js',
'./node_modules/angular-mocks/angular-mocks.js',
// ANY OTHER FILES TO LOAD FOR YOUR TESTS
// App:
'./assets/app/app.component.ts',
'./assets/app/app.component.html',
'./assets/app/app.component.spec.ts',
],
exclude: [
'./assets/app/main.aot.ts'
],
preprocessors: {
"**/*.ts": "karma-typescript"
},
reporters: ['spec', 'karma-typescript'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity,
mime: {
'text/x-typescript': ['ts', 'tsx']
}})}
In the following you can find the app.component.ts file:
import { Component } from '#angular/core';
import { Constants } from './utils/constants';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styles:[
`
#status {
background:#f8f9fa;
bottom:0;
left:0;
margin:0;
padding:0;
position:fixed;
width:100%;
}
#status div {
margin:0;
padding:5px;
text-align:center;
color:#c8c9ca;
}
`
]})
export class AppComponent {
appTitle = "App Title";
demoMode=(Constants.DEMO_MODE)?"Demo":"DefaultMode";
}
Finally, I attach the app.component.spec.ts file:
import { AppComponent } from "./app.component";
import { ComponentFixture, TestBed } from "#angular/core/testing";
import { BrowserDynamicTestingModule,
platformBrowserDynamicTesting } from '#angular/platform-browser-dynamic/testing';
import { Constants } from "./utils/constants";
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let h1: HTMLElement;
beforeEach(() => {
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
TestBed.configureTestingModule({
declarations: [AppComponent],
})
.compileComponents().then(()=>{
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
h1 = fixture.nativeElement.querySelector('h1');
}).catch((error)=>{
console.log(error);
});
});
it('should display original title', () => {
expect(h1.textContent).toContain("App Title");
});
it('status bar text should correspond to the working mode', () =>{
let text=(Constants.DEMO_MODE)?"Demo":"DefaultMode";
expect(document.getElementById("status-text").textContent).toEqual(text);
});
});
Thanks in advance!
You should be taking the nativeElement of the the text box as below,
h1 = fixture.nativeElement.querySelector('h1').nativeElement;
Also, please query the fixture inside the it statement and add the fixture.detectChanges(); inside the it to trigger the change detection