React component library can not load its own static asset - reactjs

I have a react app (using create react app) and then a component library (using webpack directly). One component in the component library needs to load a png file in one of the components that it exports. In the webpack config for the component library I have a section such as:
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
This successfully results in a file like: 29b6b66cf1a691be2b3f.png in the component libraries output directory. The issue, is that when the application uses that component and the component attempts to load the image, the img element is <img ... src="29b6b66cf1a691be2b3f.png" /> and that fails to load, since that image actually lives in the component library folder (within the react application's node_modules/component-library/ at that point).
I have scoured the internet to the best of my ability, and can not seem to figure out what the best solution would be here. Any help is appreciated. I will quickly offer clarification if needed.
UPDATE: I have discovered CopyWebpackPlugin but it is quite unfortunate that this would require me to eject the "parent" application from create react app. Something I would very much prefer to avoid.
UPDATE2: Current plan is to try following something like what is explained here. The jist of it is to utilize something like rewire to avoid needing to eject and still be able to edit the webpack config via something like:
// in ./build.js
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
// edit webpack config values here
... I will answer my own question here if I find that this approach works.

Alright, well this felt like it was much messier than it needed to be, but I was able to figure out a way to accomplish this. The pieces were as follows.
First, needed to install copy-webpack-plugin. This was not as simple as it might sound, because I needed to find one that would not conflict with the version of webpack required by my react-scripts (create react app). I determined that copy-webpack-plugin#6.4.1 was the last version to support webpack v4, so I installed that and then added the following to my package.json:
"overrides": {
"copy-webpack-plugin": {
"webpack": "4.44.2"
}
},
this ensured that it would use the version of webpack that my react-scripts installation was expecting.
Then, I also needed to install rewire, and add the following to a file called build.js:
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
const CopyPlugin = require("copy-webpack-plugin");
const patterns = [
{
context: "node_modules/component-library/path/",
from: "*.png",
to: "."
},
]
if(config.plugins === undefined){
config.plugins = [new CopyPlugin({patterns})]
}else{
config.plugins.push(new CopyPlugin({patterns}))
}
// below lets it work in dev mode too
if(config.devServer === undefined){
config.devServer = {
writeToDisk: true
}
}else{
config.devServer.writeToDisk = true;
}
Finally, had to update my build script to be:
"scripts": {
...
"build": "node ./build.js",
...
},

Related

Rename JS filles every build in React

Does anyone know how to change the names of the build's generated JS files, without using webpack? ex: every time you have a build change the filenames: 'bundle_v1.js', 'chunk_v2.js' (It doesn't necessarily need to be standardized).
Context: every time we publish our project through Azure, whether in development or production environment, we need to ctrl+f5 to clear the cache and show the changes.
I've already tried to add some configs in the config-overrides.js file but I wasn't successful.
Myconfig-overrides.js:
const {
addBabelPlugin,
override,
} = require('customize-cra');
const path = require('path');
module.exports = override(
addBabelPlugin([
'babel-plugin-root-import',
{
rootPathPrefix: '#/',
rootPathSuffix: 'src',
},
]),
);

Bundle CRA into single js file

I'm having a React App created with CRA, I need to deploy it as a widget.
People need to be able to include a js file and then just use the <App></App> Tag where ever they need it. Since CRA splits files I already tried ejecting the app, but failed miserably.
I already tried from similar questions the following:
combine-react-build-output-into-single-js-file
bundling without minification
the bundling without minification almost got my to the end. But now I would need to minify the resulting file.
This is my build.js file:
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
// Consolidate chunk files instead
config.optimization.splitChunks = {
cacheGroups: {
default: false
}
};
// Move runtime into bundle instead of separate file
config.optimization.runtimeChunk = false;
// JS
config.output.filename = '[name].js';
// CSS. "5" is MiniCssPlugin
config.plugins[5].options.filename = '[name].css';
config.plugins[5].options.publicPath = '../';
this file comes from the bundling without minification question. I'm now fairly clueless how to minify it and if it is really the right way to deploy.

How to do React Native-style multiple version builds for React with Webpack

