Rename JS filles every build in React - reactjs

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',
},
]),
);

Related

React component library can not load its own static asset

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",
...
},

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.

Webpack Config multiple entry points error with filter

I've created a React App using create-react-app and have ejected it to get more control over the build process and to turn it into a Browser Extension.
I've got the build to work correctly if there's only one input file, but now as I'm trying to create 2 extra files (which need to be separate from the main as they'll be background files), I'm getting errors while building with no idea why it's happening. I just need to have 1 file with an html page with javascript (react) and the other 2 I just need to be javascript files for the extension functions.
This is the original entry point in the Webpack file:
entry: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"),
// Finally, this is your app's code:
paths.appIndexJs
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
So that the system can spit out separate javascript files, I added in 2 more entrypoints like so:
entry: {
app: [
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"),
paths.appIndexJs
].filter(Boolean),
background: [
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"),
paths.backgroundIndexJs
].filter(Boolean),
content: [
isEnvDevelopment &&
require.resolve("react-dev-utils/webpackHotDevClient"),
paths.contentIndexJs
].filter(Boolean)
},
The appIndex- backgroundIndex, and contentIndex are all defined in the path file and pointing to valid directories.
When trying to run npm run build on this, I keep getting the error:
Cannot read property 'filter' of undefined
and cannot figure out what is going on with this.
I can't find out anything regarding why this entry point requires the ".filter(Boolean)" or where I can turn it off so it's not required when building.
I thought maybe I could build like this:
entry: {
app: paths.appIndexJs,
background: paths.backgroundIndexJs,
content: paths.contentIndexJs
},
but this also returns the same error with canot read property 'filter' of undefined.
Out of curiosity I tried the following which I understand merges the files together:
entry: [
paths.appIndexJs,
paths.backgroundIndexJs,
paths.contentIndexJs
].filter(Boolean),
And the project builds but even then, I don't see any of the functions from the 2 different files show up.
I've created React Apps from scratch and I know that the multiple entry points method works to generate multiple files, but when the project was created using the create-react-app command and ejected, I cannot get the entry point to work as expected.
I think the problem here is not how the entry is being set, but the errors that show when trying to build without the "filter(Boolean)" set on the entry line.
The only difference I can see from the "usual" and the ejected create-react-app is that the "usual" way for the webpack looks like this:
// This has worked in the past when making configs from scratch
module.exports = {
entry: {
app: paths.appIndexJs
background: paths.backgroundIndexJs
paths.contentIndexJs
},
and ejected create-react-app looks like this:
// this is auto generated and unsure if is the reason why it fails to
module.exports = function(webpackEnv) {
const isEnvDevelopment = webpackEnv === "development";
const isEnvProduction = webpackEnv === "production";
......
return {
entry: {
app: [paths.appIndexJs]
background: [paths.backgroundIndexJs],
content: [paths.contentIndexJs]
},
.....
};
};
Unsure if this has any effect on the entry points but I can't see anything else that could be relevant to this.
What is the error message filter(Boolean) and how can I get around this?
found the solution here from gitmemory
The problem was in the ManifestPlugin configuration from CRA. It assumes that the entry point will be called main, the default from Webpack:
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: publicPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter( // <--- This line here
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
fix was
...
const entrypointFiles = {};
Object.keys(entrypoints).forEach(entrypoint => {
entrypointFiles[entrypoint] = entrypoints[entrypoint].filter(fileName => !fileName.endsWith('.map'));
});
...

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.

How to extract multiple theme stylesheets with webpack?

I am trying to make a React app themeable. For now themes only consist of different sets of Sass variables which define different header colors, etc.
From my current understanding the ExtractTextPlugin seems to be my best bet as I don't want my styles to be inlined and rather have separate files per theme.
So I created two themes:
src/scss/themes/theme-a.scss
src/scss/themes/theme-b.scss
The themes import the basic layout and common styles and override the relevant variables.
But the only way I managed to make webpack create separate css files
for both themes was to create distinct entry points for each theme in
my webpack.prod.config:
entry: {
app: './src/js/init.js',
theme-a: './src/scss/themes/theme-a.scss',
theme-b: './src/scss/themes/theme-b.scss'
},
But adding a new entry point for every new theme that gets added feels wrong and I figure there must be a better way?
Here's your solution:
npm i --save-dev file-loader
In the loaders section, add this:
{
test: /themes\/.+\.scss$/,
loader: "file-loader?name=./compiled-themes/css/[name].css!css!sass"
},
There may be more scss files, if so there must be another section which bundles them as usual, but skips the themes:
{
test: /\.scss$/,
exclude: /themes\/.+\.scss$/,
loader: "css!sass"
},
The file loader writes files by filename, hash and extension so you are able to preserve the name.
Note the name=./compiled-themes/css/[name].css part, where [] vars are substituted.
https://github.com/webpack/file-loader
To avoid having to manually add the themes one possible solution is to create a function that reads contents of the themes folder and programatically add an entry for each *.scss file.
Something along the lines of this:
function getThemes(themePath) {
var themes = {};
fs.readdirSync(themePath).forEach(function(fileName) {
var fileNameWithPath = path.join(themePath, fileName);
var stat = fs.lstatSync(fileNameWithPath);
if (stat.isDirectory()) return;
if (!/\.scss$/.test(fileName)) return;
var nameWithoutExt = path.basename(fileName, '.scss');
themes[nameWithoutExt] = fileNameWithPath;
});
return themes;
}
var themes = getThemes('./src/scss/themes');
var config= {
entry: _.merge({ app: './src/js/init.js' }, themes),
// rest of options
};
This will require you to restart your dev-server or re-run you webpack build when adding new theme files though, but at least you won't have to touch your webpack config.

Resources