I'm trying to make react-loadable work with ServerSide Rendering. I've got a quite big app using multiple HOCs which connect components, add routers etc.
Components seem to go okay, instead the smart components connected via react-redux or other HOCs.
react-loadable doesn't seem to so this as seperate modules and in the .json that is created undefined is thrown.
Example of dumb components loaded correctly:
"common/HeaderTitle": [
{
"id": 667,
"name": "./src/app/components/common/HeaderTitle.jsx",
"file": "0.650d44243e1a15fa7627.js"
},
{
"id": 667,
"name": "./src/app/components/common/HeaderTitle.jsx",
"file": "0.650d44243e1a15fa7627.js.map"
},
...
],
Example of smart components which don't load:
"undefined": [
{
"id": 626,
"name": "./src/app/components/modules/Home/index.jsx",
"file": "0.650d44243e1a15fa7627.js"
},
...
],
The html render of my server looks like the following. Everything in here is copied from the official docs:
app.use((req, res) => {
const modules = [];
const html = SSR ? renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App />
</Loadable.Capture>
</StaticRouter>
</Provider>,
) : ' ';
console.log('modules', modules);
const bundles = getBundles(stats, modules);
console.log('bundles', bundles);
res.status(200).send(renderFullPage({ html, bundles }));
});
Which logs as expected:
modules [ './modules/Home' ]
bundles [ undefined ]
TypeError: Cannot read property 'file' of undefined
This is really driving me crazy. The package seems really good and I would like to use it. But I can't find anything about issues like this and there ain't an issue section on Github.
Open to any suggestion or help. Thanks in advance.
Just a wild guess, but I think it has something to do with the fact you're importing the index.js, and react-loadable doesn't pick this up in Loadable.Capture. Or the wepback plugin doesn't create the correct mapping in the react-loadable.json file.
A trick I used in a production app (complete with react-router, redux, react-loadable) is to use webpack's named chunks:
const AsyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "myNamedChunk" */ './SomeComponent'),
modules: ['myNamedChunk']
});
This way you control the module name and explicitly tell react-loadable what to map.
I also wrote an article on how to achieve this in a create-react-app project: https://medium.com/bucharestjs/upgrading-a-create-react-app-project-to-a-ssr-code-splitting-setup-9da57df2040a
Related
I am currently working on a project that someone else built and I was asked to implement server side rendering, it's a huge project and it uses a custom routing system based on a "builder JSON" taken by a main component that selects which components to render based on the route, it is meant to keep the app dynamic and to adjust to the needs of several customers.
I have been checking everywhere trying to find an answer but im new to SSR and it's a big challenge.
I am currently testing an approach using express that looks like this:
import 'babel-polyfill';
import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router';
import bodyParser from 'body-parser';
import { App } from '../src/App';
const app = express();
const PORT = process.env.PORT || 8000;
app.use(bodyParser.json());
app.use(express.static('ssrBuild'))
app.get('*', (req, res) => {
const context = {}
const content = ReactDOMServer.renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
const html = `
<html>
<head>
</head>
<body>
<div id="root">
${content}
</div>
</body>
</html>
`;
res.send(html);
});
app.listen(PORT, () => {
console.log(`App running on port ${PORT}`);
});
The problem I am currently having is that the App component calls the "complex routing component" from another repository in the node_modules (also build by them), my webpack config is taking the App component and finding jsx, which cant obviouly be run in the server.
Webpack config:
const path = require('path');
const webpackNodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
entry: {
server: './ssr/server.js',
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, '..', 'ssrBuild'),
publicPath: '/ssrBuild'
},
module: {
rules: [
{
test: /\js$/,
loader: 'babel-loader',
exclude: '/node_modules/',
options: {
presets: [
'#babel/react',
['#babel/preset-env', {
targets: { browsers: ['last 2 versions'] }
}]
],
plugins: [
['#babel/plugin-proposal-decorators', { 'legacy': true }],
'#babel/plugin-proposal-class-properties',
'#babel/plugin-syntax-function-bind',
'#babel/plugin-transform-async-to-generator',
'#babel/plugin-proposal-export-default-from',
'babel-plugin-jsx-control-statements',
'react-hot-loader/babel',
'lodash',
]
}
}
]
},
externals: [webpackNodeExternals()]
}
Is there a way I can tell webpack to transpile the jsx in the node_modules folder "on the go"? What other solutions could there be?
Also, the whole point of this project is to impprove the SEO for these apps, I would only need to SSR the index page and any direct link to content in it. No need to SSR the entire app is there a way this can be achieved?
I was also wondering if refactoring the entire app to use Next.js would be worth it if this were possible only for the index page and any direct link.
Thank you in advance!
If the team can afford it then you should definitely go and try a framework. This will me more maintainable on the long term. I would recommend you try Next.js over Gatsby, Both are great options but in my opinion Next has two or three advantages like Incremental Static Regeneration (Regenerate if your content change continuously) or you can choose between use Server Sider or Static generation based on your routes. You can use SSR on your dashboard and SSG on your home and landing pages for example.
If you don't need any type of pre-rendering you can go for client-side only and even then Next are gonna make a couple of optimization that will speed up your site.
On the long term it will save you a lot of time and it will be easy to maintain
I recently decided to use PrismJS with React and to avoid import repetitions I managed to use this babel-plugin-prismjs package in order to load plugins, languages and so on.
As indicated in the plugin documentation, I've created a .babelrc file located in my root folder :
{
"plugins": [
["prismjs", {
"languages": [
"applescript",
"css",
"javascript",
"markup",
"scss"
],
"plugins": ["line-numbers"],
"theme": "twilight",
"css": true
}]
]
But I found that this file seemed to be ignored, as nothing is loaded and when console-logging my imported Prism object I'm only seeing syntax highlighting for the default languages.
The file where I want to get syntax highlighting have an import Prism from 'prismjs' statement and Prism.highlightAll()
So yes I can keep importing manually plugins, theme and languages in each of my files but I would want to find the reason of such an issue.
Thank you !
You're halfway there. You still need to import prismjs somewhere, usually in your app.js file, and then call Prism.highlightAll() in the appropriate file of the page you want syntax highlighting on.
My process was as follows:
Setup my .babelrc.js file:
const env = require('./env-config')
module.exports = {
presets: ['next/babel'],
plugins: [
[
'transform-define',
env,
],
[
'prismjs', {
'languages': ['javascript', 'css', 'html', 'jsx'],
'plugins': ['line-numbers', 'show-language', 'copy-to-clipboard'],
'theme': 'tomorrow',
'css': true
},
]
],
}
Import prismjs into my _app.js file (since I'm using Next.js, but with React, you would import this into our app.js file:
// ...
import 'prismjs'
// ...
Use the prismjs API to call the .highlightAll() method on your desired page(s):
function usePrismHighlightAll() {
useEffect(() => {
Prism.highlightAll()
}, [])
}
export default function Page () {
usePrismHighlightAll()
// ...
}
As a sidenote,
could also move the usePrismHighlightAll() hook somewhere into your app.js file so that you could enable syntax highlighting in all your pages if that would save you from recalling the hook in several locations.
However, I tried doing this, and unfortunately, it did not work in Next.js:
/* DO NOT USE THIS CODE NEXT.JS AS IT DOES NOT WORK */
import 'prismjs'
// ...
function usePrismHighlightAll() {
useEffect(() => {
Prism.highlightAll()
}, [])
}
export default function App ({ Component, pageProps }) {
usePrismHighlightAll()
return (
<>
<Layout>
<Header />
<Component {...pageProps} />
</Layout>
</>
)
}
/* DO NOT USE THIS CODE NEXT.JS AS IT DOES NOT WORK */
So, stick with the process I outlined in steps 1-3.
Tthat worked for me to enable syntax highlighting on the first load of the page, let me know if this works for you.
I am really struggling to find any good examples for typescript and code splitting.
There are babel plugins that cover this but really thin on the ground for typescript examples.
I am using react so I would like to use React.Lazy but I cannot find anything that covers the webpack code splitting side of things
I did find this old example but it uses the now deceased CommonsChunkPlugin:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: function (module) {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.includes("node_modules");
}
})
],
It uses react-loadable but obviously cannot use the babel plugin so the example is manually adding the magic webpack comments:
export const LoadableHello = Loadable({
loader: () => import(/* webpackChunkName: "hello" */ "./Hello"),
loading: () => <div>loading ...</div>
});
Can anyone help me understand:
How can I setup codesplitting for dynamic imports in the webpack side of things:
Can I use React.Lazy with typescript?
I think I need to make sure ts is not removing the comments.
The answer was to ensure these values were set in my tsconfig.json:
"compilerOptions": {
"jsx": "react",
"lib": ["es5", "es2015", "es2016", "dom", "es2015.promise"],
"module": "esnext",
"moduleResolution": "node"
},
I then need to add the magical webpack comment to any lazy import, maybe you don't need to do this if you are using babel and typescript:
const TreeContainer = React.lazy(() =>
import(/*
webpackChunkName: "tree-container",
webpackPrefetch: true
*/ '../TreeContainer')
);
This only worked on webpack 4.28.4. 4.29.x did not work.
webpack above version 2 supports Dynamic import works out of the box.
if you really interested in seeing some example try to install CRA with typescript support with this command.
npx create-react-app my-app --typescript.
then browse to node_module and open react-sctipts package and browse to config folder and there you can find webpack config for both develop and production.
Using create-react-app with Typescript there was no setup needed. You can take examples from the docs. E.g.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
I am trying to add in redux to an existing sailsjs v0.12 project without updating every package in the gigantic pile. The current react stuff is loaded via bower and the .bower.json file for react is:
{
"name": "react",
"main": [
"react.js",
"react-dom.js"
],
"ignore": [],
"homepage": "https://github.com/facebook/react-bower",
"version": "0.14.9",
"_release": "0.14.9",
"_resolution": {
"type": "version",
"tag": "v0.14.9",
"commit": "81ae9f4ab02851be30ce2b1eef3dfd78d291cfc8"
},
"_source": "https://github.com/facebook/react-bower.git",
"_target": "~0.14.7",
"_originalSource": "react"
}
I am trying to figure out what versions of react-redux and redux I can load via bower to work with the existing react version and where to get them, it seems there are a bunch of different sources and builds -should I just install certain (but which?) versions via npm and copy them over to the bower_components folder? The project automatically loads everything under that folder.
Update:
We copied redux version 3.0.4(via cdnjs) and react-redux version 4.4.0 into the bower_components folder. In one of the jsx files at the top I include that stuff like so:
requirejs.config({
urlArgs: "v=" + ssweb.buildNumber,
paths: {
'react': '/bower_components/react/react-with-addons.min',
'reactdom': '/bower_components/react/react-dom.min',
'label': '/js/shared/reactLabel',
'moment': '/bower_components/moment/moment',
'redux': '/bower_components/redux/index',
'reactRedux': '/bower_components/react-redux/dist/react-redux.min',
'rootReducer': '/js/reducers/rootReducer',
},
shim: {
'reactRedux': ["react", "redux"]
}
});
require(['react', 'label', 'moment', 'reactdom', 'redux' ,'reactRedux','rootReducer'],
function (React, Label, moment, ReactDOM, Redux, ReactRedux, rootReducer) {
var createStore = Redux.createStore;
var con = ReactRedux.connect;
console.log("rootReducer ", rootReducer);
const store = createStore(rootReducer);
const Provider = ReactRedux.Provider;
console.log("Provider ", Provider);
console.log("store ",store);
console.log("con ", con);
console.log("rootReducer ", rootReducer);
class PCycle extends React.Component {
...
render(){
const measUnit = this.state.measUnit;
return (
<Provider store={store}>...extra stuff</Provider>
);
}
}
$(function () {
ReactDOM.render(<PCycle avariable={window.theVariable} />, document.getElementById('p_container') );
});
});
And that seems to be a start as the redux objects get printed to the console. Now I want to like redux to my components. In the code I have seen in an example react/redux project (by net ninja on youtube) using ES6 it looks like so:
const mapStateToProps = (state, ownProps)=>{
let id = parseInt(ownProps.match.params.post_id);
return{
post: state.posts.find(item => item.id === id)
}
}
const mapDispatchToProps = (dispatch)=>{
return {
deletePost: (id) => {
return dispatch(deletePost(id))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Post);
So my question now is: how do I do that last part in my code in which I do not import things but used self calling functions contained in other self calling functions.
I have a .ejs page on which 5 different components - each made like the above PCycle.jsx above - coexist and I want them to share the same redux store so an update in one component can affect the state of another. They are included on the .ejs page with script tags.
I've updated react-hot-loader to v.3.0.0 and when I change something in component I get updates in the browser but I also get this warning in the console:
React Hot Loader: this component is not accepted by Hot Loader.
Please check is it extracted as a top-level class, a function or a
variable. Click below to reveal the source location:
The problem is I'm not seeing anything in the stack which would suggest me where is the error.
webpack entry
client: [
'react-hot-loader/patch',
'webpack/hot/only-dev-server',
'wicg-focus-ring',
PATHS.clientBundleEntry,
],
eslint
"plugins": [
"react-html-attrs",
"transform-runtime",
"transform-class-properties",
"styled-jsx-postcss/babel",
"react-hot-loader/babel"
]
client.jsx
function render() {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<BrowserRouter>
{app}
</BrowserRouter>
</Provider>
</AppContainer>,
document.getElementById('app'),
);
}
render();
if (module.hot) {
module.hot.accept('./app', () => { render(); });
}
EDIT:
I have changed:
export default withRouter(
connect(
(state: ReduxState) => ({
error: state.requestState.loginError,
}),
{ loginUser },
)(LoginContent),
);
into:
const withRouterLoginContent = withRouter(LoginContent);
export default connect(
(state: ReduxState) => ({
error: state.requestState.loginError,
}),
{
loginUser,
},
)(withRouterLoginContent);
... and it helped in some cases. Not sure what's the difference though.
I experienced this same problem and was able to solve it by not using "functional composition" to combine multiple higher-order-components, as described in React Hot Loader's Troubleshooting Guide.
Their "solution" at the bottom of the page fixed the warning for me. Reproduced here, it's a matter of converting:
const SuperComponent =
connect()( <-- last HoC
withSomeStuff( <-- first HoC
Component <-- a real component
)
);
To:
const WithSomeStuffComponent = withSomeStuff(Component);
const SuperComponent = connect()(WithSomeStuffComponent);