Webpack + `create-react-app` | ProvidePlugin Not Loading React as a Module - reactjs

* Note: I barely know anything about Webpack.
I want to load the react node module and other modules in Webpack via ProvidePlugin to have global access to them.
I created a create-react-app and ran eject and got access the pre-defined configuration for Webpack create-react-app provides.
I read this post about loading react globally via PluginProvidor and read about PluginProvidor itself in the Webpack docs, where the latter states:
By default, module resolution path is current folder (./**) and node_modules
Based on that, in webpack.config.js, in plugins, I added the following PluginProvidor:
...
plugins: [
new webpack.ProvidePlugin(
{
React: 'react'
}
)
]
...
But it didn't work - in a JSX file when I call React (e.g class MyComponent extends React.Component { ... }) I get an error that says that React isn't defined (and also a React-specfic error that React must be defined in JSX files).
Why doesn't it work? As far as I understand, I'm giving the same identifier I call in my JSX file, and like I mentioned, according to the Webpack docs, to the path of the react module in node_modules - both as required for it to work.
My configuation file: webpack.config.js

It's not a good idea to use ProvidePlugin, and even worse is to eject your CRA.
Instead of ProvidePlugin use globals:
// globals.js
import React from 'react';
window.React = React;
and then import './globals'
import './globals';
// no need import React
import ReactDOM from 'react-dom';
...
For adding plugins to CRA web pack refer to react-app-rewired.
Example of adding a plugin:
/* config-overrides.js */
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = function override(config, env) {
if (!config.plugins) {
config.plugins = [];
}
config.plugins.push(new MonacoWebpackPlugin());
return config;
};
Demo:

Related

Swiper 8 and jest

Swiper 8 and Jest (support ESM) Must use import to load ES Module
enter image description here
enter image description here
How we can solve if I need to keep swiper 8 (without downgrade)
In case someone runs into this same issue but is using NextJs 12.2, next/jest and Jest 28 the answer is a variation from Francisco Barros' answer.
// jest.config.js
const nextJest = require('next/jest')
const createJestConfig = nextJest({
// Path to Next.js app to load next.config.js
dir: './'
})
/** #type {import('#jest/types').Config.InitialOptions} */
const customJestConfig = {
/**
* Custom config goes here, I am not adding it to keep this example simple.
* See next/jest examples for more information.
*/
}
module.exports = async () => ({
/**
* Using ...(await createJestConfig(customJestConfig)()) to override transformIgnorePatterns
* provided byt next/jest.
*
* #link https://github.com/vercel/next.js/issues/36077#issuecomment-1096635363
*/
...(await createJestConfig(customJestConfig)()),
/**
* Swiper uses ECMAScript Modules (ESM) and Jest provides some experimental support for it
* but "node_modules" are not transpiled by next/jest yet.
*
* #link https://github.com/vercel/next.js/issues/36077#issuecomment-1096698456
* #link https://jestjs.io/docs/ecmascript-modules
*/
transformIgnorePatterns: [
'node_modules/(?!(swiper|ssr-window|dom7)/)',
]
})
The transformIgnorePatterns on jest.config.js prevents the Swiper files from being transformed by Jest but it affects the CSS files that are provided by this package.
Mocking these CSS files is the solution with the smallest configuration.
In my case, it didn't matter much about having access to these CSS files while testing but there are other approaches if these files are relevant to you.
// jest.setup.js
import "#testing-library/jest-dom/extend-expect";
jest.mock("swiper/css", jest.fn());
export {};
I created a repository for a full reference of the necessary setup.
https://github.com/markcnunes/with-jest-and-esm
Have in mind this setup might have to change for future Next.js / next/js versions but just sharing this approach in case this is useful for other people using this same setup.
I have the same issue and still searching for a solution; I believe what the OP is asking is how can we transform swiper/react into a CJS module on the JEST pipeline.
Since using ESM Experimental in jest is not a good option...
Downgrading to v6 is not an option;
Any code that imports Swiper v8 fails in JEST because Swiper 8 only exports ESM;
A few days have passed since my original response. In the mean time I have found a solution that I have been using to effectively use Swiper v8 while still being able to test components that depend on it using jest.
The trick is to map the ESM imports to actual JavaScript and CSS files exported by Swiper, which can then be processed by babel and transpiled into CJS.
Create a file
// ./mocks/misc/swiper.js
module.exports = {
// Rewrite Swiper ESM imports as paths (allows them to be transformed w/o errors)
moduleNameMapper: {
"swiper/react": "<rootDir>/node_modules/swiper/react/swiper-react.js",
"swiper/css": "<rootDir>/node_modules/swiper/swiper.min.css",
"swiper/css/bundle": "<rootDir>/node_modules/swiper/swiper-bundle.min.css",
"swiper/css/autoplay": "<rootDir>/node_modules/swiper/modules/autoplay/autoplay.min.css",
"swiper/css/free-mode": "<rootDir>/node_modules/swiper/modules/autoplay/free-mode.min.css",
"swiper/css/navigation": "<rootDir>/node_modules/swiper/modules/autoplay/navigation.min.css",
"swiper/css/pagination": "<rootDir>/node_modules/swiper/modules/autoplay/pagination.min.css"
},
// Allow Swiper js and css mapped modules to be imported in test files
transform: {
"^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }],
"^.+\\.(css)$": "<rootDir>/jest.transform.js"
},
// Do not transform any node_modules to CJS except for Swiper and Friends
transformIgnorePatterns: ["/node_modules/(?!swiper|swiper/react|ssr-window|dom7)"]
}
Create another file in the root of your repository
// jest.transform.js
"use strict"
const path = require("path")
// Requried to fix Swiper CSS imports during jest executions, it transforms imports into filenames
module.exports = {
process: (_src, filename) => `module.exports = ${JSON.stringify(path.basename(filename))};`
}
Finally in your jest configuration use the things you created
// jest.config.js
const swiperMocks = require("./mocks/misc/swipper");
module.exports = {
...yourConfigurations
moduleNameMapper: {
...yourOtherModuleNameMappers,
...swiperMocks.moduleNameMapper,
},
transform: {
...yourOtherTransforms
...swiperMocks.transform,
},
transformIgnorePatterns: [
...yourOtherTransformsIgnorePatterns
...swiperMocks.transformIgnorePatterns,
],
}

I'm unable to load #apollo/client on a React Native Expo app

I'm trying to load #apollo/client on a React Native Expo app.
And I get this error:
While trying to resolve module #apollo/client from file /Users/andrepena/git/anglio-mobile-rn/screens/dictionary/index.tsx, the package /Users/andrepena/git/anglio-mobile-rn/node_modules/#apollo/client/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (/Users/andrepena/git/anglio-mobile-rn/node_modules/#apollo/client/main.cjs. Indeed, none of these files exist
Then I searched Stackoverflow and someone said I should add this to my metro.config.json
const { getDefaultConfig } = require("#expo/metro-config");
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push("cjs");
module.exports = defaultConfig;
But now, all imports from #apollo/client simply return undefined.
Example:
import { ApolloClient, InMemoryCache } from "#apollo/client";
console.log(ApolloClient); // undefined
console.log(InMemoryCache); // undefined
In fact, #apollo/client is exporting this object:
Object {
"default": 17,
}
Any suggestion?
This metro.config.js worked for me: (remember to install #expo/metro-config)
const { getDefaultConfig } = require('#expo/metro-config');
const config = getDefaultConfig(__dirname, {
// Initialize in exotic mode.
// If you want to preserve `react-native` resolver main field, and omit cjs support, then leave this undefined
// and skip setting the `EXPO_USE_EXOTIC` environment variable.
mode: 'exotic',
});
module.exports = config;
The exotic thing makes Metro to be able to find the weird cjs module that #apollo/client exports

Where to register postcss plugin in react app

In order to use this package, I think I need to register it. I've already installed it with npm.
I created the project with npx create-react-project my-project. I tried putting in the requirements into index.js like so...
import reportWebVitals from './reportWebVitals';
import postcss from 'postcss';
postcss([ require('postcss-modules-values-replace', require('postcss-calc'))])
But I got an error Cannot statically analyse 'require(...,...) and it looks like it's because I'm supposed to do something with webpack but I don't see any webpack.js files in my project so I'm not sure how to register the plugin.
Place it before other plugins:
postcss([ require('postcss-modules-values-replace'), require('postcss-calc') ]);
create postcss.config.js file and define the plugins
module.exports = (ctx) => ({
plugins: [
require('postcss-modules-values-replace')({fs: ctx.webpack._compiler.inputFileSystem}),
require('postcss-calc'),
]
});

React under Symfony and Webpack Encore, it doesn't find dependencies

Good day! I have the React application, which I want to make a part of my Symfony project. It is a structure of src folder of Symfony project:
src
Bundles
MyReactApp
components
Add.jsx
Article.jsx
News.jsx
data
newsData.json
App.css
App.jsx
index.jsx
It's a start of App.jsx:
import React from 'react'
import 'App.css';
import {News} from 'components/News';
import {Add} from 'components/Add';
import newsData from 'data/newsData'
class App extends React.Component {
...
I try to execute the command yarn encore dev and get an error:
These dependencies were not found:
* App.css in ./src/Bundles/MyReactApp/App.jsx
* components/Add in ./src/Bundles/MyReactApp/App.jsx
* components/News in ./src/Bundles/MyReactApp/App.jsx
* data/newsData in ./src/Bundles/MyReactApp/App.jsx
It's my webpack.config.js:
let Encore = require('#symfony/webpack-encore');
Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
.enableVersioning(Encore.isProduction())
.addEntry('react_app', './src/Bundles/MyReactApp/index.jsx')
.enableReactPreset()
.configureBabel(function(babelConfig) {
babelConfig.plugins.push('#babel/plugin-proposal-class-properties');
})
.enableBuildNotifications()
;
module.exports = Encore.getWebpackConfig();
Why I get the error? App.css is located near with App.jsx, but Webpack don't find it. I tried to use *.js instead of *.jsx, but it didn't give any changes.

babel-loader, webpack, ES2015 modules: "Element type is invalid"

Github repo with everything in: https://github.com/b-paul/react-lifecycle
Update 12/18: A large part of the problem was the npm commands used to run the project. I had noticed that npm build was not successful, but npm start reported building OK. Full answer below on why that didn't work as expected. The rest of this question is being kept for posterity.
I'm having trouble with basic setup for my first webpack project. I'm using React and Babel, and the following webpack.config.js:
var path = require('path');
module.exports = {
entry: [ path.resolve('./js/app.js'),
'webpack-dev-server/client?http://localhost:8080' ],
output: {
path: path.resolve('./js/build'),
filename: 'app.min.js'
},
module: {
loaders: [
{ test: /\.jsx?$/,
loader: 'babel',
query: {
presets: ['react', 'stage-1', 'es2015']
},
include: path.resolve('js/') } ]
},
devtool: 'source-map'
};
js/app.js
import Lifecycle from './components/lifecycle';
import React from 'react';
import {render} from 'react-dom';
var shell = document.createElement('div');
shell.className = 'app-shell';
document.body.appendChild(shell);
render(<Lifecycle />, shell);
js/components/lifecycle.js
import React from 'react';
class Lifecycle extends React.Component {
render() {
return (
<div>Hello, world</div>
);
}
}
export default Lifecycle;
The above builds without errors, but it won't render "Hello, world". I'm getting the error, "Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined." in the browser console.
I can trace this as far as some generated babel code that tries to check if my module Lifecycle is an ES6 module (babel recognizes it as such) and expects it to have a property default on the internal module object (it doesn't). Here is that generated code:
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _lifecycle = __webpack_require__(1);
var _lifecycle2 = _interopRequireDefault(_lifecycle);
var _react = __webpack_require__(2);
var _react2 = _interopRequireDefault(_react);
var _reactDom = __webpack_require__(159);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
(0, _reactDom.render)(_react2.default.createElement(_lifecycle2.default, null), document.body);
/***/ }
Final note: I referenced https://github.com/code0wl/react-example-es2015 several times for setup, and I can clone and build that example repo into a working app with no problems. I'm realize that I must've missed some essential part of what's happening in that repo, but I can't see it.
works for me
git clone https://github.com/b-paul/react-lifecycle.git
cd react-lifecycle.git
npm install
npm run build
npm run start
# go to http://localhost:8090/index.html
Is there are reason for creating your app's container div dynamically in app.js rather than just putting it in index.html? I cloned your repo and got the app to build by making the following changes:
index.html:
<body>
<div id="app-shell"></div>
<script type="application/javascript" src="js/build/app.min.js"></script>
</body>
app.js
import React from 'react';
import {render} from 'react-dom';
import Lifecycle from './components/lifecycle';
render(<Lifecycle />, document.getElementById('app-shell'));
I think the “Element type is invalid” error was a red herring. Prior to that I was seeing:
Warning: React.createElement: type should not be null, undefined,
boolean, or number. It should be a string (for DOM elements) or a
ReactClass (for composite components).warning #
app.min.js:2233createElement # app.min.js:19531(anonymous function) #
app.min.js:61__webpack_require__ # app.min.js:20(anonymous function) #
app.min.js:40(anonymous function) # app.min.js:43 app.min.js:2233
Warning: render(): Rendering components directly into document.body is
discouraged, since its children are often manipulated by third-party
scripts and browser extensions. This may lead to subtle reconciliation
issues. Try rendering into a container element created for your app.
Which is likely due to the way you are creating the shell element in app.js...
This question should have been called "why won't my NPM scripts update my bundle."
At some point early in development, I had successfully bundled my code with webpack. That bundle was on disk as js/build/app.min.js. After that point, I thought I was using NPM scripts from package.json to rebuild the app with each change, but in-browser, I was still getting the old behavior (from the original bundle). I missed two facts:
Fact 1: npm build is a recognized NPM command, but it does not invoke scripts.build from package.json. As in #zhongjie-wu 's answer, I needed to do npm run build. npm start, on the other hand, does invoke scripts.start, which successfully built a bundle with webpack-dev-server.
Fact 2: webpack-dev-server does not recognize output.path from the webpack config file as part of the route when serving the rebuilt bundle. Because of this, given the configuration I used, webpack builds to /js/build/app.min.js, but webpack-dev-server serves that bundle from memory as /app.min.js. Since my HTML references the original, I didn't see the dev server bundle.
Layout.js
export default class Layout extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="Layout">
</div>
)
}
};
.babelrc
need add stage-1 preset for try transpiling classes
{
"presets": ["stage-1", "es2015", "react"],
"plugins": ["transform-runtime"]
}
and entry file need doing ES5 export:
import React from 'react';
import Layout from './Layout';
import {render} from 'react-dom';
if (typeof document !== 'undefined') {
render(<Layout />, document.getElementById('app'));
}
module.exports = Layout;
its working for me

Resources