'window is not defined' error when using style-loader with webpack - reactjs

Building a server side react app and while using Webpack I am having issues with Style-Loader.
I am using version "^0.23.1" and when running a script to bundle and build there is an issue from Style-Loader.
The issue is window is not defined
webpack:///./node_modules/style-loader/lib/addStyles.js?:23
return window && document && document.all && !window.atob;
Has anyone run into this issue? After looking through Stack and the Github issues for style-loader I am not finding any solution.
Here is my webpack file:
const path = require('path');
const webpack = require('webpack');
module.exports = {
// webpack to use node
target: 'node',
entry: './src/index.js',
output: {
filename: 'client-build.js',
path: path.resolve(__dirname, 'build/public'),
publicPath: '/build/public'
},
module: {
rules: [
{
test: /\.js$|\.jsx$/,
loader: 'babel-loader',
exclude: '/node_modules/',
options: {
presets: [
'#babel/preset-react'
]
}
},
{
test: /\.(s*)css$/,
loader: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.jpeg$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$|\.jpg$|\.pdf$/,
loader: 'file-loader',
query: {
name: 'assets/img/[name].[ext]'
},
},
]
},
plugins: [
new webpack.ProvidePlugin({
"React": "react",
}),
],
}
If there is anything else you need to see I can post it.

style-loader tries to inject styles into the head of the website (window / document), which will be non-existent on your server on render / execution.
You need to remove this loader from your server-config and replace it with something else (e.g. ExtractTextPlugin or MiniCSSExtractplugin, depending on your webpack version)

I think your problem is, that there is no window object when running js code on a node server. Which also makes sense, as your server has no window where your code is rendered. You can use the global object for global references instead, see this related post here: Does node.js have equivalent to window object in browser

If I got it correctly I think you are trying to use style-loader for bundling server side code.If it is the case try doing this instead of doing this:
loader: ['style-loader', 'css-loader', 'sass-loader']
Try this:
loader: ['css-loader/locals', 'sass-loader']
Style loader is not supposed to be used on the server side code. So we provide kind of a null-loader instead of css-loader and remove style loader. This should do the trick I guess.

I had a this problem where I needed some themes and styles from a component-library which in turn used webpack and style-loader.
My project was pure script and is supposed to generate some files and therefore had no browser. It would not compile at all since style-loader(and some other libs) tried to inject styles in the tag.
I ended up mocking window and document so that the imported project could compile.
NOTE that this worked in my case where I only needed a minor part of my component-library, if you use this in a more complicated project there will probably be some weird bugs. But it might help someone figure out a similar problem
Run this before you do the actual import
Since it is the actual import that causes the problem you need to do the hack before importing.
import * as Hack from './hack/StyleLoaderHack';
Hack.runHack();
...
import {X} from 'your library'
StyleLoaderHack.js
class HackStyle {
position;
constructor() {
this.position = [];
}
}
class HackElement {
className;
childNodes;
style;
constructor(tag) {
this.className = tag;
this.attributes = [];
this.childNodes = [];
this.style = new HackStyle();
}
appendChild = (child) => {
let append;
if (!(child instanceof HackElement)) {
append = new HackElement(child);
} else {
append = child;
}
this.childNodes.push(append);
return append;
};
insertBefore = (newChild, refChild) => {
let insert;
if (!(newChild instanceof HackElement)) {
insert = new HackElement(newChild);
} else {
insert = child;
}
this.childNodes.push(insert);
};
setAttribute = (qualifiedName, value) => {
// sketchy but works
this.attributes.push(qualifiedName);
this.attributes.push(value);
};
}
class HackDocument {
head;
constructor() {
this.head = new HackElement("head");
}
createElement = (tagName) => {
const element = new HackElement(tagName);
return element;
};
querySelector = (target) => {
const node = new HackElement(target);
return node;
};
querySelectorAll = (target) => {
if (target === "[data-emotion-css]") {
return [];
}
const node = new HackElement(target);
return [node];
};
createTextNode = (data) => {
return new HackElement(data);
};
}
/**
* Adds some function to global which is needed to load style-loader, emotion, create-emotion and react-table-hoc-fixed-columns.
*/
export const runHack = () => {
global.window = {};
const hackDocument = new HackDocument();
global.document = hackDocument;
};

Related

How to add typescript paths to storybook

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);

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

mp3 files in Webpack 5 w/ Nextjs

I'm currently working with next#11.1.2 and webpack v5 and got stuck for hours with fixing mp3 loading. I tried several other solutions from stack and GitHub. None of them worked for me.
Type error: Cannot find module 'public/sounds/bighit.mp3' or its corresponding type declarations.
14 |
15 | // Assets
> 16 | import sound_bighit from "public/sounds/bighit.mp3"
| ^
info - Checking validity of types .%
Here is my last configuration for webpack:
const path = require('path')
const SRC = path.resolve(__dirname, 'public/sounds/')
module.exports = {
webpack: (config, { }) => {
config.module.rules.push({
test: /\.mp3$/,
incluse: SRC,
use: {
loader: 'file-loader',
options: {
name: '[name].[contenthash].[ext]',
outputPath: 'public/sounds/',
publicPath: 'public/sounds/'
}
}
})
// config.module.rules.push({
// test: /\.mp3$/,
// use: {
// loader: 'file-loader',
// },
// })
// config.module.rules.push({
// test: /\.mp3/,
// use: {
// loader: 'url-loader',
// },
// })
return config
}
}
There is no need to import such files. Next.js supports putting the assets in public folder. Remove your custom webpack configuration and then simply do this:
<audio controls src="/sounds/bighit.mp3" />
Refer: Static File Serving
Next.js can serve static files, like images, under a folder called public in the root directory. Files inside public can then be referenced by your code starting from the base URL (/).
Also, the error that you were getting was a TypeError, to fix it you can try:
// types/sounds.d.ts
declare module "*.mp3" {
const content: string;
export default content;
}
Refer: Importing Other Assets | TypeScript - webpack

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?

Debugging create-react-app Build used for Unpacked Chrome Extension

My current build process is to build the project with create-react app, then load the unpacked build as an extension. From there, I launch it, but when I do, I get an error in my main.[hash].js:formatted file.
Normally, I'd click on that error to go to the line number, drop a debugger, reload, and then figure out what's causing the problem. However, the file seems to not be source-mapped. I can't step through this code:
{
key: "render",
value: function() {
var e = this
, t = this.state
, n = t.chromeVersion,
/// ... props, etc
, v = this.props.aProp,
, u = this.state.aStateVal,
, _ = Object(H.b)(this.state.anotherVal)
return p.a.createElement("div", {
// etc etc
which should instead look something like this:
render() {
const {
chromeVersion,
// other state values
} = this.state;
const error = this.props.aProp
const display = this.state.aStateVal,
let getStuff = aFunction(
this.state.anotherVal
);
return (
<div /...
// etc etc
my webpack.config.js at the base of the project looks like this:
const webpack = require("webpack");
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = "development";
}
module.exports = {
entry: `${__dirname}/src/index.js`,
output: {
path: `${__dirname}/../build`,
filename: "background.js"
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
query: {
babelrc: false,
presets: [require.resolve("babel-preset-react-app")]
}
}
]
},
plugins: [
// there used to be an uglify here, but I've removed it
],
devtool: 'source-map'
};
I run my build with the following commands:
GENERATE_SOURCEMAP=true NODE_ENV=development react-scripts build
&& webpack --config path/to/webpack.config.js
&& node /path/to/prepareManifestForExtension.js // Needed for chrome app
What am I missing to be able to debug my code when running my chrome app? How do I get error messages that point to the source mapped line?

Resources