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
Related
I have a react application with a custom Webpack configuration.
After adding Webpack aliases that matches tsconfig.json file compilerOptions->paths field the aliases were recognized by webpack.
Since storybook comes with a built in Webpack configuration, my aliases are not read by Storybook and I'm getting the following error:
Module not found: Error: Can't resolve <path with typescript alias> in <some folder path>
In Storybook main.js file, add the following:
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
...,
webpackFinal: async (config, { configType }) => {
config.resolve.plugins = [new TsconfigPathsPlugin()];<-- this line
return config;
}
};
You can install tsconfig-paths-webpack-plugin using the following command from the folder in which your application's package.json file resides:
npm i tsconfig-paths-webpack-plugin -D
Solution was derived from this discussion:
https://github.com/storybookjs/storybook/issues/6316
For future vistors of this question, since 15th July of 2022 storybooks can use Vite instead Webpack.
In that case I recommend using vite-tsconfig-paths instead of tsconfig-paths-webpack-plugin. If you are using TS paths in Vite, you probably already have this package installed.
Add this to your .storybook/main.js
const { mergeConfig } = require("vite")
const { default: tsconfigPaths } = require('vite-tsconfig-paths')
module.exports = {
// your previous configs and more...
viteFinal(config, { configType }) {
return mergeConfig(config, {
plugins: [
tsconfigPaths()
]
})
}
}
An alternative to accepted solution:
If you prefer not to install an external library such as tsconfig-paths-webpack-plugin, you can create a custom file, say:
tsconfig-webpack-utils.js
and do something similar to the following:
const { compilerOptions } = require('../tsconfig.json');
function getAliases() {
const baseUrl = getTSBaseUrl();
return Object.fromEntries(Object.entries(compilerOptions.paths).map(([key, value]) => {
return [
key.replace(/\/\*\*?$/,''),
value.map(entryPath => path.resolve(__dirname, baseUrl, entryPath.replace(/\/\*\*?$/,'/')))
]
}));
}
function getTSBaseUrl() {
return path.resolve(__dirname, `../${compilerOptions.baseUrl}`);
}
exports.addTsDefinitionsToWebpack = function(webpackConfig) {
if (!webpackConfig.resolve.modules) {
webpackConfig.resolve.modules = ['node_modules'];
}
webpackConfig.resolve.modules.push(getTSBaseUrl());
webpackConfig.resolve.alias = {
...webpackConfig.resolve.alias,
...getAliases()
};
}
This solution only works for very simple aliases. It is recommended to use an appropriate library or to expand this solution according to your needs.
You can then use it as follows in every webpack config you require it:
addTsDefinitionsToWebpack(webpackConfig);
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
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;
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?
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.