Can't get Webpack 2 HMR React to work - reactjs

Currently I'm struggling to get HMR working in my Webpack 2 setup. I'll explain my entire setup so I hope this is enough for someone to understand what's happening.
The structure of my project:
config
dev.js
prod.js
dist
css
js
index.html
node_modules
src
components
// some JavaScript components
shared
stylesheets
index.js
.babelrc
package.json
webpack.config.js
This are the contents of my webpack.config.js file, placed in the root of my project:
function buildConfig(env) {
return require('./config/' + env + '.js')(env)
}
module.exports = buildConfig;
So in this file I've the option to pass different environments to the buildConfig function. I use this options to use different config files for development and production. This are the contents in my package.json file:
{
"main": "index.js",
"scripts": {
"build:dev": "node_modules/.bin/webpack-dev-server --env=dev",
"build:prod": "node_modules/.bin/webpack -p --env=prod"
},
},
"devDependencies": {
"autoprefixer-loader": "^3.2.0",
"babel-cli": "^6.18.0",
"babel-core": "^6.24.1",
"babel-loader": "^6.2.5",
"babel-preset-latest": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"css-loader": "^0.25.0",
"extract-text-webpack-plugin": "^2.1.0",
"json-loader": "^0.5.4",
"node-sass": "^3.13.1",
"postcss-loader": "^1.3.3",
"postcss-scss": "^0.4.1",
"sass-loader": "^4.1.1",
"style-loader": "^0.13.1",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.2"
},
"dependencies": {
"babel-plugin-react-css-modules": "^2.6.0",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-hot-loader": "^3.0.0-beta.6",
"react-icons": "^2.2.1"
}
}
I've of course more fields in my package.json but I won't shown them here since they're irrelevant.
So during development I run the npm run build:dev command in my terminal. This will use the file dev.js from the config folder. This are the contents of the dev.js file:
const webpack = require('webpack');
const { resolve } = require('path');
const context = resolve(__dirname, './../src');
module.exports = function(env) {
return {
context,
entry: {
app: [
'react-hot-loader/patch',
// activate HMR for React
'webpack-dev-server/client?http://localhost:3000',
// bundle the client for webpack-dev-server
// and connect to the provided endpoint
'webpack/hot/only-dev-server',
// bundle the client for hot reloading
// only- means to only hot reload for successful updates
'./index.js'
// the entry point of our app
]
},
output: {
path: resolve(__dirname, './../dist'), // `dist` is the destination
filename: '[name].js',
publicPath: '/js'
},
devServer: {
hot: true, // enable HMR on the server
inline: true,
contentBase: resolve(__dirname, './../dist'), // `__dirname` is root of the project
publicPath: '/js',
port: 3000
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.js$/, // Check for all js files
exclude: /node_modules/,
use: [{
loader: 'babel-loader',
query: {
presets: ['latest', 'react'],
plugins: [
[
"react-css-modules",
{
context: __dirname + '/../src', // `__dirname` is root of project and `src` is source
"generateScopedName": "[name]__[local]___[hash:base64]",
"filetypes": {
".scss": "postcss-scss"
}
}
]
]
}
}]
},
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
modules: true,
importLoaders: 2,
localIdentName: '[name]__[local]___[hash:base64]'
}
},
'sass-loader',
{
loader: 'postcss-loader',
options: {
plugins: () => {
return [
require('autoprefixer')
];
}
}
}
]
}
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// enable HMR globally
new webpack.NamedModulesPlugin()
// prints more readable module names in the browser console on HMR updates
]
}
};
And last but not least, my HMR setup. I've this setup in my index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import TodoApp from './components/TodoApp';
import './stylesheets/Stylesheets.scss';
const render = (Component) => {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.querySelector('#main')
);
};
render(TodoApp);
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./components/TodoApp', () => {
render(TodoApp)
});
}
So, when I run my npm start build:dev in my browser and go to http://localhost:3000 I see my site working as expected. This is the output in the console:
dev-server.js:49 [HMR] Waiting for update signal from WDS...
only-dev-server.js:66 [HMR] Waiting for update signal from WDS...
TodoApp.js:102 test
client?344c:41 [WDS] Hot Module Replacement enabled.
The test text comes from the render function in my TodoApp component. This function looks like this:
render() {
console.log('test');
return(
<div styleName="TodoApp">
<TodoForm addTodo={this.addTodo} />
<TodoList todos={this.state.todos} deleteTodo={this.deleteTodo} toggleDone={this.toggleDone} updateTodo={this.updateTodo} />
</div>
);
}
So, now the important stuff. I update the return of this render function, which should trigger the HMR to kick in. I change the render function to this.
render() {
console.log('test');
return(
<div styleName="TodoApp">
<p>Hi Stackoverflow</p>
<TodoForm addTodo={this.addTodo} />
<TodoList todos={this.state.todos} deleteTodo={this.deleteTodo} toggleDone={this.toggleDone} updateTodo={this.updateTodo} />
</div>
);
}
This is the output I get in the console:
client?344c:41 [WDS] App updated. Recompiling...
client?344c:41 [WDS] App hot update...
dev-server.js:45 [HMR] Checking for updates on the server...
TodoApp.js:102 test
log-apply-result.js:20 [HMR] Updated modules:
log-apply-result.js:22 [HMR] - ./components/TodoApp.js
dev-server.js:27 [HMR] App is up to date.
You would say this is good. But my site doesn't update ANYTHING.
Then I change the the HMR code in my index.js to this:
// Hot Module Replacement API
if (module.hot) {
module.hot.accept();
}
And it works. I just don't get it. Why doesn't it work if this is my HMR code:
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./components/TodoApp', () => {
render(TodoApp)
});
}
BTW this setup is based on the setup from https://webpack.js.org/guides/hmr-react/
I hope that anyone can help me. If someone needs more information don't hesitate to ask. Thanks in advance!
UPDATE
Forgot to post my .babelrc file. This is it:
{
"presets": [
["es2015", {"modules": false}],
// webpack understands the native import syntax, and uses it for tree shaking
"react"
// Transpile React components to JavaScript
],
"plugins": [
"react-hot-loader/babel"
// EnablesReact code to work with HMR.
]
}

