So far I am not able to properly integrate xterm.js with reactjs due to which my code breaks in production but works while development.
HELP !!!
import React, {useEffect} from 'react';
import {Terminal} from 'xterm';
import {FitAddon} from 'xterm-addon-fit';
const UITerminal = () => {
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
useEffect(() => {
let termDocument = document.getElementById('terminal')
if (termDocument) {
term.open(termDocument)
fitaddon.fit();
}
window.addEventListener('resize', () => {
fitaddon.fit();
})
}, [])
return (<div id="terminal"></div>)
}
Below is the error response from production code. clearly it fails to import xterm
react_devtools_backend.js:4012 ReferenceError: Cannot access 'r' before initialization
at new m (96209.72626fc1cc862aea477a.bundle.js:1:165467)
at new b (96209.72626fc1cc862aea477a.bundle.js:1:159758)
at new M (96209.72626fc1cc862aea477a.bundle.js:1:57572)
at new r.exports.i.Terminal (96209.72626fc1cc862aea477a.bundle.js:1:294972)
at w (96209.72626fc1cc862aea477a.bundle.js:1:15994)
at zo (main.71e827eabc798023c129.bundle.js:1:1260000)
at Ws (main.71e827eabc798023c129.bundle.js:1:1333492)
at Wi (main.71e827eabc798023c129.bundle.js:1:1294411)
at Ui (main.71e827eabc798023c129.bundle.js:1:1294336)
at Pi (main.71e827eabc798023c129.bundle.js:1:1291367)
UPDATE
I have found out that it is happening because of my production webpack configuration but still the root cause is unidentified. please help in soughting this out. I am adding my development and production webpack config here.
Please note that the dev webpack config absolutely works fine if build with it and serve.
webpack.dev.js
const fqdn = "some.fqdn.com"
const path = require("path");
const webpack = require("webpack")
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
const fs = require('fs');
const jsonFormat = require('json-format');
var HtmlWebpackPlugin = require("html-webpack-plugin");
const jsonFromatConfig = {
type: 'space',
size: 4
}
module.exports = merge(common, {
mode: "development",
devtool: "source-map",
output: {
filename: "bundle.js",
publicPath: '/',
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html"
}),
new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
target: "web",
devServer: {
open: true,
static: {
directory: path.join(__dirname, '../public'),
},
historyApiFallback: true,
},
});
webpack.prod.js
const path = require("path");
const common = require("./webpack.common");
const { merge } = require("webpack-merge");
var MinifyPlugin = require('babel-minify-webpack-plugin')
var CompressionPlugin = require('compression-webpack-plugin');
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require("terser-webpack-plugin");
var HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = merge(common, {
mode: "production",
output: {
filename: "naming.[name].contenthash.[contenthash].bundle.js",
path: path.resolve(__dirname, "../build")
},
optimization: {
minimizer: [
new TerserPlugin(),
new HtmlWebpackPlugin( {
template: "./public/index.html",
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true
}
} ),
new MinifyPlugin({}, {
comments: false
})
],
splitChunks: {
chunks: 'all',
minChunks: 3
}
},
plugins: [
new CompressionPlugin({
test: /\.js$|\.css$|\.html$/
}),
new MiniCssExtractPlugin({ filename: "naming.[name].contenthash.[contenthash].css" }),
new CleanWebpackPlugin()
],
module: {
rules: [
{
test: /\.css/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader"
]
},
]
}
});
The error shows that you might use Xterm before its initialization,
You might find it useful to use react-aptor or the idea behind it to connect pure js packages like Xterm.js into react world.
import useAptor from 'react-aptor';
const initializer = (node, params) => {
// user params for further configuration
const terminal = new Terminal();
term.open(node);
return terminal;
}
const getAPI = (terminal, params) => {
return () => ({ terminal })
}
const ReactXterm = (props, ref) => {
const aptorRef = useAptor(ref, {
getAPI,
instantiate,
/* params: anything */
});
return <div ref={aptorRef} />;
};
function App() {
const ref = useRef();
const writeToTerminal = () => {
ref.current.terminal?.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ');
};
return (
<div>
<ReactXterm ref={ref} />
<button onClick={writeToTerminal}>write to terminal</button>
</div>
);
}
Disclosure: I am the maintainer of react-aptor
I am trying to implement push notifications through FCM in reactJS with WebPack v.5 and facing with this error:
An error occurred while retrieving token. FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('http://localhost:4000/firebase-cloud-messaging-push-scope') with script ('http://localhost:4000/firebase-messaging-sw.js'): A bad HTTP response code (404) was received when fetching the script. (messaging/failed-service-worker-registration).
This is my firebase file Code:
importScripts(
"https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js"
);
importScripts(
"https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js"
);
// Initialize the Firebase app in the service worker by passing the generated config
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
console.log("Received background message ", payload);
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
};
self.registration.showNotification(notificationTitle, notificationOptions);
});
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("../firebase-messaging-sw.js")
.then(function (registration) {
console.log("Registration successful, scope is:", registration.scope);
})
.catch(function (err) {
console.log("Service worker registration failed, error:", err);
});
}
Here is my WebPack file code:
const path = require("path");
const ReactRefreshWebpackPlugin = require("#pmmmwh/react-refresh-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const FaviconsWebpackPlugin = require("favicons-webpack-plugin");
// const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin");
// const runtime = require("serviceworker-webpack-plugin/lib/runtime");
const { IgnorePlugin } = require("webpack");
let mode = "development";
let target = "web";
const plugins = [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: "./public/index.html",
}),
new FaviconsWebpackPlugin({
logo: "./public/favicon.ico", // svg works too!
}),
new IgnorePlugin({
resourceRegExp: /^#progress$/,
}),
// new ServiceWorkerWebpackPlugin({
// entry: path.join(__dirname, "./public/firebase-messaging-sw.js"),
// }),
];
// if ("serviceWorker" in navigator) {
// const registration = runtime.register();
// }
if (process.env.NODE_ENV === "production") {
mode = "production";
// Temporary workaround for 'browserslist' bug that is being patched in the near future
target = "browserslist";
}
if (process.env.SERVE) {
// We only want React Hot Reloading in serve mode
plugins.push(new ReactRefreshWebpackPlugin());
}
module.exports = {
// mode defaults to 'production' if not set
mode: mode,
// This is unnecessary in Webpack 5, because it's the default.
// However, react-refresh-webpack-plugin can't find the entry without it.
entry: "./src/index.js",
output: {
// output path is required for `clean-webpack-plugin`
path: path.resolve(__dirname, "dist"),
// this places all images processed in an image folder
assetModuleFilename: "images/[hash][ext][query]",
publicPath: "/",
},
module: {
rules: [
{
test: /\.(s[ac]|c)ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
// This is required for asset imports in CSS, such as url()
options: { publicPath: "" },
},
"css-loader",
"postcss-loader",
// according to the docs, sass-loader should be at the bottom, which
// loads it first to avoid prefixes in your sourcemaps and other issues.
"sass-loader",
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
/**
* The `type` setting replaces the need for "url-loader"
* and "file-loader" in Webpack 5.
*
* setting `type` to "asset" will automatically pick between
* outputing images to a file, or inlining them in the bundle as base64
* with a default max inline size of 8kb
*/
type: "asset",
/**
* If you want to inline larger images, you can set
* a custom `maxSize` for inline like so:
*/
// parser: {
// dataUrlCondition: {
// maxSize: 30 * 1024,
// },
// },
},
{
test: /\.(woff|woff2|ttf|otf|eot)$/,
use: [
{
loader: "file-loader",
options: { name: "[path][name]-[hash:8].[ext]" },
},
],
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
// without additional settings, this will reference .babelrc
loader: "babel-loader",
options: {
/**
* From the docs: When set, the given directory will be used
* to cache the results of the loader. Future webpack builds
* will attempt to read from the cache to avoid needing to run
* the potentially expensive Babel recompilation process on each run.
*/
cacheDirectory: true,
},
},
},
],
},
plugins: plugins,
target: target,
devtool: "source-map",
resolve: {
extensions: [".js", ".jsx"],
},
// required if using webpack-dev-server
devServer: {
contentBase: "./dist",
historyApiFallback: true,
hot: true,
port: 4000,
},
};
Still in my journey of deploying my ssr app on firebase. Almost there.
I only have an issue with my images. I am getting a 400 error message on the console. Pictures are from an external url. The data fetched is correct but it does display somehow. You will see below my server.js file and next.config.js.
Can someone tell me what is missing, please?
server.js
const { https } = require('firebase-functions');
const { default: next } = require('next');
const isDev = process.env.NODE_ENV !== 'production';
const server = next({
dev: isDev,
//location of .next generated after running -> yarn build
conf: { distDir: '.next' },
image :{ domain :['assets.coingecko.com'],}
});
const nextjsHandle = server.getRequestHandler();
exports.nextServer = https.onRequest((req, res) => {
return server.prepare()
.then(() => {
return nextjsHandle(req, res)
});
});
next.config.js
const webpack = require('webpack');
const path = require('path');
//const withPlugins = require('next-compose-plugins');
//const optimizedImages = require('next-optimized-images');
module.exports = {
images: {
domains: ['assets.coingecko.com'],
loader: 'imgix',
path: 'https://assets.coingecko.com/',
},
reactStrictMode: true,
entry: './src/index.js',
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
module: {
rules: [
//...
{
test: /\.(png|jp(e*)g|svg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[hash]-[name].[ext]',
},
},
],
},
],
},
//...
}
first i wanted to thank you for your help #juliomalves. I found answer to my issue. I have answered the questions in details here [1][ https://stackoverflow.com/questions/69974890/how-to-set-up-next-image-loader-url-correctly-for-external-url/70052871#70052871]
if anyone find himself in the same situation.
I have a React/Typescript project with Storybook. Storybook works great, but as soon as I start importing files with aliases, it crashes.
Example:
import Foo from "#components/foo" => crash
import Foo from "../../components/foo" => ok
The app works fine with the aliases. The issue is only related to Storybook.
Here is my storybook config:
module.exports = {
stories: ["../**/stories.tsx"],
webpackFinal: (config) => {
return {
...config,
module: {
...config.module,
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
};
},
typescript: {
check: false,
checkOptions: {},
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
My webpack config:
/* eslint-env node */
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const Dotenv = require("dotenv-webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProductionMode = (mode) => mode === "production";
module.exports = () => {
const env = require("dotenv").config({ path: __dirname + "/.env" });
const nodeEnv = env.parsed.NODE_ENV;
return {
mode: "development",
entry: "./src/index.tsx",
output: {
path: path.join(__dirname, "./dist"),
filename: "[name].[contenthash].bundle.js",
publicPath: "/",
},
resolve: {
extensions: [".ts", ".tsx", ".js", "jsx", ".json"],
alias: {
"#api": path.resolve(__dirname, "src/api/"),
"#assets": path.resolve(__dirname, "src/assets/"),
"#components": path.resolve(__dirname, "src/components/"),
"#containers": path.resolve(__dirname, "src/containers/"),
"#data": path.resolve(__dirname, "src/data/"),
"#i18n": path.resolve(__dirname, "src/i18n/"),
"#models": path.resolve(__dirname, "src/models/"),
"#pages": path.resolve(__dirname, "src/pages/"),
"#src": path.resolve(__dirname, "src/"),
"#stores": path.resolve(__dirname, "src/stores/"),
"#utils": path.resolve(__dirname, "src/utils/"),
},
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
{ test: /\.(png|jpg|jpeg|gif)$/, use: ["file-loader"] },
{
test: /\.svg$/,
use: [
{
loader: "babel-loader",
},
{
loader: "react-svg-loader",
options: {
jsx: true,
},
},
],
},
],
},
devServer: {
historyApiFallback: true,
port: 3000,
inline: true,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new Dotenv(),
],
optimization: {
minimize: isProductionMode(nodeEnv),
minimizer: isProductionMode(nodeEnv) ? [new TerserPlugin()] : [],
splitChunks: { chunks: "all" },
},
};
};
How to fix this? I am on webpack 5.24.2 and storybook 6.1.20, so these are the latest versions.
Just add this in your .storybook/main.js
const path = require('path');
module.exports = {
"stories": [
"../components/**/*.stories.mdx",
"../components/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
'#storybook/preset-scss',
],
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
'#/interfaces': path.resolve(__dirname, "../interfaces"),
};
return config;
}
}
here interface is folder at my project root
It works For Me
This worked for me when I had the same problem:
Install a package in dev deps yarn add -D tsconfig-paths-webpack-plugin.
Then adjust your ./storybook/main.js config:
... // other imports
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
...
webpackFinal: (config) => {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
})
);
return { ... }
}
...
From the docs:
// .storybook/main.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
webpackFinal: async (config) => {
config.resolve.plugins = [
...(config.resolve.plugins || []),
new TsconfigPathsPlugin({
extensions: config.resolve.extensions,
}),
];
return config;
},
};
Link
My React/TypeScript Storybook project uses Vite rather than Webpack.
The readme for storybook-builder-vite clarifies "The builder will not read your vite.config.js file by default," so anything that you specified in there may be having no influence whatsoever on the Storybook build; instead, you have to customise the Storybook-specific Vite config via the viteFinal option in .storybook/main.js.
Here's how I went about introducing vite-tsconfig-paths into the Storybook Vite config to resolve tsconfig path aliases:
// .storybook/main.js
const path = require("path");
const tsconfigPaths = require("vite-tsconfig-paths").default;
module.exports = {
"stories": [
"../frontend/**/*.stories.mdx",
"../frontend/**/*.stories.#(js|jsx|ts|tsx)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-interactions"
],
"framework": "#storybook/react",
"core": {
"builder": "storybook-builder-vite"
},
/**
* A option exposed by storybook-builder-vite for customising the Vite config.
* #see https://github.com/eirslett/storybook-builder-vite#customize-vite-config
* #param {import("vite").UserConfig} config
* #see https://vitejs.dev/config/
*/
viteFinal: async (config) => {
config.plugins.push(
/** #see https://github.com/aleclarson/vite-tsconfig-paths */
tsconfigPaths({
// My tsconfig.json isn't simply in viteConfig.root,
// so I've passed an explicit path to it:
projects: [path.resolve(path.dirname(__dirname), "frontend", "tsconfig.json")],
})
);
return config;
},
}
In case you use #storybook/vite-builder. This neat config works for me
const tsconfigPaths = require("vite-tsconfig-paths");
...
module.exports = {
...
async viteFinal(config) {
return {
...config,
plugins: [...config.plugins, tsconfigPaths.default()],
};
},
};
If you're using webpack 5 you'll need to specify that webpack5 should be used by also adding the following in addition to the previous answers:
core: {
builder: "webpack5",
},
Final storybook/main.js would then resemble:
// .storybook/main.js
const path = require('path');
const appWebpack = require(path.join(process.cwd(), 'webpack.config.js'));
module.exports = {
stories: ['../src/**/*.stories.#(tsx|mdx)'],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
'#storybook/preset-scss'
],
core: {
builder: "webpack5",
},
webpackFinal: async (config) => {
config.resolve.modules = [
...(config.resolve.modules || []),
...[path.resolve(process.cwd(), "src")],
];
config.resolve.alias = {
...(config.resolve.alias || {}),
...appWebpack().resolve.alias,
};
return config;
},
};
This will allow both absolute paths as well as aliases (as long as those aliases are properly set up in your main webpack.config.js and jsconfig.json/tsconfig.json of course)
Edited
Having trouble after the fact specifically with aliases, I took another trip down the webpack rocky-road.
I've updated the original 'final' for the .storybook/main.js above, explicitly merging in the alias as well as the modules nodes.
Edit 2
Be aware, eslint is going to squawk over using an alias within global decorators you create (and add to .storybook/preview.js). You can safely ignore this - they still work. If/when I figure out how to correct this as well, I'll come back and add a 3rd edit.
We're using Vite and typescript project references, for us adding the following to the storybook main.cjs worked;
viteFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
'#some-alias': path.resolve(__dirname, '../../some/ts/project/reference'),
};
return config;
}
As an alternative to Jamie Birch's excellent answer, if you're using vite and don't want to install vite-tsconfig-paths, you can just edit .storybook/main.js and add viteFinal to the config, like this:
const path = require('path');
module.exports = {
// ... whatever you already have here
viteFinal: async (config) => {
if (config.resolve.alias) {
config.resolve.alias.push({ find: '#', replacement: path.resolve(__dirname, '../src') + '/' });
} else {
config.resolve.alias = [{ find: '#', replacement: path.resolve(__dirname, '../src') + '/' }];
}
return config;
}
}
I installed file-loader in my next.js project and configured my next.config.js to be like this:
module.exports = {
entry: './src/index.js',
webpack: config => {
const env = Object.keys(process.env).reduce((acc, curr) => {
acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
return acc;
}, {});
config.plugins.push(new webpack.DefinePlugin(env));
config.module.rules.push({
test: /\.(png|jp(e*)g|svg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[hash]-[name].[ext]',
},
},
],
});
return config;
}
};
I then have an image in /public/images/book-reading.svg
So I tried to import the image like this in a component I have within /src/components:
import BookReading from '../../public/images/book-reading.svg';
And using it like this:
<img src={BookReading} />
However the image does not show and I get this warning:
Warning: Prop src did not match. Server:
"images/364068d183bb962a8423031f65bab6ad-book-reading.svg" Client:
"/_next/images/364068d183bb962a8423031f65bab6ad-book-reading.svg"
Any ideas?
You need to add the publicPath and the outputPath to file-loader's options.
module.exports = {
webpack: config => {
config.module.rules.push({
test: /\.(png|jp(e*)g|svg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[hash]-[name].[ext]',
publicPath: `/_next/static/images/`,
outputPath: 'static/images',
},
},
],
});
return config;
}
};
This is not your case but for the sake of completeness: if you had used a different basePath, you'd have needed to add it at the beginning of your publicPath.
Source