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'
},
Related
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 !
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;
}
}
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.
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.
I am build an isomorphic app using react, react-router, express and webpack. Now I want to use css modules to import css.
I use import './index.css' in index.jsx, it works fine on client, but doesn't work on server rendering. The error is Error: Cannot find module './index.css'.
components/index.jsx
import React, {Component, PropTypes} from 'react';
import style from './index.css';
class App extends Component {
constructor(props, context) {
super(props, context);
}
render() {
return (
<div id="login">
// ...
</div>
);
}
};
export default App;
server/router/index.js
import url from 'url';
import express from 'express';
import swig from 'swig';
import React from 'react';
import {renderToString} from 'react-dom/server';
import {match, RouterContext} from 'react-router';
import routes from '../../client/routes/routes';
import DataWrapper from '../../client/container/DataWrapper';
import data from '../module/data';
const router = express.Router();
router.get('*', async(req, res) => {
match({
routes,
location: req.url
}, async(error, redirectLocation, props) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.status(302).redirect(redirectLocation.pathname + redirectLocation.search);
} else if (props) {
let content = renderToString(
<DataWrapper data={data}><RouterContext {...props}/></DataWrapper>
);
let html = swig.renderFile('views/index.html', {
content,
env: process.env.NODE_ENV
});
res.status(200).send(html);
} else {
res.status(404).send('Not found');
}
});
});
export default router;
webpack.config.dev.js(for webpack-dev-server)
var webpack = require('webpack');
var config = require('./config');
module.exports = {
devtool: 'inline-source-map',
entry: [
'webpack-dev-server/client?http://localhost:' + config.webpackPort,
'webpack/hot/only-dev-server',
'./src/client/entry',
],
output: {
path: __dirname + '/public/js',
filename: 'app.js',
publicPath: 'http://localhost:' + config.webpackPort + '/public/js',
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify('development')
}
})
],
resolve: {
extensions: ['', '.js', '.jsx', '.css']
},
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'react-hot',
exclude: /node_modules/
}, {
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/
}, {
test: /\.css$/,
loader: 'style-loader!css-loader?modules',
exclude: /node_modules/
}, {
test: /\.(png|woff|woff2|svg|ttf|eot)$/,
loader: 'url-loader',
exclude: /node_modules/
}]
}
}
I'd recommend using webpack to compile UI code for both client and server side in that case. Just set target: "node" in webpack config to produce bundle which can executed in Node environment.
That article might help for compiling your server side code with Webpack: http://jlongster.com/Backend-Apps-with-Webpack--Part-I
Especially on how to exclude node_modules with the externals key.
A very bare config might look like:
'use strict';
const path = require('path');
const fs = require('fs');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const rootDir = path.resolve(__dirname, '..');
const distDir = path.join(rootDir, 'dist');
const srcDir = path.join(rootDir, 'src');
const localStyles = new ExtractTextPlugin('local.css', { allChunks: true });
const nodeModules = fs.readdirSync('node_modules')
.filter(dir => !dir.startsWith('.'))
.reduce((acc, prop) => {
acc[prop] = 'commonjs ' + prop;
return acc;
}, {});
const loaders = [
{
test: /\.(js|jsx)$/,
include: srcDir,
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true,
},
},
{
test: /\.css$/,
include: srcDir,
loader: localStyles.extract(
'style',
'css?modules&localIdentName=[name]-[local]_[hash:base64:5]'
),
},
{
test: /\.json$/,
loader: 'json',
},
];
module.exports = {
target: 'node',
entry: {
server: ['server/index'],
},
output: {
path: distDir,
filename: '[name].bundle.js',
},
externals: nodeModules,
module: {
loaders,
},
plugins: [
localStyles,
],
};
Another solution (Webpack free) could be to use babel-plugin-css-modules-transform
.