The imports are static and after an update has been identified in module.hot.accept you render the exact same component again, as the TodoApp still holds the old version of your module and HMR realises that and doesn't refresh or change anything in your app.
You want to use Dynamic import: import(). To make it work with babel you need to add babel-plugin-syntax-dynamic-import, otherwise it will report a syntax error as it didn't expect import to be used as a function. The react-hot-loader/babel is not needed if you use react-hot-loader/patch in your webpack config, so your plugins in your .babelrc become:
"plugins": [
"syntax-dynamic-import"
]
In your render() function you can now import the TodoApp and render it.
const render = () => {
import('./components/TodoApp').then(({ default: Component }) => {
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.querySelector('#main')
);
});
};
render();
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./components/TodoApp', render);
}
import() is a promise that will resolve with the module, and you want to use the default export.
Even though the above is true, the webpack documentation doesn't require you to use dynamic imports, because webpack handles ES modules out of the box, also described in react-hot-loader docs - Webpack 2, and because webpack is also handling the HMR, it will know what to do in that case. For this to work, you must not transform the modules to commonjs. You did this with ["es2015", {"modules": false}], but you also have the latest preset configured in your webpack config, which also transforms the modules. To avoid confusion, you should have all babel configurations in .babelrc instead of splitting some to the loader options.
Remove the presets in the babel-loader entirely from your webpack config and it will work as you already have the necessary presets in your .babelrc. babel-preset-latest is deprecated and if you want to use these features you should start using babel-preset-env which also replaces es2015. So your presets in .babelrc would be:
"presets": [
["env", {"modules": false}],
"react"
],

