Conditional Import at build time in react js and react native - reactjs

I want to share my logic between react js and react native
here is my logic file. it is a HOC that wrap the design component.
I use recompose to create HOCs.
import {compose, withState} from 'recompose';
import FilterOptionsPostsPaginateRelay from './FilterOptionsPostsPaginateRelay';
import handlers from './handlers';
import {withRouter} from "next/router";
export default compose(
withRouter
FilterOptionsPostsPaginateRelay,
withState('loading', 'setLoading', false),
handlers,
)
All of the HOCs are sharable between the react js and react native but the withRouter that comes from next.js router
I want to conditional import {withRouter} from "next/router"; or import {withNavigation} from 'react-navigation'; in my logic file.
I know recompose has branch HOC that I can use but I want the condition checks at build time and prevent extra codes to my bundle and increase performance.

You can create next.config.js to custom webpack config to add alias for router.
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
// Example using webpack option
config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//));
config.resolve.alias.router = process.env.IS_REACT_NATIVE ? 'react-navigation' : 'next/dist/client/router.js';
return config
},
};
then in your app, you need to use require to import router
const router = require('router');
const withRouter = router.withRouter || router.withNavigation;

I find a solution using babel-plugin-module-resolver
In web project I added following code to .babelrc
{
"plugins": [
[
"module-resolver",
{
"alias": {
"hybridRouter": "next/router"
}
}
]
]
}
In mobile project I added following code to .babelrc
{
"plugins": [
[
"module-resolver",
{
"alias": {
"hybridRouter": "react-navigation"
}
}
]
]
}
And in my logic files I used this like
import HybridRouter from 'hybridRouter';
Now HybridRouter point to next/router in web project and HybridRouter points to react-navigation in mobile project

You can have conditional imports using the require syntax. There is no need to mess with webpack, if you don't want to. Just do something like this in your case:
let withRouter;
try {
//This is expected to fail if next/router is not available
const router = require("next/router");
withRouter = router.withRouter;
} catch(ex){
const reactNav = require("react-navigation");
withRouter = reactNav.withNavigation;
}
// Use withRouter like you would normally use withRouter from next/router or withNavigation from react-navigation

Related

How to import SVG in ReactJS with craco?

