Hi I'm building a React app with SSR. Server and client part both written in typescript and transpiled separately.
Here the app structure for the better understanding:
Here the simplified webpack configs for the server and client bundles:
// webpack.client.js
module.exports = {
mode: "development",
resolve: {
modules: ["src", "static", "node_modules"],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
entry: [
"./src/client/index.tsx"
],
output: {
filename: "bundle.js",
path: PUBLIC_PATH,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
}
],
},
]
},
plugins: [
new webpack.DefinePlugin({ IS_SERVER: false })
]
};
Server config looks pretty much the except for the target and externals
//webpack.server.js
const config = {
mode: "development",
resolve: {
modules: ["src", "static", "node_modules"],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
externals: [webpackNodeExternals()],
target: 'node',
entry: [
"./src/server/index.ts"
],
output: {
filename: "bundle.js",
path: SERVER_BUILD_PATH
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: "ts-loader",
}
],
},
]
},
plugins: [
new webpack.DefinePlugin({ IS_SERVER: true })
]
};
In the server code I got a renderer function which renders React application to string.
// renderer.tsx
import React from "react";
import { renderToString } from "react-dom/server";
import { App } from "client/App";
const html = (app) => `
<html>
<head>
</head>
<body>
<div id="root">${app}</div>
<script src="/public/bundle.js"></script>
</body>
</html>
`;
export async function renderer(req) {
const app = renderToString(<App />);
return html(app);
}
Which then returns to the client by the express server.
//index.ts
app.get("*", async (req, res) => {
const content = await renderer(req);
res.send(content);
});
As you see both parts need to transpile React app. The question is - how can I reuse transpiled client code in the server bundle so that server config only need to transpile index.ts and renderer.tsx?
You can use webpack-merge package to do that
Here is my example
const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
const webpack = require("webpack");
module.exports = merge(baseConfig, {
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development'),
'BASE_URL': JSON.stringify('http://localhost:5000/')
}
})
],
watch: true
});
This can be easily done using resolve.alias:
https://webpack.js.org/configuration/resolve/#resolvealias
Simply move the helpers directory up in the app's directory hierarchy and import from it.
In your case, I would also like to redesign the app using a single webpack.config.js file at the root level of your app in which you can combine the client and server configurations using the multi-compiler feature and respect the principle "Do not repeat yourself":
https://stackoverflow.com/a/43689505/2569746
Related
How do I configure ASP.NET Core MVC (.NET6) (MPA) to run "Vue.js" with Webpack?
I would like to build a multi-page application(MPA) without using SPA in ASP.NET Core MVC.
I want the project configuration to be close to the default configuration. (Divided into wwwroot, Controllers and Views)
// ASP.NET Core MVC (.NET6) Project structure
Project
- wwwroot
- css (omit)
- dist
main.js
- js
- views
- Test1
- index.js // Import does not work
- node_modules
- vue
- ...
- src
- index.html
- index.js
- Controllers
- Test1Controller.cs
- Views
- Test1
- Index.cshtml
Do not use ".vue" files.
Currently, you can set webpack-config.js as below and run up to "npm run build".
I'm having a problem because import doesn't work in the javascript to be executed.
// /wwwroot/webpack-congig.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: {
main: "./src/index.js",
Test1: "./js/views/Test1/index.js"
},
output: {
filename: "[name].js",
path: __dirname + "/dist"
},
devtool: "source-map",
mode: "development",
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
include: path.resolve(__dirname, "/js"),
options: { presets: ["#babel/preset-env"] }
},
{
test: /\.vue$/,
loader: "vue-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
}
],
},
resolve: {
alias: {
"vue$": path.resolve("./node_modules/vue/dist/vue.esm-bundler.js")
},
extensions: ["*", ".js", ".vue"],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "./css/site.css",
chunkFilename: "[name].css"
}),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: false,
})
],
};
// /wwwroot/src/index.js
import "../node_modules/vue";
console.log("The site bundle has been loaded!");
// /wwwroot/js/views/Test1/index.js
// Import does not work
import { createApp } from "vue";
const app = createApp({
});
app.mount("#app");
I have babel loader in the library. Still after I add the library to the react application while yarn serve, I get the above error.
This is the webpack.dev.config.js (required in the webpack.config.js) in library-
//webpack.dev.config.js
const babelRCPath = require('#appfabric/infra-scripts').getConfigPath('babel', 'plugin');
const babelRCGenerator = require(babelRCPath);
const babelRC = babelRCGenerator([]);
module.exports = {
{
BaseModule: `${process.cwd()}/src/BaseModule`,
BaseObject: `${process.cwd()}/src/BaseObject`,
BaseWidget: `${process.cwd()}/src/widgets/BaseWidget`,
HOCWidget: `${process.cwd()}/src/widgets/HOCWidget`,
PortalWidget: `${process.cwd()}/src/widgets/PortalWidget`,
BaseActivator: `${process.cwd()}/src/application/BaseActivator`,
CorePlugin: `${process.cwd()}/src/application/CorePlugin`,
BaseAppDelegate: `${process.cwd()}/src/application/appdelegates/BaseAppDelegate`,
EmbeddedAppDelegate: `${process.cwd()}/src/default/appdelegates/embedded/EmbeddedAppDelegate`,
ActionType: `${process.cwd()}/src/application/appdelegates/actions/ActionType`,
types: `${process.cwd()}/src/application/appdelegates/actions/types`,
CommandActionType: `${process.cwd()}/src/application/appdelegates/actions/CommandActionType`,
CommandForResponseActionType: `${process.cwd()}/src/application/appdelegates/actions/CommandForResponseActionType`,
PluginRegistryService: `${process.cwd()}/src/default/PluginRegistryService`,
},
mode: 'development',
externals: [
'dcl',
'react',
'react-dom',
'prop-types',
'pubsub',
'semver',
'#appfabric/ui-profiler',
].map(
// Add this regex to each entry to ensure we don't miss any imports like 'web-shell-core/...`
(value) => new RegExp(`^(${value})((\\\\|/|!).+)?$`),
),
output: {
path: `${process.cwd()}/build/dist`,
filename: '[name].js',
library: 'web-shell-core',
libraryTarget: 'umd',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: babelRC,
},
},
],
},
};
This is the webpack.config.js
const developmentConfig = require('./webpack.dev.config.js');
module.exports = merge(developmentConfig, {
mode: 'production',
output: {
filename: '[name].min.js',
chunkFilename: '[name].min.js',
},
});
First I add a new file Secure.jsx(having the tags) in the library. I do npm install --save <path-to-library> on my application. After I do yarn install. Then I can see the new file Secure.jsx in the node modules in the application. When I try to run the application, I get the error.
Please let me know what am I missing and also which side(library / application) I have to add the code.
You can view my full config here
I think you also need to add this
resolve: {
modules: [
path.resolve('./node_modules')
]
},
Then import like this
import "jquery/dist/jquery.min.js";
import "bootstrap/dist/js/bootstrap.min.js";
I'm referencing an image inside my .JSX file but the generated URL is wrong.
It looks like this : http://localhost:43124/dist/dist/9ee7eb54c0eb428bb30b599ef121fe25.jpg
The folder "dist" exists with the picture but not "dist/dist". I think the problem comes from my Webpack.config.js. Here are the files :
module.d.ts
I instruct Typescript what to do with image files as mentionned here.
declare module '*.jpg'
declare module '*.svg'
Layout.tsx
I reference my logo inside React so it can be packed by Webpack.
/// <reference path="./module.d.ts"/>
import * as React from 'react';
import logo from '../img/logo.svg';
export class Layout extends React.Component<{}, {}> {
public render() {
return <img src="{logo}" width="220" alt="logo" />
}
}
webpack.config.js
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('style.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()
])
});
return [clientBundleConfig];
};
I used the default Visual Studio ASP.NET Core React + Redux template.
I have setup my Angular 2 project in .NET Core solution and I have a situation where I need to use .cshtml view files to be rendered from server and use them as templates in Angular components. I am using webpack to AOT bundle them.
How can I exclude templateUrl or template to be excluded (not to be compiled into output .js file) but rather resolved on the fly?
My lazy load component (notfound.component.ts):
import { Component } from "#angular/core";
#Component({
templateUrl: "/Home/PageNotFound" // This is my Controller/Action
})
export class NotFoundComponent
{
}
webpack.config.js:
var webpack = require("webpack");
var clean = require("clean-webpack-plugin");
var compression = require("compression-webpack-plugin");
var path = require("path");
var analyzer = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
"app": "./Client/main-aot.ts" // AoT compilation
},
devtool: "source-map",
output: {
path: __dirname + "/wwwroot/dist/bundle",
//filename: "[name]-[hash:8].bundle.js",
filename: "[name].js",
chunkFilename: "[id]-[hash:8].chunk.js",
publicPath: "/dist/bundle/",
},
resolve: {
extensions: [".ts", ".js"]
},
module: {
rules: [
{
test: /\.ts$/,
use: [
"awesome-typescript-loader",
"angular-router-loader?aot=true&genDir=aot/"
]
}
],
exprContextCritical: false
},
plugins: [
new clean(
[
__dirname + "/wwwroot/dist/bundle"
]
),
new analyzer(),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
output: {
comments: false
},
sourceMap: true
}),
new compression({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.html$/,
threshold: 10240,
minRatio: 0.8
})
]
};
When I run following NPM command, its giving me error:
"node_modules/.bin/ngc -p tsconfig-aot.json" && webpack --configebpack.config.js
Error: Compilation failed. Resource file not found:
C:/Users/Saad/Documents/Visual Studio 2017/Projects/HelloAngular/HelloAngular/Client/notfound/dist/template/notfound/notfound.html
How do you use Webpack and AngularJS together, and how about template loading and on demand fetching of resources?
An example of a well written webpack.config.js file for this purpose would be very much appreciated.
All code snippets displayed here can be accessed at this github repo. Code has been generously adapted from this packetloop git repo.
webpack.config.json
var path = require('path');
var ResolverPlugin = require("webpack/lib/ResolverPlugin");
var config = {
context: __dirname,
entry: ['webpack/hot/dev-server', './app/app.js'],
output: {
path: './build',
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.css$/,
loader: "style!css-loader"
}, {
test: /\.scss$/,
loader: "style!css!sass?outputStyle=expanded"
}, {
test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$/,
loader: "file"
}, {
test: /\.html$/,
loader: "ngtemplate?relativeTo=" + path.join(__dirname, 'app/') + "!raw"
}]
},
// Let webpack know where the module folders are for bower and node_modules
// This lets you write things like - require('bower/<plugin>') anywhere in your code base
resolve: {
modulesDirectories: ['node_modules', 'lib/bower_components'],
alias: {
'npm': __dirname + '/node_modules',
'vendor': __dirname + '/app/vendor/',
'bower': __dirname + '/lib/bower_components'
}
},
plugins: [
// This is to help webpack know that it has to load the js file in bower.json#main
new ResolverPlugin(
new ResolverPlugin.DirectoryDescriptionFilePlugin("bower.json", ["main"])
)
]
};
module.exports = config;
To import AngularJS into the main app.js you do the following:
app/vendor/angular.js
'use strict';
if (!global.window.angular) {
require('bower/angular/angular');
}
var angular = global.window.angular;
module.exports = angular;
And then use it in app.js like so,
app.js
...
var angular = require('vendor/angular');
// Declare app level module
var app = angular.module('myApp', []);
...
Is the following correct? Is there an easier way to do this? I've seen a few (not a lot by any standards) posts which listed another method.
From this reddit post comment
// Add to webpack.config.js#module#loaders array
{
test: /[\/]angular\.js$/,
loader: "exports?angular"
}
There is also another plugin which is in development right now, at stackfull/angular-seed. It seems to be in the right direction, but is really really hard to use right now.
Webpack is way awesome, but the lack of documentation and samples are killing it.
You can just require angular in all modules (files) where you need it. I have a github repository with example how to do that (also using webpack for build). In the example ES6 import syntax is used but it shouldnt matter, you can use standard require() instead.
Example:
import 'bootstrap/dist/css/bootstrap.min.css';
import './app.css';
import bootstrap from 'bootstrap';
import angular from 'angular';
import uirouter from 'angular-ui-router';
import { routing} from './app.config';
import common from './common/common.module';
import featureA from './feature-a/feature-a.module';
import featureB from './feature-b/feature-b.module';
const app = angular
.module('app', [uirouter, common, featureA, featureB])
.config(routing);
I am starting with Angular + Flux with Webpack so may be I can help you with some things.
Basically I am installing everything with NPM, it has module export system, so it works like nothing. (You can use export-loader, but why if you do not need to.)
My webpack.config.js looks like this:
var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require("html-webpack-plugin");
var nodeModulesDir = path.resolve(__dirname, './node_modules');
// Some of my dependencies that I want
// to skip from building in DEV environment
var deps = [
'angular/angular.min.js',
...
];
var config = {
context: path.resolve(__dirname, './app'),
entry: ['webpack/hot/dev-server', './main.js'],
resolve: {
alias: {}
},
output: {
path: path.resolve(__dirname, './build'),
filename: 'bundle.js'
},
// This one I am using to define test dependencies
// directly in the modules
plugins: [
new webpack.DefinePlugin({
ON_TEST: process.env.NODE_ENV === 'test'
})
],
module: {
preLoaders: [
{test: /\.coffee$/, loader: "coffeelint", exclude: [nodeModulesDir]}
],
loaders: [
{test: /\.js$/, loader: 'ng-annotate', exclude: [nodeModulesDir]},
{test: /\.coffee$/, loader: 'coffee', exclude: [nodeModulesDir]},
...
],
noParse: []
},
devtool: 'source-map'
};
if (process.env.NODE_ENV === 'production') {
config.entry = {
app: path.resolve(__dirname, './app/main.js'),
vendors: ['angular']
};
// config.output.path = path.resolve(__dirname, './dist');
config.output = {
path: path.resolve(__dirname, "./dist"),
filename: "app.[hash].js",
hash: true
};
config.plugins.push(new webpack.optimize.UglifyJsPlugin());
config.plugins.push(new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.[hash].js'));
config.plugins.push(new HtmlWebpackPlugin({
title: 'myApp',
template: path.resolve(__dirname, './app/index.html'),
inject: 'body'
}));
delete config.devtool;
}
else {
deps.forEach(function (dep) {
var depPath = path.resolve(nodeModulesDir, dep);
config.resolve.alias[dep.split(path.sep)[0]] = depPath;
config.module.noParse.push(depPath);
});
}
module.exports = config;
My main.js looks like this:
var angular = require('angular');
if(ON_TEST) {
require('angular-mocks/angular-mocks');
}
require('./index.coffee');
And index.coffee containt main angular module:
ngModule = angular.module 'myApp', []
require('./directive/example.coffee')(ngModule)