Check this issue on GitHub or just use this in your index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './components/App'
const render = Component => {
ReactDOM.render(
<AppContainer>
<Component/>
</AppContainer>,
document.getElementById('react-root')
)
}
render(App)
if(module.hot) {
module.hot.accept();
}

Related

React package, invalid hook call

I'm trying to write a NPM package with some React stuff included, at the moment it's just a component and a hook. To build the package I'm using Webpack. I've added react and react-dom to the externals section to ensure that it's not included in the bundle. I've also marked react as a peerDependency in the package.json and included it as a devDependency. Still I'm getting the error Invalid hook call when trying to use the bundle in another project. I think I've tried everything that I can Google my way to (like using the package with the purpose to solve this) with no luck.
My Webpack config looks like this at the moment:
const path = require('path');
const isProduction = process.env.NODE_ENV?.toLowerCase() === 'production';
const config = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
libraryTarget: 'umd',
},
plugins: [],
module: {
rules: [
{
test: /\.(tsx?)$/i,
loader: 'ts-loader',
exclude: ['/node_modules/'],
},
{
test: /\.(graphql|gql)$/,
exclude: /node_modules/,
loader: 'graphql-tag/loader',
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.graphql', '.json'],
},
externals: {
react: 'react',
'react-dom': 'react-dom',
},
};
module.exports = () => {
if (isProduction) {
config.mode = 'production';
} else {
config.mode = 'development';
}
return config;
};
The essentials from package.json looks like this:
{
...
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
...
"scripts": {
...
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
...
"prepublishOnly": "npm run build"
},
...
"peerDependencies": {
"react": "16.8.0 || ^17.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
},
"dependencies": {
"#apollo/client": "^3.3.19",
"apollo-server-errors": "^2.5.0",
"joi": "^17.4.0",
"lodash": "^4.17.21"
},
"devDependencies": {
...
"react": "^17.0.2",
"react-dom": "^17.0.2",
...
}
}
The hook is very simple, it's just trying to a context created by the component using useContext, to be sure that there isn't an issue with this logic I've tried to just use setState with the same result. The hook looks somewhat like this:
function useClient(): Client {
return useContext(getContext());
}
getContext is just a function which either creates or re-uses a existing React.Context (heavily inspired by Apollo Client):
const cache = new (canUseWeakMap ? WeakMap : Map)<
typeof createContext,
Context<Client | undefined>
>();
function getContext() {
let context = cache.get(createContext);
if (!context) {
context = createContext<Client | undefined>(undefined);
context.displayName = 'ClientContext';
cache.set(createContext, context);
}
return context;
}
export default getContext;
The component where I'm trying to use the hook is a simple functional component:
const HelloWorld: FC<HelloWorldProps> = () => {
const client = useClient();
return (
<div>Hello World!</div>
);
};
What am I missing? Really appreciate the help!
EDIT:
I reproduced the issue in a small sample app with just the basics, a external package using setState and then using that package in a create-react-app with the same result:
https://github.com/ganhammar/invalid-hook-call
Thanks for all the help!
The issue was that I stopped publishing packages and instead installed the dependency locally using file:../Client. That caused duplicate instances of React since it used the local-to-the-Client-package instance of React. Publishing only the built output and then installing that dependency solved the issue for me.
I found the following answer helpful for me to realize this (linking the react dependency between the two packages) if anyone else stumbles upon this.

ReactJS: Importing symlinked components error: Module parse failed: Unexpected token: You may need an appropriate loader to handle this file type

I'm writing a React component library which I want to use in other projects without much overhead ( bit, create-react-library, generact, etc. ) and without publishing. I want to use npm install ../shared_lib to add it to my project as a symlink in /node_modules. This command adds the symlink to project node_modules. In my shared_lib I just have a test to export default a <div></div>:
import React from 'react';
const TryTest = function() {
return (
<div>
TryTest
</div>
)
}
export default TryTest;
The problem I'm facing is the following error when I import the component into my working project:
import TryTest from 'shared_lib';
Error:
ERROR in ../shared_lib/src/index.js 6:4
Module parse failed: Unexpected token (6:4)
You may need an appropriate loader to handle this file type.
| const TryTest = function() {
| return (
> <div>
| TryTest
| </div>
# ./src/App.js 27:0-33 28:12-19
# ./src/index.js
# multi babel-polyfill ./src/index.js
If I import anything from shared_lib other than a file with jsx - for example, a string or a function, etc. - it works fine.
EDIT: the application webpack has resolve object's symlinks prop set to false:
resolve: {
symlinks: false
},
EDIT: After applying the solution in the answer below (https://stackoverflow.com/a/60980492/3006493), I later changed symlinks prop back to true. I didn't need to set it to false for the solution to work and render shared_lib components.
My app's loader:
{
test: /\.jsx?$/,
include: [
path.join( __dirname, 'src'), // app/src
fs.realpathSync(__dirname + '/node_modules/shared_lib'), // app/node_modules/shared_lib/dist/shared_lib.js
],
exclude: /node_modules/,
use: [ 'babel-loader' ]
}
EDIT: When I applied the solution in the answer below, the loader now looks like this:
{
test: /\.jsx?$/,
include: [
path.join( __dirname, 'src'), // app/src
fs.realpathSync(__dirname + '/node_modules/shared_lib'), // app/node_modules/shared_lib/dist/shared_lib.js
],
exclude: /node_modules/,
use: [ {
loader: 'babel-loader',
options: require("./package.json").babel
}
]
}
App's current .babelrc settings (I also tried removing .babelrc and including the presets in package.json with same result):
{
"presets": [ "#babel/preset-react", "#babel/preset-env"]
}
**EDIT: After applying the solution in the answer below, I ended up putting babel presets back into package.json.
"babel": {
"presets": [
"#babel/preset-react",
"#babel/preset-env"
]
},
I researched for a while to find a solution to this and apparently webpack has issues bundling symlinked react components? I am not using create-react-app.
So, I tried to bundle the shared_lib before importing it into the project, just to see what would happen. Here's the final webpack config (I tried other configurations as well):
const pkg = require('./package.json');
const path = require('path');
const buildPath = path.join( __dirname, 'dist' );
const clientPath = path.join( __dirname, 'src');
const depsPath = path.join( __dirname, 'node_modules');
const libraryName = pkg.name;
module.exports = [
'cheap-module-source-map'
].map( devtool => ({
bail: true,
mode: 'development',
entry: {
lib : [ 'babel-polyfill', path.join( clientPath, 'index.js' ) ]
},
output: {
path: buildPath,
filename: 'shared_lib.js',
libraryTarget: 'umd',
publicPath: '/dist/',
library: libraryName,
umdNamedDefine: true
},
// to avoid bundling react
externals: {
'react': {
commonjs: 'react',
commonjs2: 'react',
amd: 'React',
root: 'React'
}
},
module: {
rules: [
{
test: /\.jsx?$/,
include: [
clientPath
],
exclude: /node_modules/,
use: [ 'babel-loader' ],
},
]
},
devtool,
optimization: {
splitChunks: {
chunks: 'all',
},
}
}));
And the package.json for the shared_lib
{
"name": "shared_lib",
"version": "1.0.0",
"description": "",
"main": "dist/shared_lib.js",
"scripts": {
"clean": "rm -rf dist/",
"build": "$(npm bin)/webpack --config ./webpack.config.js",
"prepublish": "npm run clean && npm run build"
},
"author": "",
"license": "ISC",
"peerDependencies": {
"react": "^16.8.6"
},
"devDependencies": {
"react": "^16.8.6",
"#babel/core": "^7.9.0",
"#babel/preset-env": "^7.9.0",
"#babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"babel-polyfill": "^6.26.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
},
"babel": {
"presets": [
"#babel/preset-react",
"#babel/preset-env"
]
}
}
The package is bundled without errors:
When I try to import the component in the same way:
import TryTest from 'shared_lib';
The console.log returns undefined.
The path to the library file in my app is fine, because if I erase everything in shared_lib/dist/shared_lib.js and just write export default 1 the console.log(TryTest) in my App.js will return 1.
I tried changing libraryTarget property in shared_lib/webpack.config to libraryTarget: 'commonjs'. The result of console.log(TryTest) becomes {shared_lib: undefined}.
Has anyone ever run into this?
I found what finally worked for me and rendered the symlinked shared_lib to the app.
This answer: https://github.com/webpack/webpack/issues/1643#issuecomment-552767686
Worked well rendering symlinked shared_lib components. I haven't discovered any drawbacks from using this solution, but it's the only one that worked so far.

React Material-ui SSR - Warning: Prop `d` did not match. Server: "M 0 0 h 24 v 24 H 0 Z" Client: "M0 0h24v24H0z"

I'm working on a React site with server-side rendering & Material-ui. Everything was working great including the mui JSS stuff.
Then I added an SVG icon from #material-ui/icons
Now, Edge & IE11 are complaining:
Warning: Prop d did not match. Server: "M 0 0 h 24 v 24 H 0 Z" Client: "M0 0h24v24H0z"
The warning indicates the server and client renderings don't match, but if I get the server rendering with curl it is correct and does NOT include the spaces shown in the IE/Edge console.
All other browsers are (of course) OK.
Has anyone else encountered SSR issues in MS browsers only?
This is as small as I can get an example. It's based on material-ui-master/examples/ssr with most stuff removed:
server.js:
import express from "express";
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
function renderFullPage(html) {
return `
<!doctype html>
<html>
<body>
<div id="root">${html}</div>
<script src="build/bundle.js"></script>
</body>
</html>
`;
}
function handleRender(req, res) {
// Render the component to a string.
const html = ReactDOMServer.renderToString(
<App />
);
res.send(renderFullPage(html));
}
const app = express();
app.use('/build', express.static('build'));
// This is fired every time the server-side receives a request.
app.use(handleRender);
const port = 3000;
app.listen(port);
client.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
class Main extends React.Component {
render() {
return <App />;
}
}
ReactDOM.hydrate(
<Main />
, document.querySelector('#root')
);
App.js:
import React from 'react';
import { Menu } from "#material-ui/icons";
export default class App extends React.Component {
render() {
return (
<Menu />
);
}
}
package.json:
{
"name": "ssr",
"version": "3.0.0",
"private": true,
"dependencies": {
"#babel/core": "latest",
"#babel/node": "latest",
"#babel/plugin-proposal-class-properties": "latest",
"#babel/preset-env": "^7.4.2",
"#babel/preset-react": "latest",
"#material-ui/core": "latest",
"#material-ui/icons": "^3.0.2",
"babel-loader": "next",
"express": "latest",
"fs": "0.0.1-security",
"net": "^1.0.2",
"nodemon": "latest",
"prop-types": "latest",
"react": "latest",
"react-dom": "latest",
"react-jss": "^8.1.0",
"webpack": "latest",
"webpack-cli": "latest"
},
"scripts": {
"start-server": "SET NODE_ENV=development& nodemon --inspect ./build/server.js",
"start": "webpack -w"
}
}
webpack.config.js:
const path = require('path');
const browserConfig = {
entry: './client.js',
node: {
fs: "empty"
},
mode: process.env.NODE_ENV || 'development',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
],
},
};
const serverConfig = {
entry: './server.js',
target: 'node',
node: {
fs: "empty"
},
mode: process.env.NODE_ENV || 'development',
output: {
path: path.resolve(__dirname, 'build'),
filename: 'server.js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
],
},
};
module.exports = [browserConfig, serverConfig]
.babel.rc:
{
"presets": ["#babel/preset-env", "#babel/preset-react"],
"plugins": ["#babel/plugin-proposal-class-properties"]
}
I had a similar issue. This isn't a problem with SSR, but with conditional rendering in your JSX where the condition differs on the server and the client.
In my case, I rendered something based on a condition read from localStorage, which was only defined on the client. On server, it returned undefined, so what was rendered on the client and the server did not match.
The solution in my case was to conditionally render based on whether the component rendered on the client.
I wrote the following custom hook:
import {useEffect, useState} from "react";
export const useLoaded = () => {
const [loaded, setLoaded] = useState(false);
useEffect(() => setLoaded(true), []);
return loaded;
};
I use it as such:
// in the functional component's body
const loaded = useLoaded();
// in the JSX
{localCondition && loaded &&
<MyComponent />
}
With class components you'd use the componentDidMount lifecycle method and setState instead.
I've had the same problem ...did not match Server: ... Client: ... using NextJS (React) with an Express.js server (node server.js).
The solution was to render the component only if process.browser is true.
{
process.browser ?
<MyComponent /> :
null
}
So this does appear to be a bug in React. I logged an issue to the Material-ui project which resulted in this open issue in React:
https://github.com/facebook/react/issues/15187
The warning seems to be benign and with Edge moving over to Chromium, I'm not as concerned as I once was.

After migrating to Webpack 2, gulp or webpack-stream causes webpack build to fail

After migrating to Webpack 2, using gulp to run webpack with webpack-stream seems to cause babel or webpack to throw an error (can't tell where error originates from). Running webpack with the config and structure below builds successfully but piping it through gulp and webpack-stream causes this error:
Message:
./app/app.jsx
Module parse failed: /Users/schne482/Code/tralgo/app/app.jsx Unexpected token (11:1)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (11:1)
at Parser.pp$4.raise (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:2221:15)
at Parser.pp.unexpected (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:603:10)
at Parser.pp$3.parseExprAtom (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1822:12)
at Parser.pp$3.parseExprSubscripts (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1715:21)
at Parser.pp$3.parseMaybeUnary (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1692:19)
at Parser.pp$3.parseExprOps (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1637:21)
at Parser.pp$3.parseMaybeConditional (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1620:21)
at Parser.pp$3.parseMaybeAssign (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1597:21)
at Parser.pp$3.parseParenAndDistinguishExpression (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1861:32)
at Parser.pp$3.parseExprAtom (/Users/schne482/Code/tralgo/node_modules/webpack-stream/node_modules/acorn/dist/acorn.js:1796:19)
Details:
domain: [object Object]
domainThrown: true
What I have done:
Changed the structure of webpack.config.js to obey Webpack 2.
Moved babel config to separate .babelrc. No visible effects.
Ensured .babelrc has correct structure.
Ensured Webpack points to correct entry file (./app/app.jsx) and entry file is processed by loaders.
Ran babel (babel ./app/app.jsx) without webpack using same .babelrc and dependencies. Output was correct, no errors.
Ran webpack without gulp, output was correct.
Updated relevant packages (babel-core, babel-loader, babel presets, webpack-stream, gulp, etc..).
Checked for any simple syntax errors.
Here are the relevant files:
gulpfile.js:
const gulp = require('gulp');
const webpack = require('webpack');
const webpackStream = require('webpack-stream');
const webpack_config = require('./webpack.config');
function webpack_build_dev() {
webpack_config.devtool = 'eval-source-map';
return gulp.src('app/app.jsx')
.pipe(webpackStream(webpack_config), webpack)
.pipe(gulp.dest('./'));
}
gulp.task('webpack:build:dev', gulp.series('clean', webpack_build_dev));
./app/app.jsx:
import React from 'react';
import { render } from 'react-dom';
const app = (
<h1>Hello</h1>
);
render(
app,
document.getElementById('app')
);
./webpack.config.js:
const webpack = require('webpack');
const path = require('path');
module.exports = {
context: path.resolve(__dirname, './app'),
entry: './app.jsx',
output: {
path: path.resolve(__dirname, './public'),
filename: 'bundle.js',
},
resolve: {
modules: [
path.resolve(__dirname, './app'),
"node_modules",
],
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /(node_modules)/,
use: 'babel-loader',
},
],
},
};
./.babelrc:
{
"presets": [
"es2015",
"react"
]
}
./package.json (relevant devDependencies):
{
"devDependencies": {
"babel-core": "^6.22.1",
"babel-loader": "^6.3.2",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"webpack": "^2.2.1",
}
}
Not at a PC at the moment so can't test this, but can you try:
Change import to:
import ReactDOM from 'react-dom'
Then change render to:
ReactDOM.render (
...
)
Have you tried to add
"plugins": ["transform-object-rest-spread", "babel-plugin-transform-react-jsx"]
to your .babelrc file?

