Reacr Single SPA Micro frontend executes old scripts after a fresh deployment - reactjs

We are migrating a standalone React.js application into micro frontend architecture using the library react-single-spa https://single-spa.js.org/docs/ecosystem-react/
We noticed a problem that after a new deployment to any micro-frontend app, the Single SPA was still loading the old code. I assume it is because, the browser caches the scrips . Is there's a way to make sure latest version of the code is being used ? Or is there a sample on how to implement this?
Below is my current webpack configs.
root-config
const { merge } = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = (webpackConfigEnv, argv) => {
const orgName = "my-org";
const defaultConfig = singleSpaDefaults({
orgName,
projectName: "root-config",
webpackConfigEnv,
argv,
disableHtmlGeneration: true,
});
return merge(defaultConfig, {
plugins: [
new HtmlWebpackPlugin({
inject: false,
template: "src/index.ejs",
templateParameters: {
isLocal: webpackConfigEnv && webpackConfigEnv.isLocal,
orgName,
},
}),
],
});
};
app-config
const { merge } = require("webpack-merge");
const singleSpaDefaults = require("webpack-config-single-spa-react");
const path = require("path");
module.exports = (webpackConfigEnv, argv) => {
const defaultConfig = singleSpaDefaults({
orgName: "my-org",
projectName: "my-app",
webpackConfigEnv,
argv,
});
return merge(defaultConfig, {
resolve: {
fallback: {
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify")
},
extensions: ['.ts', '.js', 'mjs', '.tsx'],
alias: {
src: path.resolve(__dirname, 'src'),
}
},
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(woff|woff2|ttf|eot)$/,
use: 'file-loader?name=fonts/[name].[ext]!static'
}
]
}
});
};
Appreciate any help. Thank you !

Related

How to resolve aliases in Storybook?