I'm struggling to import SVG's when I'm using craco in my react app.
It's suggested to use #svgr/webpack but I'm not sure how to put it into my craco.config.js
My current setup as per this (I prob shouldn't follow someone's config that doesn't work and expect it to work tho) that does not work:
// craco.config.js
const CracoAlias = require("craco-alias");
module.exports = {
plugins: [
{
plugin: CracoAlias,
options: {
source: "tsconfig",
baseUrl: "./src",
tsConfigPath: "./tsconfig.paths.json"
}
},
],
webpack: {
configure: (config, { env, paths }) => {
config.module.rules.push({
test: /\.svg$/,
use: ["#svgr/webpack"]
});
return config;
}
}
};
The craco.config.js webpack documentation is here but it's so confusing to me without concrete examples.
Also to note:
Writing import {ReactComponent as mySvg} from "./mySvg.svg" doesn't work because it doesn't recognize it as a ReactComponent.
If I try importing directly via import mySvg from "./mySvg.svg" Typescript doesn't recognize the file.
What I'm currently doing is putting the svg into a React component and using that but it's a nightmare doing that every time. I also put this in #types/custom.d.ts, but it still doesn't work when put into <img src={mySvg} />
// #types/custom.d.ts
declare module "*.svg" {
const content: any;
export default content;
}
import {reactComponent as GoogleLogo} from "../assets/image/googlelogo.svg;
GoogleLogo is component and reactComponent is a required magic string
i find the fix your problem in Adding svgr to create-react-app via craco

Basic implementation of using microbundle not working

Please see example repo
https://github.com/inspiraller/webpack-and-microbundle
Microbundle code
mymicrobundle/src/index.js
import React from 'react'
const MyMicroBundle = ({session}) => {
return (
<div>Session = {session}</div>
)
}
export default MyMicroBundle
microbundle/package.json - for output
{
...
"source": "src/index.js",
"main": "dist/index.umd.js",
"module": "dist/index.module.js",
"exports": "dist/index.modern.js",
"unpkg": "dist/index.umd.js"
}
Importing Microbundle code
webpack-loads-microbundle/package.json
{
"dependencies": {
"#mymicrobundle/example": "file:../mymicrobundle",
}
}
webpack-load-microbundle/src/index.tsx
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import MyMicroBundle from '#mymicrobundle/example'
import './index.scss'
const App = () => {
const [session, setSession] = useState('')
return (
<div>
<h1>Hello</h1>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
Note: The microbundle package is bundled as javascript, but I'm using typescript to import it.
Though shouldn't make any difference. I'm also using pnpm instead of npm but also this should be fine as all other imports work.
Something about my path is wrong maybe.
Error
Module not found: Error: Can't resolve '#mymicrobundle/example' in 'C:\baps\react_all\webpack-and-microbundle\webpack-loads-microbundle\src'
webpack resolves modules from its process.cwd()/node_modules by default.
So in your case it is looking for #mymicrobundle/example in webpack-and-microbundle(the current working directory) which is your app directory.
You need to let webpack know, where it needs to search in addition to your project's node_modules.
This can be done using the resolve.modules key. See docs: https://webpack.js.org/configuration/resolve/#resolvemodules
So your webpack config should have something like:
resolve: {
modules: ['node_modules', 'path/to/#mymicrobundle/example'],
},

Support for the experimental syntax 'decorators-legacy' isn't currently enabled How To Enable In Create React App?

I am trying to get up and running with react create app and mobx state tree. I keep getting
Support for the experimental syntax 'decorators-legacy' isn't currently enabled (4:1):
I never used react create app so I am not sure how to enable, I tried making a .babelrc file but that did not help
{
"presets": ["#babel/preset-env"],
"plugins": [
["#babel/plugin-proposal-decorators", { "legacy": true }]
]
}
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
#inject("domainStores")
#observer
export default class MainComponent extends Component {
async componentDidMount() {}
render() {
return <div className="main-container">
helllo
</div>;
}
}
I am also open to suggestions if things have changed, I have not used the newest version of Mobx State tree so many there is a better way of doing this now?
If you only using MST without regular MobX stuff and only need inject and observer then you can use regular function syntax, it looks not that great, but does not require any setup:
export const WrappedComponent = inject("domainStores")(observer(Component)
// Or use export default here if you prefer
If you are using other features of MobX then in MobX 6 there is a new thing that will probably allow you to drop decorators (like computed, action and etc) altogether, makeAutoObservable:
import { makeAutoObservable } from "mobx"
class Store {
// Don't need decorators now
string = 'Test String';
setString = (string) => {
this.string = string;
};
constructor() {
// Just call it here
makeAutoObservable (this);
}
}
With that you don't even need decorator syntax to be enabled.
More info here
https://mobx.js.org/migrating-from-4-or-5.html
and
https://mobx.js.org/react-integration.html
Instead of using the old decorators proposal, you can use observer as a function on your components instead of as a decorator.
We can also use React's own context instead of inject, as the documentation states:
Note: usually there is no need anymore to use Provider / inject in new code bases; most of its features are now covered by
React.createContext.
This way we can use Create React App as is.
Example
import React from "react";
import { observer } from "mobx-react";
import { types } from "mobx-state-tree";
const StoresContext = React.createContext("store");
// custom hook that we can use in function components to get
// the injected store(s)
function useStore() {
return React.useContext(StoresContext);
}
const StoreModel = types.model({
things: types.array(types.string)
});
const storeInstance = StoreModel.create({ things: ["foo", "bar"] });
// instead of using the #observer decorator, we can use observer as
// a function and give it a component as argument
const MainComponent = observer(() => {
const store = useStore();
return (
<div>
{store.things.map((thing) => (
<div key={thing}>{thing}</div>
))}
</div>
);
});
export default observer(function App() {
return (
<StoresContext.Provider value={storeInstance}>
<MainComponent />
</StoresContext.Provider>
);
});
CRA does not allow you to extend your own configuration, so, in order to extend cra configuration, you will have to use customize-cra with react-app-rewired.
So, follow the steps below:
Install customize-cra, react-app-rewired and #babel/plugin-proposal-decorators using npm or yarn
add config-overrides.js at root level of your project and paste the code given below:
const {override, addDecoratorsLegacy } = require('customize-cra');
module.exports = override(addDecoratorsLegacy());
Update package.json scripts to the below ones:
"start": "react-app-rewired start",
"build": "react-app-rewired build"
P.S: if you want to use babel configuration then your config-overrides.js should be like:
const {override, addDecoratorsLegacy, useBabelRc } = require('customize-cra');
module.exports = override(addDecoratorsLegacy(), useBabelRc());
useBabelRc will load config from your root of project automatically.

ESLint: '#material-ui/core/Button' import is restricted from being used

I'm writing a React 16 application, I'm using material-ui components.
I import Button like this:
import Button from '#material-ui/core/Button';
just like it says in the official documentation: https://material-ui.com/components/buttons/
WebStorm show an error saying:
ESLint: '#material-ui/core/Button' import is restricted from being used. This loads CommonJS version of the package. To fix replace with: import { Button } from "#material-ui/core";(no-restricted-imports)
When I change the import to:
import {Button} from '#material-ui/core';
the message error goes away. The code works in both instances.
I want to know why I need to change the import for the error to disappear. why can't I just use the same code suggested by material-ui.com itself.
The template I'm using uses this .eslintrc.js
"use strict";
const fs = require("fs");
const path = require("path");
const restrictedPaths = [
{ name: "react-bootstrap" },
{ name: "#material-ui/core" }
].map(pkg =>
fs
.readdirSync(path.dirname(require.resolve(`${pkg.name}/package.json`)))
.map(component => ({
name: `${pkg.name}/${component}`,
message: `This loads CommonJS version of the package. To fix replace with: import { ${component} } from "${pkg.name}";`
}))
);
// TODO: Wait for https://github.com/facebook/create-react-app/pull/7036 to enable rules in react-scripts.
module.exports = {
extends: "eslint-config-react-app",
rules: {
// "no-script-url": "warn",
"jsx-a11y/anchor-is-valid": "warn",
"no-restricted-imports": ["error", { paths: [].concat(...restrictedPaths) }]
}
};
When you specify the full path to the file you want to import, then you import this file. When you use the import {Button} from '#material-ui/core', you let node choose the file it wants to use for this import. The package.json of a module may specify different entry points for a single #material-ui/core, depending on if working with CommonJS or ES modules.

Error resolving module specifier: react while doing dynamic import from API

I am trying to dynamically import react component library from API. The js file is fetched succesfully. The babel transpilation has happened successfully too. If I dynamically import the Dummy.js file from localhost like import Dummy from './components/js/Dummy.js', it works. However API import fails with below error. The same error occurs with react lazy too. I basically want to dynamically load the lib and publish events to it. I am newbie to react and front-end development. Please excuse if this is too silly!.
Error resolving module specifier: react
My App.js
import React, { lazy, Suspense } from 'react';
import './App.css';
import ErrorBoundary from './ErrorBoundary';
class App extends React.Component {
render(){
const loader = () => import( /*webpackIgnore: true*/ `https://example.com/Dummy.js`);
const Dummy = ReactDynamicImport({ loader });
const LoadingMessage = () => (
"I'm loading..."
)
return (
<div className="App">
<h1>Simplewidget</h1>
<div id="simplewidget">
<ErrorBoundary>
<Suspense fallback={LoadingMessage}>
<Dummy />
</Suspense>
</ErrorBoundary>
</div>
</div>
);
}
}
export default App;
rollup.config.js, After multiple attempts I arrived at below configuration https://github.com/jaebradley/example-rollup-react-component-npm-package/blob/master/rollup.config.js
// node-resolve will resolve all the node dependencies
import resolve from '#rollup/plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import commonjs from '#rollup/plugin-commonjs';
import filesize from 'rollup-plugin-filesize';
import localResolve from 'rollup-plugin-local-resolve';
export default {
input: 'src/components/js/Dummy.js',
output: {
file: 'dist/Dummy.js',
format: 'es',
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
},
// All the used libs needs to be here
external: [
'react',
'react-dom'
],
plugins: [
babel({
exclude: 'node_modules/**',
}),
localResolve(),
resolve({
browser: true,
}),
commonjs(),
filesize()
]
}
Dummy.js. I verified in dist/dummy.js that babel transpilation happened correctly. I uploaded the transpiled version to my static hosting site.
import React from "react";
import ReactDOM from "react-dom";
class Dummy extends React.Component {
render() {
return (
<h1>Hello</h1>
);
}
}
export default Dummy;
I have different targets to build, start up my server, create rollup bundle, etc in same app. So, I am pretty confident my rollup doesn't interfere with running the app.
The rollup bundling configuration isn't correct. I was trying to create an es output with commonjs plugin while actually I required an esm module. The error on 'react' is because it is unresolved. Had to make it to use window.React while doing rollup bundle. Also, the App.js should be rolled up as esm bundle to make it dynamically import Dummy.js. In the HTML where app.js's bundle is included, I had to include react and react js umd scripts.
export default {
input: "./src/components/js/Dummy.js",
output: {
file: './dist/Dummy.js',
format: 'esm'
},
plugins: [
babel({
exclude: "node_modules/**"
}),
resolve(),
externalGlobals({
"react": "window.React",
"react-dom": "window.ReactDOM",
})
]
};

Resources