How to set buildId to config (publicRuntimeConfig) or environment variable? - reactjs

We're using nextjs (9.5.3) and next-i18next (6.0.3) for translations. To implement a special caching, I need to access the build ID outside of next.config.js in i18n.js in order to set it to the locale path:
localePath: path.resolve(`./public/static/cache/${config.buildId}/locales`)
In next.config.js I can access the build ID pretty easily:
withPWA({
webpack(config, { buildId }) {
[...]
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
context: path.join(__dirname, 'public/static/locales'),
from: '**/*',
to: path.join(__dirname, `public/static/cache/${buildId}/locales`)
}
]
})
);
// not working:-(
process.env.CONFIG_BUILD_ID = buildId;
return config;
},
publicRuntimeConfig: {
buildId: process.env.CONFIG_BUILD_ID
}
});
However setting it to the environment variable process.env.CONFIG_BUILD_ID is not working, so publicRuntimeConfig.buildId will still be undefined.
Is there any way to access the build ID outside the next config?

Instead of
// not working:-(
process.env.CONFIG_BUILD_ID = buildId;
You should use Webpack DefinePlugin (https://webpack.js.org/plugins/define-plugin/). No extra imports or installs are needed: this plugin is already a part of the next.js package. Just replace those lines with:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.CONFIG_BUILD_ID': JSON.stringify(buildId)
})
);
Full next.js.config may look like this:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
config.plugins.push(
new webpack.DefinePlugin({
'process.env.CONFIG_BUILD_ID': JSON.stringify(buildId)
})
);
return config;
}
}
module.exports = nextConfig;

Related

Invalid hook call. When using external folders with webpack

I have divided my app into folders to be able to reuse code. My project consists of two folder common and common-web that I need to use in my customer-web-app project.
This is the directory structure
The customer-web-app is using razzle and its webpack config is
module.exports = {
modifyWebpackConfig({
env: {
target, // the target 'node' or 'web'
dev, // is this a development build? true or false
},
webpackConfig, // the created webpack config
webpackObject, // the imported webpack node module
options: {
razzleOptions, // the modified options passed to Razzle in the `options` key in `razzle.config.js` (options: { key: 'value'})
webpackOptions, // the modified options that will be used to configure webpack/ webpack loaders and plugins
},
paths, // the modified paths that will be used by Razzle.
}) {
console.log("TT:", webpackConfig, paths);
const copy = Object.assign({}, webpackConfig);
copy.resolve.modules = (copy.resolve.modules || []).concat([
path.resolve(__dirname, "../common-web/src"),
path.resolve(__dirname, "../common/src"),
path.resolve(__dirname, "../admin-common/src"),
]);
copy.resolve.alias = {
...(copy.resolve.alias || {}),
"#material-ui/core": path.resolve(
__dirname,
"./node_modules/#material-ui/core"
),
react: path.resolve("./node_modules/react"),
"react-dom": path.resolve("./node_modules/react-dom"),
"react-router-dom": path.resolve(
__dirname,
"./node_modules/react-router-dom"
),
"#common": path.resolve(__dirname, "../common/src"),
"#customer": path.resolve(__dirname, "../customer-common/src"),
"#web": path.resolve(__dirname, "../common-web/src"),
};
// Do some stuff...
return copy;
},
modifyWebpackOptions({
env: {
target, // the target 'node' or 'web'
dev, // is this a development build? true or false
},
options: {
webpackOptions, // the default options that will be used to configure webpack/ webpack loaders and plugins
},
}) {
webpackOptions.notNodeExternalResMatch = (request, context) => {
return /\#web|\#common|\#customer/.test(request);
};
webpackOptions.babelRule.include = webpackOptions.babelRule.include.concat([
path.resolve("../common-web/src"),
path.resolve("../common/src"),
path.resolve("../customer-common/src"),
]);
return webpackOptions;
},
};
But when I run the app its giving me
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

Webpack obfuscator not working with craco, maps disabled

today I have a very large problem using react & craco, I can't seem to get my webpack-obfuscator to do anything. I have disabled source maps, but to no avail.
This is my craco config:
const path = require("path");
const WebpackObfuscator = require('webpack-obfuscator');
module.exports = {
webpack: {
configure: (webpackConfig) => {
// Because CEF has issues with loading source maps properly atm,
// lets use the best we can get in line with `eval-source-map`
if (webpackConfig.mode === 'development' && process.env.IN_GAME_DEV) {
webpackConfig.devtool = 'eval-source-map'
webpackConfig.output.path = path.join(__dirname, 'build')
}
return webpackConfig
},
plugins: {
add: [
new WebpackObfuscator ({
rotateStringArray: true
}),
],
},
},
devServer: (devServerConfig) => {
if (process.env.IN_GAME_DEV) {
// Used for in-game dev mode
devServerConfig.writeToDisk = true
}
return devServerConfig
}
}
I get no visible maps files when building, and I've put "GENERATE_SOURCEMAP=false" in my .env file that's located where the package.json is.
Hopefully someone has the answer as to why this is happening.
Kind regards, and thanks for reading.
To upgrade a short config, you can use a construct that, if the condition is met, updates the configuration without using WebpackObfuscator:
module.exports = {
webpack: {
configure: {
...(process.env.IN_GAME_DEV && process.env.NODE_ENV === 'development' && {devtool: 'eval-source-map'})
}
}
}
Also, if you need additional properties for the configuration, in addition to the dvttool, you can add them

