I'm using react-hot-loader and I'm very confused about its example code:
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './containers/App'
ReactDOM.render(
<AppContainer>
<App/>
</AppContainer>,
document.getElementById('root')
);
// Hot Module Replacement API
if (module.hot) {
module.hot.accept('./containers/App', () => {
const NextApp = require('./containers/App').default;
ReactDOM.render(
<AppContainer>
<NextApp/>
</AppContainer>,
document.getElementById('root')
);
});
}
What I don't undestand is:
a) Why do I need to use App and NextApp ? Isn't App the same as NextApp, as they are imported from the same file ?
b) I thought best practices would be to keep requires at the beginning of the code. But there I have already import App from '../containers/App'. So:
import App from './containers/App'
const NextApp = require('./containers/App').default;
Shouldn't App and NextApp be the same ?
c) To finish, is the following code lines equivalent ? What's the difference using a pure require and a require .default ?
const NextApp = require('./containers/App');
const NextApp = require('./containers/App').default;
Sorry if those are very basic questions, but I need a hint on where to go to better understand this code.
To understand this better, you need to look at how webpack offers hot module loading for other non-react cases.
The idea is quite simple actually... Webpack detects changes happening to your code and recompiles the corresponding modules. However, it cannot safely replace the module references on the fly itself. This is why you need to implement the HMR interface yourself, hence the module.hot call in your example code.
When a newer version of a module is available, Webpack goes up the modules chain (i.e., if X imported Y and Y has changed, webpack starts going from X > W > V...) till it finds a module which implemented HMR for Y or X or W or V etc. Then it reloads the entire chain from there.
If it reaches the root without any HMR accepting the change, it refreshes the entire page :).
Now The matter of App and NextApp... App is the statically imported version of you module. As modules are parsed and loaded only once by default, App points to a constant reference which never changes. This is why another variable NextApp is used in the example which receives the changed module within the HMR code.
Finally, the require and require.default... when dealing with ES6 imports (export default MyComponent), the exported module is of the format {"default" : MyComponent}. The import statement correctly handles this assignment for you, however, you have to do the require("./mycomponent").default conversion yourself. The HMR interface code cannot use import as it doesn't work inline. If you want to avoid that, use module.exports instead of export default.
Related
Given the following directory structure, is it possible to have ALL react imports resolve to react-b?
|__node_modules
| |__react-a
|
|__app-a
| |__component-a
|
|__next-app
| |__react-b
| |__component-b
// component-a
import { useEffect } from 'react' // I need this to resolve to next-app/node_modules/react
export function() {
useEffect(() => {} , [])
return <></>
}
// component-b
import ComponentA from "../app-a/component-a"
export function() {
return <ComponentA />
}
The issue I am having is that we are migrating to a Next.JS app (next-app) but we want to continue to import components from (app-a). app-a is stuck for now on react 17.x.x but Next.JS is using 18.x.x. So when next-app is built, I need all react imports to resolve to react 18.x.x. At the time of writing this post we are using the experimental.externalDir setting to allow for importing components from outside the root of the next.js app.
The crux of it is that when importing from app-a I still need react to resolve to next-app/node_modules/react.
Webpack aliases seem to be the recommended answer generally but they don't appear to apply correctly in this situation.
I have solved this specific problem by using the next config transpilePackages list. Some dependencies that are dependent on react are causing the react version mismatch by importing react from the root node_modules. By including these packages in the transpilePackages list in the next config, it seems that next is pre-compiling these libs using the correct react version.
Example:
// next.config.js
const nextConfig = {
...other_config,
transpilePackages: ["react-focus-lock"],
}
Unfortunately I haven't fully appreciated why this imports the correct react dependency while using webpack resolve aliases does not.
For example, the recommended way of importing in React Bootstrap is to go this way:
import Button from 'react-bootstrap/Button' instead of import { Button } from 'react-bootstrap';
The reason is "Doing so pulls in only the specific components that you use, which can significantly reduce the amount of code you end up sending to the client."
source: https://react-bootstrap.github.io/getting-started/introduction/
Same for React MUI components:
import Button from '#mui/material/Button';
source: https://mui.com/material-ui/getting-started/usage/
I want to implement something similar in my React components library, to limit the usage of code in the bundle, but I don't know how they implement this specific pattern. I have looked at their code base, but I don't quite understand.
Basically it is all about modules and module files and their organization. You can have a lot of.. lets call them folders, "compoments/*" for example. "components/button", "components/alert", "component/badge", and other things. All of them will have some index.js or .ts file that will export or declare and export all the functionality that needed in order to make this component work, 'react-bootstrap/Button' for example. Ideally all those subfolders or submodules are independend from each other, no references between them but probably each one will have 1 reference to 1 common/shared submodule like "components/common" which will contain some constants, for example, and no references to other files. At the top level of them you will have another index.js or .ts file that is referencing all of those components, so "components/index.js" will import and reexport all the nested components index files. So in order to import a Button, for example, you can either import "components/index.js" file with all the other imports this file is using, either only 1 single "components/button/index.js" file which is obviously much more easy to fetch. Just imagine a tree data structure, you import root of the tree (root index.js) - you get all the tree nodes. You import one specific Node (components/button/index.js) of the tree - just load all the childs (imports) of that node.
Sorry for a long read but asuming you mentioned webpack - there is a technique called tree-shaking which will cut off all the unused things.
Info about modules: https://www.w3schools.com/js/js_modules.asp
Info about Tree-Shaking: https://webpack.js.org/guides/tree-shaking/
It might not be as complicated as you think. Let's say you write the following library:
// your-library.js
const A = 22
const B = 33
export function getA () { return A }
export function getB () { return B }
export function APlusB () { return A + B }
// a lot of other stuff here
If some consumer of your library wants to make use of the APlusB function, they must do the following:
// their-website.js
import { APlusB } from 'your-library'
const C = APlusB()
However, depending on how the code is bundled, they may or may not wind up with the entire your-library file in their web bundle. Modern bundling tools like Webpack may provide tree shaking to eliminate dead code, but this should be considered an additional optimization that the API consumer can opt into rather than a core behavior of the import spec.
To make your library more flexible, you can split up independent functions or chunks of functionality into their own files while still providing a full bundle for users who prefer that option. For example:
// your-library/constants.js
export const A = 22
export const B = 33
// your-library/aplusb.js
import { A, B } from 'constants'
export default function APlusB () { return A + B }
// your-library/index.js
// instead of declaring everything in one file, export it from each module
export * from 'constants'
export { default as APlusB } from 'aplusb'
// more exports here
For distribution purposes you can package your library like so:
your-library
|__aplusb.js
|__constants.js
|__index.js
You mentioned react-bootstrap and you can see this exact pattern in their file structure:
https://github.com/react-bootstrap/react-bootstrap/tree/master/src
and you can see they aggregate and re-export modules in their index file here:
https://github.com/react-bootstrap/react-bootstrap/blob/master/src/index.tsx
Essentially, what you are asking is:
"How to export react components"
OR
"How are react components exported to be able to use it in a different react project ?"
Now coming to your actual question:
import Button from 'react-bootstrap/Button' instead of import { Button } from 'react-bootstrap';
The reason is 'Button' component is the default export of that file react-bootstrap/Button.tsx. So there is no need for destructuring a specific component.
If you export multiple components/ functions out of a file, only 1 of them can be a default export.
If you have only 1 export in a file you can make it the default export.
Consider the file project/elements.js
export default function Button(){
// Implementation of custom button component
}
export function Link(){
// Implementation of custom Link component
}
function Image(){
// Implementation of custom Image component
}
Notice that the Button component has 'default' as a keyword and the Link component doesn't.
The Image component can't even be imported and can only be used by other functions/components in the same file.
Now in project/index.js
import 'Button', {Link} from './elements.js'
As Button component is the default export its possible to import without destructuring and as Link component is a regular export, I have to destructure it for importing.
This is what the react-hot-loader DOCs says:
https://www.npmjs.com/package/react-hot-loader
Note: You can safely install react-hot-loader as a regular dependency instead of a dev dependency as it automatically ensures it is not executed in production and the footprint is minimal.
Even though it says that. My goals are:
I want to remove react-hot-loader from my production bundle.
And I also want a single App.js file. That should work for DEV and PROD.
The only command that I have related to react-hot-loader is inside of my App.js file:
App.js
import { hot } from 'react-hot-loader/root';
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
return(
<Layout/>
);
}
export default process.env = hot(App);
If I run it just like this, I end up with the following line on my app.js transpiled and bundled file:
/* WEBPACK VAR INJECTION /(function(process) {/ harmony import / var react_hot_loader_root__WEBPACK_IMPORTED_MODULE_0__ = webpack_require(/! react-hot-loader/root */ "wSuE");
That's expected.
But if I change my App.js file to:
AppV2.js
import { hot } from 'react-hot-loader/root'; // KEEPING THE IMPORT
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
console.log(window);
return(
<Layout/>
);
}
// export default hot(App); <--- COMMENTED OUT THE hot() LINE
export default App;
And I add this line to my webpack.config.js
webpack.config.js
plugins:[
new webpack.IgnorePlugin(/react-hot-loader/)
]
I'll end up with a new transpiled app.js file with this line:
*** !(function webpackMissingModule() { var e = new Error("Cannot find module 'react-hot-loader/root'"); e.code = 'MODULE_NOT_FOUND'; throw e; }());
Note: The first '***' chars in the line above don't really exist. I had to add them in order to the ! exclation mark to be shown in the quote. Don't know why but you can't start a quote with an exclamation mark.
QUESTION
Isn't the IgnorePlugin supposed to completely ignore the react-hot-loader package? Why is it being marked as missing? See that it's not even being used on the code (since I've commented out the hot() call).
Ignore Plugin only excludes that particular module in bundle generation. However it will not remove the references to the module from your source code. Hence your webpack output is throwing that error.
One way of bypassing this error is to use the DefinePlugin to create a dummy stub for react-hot-loader. More on that here.
That said react-hot-loader itself proxies the children without any changes if the NODE_ENV is production. Check here and here. So in production mode apart from the hot() function call which directly returns your component, there is no other stuff that happens.
Another option could be:
// App.js
export default function AppFactory() {
if (process.env.NODE_ENV === "development") {
return hot(App);
} else {
return App;
}
}
// index.js:
import AppFactory from './App';
const App = AppFactory();
// ...
<App />
Now since webpack is creating bundles at build time, it knows if the mode is development or production (more on build modes) and should be able to eliminate the dead code with tree shaking and UglifyjsWebpackPlugin.
Make sure that if you are using Babel it's not transpiling your code to CommonJS - see Conclusion section, point 2 of the tree shaking page.
Pass the ambient mode to babel.
"scripts": {
"build-dev": "webpack --node-env development",
"build-prod": "webpack --node-env production",
},
I've a component loaded using react-loadable (5.4.0) that also loads some other local and external libraries:
import { validationAPI, docstringAPI, autocompleteAPI } from 'utils/request';
import something from './something';
import CodeMirror from 'react-codemirror';
import CM from 'codemirror';
import 'codemirror-docs-addon';
import 'codemirror-docs-addon/lib/main.css';
class MyComponent extends React.Component {
render() {
return (
<div>What I do here it doesn't matter</div>
)
}
}
export default MyComponent
And this is the defined loadable:
import React from 'react';
import Loadable from 'react-loadable';
const Loading = () => (
<div>
Loading stuff
</div>
);
const LoadableComponent = Loadable({
loader: () => import('./MyComponent'),
loading: Loading,
});
export default (props) => (
<LoadableComponent {...props} />
);
The issue I have: if the module I include asynchronously also imports local modules never imported before from my app, I get this error:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: object.
Check the render method of LoadableComponent.
in LoadableComponent
...
For example: in the code above I have this ./something imported. If this module has never been loaded before in my app I get the error above.
If I:
import the module somewhere else (as for validationAPI, which is imported with no issues)
remove the import
...the app works and I've the async behavior.
Note: If the imported stuff is used or not doesn't matters.
I also tried to move the something module somewhere else, but this seems not related to the module position or to relative import syntax.
Note also that externals libraries does not give me any issues: for example: the codemirror library is included only in that file, but it still works. Only local modules give me the issue.
The only workaround found: not import anything never imported before, but include the something module content inline in the component's file.
This is acceptable but bloat the file size a bit.
I'm not sure this is an issue with react-loadable or webpack or something else.
Any suggestion?
I'm running into the same issue as you... Did you ever figure it out?
I was able to get it to work with Loadable.Map(). But I feel I shouldn't even have to do it this way!
https://github.com/jamiebuilds/react-loadable#loading-multiple-resources
Is it not possible to access already 'built' components within the html file that the build is linked to?
I am trying the following -
In bundle.js
var React = require('react');
var ReactDOM = require('react-dom');
var Titles = React.createClass({
render() {
return (
<div>
<h1>{this.props.headerProp}</h1>
<h2>{this.props.contentProp}</h2>
</div>
);
}
});
In my html page -
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
<div id="con"></div>
<script type="text/javascript" src="/public/bundle.js'"></script>
<script type="text/jsx">
ReactDOM.render(<Titles headerProp = "Header from props..." contentProp = "Content
from props..."/>, document.getElementById('con'));
</script>
But console outputs React is not defined.
I have even tried to set react globally within the bundle -
window.React = React;
And calling it with window. prefixed but yields the same result.
Because you're mentiong a bundle.js file with a snippet containing commonjs style imports, I'm assuming you're using Webpack.
I have some considerations about your code.
bundle.js file will not expose any module as global. That includes React and any other module you might require inside the bundle. There isn't goint to be window.ModuleName. However, these module are accessible in the Browser via require.js because Webpack will export modules as UMD, that is, they will be accessible through either commonjs or AMD (Require.js).
I'm pretty sure that, if in the entry point of your webpack configuration file, you do something like var React = require("react"); window.React = React, that's actually going to work.
There's a Webpack module meant to expose modules globally (like in window.x) in a more ellegant way than (2) called expose-loader. You should take a look at it.
You should really try to avoid doing what you're trying to do. In your entry.js file (the entry point of your webpack configuration) should be responsible for doing something like ReactDOM.render(..., document.getElementById("#app")). So that, just by including your bundle, the app will render automatically. This is what everybody does.
JSXTransformer.js as well as the <script type="text/jsx"> have been deprecated a long time ago. Now you're supposed to use Babel to compile React.