2.0.0-rc6
Updated and also not working with 2.5.2
I am trying to use the React Router Async API to achieve some code splitting.
I have Currently a main route file and a subroute:
// routes/index.js
export const createRoutes = (store) => ({
path: '/',
component: AppView,
indexRoute: {
onEnter: (nextState, transition) => {
transition('/login')
},
},
childRoutes: [
LoginRoute(store),
]
})
And then my Login Route looks like this:
//routes/Login/index.js
export default (store) => ({
path: 'login',
/* Async getComponent is only invoked when route matches */
getComponent (nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure([], (require) => {
/* Webpack - use require callback to define
dependencies for bundling */
const requireUnauthentication = require('containers/UnauthenticatedComponent').default;
const LoginModalContainer = require('components/Login/LoginModalContainer').default;
/* Return getComponent */
cb(null, requireUnauthentication(LoginModalContainer))
/* Webpack named bundle */
}, 'login')
},
indexRoute: {
getComponent(nextState, cb){
require.ensure([], (require) => {
const LoginStart = require('components/Login/LoginStart').default;
cb(null, LoginStart);
}, 'login')
}
},
getChildRoutes: (location, cb) => {
require.ensure([], (require) => {
const routes = [
{ path: 'login-start', component: require('components/Login/LoginStart').default },
{ path: 'login-prompt', component: require('containers/LoginContainer').default },
{ path: 'phone-number', component: require('components/Register/PhonenumberSet').default }
];
cb(null, routes);
}, 'login');
}
})
The problem is when directly navigating to /login I am getting errors:
DOMLazyTree.js:62 Uncaught TypeError: Cannot read property 'replaceChild' of null
ReactDOMComponentTree.js:105 Uncaught TypeError: Cannot read property '__reactInternalInstance$nva6m7qemb9ackp2ti2ro1or' of null
I think this is related to the asynchronous route configuration, since as soon as I change something and hot reloading kicks in, everything loads fine.
When debugging it, I see that in DOMLazyTree:62:
function replaceChildWithTree(oldNode, newTree) {
oldNode.parentNode.replaceChild(newTree.node, oldNode);
insertTreeChildren(newTree);
}
the oldNode is the empty comment within my mountComponent:
<div id="app"><!-- react-empty: 1 --></div>
I have played around with several setups but could not figure out how to go around this issue.
The setup for the asynchronous routes is taken from: https://github.com/davezuko/react-redux-starter-kit
You have something misconfigured with webpack.
I My webpack config is basically an modified version of the one from the react-redux-starter-kit.
@timdorr : Any idea which area is misconfigured?
import webpack from 'webpack'
import cssnano from 'cssnano'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import config from '../config'
import _debug from 'debug'
import path from 'path';
const debug = _debug('app:webpack:config')
const paths = config.utils_paths
const {__DEV__, __PROD__, __TEST__} = config.globals
debug('Create configuration.')
const webpackConfig = {
name: 'client',
target: 'web',
devtool: config.compiler_devtool,
resolve: {
root: paths.client(),
extensions: ['', '.js', '.jsx', '.json', '.scss'],
},
module: {}
}
// ------------------------------------
// Entry Points
// ------------------------------------
const APP_ENTRY_PATHS = [
// Somehow the polyfill is already loaded, leading to double loading
// 'babel-polyfill',
paths.client('main.js')
]
webpackConfig.entry = {
app: __DEV__
? APP_ENTRY_PATHS.concat(`webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`)
: APP_ENTRY_PATHS,
vendor: config.compiler_vendor
}
// ------------------------------------
// Bundle Output
// ------------------------------------
webpackConfig.output = {
filename: `[name].[${config.compiler_hash_type}].js`,
path: paths.dist(),
publicPath: config.compiler_public_path
}
// ------------------------------------
// Plugins
// ------------------------------------
webpackConfig.plugins = [
new webpack.DefinePlugin(config.globals),
new HtmlWebpackPlugin({
template: paths.client('index.html'),
hash: false,
favicon: paths.client('static/favicon.ico'),
filename: 'index.html',
inject: 'body',
minify: {
collapseWhitespace: true
}
})
]
if (__DEV__) {
debug('Enable plugins for live development (HMR, NoErrors).')
webpackConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
)
} else if (__PROD__) {
debug('Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).')
webpackConfig.plugins.push(
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
unused: true,
dead_code: true,
warnings: false
}
})
)
}
// Don't split bundles during testing, since we only want import one bundle
if (!__TEST__) {
webpackConfig.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor']
})
)
}
// ------------------------------------
// Loaders
// ------------------------------------
// JavaScript / JSON
webpackConfig.module.loaders = [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true,
plugins: ['transform-runtime'],
presets: ['es2015', 'react', 'stage-0'],
env: {
production: {
presets: ['react-optimize']
}
}
}
},
{
test: /\.json$/,
loader: 'json'
}]
// ------------------------------------
// Style Loaders
// ------------------------------------
// We use cssnano with the postcss loader, so we tell
// css-loader not to duplicate minimization.
const BASE_CSS_LOADER = 'css?sourceMap&-minimize'
// Add any packge names here whose styles need to be treated as CSS modules.
// These paths will be combined into a single regex.
const PATHS_TO_TREAT_AS_CSS_MODULES = [
'react-toolbox',
]
// If config has CSS modules enabled, treat this project's styles as CSS modules.
if (config.compiler_css_modules) {
PATHS_TO_TREAT_AS_CSS_MODULES.push(
paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&') // eslint-disable-line
)
}
const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join('|')})`)
// Loaders for styles that need to be treated as CSS modules.
if (isUsingCSSModules) {
const cssModulesLoader = [
BASE_CSS_LOADER,
'modules',
'importLoaders=1',
'localIdentName=[name]__[local]___[hash:base64:5]'
].join('&')
webpackConfig.module.loaders.push({
test: /\.scss$/,
include: cssModulesRegex,
loaders: [
'style',
cssModulesLoader,
'postcss',
'sass?sourceMap',
'toolbox'
]
})
webpackConfig.module.loaders.push({
test: /\.css$/,
include: cssModulesRegex,
loaders: [
'style',
cssModulesLoader,
'postcss'
]
})
}
// Loaders for files that should not be treated as CSS modules.
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
webpackConfig.module.loaders.push({
test: /\.scss$/,
exclude: excludeCSSModules,
loaders: [
'style',
BASE_CSS_LOADER,
'postcss',
'sass?sourceMap'
]
})
webpackConfig.module.loaders.push({
test: /\.css$/,
exclude: excludeCSSModules,
loaders: [
'style',
BASE_CSS_LOADER,
'postcss'
]
})
// ------------------------------------
// Style Configuration
// ------------------------------------
webpackConfig.sassLoader = {
includePaths: [
paths.client('styles'),
paths.base('node_modules')
]
}
webpackConfig.postcss = [
cssnano({
autoprefixer: {
add: true,
remove: true,
browsers: ['last 2 versions']
},
discardComments: {
removeAll: true
},
discardUnused: false,
mergeIdents: false,
reduceIdents: false,
safe: true,
sourcemap: true
})
]
webpackConfig.toolbox = {
theme : paths.client('style/themes/toolbox.scss')
}
// File loaders
/* eslint-disable */
webpackConfig.module.loaders.push(
{ test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
{ test: /\.otf(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' },
{ test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
{ test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
{ test: /\.(png|jpg|gif)$/, loader: 'url?limit=8192' }
)
/* eslint-enable */
webpackConfig.module.loaders.push({
test: /\.font\.(js|json)$/,
loaders: [
'style',
'css',
'fontgen'
]
})
// ------------------------------------
// Finalize Configuration
// ------------------------------------
// when we don't know the public path (we know it only when HMR is enabled [in development]) we
// need to use the extractTextPlugin to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
if (!__DEV__) {
debug('Apply ExtractTextPlugin to CSS loaders.')
webpackConfig.module.loaders.filter((loader) =>
loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0]))
).forEach((loader) => {
const [first, ...rest] = loader.loaders
loader.loader = ExtractTextPlugin.extract(first, rest.join('!'))
Reflect.deleteProperty(loader, 'loaders')
})
webpackConfig.plugins.push(
new ExtractTextPlugin('[name].[contenthash].css', {
allChunks: true
})
)
}
export default webpackConfig
Try validate your config using this tool:
https://github.com/js-dxtools/webpack-validator
I also have a boilerplate that does route splitting, but it uses new webpack v2 and it's a universal boilerplate. But maybe it's helpful somewhat.
https://github.com/ctrlplusb/react-universally
Thanks @ctrlplusb I will have a look at it.
Still curious how to fix my current webpack setup though.