I have a React/Typescript project with Storybook. Storybook works great, but as soon as I start importing files with aliases, it crashes.
Example:
import Foo from "#components/foo" => crash
import Foo from "../../components/foo" => ok
The app works fine with the aliases. The issue is only related to Storybook.
Here is my storybook config:
module.exports = {
stories: ["../**/stories.tsx"],
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
};
},
typescript: {
check: false,
checkOptions: {},
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
My webpack config:
/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProductionMode = (mode) => mode === "production";
module.exports = () => {
const env = require("dotenv").config({ path: __dirname + "/.env" });
const nodeEnv = env.parsed.NODE_ENV;
return {
mode: "development",
entry: "./src/index.tsx",
output: {
path: path.join(__dirname, "./dist"),
filename: "[name].[contenthash].bundle.js",
publicPath: "/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
alias: {
"#api": path.resolve(__dirname, "src/api/"),
"#assets": path.resolve(__dirname, "src/assets/"),
"#components": path.resolve(__dirname, "src/components/"),
"#containers": path.resolve(__dirname, "src/containers/"),
"#data": path.resolve(__dirname, "src/data/"),
"#i18n": path.resolve(__dirname, "src/i18n/"),
"#models": path.resolve(__dirname, "src/models/"),
"#pages": path.resolve(__dirname, "src/pages/"),
"#src": path.resolve(__dirname, "src/"),
"#stores": path.resolve(__dirname, "src/stores/"),
"#utils": path.resolve(__dirname, "src/utils/"),
},
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
devServer: {
historyApiFallback: true,
port: 3000,
inline: true,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new Dotenv(),
],
optimization: {
minimize: isProductionMode(nodeEnv),
minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
splitChunks: { chunks: "all" },
},
};
};
How to fix this? I am on webpack 5.24.2 and storybook 6.1.20, so these are the latest versions.
Just add this in your .storybook/main.js
const path = require('path');
module.exports = {
"stories": [
"../components/**/*.stories.mdx",
"../components/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
'#storybook/preset-scss',
],
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
'#/interfaces': path.resolve(__dirname, "../interfaces"),
};
return config;
}
}
here interface is folder at my project root
It works For Me
This worked for me when I had the same problem:
Install a package in dev deps yarn add -D tsconfig-paths-webpack-plugin.
Then adjust your ./storybook/main.js config:
... // other imports
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
...
webpackFinal: (config) => {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
})
);
return { ... }
}
...
From the docs:
// .storybook/main.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
webpackFinal: async (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
return config;
},
};
Link
My React/TypeScript Storybook project uses Vite rather than Webpack.
The readme for storybook-builder-vite clarifies "The builder will not read your vite.config.js file by default," so anything that you specified in there may be having no influence whatsoever on the Storybook build; instead, you have to customise the Storybook-specific Vite config via the viteFinal option in .storybook/main.js.
Here's how I went about introducing vite-tsconfig-paths into the Storybook Vite config to resolve tsconfig path aliases:
// .storybook/main.js
const path = require("path");
const tsconfigPaths = require("vite-tsconfig-paths").default;
module.exports = {
"stories": [
"../frontend/**/*.stories.mdx",
"../frontend/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-interactions"
],
"framework": "#storybook/react",
"core": {
"builder": "storybook-builder-vite"
},
/**
* A option exposed by storybook-builder-vite for customising the Vite config.
* #see https://github.com/eirslett/storybook-builder-vite#customize-vite-config
* #param {import("vite").UserConfig} config
* #see https://vitejs.dev/config/
*/
viteFinal: async (config) => {
config.plugins.push(
/** #see https://github.com/aleclarson/vite-tsconfig-paths */
tsconfigPaths({
// My tsconfig.json isn't simply in viteConfig.root,
// so I've passed an explicit path to it:
projects: [path.resolve(path.dirname(__dirname), "frontend", "tsconfig.json")],
})
);
return config;
},
}
In case you use #storybook/vite-builder. This neat config works for me
const tsconfigPaths = require("vite-tsconfig-paths");
...
module.exports = {
...
async viteFinal(config) {
return {
...config,
plugins: [...config.plugins, tsconfigPaths.default()],
};
},
};
If you're using webpack 5 you'll need to specify that webpack5 should be used by also adding the following in addition to the previous answers:
core: {
builder: "webpack5",
},
Final storybook/main.js would then resemble:
// .storybook/main.js
const path = require('path');
const appWebpack = require(path.join(process.cwd(), 'webpack.config.js'));
module.exports = {
stories: ['../src/**/*.stories.#(tsx|mdx)'],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
'#storybook/preset-scss'
],
core: {
builder: "webpack5",
},
webpackFinal: async (config) => {
config.resolve.modules = [
...(config.resolve.modules || []),
...[path.resolve(process.cwd(), "src")],
];
config.resolve.alias = {
...(config.resolve.alias || {}),
...appWebpack().resolve.alias,
};
return config;
},
};
This will allow both absolute paths as well as aliases (as long as those aliases are properly set up in your main webpack.config.js and jsconfig.json/tsconfig.json of course)
Edited
Having trouble after the fact specifically with aliases, I took another trip down the webpack rocky-road.
I've updated the original 'final' for the .storybook/main.js above, explicitly merging in the alias as well as the modules nodes.
Edit 2
Be aware, eslint is going to squawk over using an alias within global decorators you create (and add to .storybook/preview.js). You can safely ignore this - they still work. If/when I figure out how to correct this as well, I'll come back and add a 3rd edit.
We're using Vite and typescript project references, for us adding the following to the storybook main.cjs worked;
viteFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
'#some-alias': path.resolve(__dirname, '../../some/ts/project/reference'),
};
return config;
}
As an alternative to Jamie Birch's excellent answer, if you're using vite and don't want to install vite-tsconfig-paths, you can just edit .storybook/main.js and add viteFinal to the config, like this:
const path = require('path');
module.exports = {
// ... whatever you already have here
viteFinal: async (config) => {
if (config.resolve.alias) {
config.resolve.alias.push({ find: '#', replacement: path.resolve(__dirname, '../src') + '/' });
} else {
config.resolve.alias = [{ find: '#', replacement: path.resolve(__dirname, '../src') + '/' }];
}
return config;
}
}

Webpack - TypeError: $ is not a function