Storybook 6, use module paths?

Is it possible to configure storybook 6 to use the module paths in my tsconfig.json file to work with sass-loader (or just to replicate the same pattern if that's not possible).
Ideally, I'd like to be able to add a sass loader with this option:
additionalData: `
#use '#themes' as vars;
#use '#themes/breakpoints' as bp;
`,
instead of
additionalData: `
#use '../themes' as vars;
#use '../themes/breakpoints' as bp;
`,
My tsconfig.json file has this section in it which works well inside .ts files but obviously doesn't work in sass files:
"paths": {
"#components/*": ["./components/*"]
}
If I could replicate that for themes, that'd be amazing.
Turns out it was pretty easy:
in main.js. Add the following to your module.exports:
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
"#themes": path.resolve(__dirname, "../themes/default")
}
return config;
}
Actually, past Alex, there is a better solution now using the ts-config-paths-webpack-plugin that is automatic and doesn't require repeating config code:
// main.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const pathsPlugin = new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, '../tsconfig.json')
})
webpackFinal: async (config) => {
if (config.resolve.plugins) {
config.resolve.plugins.push(pathsPlugin);
} else {
config.resolve.plugins = [pathsPlugin];
}
...

How to implement react-dates css in css-modules?

so react-dates css worked fine until i moved to css-modules. i tried importing the css file from node modules in index.js files and in the head of the html file but none of them worked.
Any help or suggestion will be greatly appreciated.
This is working perfectly for this problem
In webpack.config.js define a function which checks your file to
decide if it's css module or global; done using getLocalIdent
option.
This is the method that I'm currently using in my setup.
This also requires your files to have some naming convention,
[name].module.css for css modules and [name].css for regular files.
See example below:
// regex to test for modules, loaderUtils is part of webpack dependencies
const cssModuleRegex = new RegExp(/\.module\.(less|css)$/);
const loaderUtils = require("loader-utils");
// inside webpack rules
{
test: /\.(less|css)$/,
use: [
{
loader: CssExtractPlugin.loader,
options: { hot: is_dev, reloadAll: is_dev }
},
{
loader: "css-loader",
options: {
modules: {
localIdentName: '[local]___[hash:base64:5]',
getLocalIdent: getLocalIdent
}
}
},
"postcss-loader",
"less-loader"
]
}
// this is a copy of the default function, modified slightly to achieve our goal
function getLocalIdent(loaderContext, localIdentName, localName, options) {
// return local name if it's a global css file
if (!cssModuleRegex.test(loaderContext.resourcePath)) {
return localName;
}
if (!options.context) {
// eslint-disable-next-line no-param-reassign
options.context = loaderContext.rootContext;
}
const request = path
.relative(options.context, loaderContext.resourcePath)
.replace(/\\/g, '/');
// eslint-disable-next-line no-param-reassign
options.content = `${options.hashPrefix + request}+${localName}`;
// eslint-disable-next-line no-param-reassign
localIdentName = localIdentName.replace(/\[local\]/gi, localName);
const hash = loaderUtils.interpolateName(
loaderContext,
localIdentName,
options
);
return hash
.replace(new RegExp('[^a-zA-Z0-9\\-_\u00A0-\uFFFF]', 'g'), '-')
.replace(/^((-?[0-9])|--)/, '_$1');
}
Source: How to apply global styles with CSS modules in a react app?

Webpack: process.env undefined using DefinePlugin and DotEnv

I would like to get my variable from my .env file but I always get undefined
This is my js code :
require('dotenv').config();
class Header extends React.Component{
constructor(props){...}
render(){
console.log("NODE ENV", process.env.NODE_ENV);
console.log("REACT_APP_MYAPP", process.env.REACT_APP_MYAPP);
...
}
}
This prints :
NODE_ENV development
REACT_APP_MYAPP undefined
In my package.json there is :
"scripts":{
"start" : "webpack-dev-server --config webpack.dev.js",
"build" : "webpack --config webpack.prod.js"
}
And in my webpack.dev.js:
const webpack = require("webpack");
const merge = require("webpack-merge");
const path = require("path");
const common = require("./webpack.common.js");
module.exports = merge.smart(common, {
devServer: {
contentBase: path.resolve(__dirname, "dist"),
hot: true,
overlay: {
warnings: true,
errors: true
},
inline :true,
historyApiFallback: true,
port: 8085
},
devtool: "inline-sourcemap",
optimization: {
namedModules: true
},
plugins: [
new webpack.HotModulReplacementPlugin(),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
"process.env.REACT_APP_MYAPP": JSON.stringify(process.env.REACT_APP_MYAPP)
})
],
mode: "development"
});
And I placed my .env file at the root of my project, next to webpack.dev.js and package.json:
REACT_APP_MYAPP=http://localhost:8080/
So I think, it doesn't success to get the variable in the file.
How can I get the REACT_APP_MYAPP value in the code please ?
Journeying through the deep, dark rabbit hole will lead you to the following facts:
Webpack 5 no longer provides process or other Node.js variables.
The role of DefinePlugin requires redefining.
Values passed to DefinePlugin must be stringified, even if they're strings.
EnvironmentPlugin makes things more confusing, given Webpack 5.
process.env isn't really something you should be using in the frontend.
You can use polyfills to re-add process and others back in, but there is a reason it's no longer available.
dotenv-wepack is neither necessary nor the way to go.
The reason for using process.env is to access variables globally. But it's a passenger on the wrong flight. We can drop it and instead just access the intended variable directly:
plugins: [
new DefinePlugin({
// With dotenv (values must be stringified)
...Object.entries(dotenv.config().parsed).reduce((acc, curr) => ({...acc, [`${curr[0]}`]: JSON.stringify(curr[1]) }), {}),
// Without dotenv
'myString': JSON.stringify('IAmAString')
})
]
In the frontend:
declare var myString: string;
console.log(myString); // 'IAmAString'
If you have several objects with variables it makes sense to abstract the stringification:
// Create a function to stringify values
function stringifyValues(object: {[key: string]: any;}){
return Object.entries(object).reduce((acc, curr) => ({...acc, [`${curr[0]}`]: JSON.stringify(curr[1]) }), {} as { [key: string]: string; });
}
// use with DefinePlugin
plugins: [
new DefinePlugin({
...stringifyValues(dotenv.config().parsed),
...stringifyValues(dotenv.config({ path: '/.env.special' }).parsed),
'myObject': stringifyValues({
name: 'Object',
description: 'to be an object'
})
})
]
If you really want access to process.env:
plugins: [
new DefinePlugin({
// this might expose confidential data about your environment
'process.env': JSON.stringify(process.env),
// the correct way
'process.env.USERNAME': JSON.stringify('Donald Hump')
})
]
In the frontend:
declare var process: any;
console.log(process) // will NOT work, because process hasn't been injected
console.log(process.env); // will work but risky
console.log(process.env.USERNAME); // correct: 'Donald Hump'
First solution by adding REACT_APP_MYAPP in start didn't worked.
But second solution worked.
SOLUTION:
Adding require('dotenv').config() file inside my webpack.dev.js and replacing :
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
"process.env.REACT_APP_MYAPP": JSON.stringify(process.env.REACT_APP_MYAPP)
})
with
new webpack.EnvironmentPlugin(['NODE_ENV', 'REACT_APP_MYAPP']);
Thank you!
There's a few ways you could make this work.
The easiest to test is to change your "start" : "webpack-dev-server --config webpack.dev.js", to "start" : "REACT_APP_MYAPP=http://localhost:8080/ node webpack-dev-server --config webpack.dev.js",
This will inject the environment variable and it will be available during the webpack build process. You can use this technique whenever using npm or node to run a command. For instance, NODE_ENV='development REACT_MY_APP=http://localhost:8080/ node myapp.js and both will be available on process.env.
You could also call your require('dotenv').config() file inside your webpack.dev.js. Then it would be set during your usage of the DefinePlugin.
Generally, you don't use the npm start command to run your development server.
As the application grows you should look at the Webpack Environment Plugin. Then you can use the .env for the "production" build settings, requiring it in the production webpack.prod.js, while setting your default/fallback environment variables using the plugin.
It works for me in my custom React app model, only if I do not include 'REACT_APP_' in variable names:
in webpack.config.js:
// Node import
const path = require('path');
const webpack = require('webpack');
require('dotenv').config();
module.exports = [
plugins: [
new webpack.EnvironmentPlugin(['NODE_ENV', 'API'])
],
]
in .env:
NODE_ENV=development
API=http://localhost:5000
When I deploy the app though (on Netlify for example), I exclude NODE_ENV from the production environment variables.

Resources