This is for the latest release of angular(rc4). I have been following the guide for using webpack with angular 2 provided here Angular 2 Webpack intro. No notable errors when packaging with webpack occur. A handful of warnings about dropping unused variables. The website does seem to bootstrap as many of my components load, but not all load correctly. I am receiving the following error
Uncaught ReferenceError: System is not defined
Along with many REST errors that don't make much of any sense. I am running the webpack-dev-server locally on port 8080, but I am not sure why it is trying to perform REST calls to it. I am making rest calls to another machine in some components but those are not being fired off.
POST http://localhost:8080/undefinedsession 404 (Not Found)
I have my webpack configured very similarly to the example in the angular doc. I am already using
"module": "commonjs",
in my tsconfig.json (as suggested here). Note that in my vendor.ts, I have commented out many references to systemjs modules. The example from the angular doc says they were removed in lieu of webpack. When I include them, I still see the system not defined error, along with additional errors
./~/systemjs/dist/system.src.js
Critical dependencies: 1642:42-49 require function is used in a way in
which dependencies cannot be statically extracted
Anyone else having this issue?
Here's my webpack.common.js:
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');
var path = require('path');
module.exports = {
entry: {
'polyfills': './app/polyfills.ts',
'vendor': './app/vendor.ts',
'app': './app/main.ts'
},
resolve: {
extensions: ['', '.js', '.ts', '.config'],
root: [path.resolve('./')]
},
node: {
fs: "empty"
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: ['ts', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file?name=assets/[name].[hash].[ext]'
},
{
test: /\.config$/,
loader: 'file'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw'
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'index.html'
})
]
};
And here is the webpack.dev.js:
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
publicPath: 'http://localhost:8080/',
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
plugins: [
new ExtractTextPlugin('[name].css')
],
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});
Note that I have setup three entry files in line with the angular doc. Here are those for completeness
Main.ts:
import { bootstrap } from '#angular/platform-browser-dynamic';
import { HTTP_BINDINGS, HTTP_PROVIDERS } from '#angular/http';
import { disableDeprecatedForms, provideForms } from '#angular/forms';
import 'rxjs/Rx';
import { enableProdMode, provide, PLATFORM_DIRECTIVES } from '#angular/core';
import { ROUTER_DIRECTIVES } from '#angular/router';
import { Title } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { AppSettingsService } from './app.settings';
import { APP_ROUTER_PROVIDERS } from './app.routes';
// css
import 'css/material.blue_grey-blue.min.css';
import 'css/styles.css';
bootstrap(AppComponent, [
APP_ROUTER_PROVIDERS,
HTTP_BINDINGS,
HTTP_PROVIDERS,
Title,
AppSettingsService,
disableDeprecatedForms(),
provideForms(),
provide(PLATFORM_DIRECTIVES, { useValue: [ROUTER_DIRECTIVES], multi: true })
]).catch((err: any) => console.error(err));
polyfills.ts:
import 'core-js/es6';
import 'reflect-metadata';
require('zone.js/dist/zone');
if (process.env.ENV === 'production') {
// Production
} else {
// Development
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}
vendor.ts:
// Angular 2
import '#angular/platform-browser';
import '#angular/platform-browser-dynamic';
import '#angular/core';
import '#angular/common';
import '#angular/http';
import '#angular/router';
// RxJS
import 'rxjs';
// From index.html
import 'js/material.js';
//import 'node_modules/core-js/client/shim.min.js';
//import 'node_modules/zone.js/dist/zone.js';
//import 'node_modules/reflect-metadata/Reflect.js';
//import 'node_modules/systemjs/dist/system.src.js';
//import 'systemjs.config.js';
Related
My own modules get included in the build but when using a public node module it does not get included for some reason. Here is the code below.
import helloWorld from "./module";
import Redux from 'redux'
console.log(Redux)
console.log(helloWorld)
when I open the browser Redux is undefined but helloWorld works properly and has my object.
The question is, how can I include redux in my build.
Here is my simple WebPack Config
const path = require('path');
module.exports = {
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
}
]
}
}
Are you sure entity with name Redux exists in redux module.
Try import { createStore } from 'redux' instead. The set up looks fine.
We want to divide our large frontend projects into multiple separately deployed projects which are easier to work with. I am trying to include a bundled ngModule to handle a route from within another app. The apps must be ignorant of each other's configuration. The bundles will share some large dependencies(like Angular) via globals. We don't need to shake across the bundles and we may just have to accept some duplicate dependencies.
The root router complains that
Error: No NgModule metadata found for 'TestsetModule'.
which leads me to believe the child module is not being angular compiled on load, or is not registering its module for some reason. I think it may be necessary to manually compile the module, but I'm not sure how to use this https://angular.io/api/core/Compiler#compileModuleAndAllComponentsAsync
The root app loads the child via a route:
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
const load = require("little-loader");
const routes: Routes = [
{ path: ``, loadChildren: () => new Promise(function (resolve) {
load('http://localhost:3100/testset-module-bundle.js',(err: any) => {
console.log('global loaded bundle is: ', (<any>global).TestsetModule )
resolve((<any>global).TestsetModule)
}
)
})}
];
export const HostRouting: ModuleWithProviders = RouterModule.forRoot(routes);
I also tried using angular router's string resolution syntax rather than this weird global thing you see but I had similar issues.
Here is the module which is being loaded, very standard except for the global export:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { HttpModule } from '#angular/http';
//import { MaterialModule } from '#angular/material';
import { FlexLayoutModule } from '#angular/flex-layout';
import { FormsModule } from '#angular/forms';
import { LoggerModule, Level } from '#churro/ngx-log';
import { FeatureLoggerConfig } from './features/logger/services/feature-logger-config';
import { TestsetComponent } from './features/testset/testset.component';
import { TestsetRouting } from './testset.routing';
#NgModule({
imports: [
CommonModule,
//MaterialModule,
FlexLayoutModule,
HttpModule,
FormsModule,
LoggerModule.forChild({
moduleName: 'Testset',
minLevel: Level.INFO
}),
TestsetRouting,
],
declarations: [TestsetComponent],
providers: [
/* TODO: Providers go here */
]
})
class TestsetModule { }
(<any>global).TestsetModule = TestsetModule
export {TestsetModule as default, TestsetModule};
Here is the webpack configuration of the root bundle. Note the global exports via the poorly named "ProvidePlugin".
const webpack = require('webpack');
const AotPlugin = require('#ngtools/webpack').AotPlugin;
const path = require('path');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
const IgnorePlugin = require('webpack/lib/IgnorePlugin');
const PolyfillsPlugin = require('webpack-polyfills-plugin');
const WebpackSystemRegister = require('webpack-system-register');
module.exports = (envOptions) => {
envOptions = envOptions || {};
const config = {
entry: {
'bundle': './root.ts'
},
output: {
libraryTarget: 'umd',
filename: '[name].js',//"bundle.[hash].js",
chunkFilename: '[name]-chunk.js',
path: __dirname
},
externals: {
},
resolve: {
extensions: ['.ts', '.js', '.html'],
},
module: {
rules: [
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.css$/, loader: 'raw-loader' },
]
},
devtool: '#source-map',
plugins: [
new webpack.ProvidePlugin({
'angular': '#angular/core',
'ngrouter': '#angular/router',
'ngxlog':'#churro/ngx-log'
})
]
};
config.module.rules.push(
{ test: /\.ts$/, loaders: [
'awesome-typescript-loader',
'angular-router-loader',
'angular2-template-loader',
'source-map-loader'
] }
);
}
return config;
};
And here is the webpack configuration of the child bundle. Note the "externals" which look for angular as a global.
module.exports = (envOptions) => {
envOptions = envOptions || {};
const config = {
entry: {
'testset-module-bundle': './src/index.ts'
},
output: {
//library: 'TestsetModule',
libraryTarget: 'umd',
filename: '[name].js',//"bundle.[hash].js",
chunkFilename: '[name]-chunk.js',
path: path.resolve(__dirname, "dist")
},
externals: {
//expect these to come from the app that imported us
// name to be required : name from global
'angular': '#angular/core',
'ngrouter': '#angular/router',
'ngxlog': '#churro/ngx-log'
},
resolve: {
extensions: ['.ts', '.js', '.html'],
},
module: {
rules: [
{ test: /\.html$/, loader: 'raw-loader' },
{ test: /\.css$/, loader: 'raw-loader' },
]
},
devtool: '#source-map',
plugins: [
]
};
config.module.rules.push(
{ test: /\.ts$/, loaders: [
'awesome-typescript-loader',
'angular-router-loader',
'angular2-template-loader',
'source-map-loader'
] }
);
}
return config;
};
And for good measure here is my tsconfig file which 'awesome-typescript-loader' reads.
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"baseUrl": ".",
"rootDir": "src",
"outDir": "app",
"paths": {
"#capone/*": [
"*"
],
"#angular/*": [
"node_modules/#angular/*"
],
"rxjs/*": [
"node_modules/rxjs/*"
]
}
},
"exclude": ["node_modules", "src/node_modules", "compiled", "src/dev_wrapper_app"],
"angularCompilerOptions": {
"genDir": "./compiled",
"skipMetadataEmit": true
}
}
If you're still reading, awesome. I was able to get this working when both bundles are part of the same webpack config and the child module is just a chunk. Angular is designed to do that. But our use case is to have the children and parent be ignorant of each other until runtime.
As you have mentioned
The apps must be ignorant of each other's configuration.
I had a similar problem in Angular2. I solved it by creating a sub-application. A separate sub-main.browser.ts and index.html file. It had its own dependencies, sharing the same node modules. Both main modules bootstrapping different app-component. We were working on Angular without angular-cli.
In webpack config, I added
entry: {
'polyfills': './src/polyfills.browser.ts',
'main' . : './src/main.browser.aot.ts',
'sub-main' : '/src/sub-main.browser.ts'
},
and a more detailed HtmlWebpackPlugin. In the chunks, we load only modules that will be used in both the app. If we see polyfills is common.
new HtmlWebpackPlugin({
template: 'src/index.html',
title: METADATA.title,
chunksSortMode: 'dependency',
metadata: METADATA,
inject: 'head',
chunks: ['polyfills','main']
}),
new HtmlWebpackPlugin({
template: 'src/index2.html',
title: 'Sub app',
chunksSortMode: 'dependency',
metadata: METADATA,
inject: 'head',
filename: './sub-app.html',
chunks: ['polyfills','sub-main']
}),
The next task was to create separate endpoints for both sub apps for dev environment.
devServer: {
port: METADATA.port,
host: METADATA.host,
historyApiFallback: true,
watchOptions: {
aggregateTimeout: 300,
poll: 1000
},
proxy: {
"/sub-app": {
target: "http://localhost:3009",
bypass: function(req, res, proxyOptions) {
return "/index2.html";
}
}
}
},
Now when I build the project two different HTML files are generated. Each with their own javascript bundle dependencies and common assets. They can be deployed on a different server as well.
I was able to finish my POC with lots of trial and error. My suggestion will be to look a step above angular. See how webpack is deploying your current project. And If you can configure it to serve your purpose.
I want to build a react component library as a node module to then import it into different projects. But if I try to import a component it just returns an empty object.
button.jsx:
import React, {Component} from 'react'
export class Button extends Component {
render() {
return <button className='btn'>Hello Button comp</button>
}
}
export default Button
index.js
var Button = require('./button/button').default;
module.exports = {
Button: Button
}
webpack.config.js
const Path = require('path');
module.exports = {
resolve: {
extensions: ['.js', '.jsx']
},
entry: {
app: './src/components/index.js'
},
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.jsx$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [
'es2015',
'react'
]
},
exclude: /node_modules/,
include: [
Path.resolve(__dirname, 'src')
]
}
]
}
}
Main property in package.json is bundle.js
I figured out that when I import Button in a project it is just an empty object. It seems to me as if webpack doesn't bundle the index file properly. Any ideas what could be wrong here?
A webpack bundle does not expose your exports by default, as it assumes that you're building an app and not a library (which is the far more common use of webpack). You can create a library by configuring output.library and output.libraryTarget.
output: {
path: __dirname,
filename: 'bundle.js',
library: 'yourLibName',
libraryTarget: 'commonjs2'
},
output.libraryTarget is the format of the module, which would also allow you to expose the library as a global variable. commonjs2 is the module format that Node uses. See What is commonjs2? for the difference between commonjs and commonjs2.
Since you're using React, you'll expect that the consumer of the library will have React present as a dependency and therefore you don't want to include it in your bundle. To do that you can define it as an External. This is shown in Authoring Libraries, which walks you through a small example.
I got my webpack Hot Module Replacement working. I was told that once it is working, I would no longer have to do a full refresh for my code. This is not the case! I still require a refresh when I make changes to my code! (App.js).
How can I properly enable webpack HMR?
Link to the project on github
This is my entry point
import './styles/index.css';
import App from './components/App';
import React from 'react';
import { render } from 'react-dom';
const rootDOMNode = document.getElementById('app');
function renderRoot() {
render(<App/>, rootDOMNode);
}
renderRoot();
if (module.hot) {
module.hot.accept('./components/App', () => {
console.log('Accepting the updated module');
renderRoot();
});
}
webpack.config.js:
const webpack = require("webpack");
const path = require("path");
const CleanWebpackPlugin = require( "clean-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: [
"./app/index"
],
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "dist"), // Note: Physical files are only output by the production build task `npm run build`.
publicPath: "/",
filename: "bundle.js"
},
devServer: {
contentBase: path.resolve(__dirname, "dist"),
hot: true,
port: 3000,
open: true,
compress: true
},
plugins: [
new ExtractTextPlugin({
disable: false,
filename: "css/style.css",
allChunks: true
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
],
module: {
rules: [
{ test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react']
}
} },
// { test: /(\.css)$/, use: ["style-loader", "css-loader"] },
{ test: /(\.css)$/, use: ExtractTextPlugin.extract(["css-loader"]) },
{ test: /\.(png|svg|jpg|gif)$/, use: ["file-loader"] },
// for fonts
{ test: /\.(woff|woff2|eot|ttf|otf)$/, use: ["file-loader"] }
]
}
};
The reason it is not working is because you have to re-require your app once you get the hot update, otherwise you are just re-rendering your original app.
the following code should work:
import './styles/index.css';
//import App from './components/App';
import React from 'react';
import { render } from 'react-dom';
const rootDOMNode = document.getElementById('app');
let App;
function renderRoot() {
App = require('./components/App').default; // we have to re-require this every time it changes otherwise we are rendering the same old app.
render(<App/>, rootDOMNode);
}
renderRoot();
if (module.hot) {
module.hot.accept('./components/App', () => {
console.log('Accepting the updated module');
renderRoot();
});
}
I use hot reloading with webpack-dev-server by adding a script in package.json.
webpack-dev-server --output-public-path=/dist/ --inline --hot
I;m wondering if it's possible to build a bundle with some javascript files but without dependencies?
I want to have small bundles with React components (each react component in my case is builded from few react components, for example Comment component incldues comment box, list, and form),
I can split React components to a separate files by specifying few entry points in webpack, but if I have:
1. Component comment
2. Component newsletter
and both of them require ReactDOM, files which will be generated will have like 600kb, where my react components contain only ~100 lines of js code.
I would like to have one more file which will contain all the code which would come from "require('react-dom'), and those two files which will only have the React component code. is that possible?
My current setup:
'use strict';
import path from 'path';
import CommonsChunkPlugin from "webpack/lib/optimize/CommonsChunkPlugin";
module.exports = {
entry: {
app: "./app.js",
newsletter: "./components/renderers/newsletter.renderer.js",
comment: "./components/renderers/comment.renderer.js"
},
output: {
path: path.join(__dirname),
filename: "built/[name].entry.js"
},
devtool: 'sourcemaps',
cache: true,
debug: true,
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: [/(node_modules)/],
loader: 'babel'
}
],
resolve: {
extensions: ['', '.js', '.jsx']
}
},
plugins: [
new CommonsChunkPlugin({
name: "comment.js",
chunks: ["comment", "app"],
minChunks: 2
}),
new CommonsChunkPlugin({
name: "newsletter.js",
chunks: ["newsletter", "app"],
minChunks: 2
})
]
};
Comment.renderer.js:
import CommentBox from './../comment/commentBox';
ReactDOM.render(
<CommentBox/>,
document.getElementById("comment")
);
Newsletter.renderer.js:
import Newsletter from './../newsletter/newsletter';
ReactDOM.render(
<Newsletter/>,
document.getElementById("newsletter")
);
app.js:
'use strict';
import React from 'react';
import ReactDOM from 'react-dom';
import client from './lib/client';
Ok I've managed how to do that:
import path from 'path';
import CommonsChunkPlugin from "webpack/lib/optimize/CommonsChunkPlugin";
module.exports = {
entry: {
vendor: ["react","react-dom", "underscore"],
comment: "./components/renderers/comment.renderer.js",
newsletter: "./components/renderers/newsletter.renderer.js"
},
output: {
path: path.join(__dirname),
filename: "built/[name].bundle.js"
},
devtool: 'sourcemaps',
cache: true,
debug: true,
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: [/(node_modules)/],
loader: 'babel'
}
],
resolve: {
extensions: ['', '.js', '.jsx']
}
},
plugins: [
new CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity
})
]
};
this part:
minChunks: Infinity
will ensure that code included in bundle "vendor" is not included in any other bundle. Thanks to this approach, comment and newsletter will contain only my React components.
I use following code in webpack.config.js to exclude the external dependencies from bundle.
module.exports = {
...
...
externals:{
"react": "React",
"react-dom": "ReactDOM"
},
...
...
}
I found this answer from this link
To complement #AnandShanbhag's answer, you can take all the dependencies from package.json and turn them into externals with a function:
module.exports = {
...
// put everything inside package.json dependencies as externals
externals: Object.keys(require('./package.json').dependencies)
.reduce(
function (acc, cur) {
acc[cur] = cur
return acc
},
new Object()
),
...
}