i am new to webpack and i am creating a react application using webpack. I am getting this error while compiling TypeError: $ is not a function.
I am not using jquery but my node modules include some third party libraries.
I was only able to find one related article to this problem but it related to jquery.
Webpack - $ is not a function
Is there anything wrong with my webpack and babel config:
webpack.config.js
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = (env) => {
console.log("WEBPACK ENV: ", env);
const isDevMode = env !== "production";
let config = {
entry: ["./src/index.js"],
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js"
},
resolve: {
extensions: [".js", ".jsx"]
},
plugins: [
new CleanWebpackPlugin(),
new FaviconsWebpackPlugin("./src/logo.png"),
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true
}
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css"
})
],
module: {
rules: [
{
test: /\.scss$/,
use: ["css-loader", "sass-loader"]
},
{
test: /\.jsx?$/,
exclude: /node_modules\/(?!(sdk1|sdk2)\/).*/,
use: ["babel-loader"]
},
{
test: /\.(ac3|gif|jpe?g|m4a|mp3|ogg|png|svg|otf)$/,
loader: "file-loader",
options: {
outputPath: "./assets"
}
}
]
},
optimization: {
minimize: true,
minimizer: [new OptimizeCssAssetsPlugin(), new TerserPlugin()],
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
}
}
};
// Mode
config.mode = isDevMode ? "development" : "production";
// Dev Tools
config.devtool = isDevMode ? "inline-source-map" : false;
// Plugins
if (!isDevMode) {
config.plugins.push(new BundleAnalyzerPlugin({ analyzerPort: 8181 }));
}
// Dev Server
if (isDevMode) {
config.devServer = {};
config.devServer.disableHostCheck = true;
}
return config;
};
babel.config.js
module.exports = {
plugins: [
"#babel/plugin-syntax-dynamic-import",
"#babel/plugin-transform-object-assign",
[
require.resolve("babel-plugin-module-resolver"),
{
root: ["./src/"],
alias: {
js: "./src/js",
scss: "./src/scss",
components: "./src/js/components",
containers: "./src/js/containers",
phaser_path: "./src/js/phaser",
services: "./src/js/services",
constants: "./src/js/constants"
}
}
]
],
presets: [
[
"#babel/preset-env",
{
useBuiltIns: "usage",
corejs: 3,
modules: false,
debug: true,
targets: {
browsers: [">0.25%", "ie >= 11"]
}
}
],
[
"#babel/preset-react",
{
development: true
}
]
]
};
I think it is related to the html-webpack-plugin but i don't know for sure. Any help is appreciated.
Thanks
I am currently using this as a workaround to polyfill for now as the useBuiltIns property is not working for me.
Installed the following packages: babel-polyfill, es6-promise, whatwg-fetch
npm i babel-polyfill
npm i --save-dev es6-promise whatwg-fetch
In webpack.config.js did these changes
Require es6-promise on top require("es6-promise").polyfill();
Add whatwg-fetch and babel-polyfill to the entry property in config like this
entry: ["whatwg-fetch", "babel-polyfill", "./src/index.js"]

i18next + React + Webpack - getFixedT is not a function