Webpack won't parse JSX no matter what I try

Been at it a couple days...
Trying to use Webpack to do lazy-loading of React modules, app-shell rendered first on the server, then Router injected on the client. It was compiling fine when using gulp, but with Webpack, I get the infamous:
ERROR in ../server/components/routes/AppRouter.jsx
Module parse failed: /web/sites/react-lazy/server/components/routes/AppRouter.jsx Unexpected token (11:6)
You may need an appropriate loader to handle this file type.
AppRouter.jsx:
import React from 'react';
import { Router, Route, IndexRoute } from 'react-router';
import routes from './AppRoute.jsx';
import createMemoryHistory from 'history/lib/createMemoryHistory';
if (typeof require.ensure !== 'function') require.ensure = (d, c) => c(require)
export default class AppRouter extends React.Component {
render() {
return (
<Router routes={routes} history={createMemoryHistory()} /> // < -- errors here
);
}
}
My webpack.config.js:
var path = require('path')
module.exports = {
context: __dirname + "/src",
entry: './js/app.es6.js',
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
include: path.resolve(__dirname, 'src/js'),
exclude: /(node_modules)/,
query: {
presets: ["es2015", "stage-0", "react"],
}
}
]
},
output: {
path: __dirname + '/build',
publicPath: '/build',
filename: 'app.js',
chunkFilename: '[id].chunk.js',
}
}
My directory structure:
/
webpack.config.js
.babelrc
/src
/js
app.es6.js
.babelrc // <-- also put a .babelrc here in case
My package.json:
"devDependencies": {
"babel": "^6.5.2",
"babel-core": "^6.14.0",
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.14.0",
"webpack": "^1.13.2",
...
}
I've tried:
duplicating the .babelrc file into the main directory and entry point directory, not sure which is necessary.
registering
babel-register: in app.es6.js:
require('babel-register')({
presets: [ 'es2015', 'stage-0', 'react' ]
});
runnning webpack with '--config webpack.config.js' argument
If I downgrade the babel, babel-core, and babel-loader version to 5.* (from 6.*), there is a different error:
ERROR in ./js/app.es6.js
Module build failed: ReferenceError: [BABEL] /web/sites/react-lazy/src/js/app.es6.js: Unknown option: direct.presets
I'm fairly new to React and webpack, I should say. Anyone know any other tricks?
Solution:
webpack config's 'include' property must include all directories you need to pass through loaders
Your entry file uses components that are located here:
/web/sites/react-lazy/server/components/
However, your include configuration only allows files below /web/sites/react-lazy/src/js/ to be passed to the loader (include means: "any files that should be passed to this loader must be located in this directory").
So either remove the include configuration, or make it an array that also includes the other locations where .jsx files can be found.

Resources