Motivation
I am maintaining an app that is white-labelled for numerous separate brands, which vary mainly in style but also sometimes in core UX. The current (Backbone) solution involves keeping shared code in a separate repo and then building the separate apps with Grunt, with much of the style code and some view overrides for each project living in its own folder. We simply run all the grunt tasks one after the other using a shell script. We're going to build new versions of this thing in React going forward and want to minimize duplicate code, which has now become a major problem in the legacy version.
Desired outcome
The React Native packager builds two versions of its app at the same time. It looks at an import statement like import ComponentA from './ComponentA.js' and goes looking for either ComponentA.android.js or ComponentA.ios.js first, then falls back to importing ComponentA.js if it doesn't find a platform-specific one. I would like to replicate this behavior in Webpack. So I would like to have a folder that looks like this:
react_clients/src/components
|_ ComponentB.js // import ComponentA from './ComponentA.js';
|_ ComponentA.js
|_ ComponentA.brand1.js
|_ ComponentA.brand2.js
Webpack should build ComponentB.js as follows:
brand1.bundle.js imports from ComponentA.brand1.js
brand2.bundle.js imports from ComponentA.brand2.js
brand3.bundle.js and brand4.bundle.js import from ComponentA.js
This would also apply to styles, ideally with the same naming convention.
If necessary, Webpack could be run separately for each version, either using different webpack.config files or accepting command line arguments. The key thing is to avoid duplicating application code.
Current code
The starting point for Webpack is a freshly-generated and ejected create-react-app project.
PS: Apologies in advance if this turns out to be a duplicate but this has been a very tricky question to research. I suspect the answer will have something to do with an advanced configuration of https://webpack.js.org/configuration/resolve/ but can't figure it out yet.
Alright folks here's what I ended up doing:
Dev
In .env.development I specify a variable for the name of the project I want to do dev on:
REACT_APP_VERSION_NAME=brand1
Then in webpack.config.dev.js I take advantage of module resolution to achieve the behavior described above:
const JS_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.js`;
const STYLE_PROJECT_EXTENSION = `.${process.env.REACT_APP_VERSION_NAME}.pcss`;
const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];
...
module.exports = {
...
extensions,
...
}
Then in the code I can simply do the following:
import ComponentA from './componentA';
import Styles from './styles';
And everything works as expected.
Production
I don't specify REACT_APP_VERSION_NAME in .env.production. Instead the relevant config files export functions, and I iterate over the versions I want to build.
First, I created a separate version of config/paths.js that exports a function instead of a static object:
module.exports = function(projectName) {
return {
...
appBuild: resolveApp('build/' + projectName),
...
};
}
And my webpack.config.prod.js looks like this:
...
const getPaths = require('./paths.prod');
...
module.exports = function(projectName) {
const paths = getPaths(projectName);
const JS_PROJECT_EXTENSION = `.${projectName}.js`;
const STYLE_PROJECT_EXTENSION = `.${projectName}.pcss`;
const extensions = [JS_PROJECT_EXTENSION, '.js', '.json', '.jsx', STYLE_PROJECT_EXTENSION, '.pcss'];
...
return {
...
output: {
...
filename: projectName + '-assets/js/[name].[chunkhash:8].js',
chunkFilename: projectName + '-assets/js/[name].[chunkhash:8].chunk.js',
...
}
... [etc, adding projectName to any output that needs to be built separately]
};
}
Finally, just wrap most of the action in scripts/build.js in a loop:
...
[various imports]
...
process.argv[2].split(' ').forEach(projectName => {
const config = require('../config/webpack.config.prod')(projectName);
const paths = require('../config/paths.prod')(projectName);
...
[rest of build.js as normal]
}
After that it's just a matter of pointing your server at the right files for each version, and running yarn build "brand1 brand2" when you want to build.
Going to accept this answer since it's working for me for now but would love to hear about potential improvements from anyone who comes across it in the future.

Customizing antd Less or Css for create-react-app

I am using create-react-app as starter kit and was having a go at antd component library.
I can't seem to find a way to customize their styles (for branding purpose). Documentation doesn't seem to give clear directions regarding customization.
Docs link: https://ant.design/docs/react/customize-theme
They suggest two ways to do it though :
1.Using theme property . But this only works for antd-init or dva-cli boilerplates and not for create-react-app
2.Overriding Less variables .
Now to make the either of these option work for create-react-app without eject , what are the steps I need to take?
You question is not very well researched I am afraid. At a minimum, read the obvious resurces like the instructions at https://ant.design/docs/react/use-with-create-react-app and older Stackoverflow questions (like How do I use .babelrc to get babel-plugin-import working for antd?) before posting a question.
As it stands, CRA does not support changing the Babel configuration. This is needed to include the antd babel-plugin, which is needed to support importing only the necessary antd modules.
As a result you can only import the whole monolithic antd in a non-ejected CRA app.
if you don't intend to use eject you should use config-overrides.js to specify how you want to modify the default webpack.config.js that is part of react-scripts. Then once you have a config-overrides you can add a less Rule that has an option for modifyVars. Here you can specify any changes to the less variables.
function addLessRule(config, env)
{
const { getLoader, loaderNameMatches } = require("react-app-rewired");
const fileLoader = getLoader(
config.module.rules,
rule => loaderNameMatches(rule, 'file-loader')
);
const lessExtension = /\.less$/;
fileLoader.exclude.push(lessExtension);
const cssRules = getLoader(
config.module.rules,
rule => String(rule.test) === String(/\.css$/)
);
var lessModifyVars = {
'primary-color': '#990000',
'link-color': '#1DA57A',
'border-radius-base': '2px',
'layout-header-background' : '#f0f2f5'
};
var lessLoaders = [
{ loader: 'style-loader'} ,
{ loader: 'css-loader' },
{ loader: 'less-loader', options: {
modifyVars: lessModifyVars
} }
];
lessRule = {
test : lessExtension,
use: lessLoaders,
}
config.module["rules"].push( lessRule );
return config
}
If you want to customize antd without using react eject and don't want to change use forked versions of create-react-app or react scripts then
you can simply create a less file (let's say main.less ) , import antd.less and replace the default variables of ant design in this main.less file.
Now compile this less file using lessc (npm i -g less).
lessc "main.less antd.css" --js
--js is for inline javascript in less
Now simply include these complied css in your app.
take a look at https://medium.com/#aksteps/782c53cbc03b

`requestAnimationFrame` polyfill error in Jest tests

Got this error after upgrading to React when I ran my Jest unit tests:
React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers.
How do I fix it?
I'm using Jest 18.1.0.
Found a workaround!
Steps:
Create the file __mocks__/react.js
Add the following into __mocks__/react.js
const react = require('react');
// Resolution for requestAnimationFrame not supported in jest error :
// https://github.com/facebook/react/issues/9102#issuecomment-283873039
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
throw new Error('requestAnimationFrame is not supported in Node');
};
module.exports = react;
Run jest !
As marked on comments on the code
This is the solution from
https://github.com/facebook/react/issues/9102#issuecomment-283873039
this worked for me:
Install raf
npm install --saveDev raf or yarn add -D raf
Add the polyfill to your setupFiles in your jest config in package.json like this:
'setupFiles': ['raf/polyfill']
Note: if you have other setup files in this array, you may want to put raf/polyfill first.
If you just need to polyfill it for tests, then you don't actually need the throttling.
Create a new file with this code:
global.requestAnimationFrame = function (cb) {
return setTimeout(cb, 0);
};
Add that file to the jest/setupFiles array in your package.json.
If you are using create-react-app, some of these solutions will not work well (or at all, in the case of setupFiles). What does work well is creating a file at src/setupTests.js and adding your mock in there:
global.requestAnimationFrame = (cb) => { cb(); };
You can also add other global mocks in there (e.g. localStorage and navigator.serviceWorker).
Another working solution!
The idea is to load a simple shim before each spec, by using the setupFiles property in the jest config.
Create a file shim.js file (preferably in your root dir) and have this code in it:
global.requestAnimationFrame = (callback) => {
setTimeout(callback, 0);
};
Next, you may be having redundant code that keep reappearing in all/most of your files - and you want to put them in a single file and have them run before each spec also, to do that:
Create a setup.js file in the root dir too. A good piece of redundant code to D.R.Y is the react enzyme adapter configuration code. Paste it here
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
Now create the jest.config.js file, to specify the paths of the two files
{
module.exports = {
"setupFiles": ["<rootDir>shim.js", "<rootDir>setup.js"]
}
}
N.B: The jest config file take json, so make sure json's in. Also, if your shim.js and setup.js files are not in the same dir as your jest.config.js, adjust the path accordingly.
Hope this helps!
Credit: https://github.com/facebook/jest/issues/4545
Here's a realistic way to mock requestAnimationFrame:
let time;
const maxTimeElapsed = 2000;
beforeEach(() => {
time = 0;
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
time += 100;
if (time < maxTimeElapsed) {
return cb(time) as any;
}
});
});
in your test, it repeatedly calls the RAF callback until it reaches the max elapsed time that you set. It happens instantly, so you don't need to stall it.
Just upgrade your react-scripts to 1.0.15 or above. It has been officially fixed after that version. See more details in https://github.com/facebook/create-react-app/issues/3199
Search the comment of gaearon commented on 31 Oct 2017
If you use TypeScript, the best solution is;
window.requestAnimationFrame = (): number => {
window.clearTimeout();
return 0;
};
Before all your describe and test suits.
According raf package docs at this moment need to run polyfill() function and specify object that need to be polyfilled. In my case works:
require('raf').polyfill(global.window)
Turns out it was because I upgraded enzyme without upgrading react and react-dom.
React 15.5 brings about some deprecations that caused many dependent libraries to have to update too. Make sure you are updating react, react-dom and check the README of those dependent packages for new libraries you have to install. For instance, Enzyme 2.8.2 now requires react-addons-test-utils as a dependency.

Resources