I'm having some trouble with React, i18next and Webpack. I've tried many solutions, but none of them worked. When I try to build my application, it builds successfully. But, when I try to open it, the console shows an error. My webpack.config and the error stacktrace are below.
webpack.prod.config.js
const webpack = require('webpack');
const path = require('path');
const htmlWebpackPlugin = require("html-webpack-plugin")
const miniCSSExtractPlugin = require("mini-css-extract-plugin")
const terserPlugin = require("terser-webpack-plugin")
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
const cleanWebpackPlugin = require("clean-webpack-plugin")
const i18nPlugin = require("i18n-webpack-plugin")
const options = require("../src/controllers/i18n").options
const locales = require("../src/controllers/i18n/locales")
options.backend.loadPath = "." + options.backend.loadPath
const config = {
mode: "production",
output: {
path: path.resolve(__dirname, '../dist'),
publicPath: "./",
filename: 'bundle.js'
},
resolve: {
extensions: [" ", ".js", ".jsx"],
alias: {
"#components": path.resolve(__dirname, "../src/components"),
"#views": path.resolve(__dirname, "../src/views")
}
},
optimization: {
minimizer: [
new terserPlugin({
cache: true,
parallel: true,
include: /\.(js|jsx)$/
}),
new OptimizeCSSAssetsPlugin({})
]
},
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [{
loader: "babel-loader"
}, {
loader: "i18next-loader"
}]
},
{
test: /\.css$/,
use: [
miniCSSExtractPlugin.loader,
{
loader: "css-loader",
}
]
}, {
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader?name=images/[hash].[ext]',
options: {
name: "assets/images/[hash].[ext]"
}
}]
}, {
test: /\.(ttf|woff(2)|eof|svg)$/,
use: [{
loader: "file-loader",
options: {
name: "assets/fonts/[hash].[ext]",
}
}]
}
],
},
plugins: [
new htmlWebpackPlugin({
template: path.join(__dirname, "..", "public", "index.html")
}),
new miniCSSExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new cleanWebpackPlugin("../dist/*", {
root: __dirname + "../",
allowExternal: true
}),
new i18nPlugin(locales,{
functionName: "t",
nested: true
},true)
]
};
module.exports = config;
The npm run build runs normally, no errors about i18next. Then, when I open the application, I got this error:
bundle.js:33 TypeError: r.getFixedT is not a function
at L (bundle.js:12)
at bundle.js:12
at Xo (bundle.js:33)
at Ia (bundle.js:33)
at qi (bundle.js:33)
at $i (bundle.js:33)
at jl (bundle.js:33)
at Cl (bundle.js:33)
at Pl (bundle.js:33)
at Ji (bundle.js:33)
Hope somebody can helps me.
I've found what was the problem. The i18next documentation says that I should run the init inside the webpack.config.js.
The Problem
My initial problem was loading the locales. The i18n couldn't find the files after the build, because the webpack doesn't recognize the i18n-xhr-backend requiring the .json files. After the build, there was no translation files. Then, I've tried to let webpack deal with the i18n, and I got another problem, on next paragraph.
The React needs the i18n instance to be add to the i18nextProvider. But, the way that I was doing it, there was no i18n instance to refence inside React. Then, it coudn't find the translation function or anything else. I've also found the i18nWebpackPlugin, but it also didn't solved my problem, because it don't give access to the i18n instance inside react. At the end, I had two unsolved problems.
The Solution
My solution was quite simple. I've created a new i18n config to the development env and let the webpack deal with the .json, copying it to a new folder after build. I'll let my webpack and i18n config files below. The steps were:
Bring i18n.init() back to i18n/index.js
Create different options for each environment
Configure webpack to copy translation files
Import the i18n instance on App.js again
Now, everything works like a charm.
OBS: To webpack recognize the .json files, you need to import it somewhere. I did inside the resources.js file.
webpack.prod.config.js
const webpack = require("webpack");
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
const miniCSSExtractPlugin = require("mini-css-extract-plugin");
const terserPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const cleanWebpackPlugin = require("clean-webpack-plugin");
const config = {
mode: "production",
output: {
path: path.resolve(__dirname, "../dist"),
publicPath: "./",
filename: "bundle.js"
},
resolve: {
extensions: [" ", ".js", ".jsx"],
alias: {
"#components": path.resolve(__dirname, "../src/components"),
"#views": path.resolve(__dirname, "../src/views"),
"#static": path.resolve(__dirname, "../src/static")
}
},
optimization: {
minimizer: [
new terserPlugin({
cache: true,
parallel: true,
include: /\.(js|jsx)$/
}),
new OptimizeCSSAssetsPlugin({})
]
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
},
{
test: /\.css$/,
use: [
miniCSSExtractPlugin.loader,
{
loader: "css-loader"
}
]
},
{
test: /\.(png|jpg|gif|ico)$/,
use: [
{
loader: "file-loader?name=images/[hash].[ext]",
options: {
name: "assets/images/[hash].[ext]"
}
}
]
},
{
test: /\.(ttf|woff2?|eo(f|t)|svg)$/,
use: [
{
loader: "file-loader",
options: {
name: "assets/fonts/[hash].[ext]"
}
}
]
},
{
test: /\.(json)$/,
type: "javascript/auto",
use: [
{
loader: "file-loader",
options: {
name: "[folder]/[name].[ext]",
outputPath: "assets/locales/"
}
}
]
}
]
},
plugins: [
new htmlWebpackPlugin({
template: path.join(__dirname, "..", "public", "index.html")
}),
new miniCSSExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new cleanWebpackPlugin("../dist/*", {
root: __dirname + "../",
allowExternal: true
})
]
};
module.exports = config;
i18n/index.js
const i18n = require("i18next");
const initReactI18next = require("react-i18next").initReactI18next;
const langDetector = require("i18next-browser-languagedetector");
const backend = require("i18next-xhr-backend");
const moment = require("moment");
const resources = require("../../static/locales");
/*
Other codes...
*/
i18n.use(langDetector).use(initReactI18next);
var options;
switch (process.env.NODE_ENV) {
case "test":
options = {
whitelist: ["en", "pt"],
fallbackLng: "en",
resources,
ns: "translation",
defaultNS: "translation",
interpolation: {
format: function(value, format, lng) {
if (value instanceof Date) return moment(value).format(format);
return value.toString();
}
}
};
break;
case "production":
i18n.use(backend);
options = {
whitelist: ["en-US", "pt-BR"],
fallbackLng: {
pt: ["pt-BR"],
en: ["en-US"],
default: ["en"]
},
ns: ["button", "common", "lng", "info"],
defaultNS: "common",
backend: {
loadPath: "./assets/locales/{{lng}}/{{ns}}.json"
},
detection: {
order: ["querystring", "cookie", "navigator", "localStorage"]
},
lookupQuerystring: "lng",
caches: ["localStorage", "cookie"],
react: {
wait: true
},
interpolation: {
format: function(value, format, lng) {
if (format === "uppercase") return value.toUpperCase();
if (value instanceof Date) return moment(value).format(format);
return value;
}
}
};
break;
case "development":
i18n.use(backend);
options = {
whitelist: ["en-US", "pt-BR"],
fallbackLng: {
pt: ["pt-BR"],
en: ["en-US"],
default: ["en"]
},
ns: ["button", "common", "lng", "info"],
defaultNS: "common",
backend: {
loadPath: "./src/static/locales/{{lng}}/{{ns}}.json"
},
detection: {
order: ["querystring", "cookie", "navigator", "localStorage"]
},
lookupQuerystring: "lng",
caches: ["localStorage", "cookie"],
react: {
wait: true
},
interpolation: {
format: function(value, format, lng) {
if (format === "uppercase") return value.toUpperCase();
if (value instanceof Date) return moment(value).format(format);
return value;
}
}
};
break;
}
i18n.init(options);
module.exports = i18n;

