I have a microfrontend app with a custom webpack(v5) configuration with react, typescript and the module federation plugin.
Everything works well in develop mode. Also, the built projects works fine when imported and rendered into the microfrontend container app (container app wires together all microfrontend apps).
But, when a build is executed and i try to run the production build locally, using serve -s dist -p 9997, i only get a blank page with no markup rendered. (Apologies in advance for the long post):
webpack build config file
module.exports = {
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx"], // file extensions we allow webpack to evaluate
},
mode: "production",
output: {
filename: "[name].[contenthash].js",
clean: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
// "#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript",
],
plugins: ["#babel/plugin-transform-runtime"],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "microfrontend_app", // name of the app used by the container to get the app code
filename: "remoteEntry.js", // name of the file this app sends to the container - DO NOT CHANGE.
exposes: {
"./MicrofrontendApp": "./src/bootstrap", // alias, lets the container app import this app as a module with name PortalNavigationApp
},
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
webpack dev config file
module.exports = {
mode: "development",
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx"], // file extensions we allow webpack to evaluate
},
devServer: {
port: 9998,
historyApiFallback: {
index: "/index.html",
},
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
// "#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript",
],
plugins: ["#babel/plugin-transform-runtime"],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "microfrontend_app", // name of the app used by the container to get the app code
filename: "remoteEntry.js", // name of the file this app sends to the container - DO NOT CHANGE.
exposes: {
"./MicrofrontendApp": "./src/bootstrap", // alias, lets the container app import this app as a module with name PortalNavigationApp
},
shared: packageJson.dependencies,
}),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
],
};
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title1</title>
</head>
<body>
<div id="_microfrontend-app"></div>
</body>
</html>
App.tsx
export default ({ history }: IApp): JSX.Element => {
console.log('loading App.tsx');
return (
<MainWrapper>
<Router history={history as unknown as H.History}>
<h1>hello from wellcom template app</h1>
</Router>
</MainWrapper>
);
};
const MainWrapper = styled.main``;
bootstrap.tsx
const mount = (
el: Element,
{
onNavigate,
defaultHistory,
initialPath,
}: {
onNavigate: (() => void) | null;
defaultHistory: any;
initialPath: string | null;
}
): unknown => {
// if in development and isolation, use browser history. If not, use memory history
const history =
defaultHistory ||
createMemoryHistory({
initialEntries: [initialPath || ""],
});
if (onNavigate) {
history.listen(onNavigate);
}
ReactDOM.render(<App history={history} />, el);
return {
onParentNavigate({ pathname: nextPathName }: { pathname: string }) {
const { pathname } = history.location;
//prevent infinite routing loop
if (pathname !== nextPathName) {
history.push(nextPathName);
}
},
};
};
// If we are in development and in isolation call mount immediately
if (process.env.NODE_ENV === "development") {
const devRoot = document.querySelector("#_microfrontend-app");
if (devRoot) {
mount(devRoot, {
defaultHistory: createBrowserHistory(),
initialPath: null,
onNavigate: null,
});
}
}
export { mount };
index.ts
import("./bootstrap");
This is the output i get when running webpack.dev.js:
And this is the output i get after running webpack build and serving using serve -s dist -p 9997:
I just cannot figure out why this happens. This is the output of my dist folder:
The index.html correctly appends the main.975.... js file. If i do a CTRL+F looking for "hello from App.tsx" in all the js files, i find it in the 499.63...js. So the markup logic exists in build.
Also, the main.975 js file seems to be aggregating all other js files:
I just cannot understand why the markup is not being rendered in the build version? I suspect there is something im missing in the webpack prod file. I tried to search through a create-react-app webpack file to compare, but there is so much stuff there i got completely lost.
Figured it out. Pretty silly, but in bootstrap.tsx, the mount function is never called if development = 'production'.
So just removing the
if (process.env.NODE_ENV === "development") {
...
}
if check made it work.
Related
I'm hoping someone will be able to help.
I'm trying to use single-spa-angularjs to pull in a legacy AngularJS application into a React app. So the React app is the host. In the same React app, I'm also using Module Federation to pull in a separate React microfrontend.
So to sum it up, I have three apps:
React app - host and container
AngularJS app - legacy app using
AngularJS version 1.4.8
React microfrontend
All three apps are completely separate and hosted separately.
I was able to pull in the React microfrontend using Module Federation with no issues. The problem that I'm facing is pulling the AngularJS app into the host React app (app 1).
I'm getting a lot of errors in the console but I think this is the most relevant one:
xxx.js:701 Uncaught Error: application 'AngularJS' died in status LOADING_SOURCE_CODE: Module parse failed: Unexpected token (1:1)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> <!DOCTYPE html>
| <html lang="en" ng-app="MyAngularJSApp" ng-controller="MyAngularJSCtrl">
| <head>
at eval (index.html:1:7)
at ./index.html (node_modules_angularjs-bootstrap-datetimepicker_node_modules_moment_locale_sync_recursive_-no-1f57b2.app.bundle.js:27:1)
at __webpack_require__ (remoteEntry.js:44:42)
at eval (app.entry.js:21:69)
at ./app/app.entry.js (node_modules_angularjs-bootstrap-datetimepicker_node_modules_moment_locale_sync_recursive_-no-1f57b2.app.bundle.js:113:1)
at __webpack_require__ (remoteEntry.js:44:42)
at eval (container_entry:3:298)
at u.m.<computed> (bundle.js:2:262091)
at u (bundle.js:2:259451)
This is my current set up:
AngularJS App:
We're using webpack so we have a webpack.config.js file and the most important part is:
module.exports = function (env = {}) {
const isProd = !!env.prod;
const buildpath = env.buildpath ? env.buildpath : isProd ? 'dist' : 'build';
return {
entry: path.resolve(__dirname, 'app/app.entry.js'),
output: {
path: path.resolve(__dirname, buildpath),
filename: 'js/app.bundle.js',
clean: true,
},
plugins: [
new webpack.container.ModuleFederationPlugin({
name: 'MyAngularJSApp',
filename: 'remoteEntry.js',
remotes: {},
exposes: {
'./App': path.resolve(__dirname, 'app/app.entry.js'),
},
}),
new HtmlWebpackPlugin({
inject: 'body',
template: path.resolve(__dirname, 'index.html'),
filename: path.resolve(buildpath, 'index.html'),
})
],
module: {
......
},
resolve: {
......
},
optimization: {
......
},
};
};
In app/app.entry.js I've added the following:
import singleSpaAngularJS from 'single-spa-angularjs';
import angular from 'angular';
import app from '../index.html'
const domElementGetter = () => document.getElementById('AngularJSApp');
const ngLifecycles = singleSpaAngularJS({
angular: angular,
domElementGetter,
mainAngularModule: 'AngularJSApp',
template: app,
});
export const bootstrap = ngLifecycles.bootstrap;
export const mount = ngLifecycles.mount;
export const unmount = ngLifecycles.unmount;
In the host/consuming React application:
webpack.config.js:
const path = require('path');
const webpack = require('webpack')
const { ModuleFederationPlugin } = webpack.container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
const dependencies = require('./package.json').dependencies;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
entry: './src/index.tsx',
mode: 'production',
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
module: {
rules: [
{
test: /\.(jsx|js)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
exclude: /\.lazy\.html$/,
use: [
{
loader: 'html-loader',
options: {
minimize: true,
},
},
],
},
// Use Babel to transpile TypeScript and TypeScript / React files to ES5
{
test: /\.(tsx|ts)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
reactMFE: 'reactMFE#http://localhost:3000/remoteEntry.js',
AngularJSApp: 'AngularJSApp#http://localhost:3004/remoteEntry.js',
},
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: dependencies.react,
},
'react-dom': {
singleton: true,
eager: true,
requiredVersion: dependencies['react-dom'],
}
},
}),
new webpack.DefinePlugin({ // <-- key to reducing React's size
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
// new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
I've registered the Angular app in the entry point /src/index.tsx. The same file also imports 'bootstrap' to resolve the eager consumption error when using module federation:
/src/index.tsx:
import("./bootstrap")
import {registerApplication} from 'single-spa'
registerApplication(
'AngularJSApp',
//#ts-ignore
() => import('AngularJSApp/App'),
location => location.pathname.startsWith('/')
)
/src/bootstrap.tsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
I've made changes to load in the scripts for the imported apps in public/index.html:
<html>
<head>
<script src="http://localhost:3000/remoteEntry.js"></script>
<script src="http://localhost:3004/remoteEntry.js"></script>
</head>
<body>
<div>Container App</div>
<div id="root"></div>
{/* I've put these two lines as a last ditch attempt - not sure which one of them should be working*/}
<div id="AngularJSApp"></div>
<div id="single-spa-application:AngularJSApp"></div>
</body>
</html>
Can someone advise on how I can get this working, it would be much appreciated.
Thank you
I have a webpack 'library' project with a webpack config that looks like this:
module.exports = ({ mode }) => {
const isProd = mode === 'production';
return {
mode,
entry: './src/index.tsx',
output: {
library: 'MyLibraryComponents',
path: path.resolve(__dirname, 'build'),
filename: getOutputFileName(isProd),
},
devServer: { ... },
optimization: { ... },
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
},
{
test: /\.less$/i,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'less-loader',
},
],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
scriptLoading: 'blocking',
template: resolveAppPath('public/index.html'),
}),
],
};
};
This webpack build creates a global variable called "MyLibraryComponents", which has methods on it that render react components given an HTMLElement using ReactDOM.render(). My project is set up this way because I'm working in a legacy application, but want to re-write certain parts of the UI in react.
Here is what the entry file for the library looks like. The entry file is in .ts but I've simplified it a bit here:
// src/index.tsx
import ReactDOM from 'react-dom';
export Examples = {
FooComponent: {
async render(htmlElement, props) {
const { default: Foo } = await import("./components/Foo");
ReactDOM.render(<Foo {...props} />, htmlElement);
}
},
BarComponent: {
async render(htmlElement, props) {
const { default: Bar } = await import("./components/Bar");
ReactDOM.render(<Bar {...props} />, htmlElement);
}
}
};
When the webpack main bundle is downloaded using a <script src=''>, it creates an object on window called MyLibraryComponents. I can then render react components into the legacy app like this: MyLibraryComponents.Examples.FooComponent.render(...)
As more components are added to this library, I'm interested in potentially using Module Federation as a means to independently deploy components, and to isolate dependencies, etc.
However, in all the module federation project examples I've seen it is necessary to have a "bootstrap" file and have your webpack entry file do this. And then your "real" entry file to your app should be renamed to bootstrap.js:
import("./bootstrap")
Apparently, this ensures that the project's shared modules are loaded by the browser first, or something like that. The reason isn't 100% clear to me yet.
The issue I'm seeing is that my project's entry file actually has "exports" that my webpack library depends on, whereas if this was a traditional react app, it would just do something like this:
import ReactDOM from "react-dom";
import App from "./App;
ReactDOM.render(<App />, document.getElementById("root"));
If I update my entry point file to just import('./bootstrap'), then my library just ends up being an empty object when I inspect it in the console.
Is there a way I can write the 'bootstrap' entry file so that I can support module federation in this project?
Thank you for the help.
I needed to update my new index file to do something like this:
// index entry file
import("./bootstrap").then(MyLibraryComponents => {
window.MyLibraryComponents = MyLibraryComponents;
});
Any code using referencing window.MyLibraryComponents needs to ensure this exists now though since it's now being "loaded" asynchronously. Or you can do something like this:
setTimeout(() => {
// Do something with window.MyLibraryComponents...
}, 1000);
We have a react-native project implemented using typescript, react-navigation, react-native-gesture-handler, redux/toolkit as the main packages
recently we integrated react-native-web into our project, but it is not running correctly.
there are several problems with our project:
we cannot load custom modules when we import them. for example:
import MyCustomComponent from './components/MyCustomComponent'
<View style={{flex: 1, backgroundColor: 'green'}}>
<MyCustomComponent/> <--- does not show up, event when it contains a simple View component, we will see a blank screen
</View>
but when I define MyCustomComponent inside the current file, it shows up with no problem:
function MyCustomComponent() {
return(
<View style={{flex:1, backgroundColor: 'yellow'}}></View>
)
}
export default function MyMainComponent() {
return (
<View style={{flex:1, backgroundColor: 'green'}}>
<MyCustomComponent/> <---- this shows up
</View>
)
}
anything that goes inside the redux Provider will not show up any more.
I think our webpack configuration is wrong, but since I'm not expert in web development, I need some help to figure out what's wrong. here is our webpack configuration:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const appDirectory = path.resolve(__dirname);
const {presets} = require(`${appDirectory}/../babel.config.js`);
const compileNodeModules = [
// Add every react-native package that needs compiling
'react-native-gesture-handler',
'react-redux',
'react-native-reanimated',
'react-native-confirmation-code-field',
'react-native-calendars',
'#react-native-google-signin/google-signin',
'react-native-compressor',
'react-native-swipe-gestures',
'#react-native-async-storage',
'react-native-shared-element',
'#react-navigation',
'react-native-material-menu',
'#reduxjs/toolkit',
'react-navigation-shared-element',
'react-native-collapsible-tab-view',
'react-native-image-crop-picker',
'#react-native-community',
'react-nativbe-safe-area-context/lib',
'react-native-screens',
].map(moduleName =>
path.resolve(appDirectory, `../node_modules/${moduleName}`),
);
const babelLoaderConfiguration = {
test: /\.js$|tsx?$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(__dirname, '../index.web.js'), // Entry to your application
path.resolve(__dirname, '../src/index.web.tsx'), // Change this to your main App file
path.resolve(__dirname, '../src'),
...compileNodeModules,
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets,
plugins: ['react-native-web'],
},
},
};
const svgLoaderConfiguration = {
test: /\.svg$/,
use: [
{
loader: '#svgr/webpack',
},
],
};
const tsLoaderConfiguration = {
test: /\.(ts|tsx|web.ts|web.tsx)?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
};
const imageLoaderConfiguration = {
test: /\.(gif|jpe?g|png)$/,
use: {
loader: 'url-loader',
options: {
name: '[name].[ext]',
esModule: false,
},
},
};
module.exports = {
entry: {
app: path.join(__dirname, '../index.web.js'),
},
output: {
path: path.resolve(appDirectory, 'dist'),
publicPath: '/',
filename: 'arcelor.bundle.js',
},
resolve: {
extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
alias: {
'react-native$': 'react-native-web',
},
},
module: {
rules: [
babelLoaderConfiguration,
imageLoaderConfiguration,
svgLoaderConfiguration,
tsLoaderConfiguration,
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, './index.html'),
filename: 'index.html',
inject: 'body',
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
// See: https://github.com/necolas/react-native-web/issues/349
// __DEV__: JSON.stringify(true),
__DEV__: process.env.NODE_ENV !== 'production',
process: {env: {}},
}),
],
};
I'm using webpack#^5.65.0
could anyone help me figure out what is the problem and how can I make react-native-web work with our project? thanks
getting Webpack up and running from scratch is not an easy task. I suggest you start with a ready to use an approach like cra or expo. then work your way up to customization.
Create-React-App
firstly, install the dependencies:
yarn add react-native-web react-scripts react-dom
create an HTML file in public/index.html and put the following content inside: gh:facebook/create-react-app/cra-template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>Your App Title</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
rename the index.js in project root to index.native.js. see
create a js file in src/index.js and put the following content inside:
import { AppRegistry } from "react-native";
import { App } from "./App";
AppRegistry.registerComponent(appName, () => App);
AppRegistry.runApplication("App", {
rootTag: document.getElementById("root"),
});
run the app by running react-scripts start
Customization
you may need to integrate preprocessors like react-native-reanimated/plugin to your babel config or edit your WebPack to add global variables like process.env. In order to do that you can either use react-scripts eject to have access to said configs or use tools like customize-cra.
Expo (Recommened)
In my opinion Expo is the best way to do it. Expo basically is create-react-app but for react-native that supports the react-native-web.
You can set up the expo for the web for your project by following the official guide.
install dependencies:
yarn add --global expo-cli
expo install react-native-web react-dom
yarn add expo
modify your root index.js to something like this:
import { registerRootComponent } from 'expo';
import App from './App';
registerRootComponent(App);
at this point you are ready to go. just rust expo start:web.
Customization
By running expo customize:web, you have access to Babel and Webpack config files.
Typescript basePath
If you are using "baseUrl": "src" in your tsconfig.json. you may need to set up the Babel too. because it may not necessarily follow your tsconfig.
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin',
[
'module-resolver',
{
root: ['src'],
extensions: ['.tsx', 'json', '.ts', '.js'],
},
],
],
};
};
Recently we found out that we have to use SSR for Our React Project.
I have checked with every method that I know and almost tested all methods that I've found on medium and other sites. And after a lot of work, I decided that we have to migrate to Next JS.
While the process of migrating everything is fine, but for the style sheets.
In the old version of our app, we used webpack to bundle our styles with the project and everything was fine.
This is the webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const port = process.env.PORT || 3000;
const extractSCSS = new ExtractTextPlugin('./[name].css');
// const UglifyJS = require('uglifyjs-webpack-plugin');
module.exports = {
mode: 'development',
output: {
filename: 'bundle.[hash].js',
publicPath: '/'
},
devtool: 'source-map',
module: {
rules: [
// First Rule
{
test: /\.(js)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
// Second Rule
{
test: /\.scss$/,
use: ['css-hot-loader'].concat(extractSCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader?sourceMap',
options: { alias: { '../img': '../public/img' }, sourceMap: true }
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
}))
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
favicon: 'public/favicon.ico'
}),
extractSCSS,
],
devServer: {
host: 'localhost',
port: port,
historyApiFallback: true,
open: true
}
};
and after I migrated the app, my next.config.js looks like this:
// next.config.js
const withSass = require('#zeit/next-sass')
const withCSS = require('#zeit/next-css')
module.exports = withCSS( withSass(
{
webpack(config, options) {
config.module.rules.push({
test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/,
use: {
loader: 'url-loader',
options: {
limit: 100000
}
}
},
)
return config
},
}
))
The problem is that everything renders correctly but there are no stylesheets in it and it doesn't contain any style. Is there anybody who could help me to solve this problem?
For just using CSS from node_modules you don't need this fancy config.
3 simple steps:
Install next-css plugin:
npm install --save #zeit/next-css
Create in your root directory next.config.js with the following content:
// next.config.js
const withCSS = require('#zeit/next-css')
module.exports = withCSS({
cssLoaderOptions: {
url: false
}
})
Now you should be able to import styleshets from node_modules like this:
import 'bootstrap-css-only/css/bootstrap.min.css';
Note: Using Next v 8+
Background:
I spent a few hours trying to simply import a CSS installed as a node_module and the various solutions are mostly hacky workarounds, but as shown above, there is a simple solution.
It was provided by one of the core team members: https://spectrum.chat/next-js/general/ignoring-folders-files-specifically-fonts~4f68cfd5-d576-46b8-adc8-86e9d7ea0b1f
This is not a real answer but CSS in Next.js is just SUCKS! I find myself constantly struggle to make it work so what I decided is to follow their docs and simply use:
const App = () => {
return (
{style}
<div/>
);
}
let style = (<style jsx>
{`
.someClass {}
`}
</style> )
export default App;
This way you can have CSS as you might have in regular HTML without any external imports
source
You don't need both withCSS & withSass plugins.
If you are using Sass the withSass plugin will compile it to CSS.
Just make sure you add the path to the CSS file in your _document.js file inside the Head component like this:
<Head>
<link rel="stylesheet" href="/_next/static/style.css" />
</Head>
For importing css you can use Head component of the nextJS.
import Head from 'next/head';
<Head>
<link rel="stylesheet" href="path of the css" />
</Head>
I need to import default Kendo-ui template styles from this site https://www.telerik.com/kendo-react-ui/components/styling/.
When it trying to load styles, it throws NodeInvocationException: Prerendering failed because of error: Error: Module parse failed: "project folder"\node_modules\#progress\kendo-theme-default\dist\all.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
Here is my layout.ts file where I want to import style template
import * as React from 'react';
import { NavMenu } from './NavMenu';
import "#progress/kendo-theme-default/dist/all.css";
export class Layout extends React.Component<{}, {}> {
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<NavMenu />
</div>
<div className='col-sm-9'>
{ this.props.children }
</div>
</div>
</div>;
}
}
webpack.config looks like this, its default configuration from auto generated react-redux asp.net project
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const merge = require('webpack-merge');
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
// Configuration in common to both client-side and server-side bundles
const sharedConfig = () => ({
stats: { modules: false },
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
});
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig(), {
entry: { 'main-client': './ClientApp/boot-client.tsx' },
module: {
rules: [
{ test: /\.css$/, use: ExtractTextPlugin.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
]
},
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new ExtractTextPlugin('site.css'),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig(), {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot-server.tsx' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
In order for the external .css files to work ( like kendo-theme-default coming from node_modules ), a postcss-loader is required.