deploying officejs fabric react word add in

I have a word add in written in typescript using Officejs and office-ui-fabric-react. Everything works fine with the server running locally. I'd like to deploy into AWS S3 with Cloudfront. I'm building with npm run build, and npm run deploy (using deploy command "aws --profile profile-name s3 sync dist/ s3://bucketname". Static web site hosting is enabled in the S3 bucket. All files from the dist directory are seen in the bucket. After inserting the add in into Word with a manifest.xml file that points to the cloudfront endpoint I'm getting the error "Uncaught ReferenceError: React is not defined". The same error occurs when I point directly to the S3 static web endpoint. To see if I've missed anything I deployed a generic create-react-app using the steps above and it runs fine. I'm assuming that the problem lies with my webpack config so I've included that here (common, and prod). I'd be happy to include anything else that's needed. I'm also open to other deployment options if using AWS is causing the problem.
webpack.common.js:
const webpack = require('webpack');
const path = require('path');
const package = require('../package.json');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const autoprefixer = require('autoprefixer');
const build = (() => {
const timestamp = new Date().getTime();
return {
name: package.name,
version: package.version,
timestamp: timestamp,
author: package.author
};
})();
const entry = {
vendor: [
'react',
'react-dom',
'core-js',
'office-ui-fabric-react'
],
app: [
'react-hot-loader/patch',
'./index.tsx',
],
'function-file': '../function-file/function-file.ts'
};
const rules = [
{
test: /\.tsx?$/,
use: [
'react-hot-loader/webpack',
'ts-loader'
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: {
loader: 'file-loader',
query: {
name: 'assets/[name].[ext]'
}
}
}
];
const output = {
path: path.resolve('dist'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
};
const WEBPACK_PLUGINS = [
new webpack.NamedModulesPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.BannerPlugin({ banner: `${build.name} v.${build.version} (${build.timestamp}) © ${build.author}` }),
new webpack.DefinePlugin({
ENVIRONMENT: JSON.stringify({
build: build
})
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
autoprefixer({ browsers: ['Safari >= 8', 'last 2 versions'] }),
],
htmlLoader: {
minimize: true
}
}
})
];
module.exports = {
context: path.resolve('./src'),
entry,
output,
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss', '.css', '.html']
},
module: {
rules,
},
optimization: {
splitChunks: {
chunks: 'async',
minChunks: Infinity,
name: 'vendor'
}
},
plugins: [
...WEBPACK_PLUGINS,
new ExtractTextPlugin('[name].[hash].css'),
new HtmlWebpackPlugin({
title: 'letterConfig',
filename: 'index.html',
template: './index.html',
chunks: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
title: 'letterConfig',
filename: 'function-file/function-file.html',
template: '../function-file/function-file.html',
chunks: ['function-file']
}),
new CopyWebpackPlugin([
{
from: '../assets',
ignore: ['*.scss'],
to: 'assets',
}
])
]
};
webpack.prod.js:
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const ENV = process.env.NODE_ENV = process.env.ENV = 'development';
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
performance: {
hints: "warning"
},
optimization: {
minimize: true
}
});
index.tsx:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
import App from './components/App';
import './styles.less';
import 'office-ui-fabric-react/dist/css/fabric.min.css';
initializeIcons();
let isOfficeInitialized = false;
const title = 'letterConfig';
const render = (Component) => {
ReactDOM.render(
<AppContainer>
<Component title={title} isOfficeInitialized={isOfficeInitialized} />
</AppContainer>,
document.getElementById('container')
);
};
/* Render application after Office initializes */
Office.initialize = () => {
console.log('init');
isOfficeInitialized = true;
render(App);
};
/* Initial render showing a progress bar */
render(App);
if ((module as any).hot) {
(module as any).hot.accept('./components/App', () => {
const NextApp = require('./components/App').default;
render(NextApp);
});
}
It turned out that it was looking for a globally defined "React", and so not resolving via an npm module. In the webpack.prod.js file removing the following solved the problem:
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},

How do you configure Webpack to clear the React Warning for production minification?

I am sure everyone has seen this error but here it is again:
Warning: It looks like you're using a minified copy of the development build of React. When deploying React apps to production, make sure to use the production build which skips development warnings and is faster. See https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build for more details.
So of course I followed the instructions in the link provided, yet though I have made all the necessary updates to my code, I am still getting this error.
According to some other answers I have seen on StackOverflow and Github, the process.env.NODE_ENV being set to production through the Webpack plugin DefinePlugin tells React to build using the minified version. So I logged process.env.NODE_ENV in my main application component and it is in fact being set to production by the plugin and still I am getting the warning.
So even though the environment variable is being set to production, I am getting the warning and my React Dev Tools says:
This page is using the development build of React. 🚧
Note that the development build is not suitable for production.
Make sure to use the production build before deployment.
I cannot seem to isolate where the problem might be here since I have done all the necessary changes to get the production build to work.
Here is my webpack.config.js file:
const webpack = require('webpack');
const resolve = require('path').resolve;
const SRC_BASE = resolve(__dirname, 'src');
const merge = require('webpack-merge');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const argv = require('yargs').argv;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const definePlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
__PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false')),
__PRODUCTION__: JSON.stringify(JSON.parse(process.env.NODE_ENV === 'production' || 'false')),
'process.env': {
NODE_ENV: process.env.NODE_ENV === 'production' ? // set NODE_ENV to production or development
JSON.stringify('production') : JSON.stringify('development'),
},
});
const loaderOptionsPlugin = new webpack.LoaderOptionsPlugin({ options: { context: __dirname } });
const commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
const cssOutput = new ExtractTextPlugin({ filename: 'style.css', allChunks: true });
const sourceMapPlugin = new webpack.SourceMapDevToolPlugin({ filename: '[name].map' });
const htmlPlugin = new HtmlWebpackPlugin({
template: `${SRC_BASE}/index.template.ejs`,
filename: '../index.html', // relative to public/build/ so this is public/index.html
inject: true,
hash: true,
});
let config = {
cache: true,
entry: {
main: ['babel-polyfill', resolve(SRC_BASE, 'index')],
},
output: {
path: resolve(__dirname, 'public/build'),
filename: '[name].bundle.js',
publicPath: '/build/',
sourceMapFilename: '[name].map',
},
resolve: {
modules: [
SRC_BASE,
'node_modules',
],
extensions: ['.js', '.jsx'],
},
module: {
rules: [
{
test: [/\.jsx$/, /\.js$/],
loader: 'babel-loader',
exclude: /(local_modules|node_modules|bower_components)/,
query: {
presets: [
'react',
'es2015',
'stage-1',
],
},
},
{
test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/,
loader: 'file-loader',
},
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader!sass-loader',
}),
},
],
},
node: {
fs: 'empty',
},
plugins: [
definePlugin,
commonsPlugin,
cssOutput,
htmlPlugin,
loaderOptionsPlugin,
sourceMapPlugin,
],
};
// Only load dashboard if we're watching the code
if (argv.watch) {
const DashboardPlugin = require('webpack-dashboard/plugin');
config = merge(config, { plugins: [new DashboardPlugin()] });
}
if (process.env.NODE_ENV === 'production') {
console.log('******* I AM MERGING PRODUCTION CONFIGS ******');
console.log(`process.env.NODE_ENV = ${process.env.NODE_ENV}`);
config = merge(config, {
devtool: 'cheap-module-source-map',
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false,
}),
new webpack.optimize.UglifyJsPlugin(),
],
module: {
rules: [
{ test: /redux-logger/, loader: 'null-loader' },
],
},
});
}
module.exports = config;
And here is my gulpfile.js tasks that runs gulp build --production:
/* Production Builds use this task */
gulp.task('webpack', (done) => {
if (argv.production) {
process.env.BUILD_DEV = false;
process.env.NODE_ENV = 'production';
}
const buildConfig = require('./webpack.config');
const compiler = webpack(buildConfig);
const tag = '[webpack]';
const info = gutil.colors.green;
const error = gutil.colors.red;
const warning = gutil.colors.yellow;
const filterStackTraces = err =>
err.toString().split(/[\r\n]+/).filter(line => ! line.match(/^\s+at Parser/)).join(EOL);
if (argv.watch) {
compiler.watch({}, (err, stats) => {
const statDetails = stats.toJson();
if (err) {
gutil.log(error(tag), err.toString({ colors: true }));
}
else if (stats.hasErrors()) {
statDetails.errors.forEach(ex => gutil.log(error(tag), filterStackTraces(ex)));
}
else if (stats.hasWarnings()) {
statDetails.warnings.forEach(wx => gutil.log(warning(tag), filterStackTraces(wx)));
}
else {
statDetails.chunks.forEach(chunk => {
if (chunk.entry) gutil.log(info(tag), `Built ${chunk.files[0]} (${chunk.size} bytes)`);
});
gutil.log(info(tag), 'Build complete');
}
});
}
else {
compiler.run((err, stats) => {
if (err) {
return done(new gutil.PluginError('webpack', err));
}
if (stats.hasErrors()) {
const statDetails = stats.toJson();
statDetails.errors.forEach(ex => gutil.log(error(tag), filterStackTraces(ex)));
return done(new gutil.PluginError('webpack', 'Parse/ build error(s)'));
}
gutil.log(gutil.colors.green(tag), stats.toString({ colors: true }));
done();
});
}
});
gulp.task('build', ['webpack']);
After stumbling around the interweb for some different ways to configure webpack to overcome this problem I found a configuration option in the UglifyJsPlugin that cleared the error.
plugins: [
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/, <------This option fixed it
})
]
All though this cleared the warning in the console, I am still seeing my React Dev Tools saying that it is not using the production version.
I've had the same problem, but it doesn't seem to be a problem with webpack- it's likely something to do with Gulp. Have you tried running webpack with the config directly from the command line (i.e. webpack --config webpack-build.config.js or whatever) ? That produced a build without the warning quite happily. Here's my webpack config, cobbled together from various sources:
const path = require('path')
const webpack = require('webpack')
module.exports = {
devtool: 'cheap-module-source-map',
entry: './src/scripts/app',
output: {
path: path.join(__dirname, 'dist/scripts'),
filename: 'bundle.js',
publicPath: '/static/'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['env', 'react']
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true
},
comments: false
})
]
}
If I track down what's going on with gulp + webpack I'll write an update.

Resources